Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/mempool/mempool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export class Mempool {
})

if (conflicting) {
const { userOpInfo, reason } = conflicting
const { userOpInfo, conflictReason } = conflicting
const conflictingUserOp = userOpInfo.userOp

const hasHigherPriorityFee =
Expand All @@ -372,10 +372,17 @@ export class Mempool {
const hasHigherFees = hasHigherPriorityFee && hasHigherMaxFee

if (!hasHigherFees) {
const message =
reason === "conflicting_deployment"
? "AA10 sender already constructed: A conflicting userOperation with initCode for this sender is already in the mempool"
: "AA25 invalid account nonce: User operation already present in mempool"
let message: string
if (conflictReason === "conflicting_deployment") {
message =
"AA10 sender already constructed: A conflicting userOperation with initCode for this sender is already in the mempool"
} else if (conflictReason === "conflicting_7702_auth") {
message =
"Sender already has an inflight EIP-7702 authorization"
} else {
message =
"AA25 invalid account nonce: User operation already present in mempool"
}

// Re-add to outstanding as it wasn't replaced
await this.store.addOutstanding({
Expand Down
51 changes: 33 additions & 18 deletions src/store/outstanding/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,46 +82,61 @@
return true
}

async popConflicting(

Check failure on line 85 in src/store/outstanding/memory.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=pimlicolabs_alto&issues=AZqnqvB785D-l9bgMCd4&open=AZqnqvB785D-l9bgMCd4&pullRequest=644
userOp: UserOperation
): Promise<ConflictingOutstandingType> {
const outstandingOps = this.dump()

let conflictingReason: ConflictingOutstandingType
for (const existingUserOpInfo of outstandingOps) {
const { userOp: existingUserOp, userOpHash: existingUserOpHash } =
existingUserOpInfo

for (const userOpInfo of outstandingOps) {
const { userOp: mempoolUserOp } = userOpInfo
const isSameSender = existingUserOp.sender === userOp.sender

const isSameSender = mempoolUserOp.sender === userOp.sender
if (isSameSender && mempoolUserOp.nonce === userOp.nonce) {
const removed = await this.remove([userOpInfo.userOpHash])
// Check each conflict type.
const isConflictingNonce =
isSameSender && existingUserOp.nonce === userOp.nonce

const isConflictingEip7702Auth =
isSameSender && userOp.eip7702Auth && existingUserOp.eip7702Auth

const isConflictingDeployment =
isSameSender &&
isDeployment(userOp) &&
isDeployment(existingUserOp)

if (isConflictingNonce) {
const removed = await this.remove([existingUserOpHash])
if (removed.length > 0) {
conflictingReason = {
reason: "conflicting_nonce",
return {
conflictReason: "conflicting_nonce",
userOpInfo: removed[0]
}
}
break
}

const isConflictingDeployment =
isSameSender &&
isDeployment(userOp) &&
isDeployment(mempoolUserOp)
if (isConflictingEip7702Auth) {
const removed = await this.remove([existingUserOpHash])
if (removed.length > 0) {
return {
conflictReason: "conflicting_7702_auth",
userOpInfo: removed[0]
}
}
}

if (isConflictingDeployment) {
const removed = await this.remove([userOpInfo.userOpHash])
const removed = await this.remove([existingUserOpHash])
if (removed.length > 0) {
conflictingReason = {
reason: "conflicting_deployment",
return {
conflictReason: "conflicting_deployment",
userOpInfo: removed[0]
}
}
break
}
}

return conflictingReason
return undefined
}

async contains(userOpHash: HexData32): Promise<boolean> {
Expand Down
41 changes: 39 additions & 2 deletions src/store/outstanding/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
private readonly readyQueue: string // Queue of userOpHashes (sorted by composite of userOp.nonceSeq + userOp.maxFeePerGas)
private readonly userOpHashMap: string // userOpHash -> boolean
private readonly deploymentHashMap: string // sender -> deployment userOpHash
private readonly eip7702AuthHashMap: string // sender -> eip7702Auth userOpHash
private readonly senderNonceQueueTtl: number // TTL for sender nonce queues in seconds

constructor({
Expand All @@ -51,6 +52,7 @@
this.senderNonceKeyPrefix = `${redisPrefix}:sender`
this.userOpHashMap = `${redisPrefix}:userop-hash`
this.deploymentHashMap = `${redisPrefix}:deployment-senders`
this.eip7702AuthHashMap = `${redisPrefix}:eip7702-auth-senders`

// Calculate TTL for sender nonce queues (10 blocks worth of time)
this.senderNonceQueueTtl = config.blockTime * 10
Expand Down Expand Up @@ -93,7 +95,7 @@

if (removedUserOps.length > 0) {
return {
reason: "conflicting_nonce" as const,
conflictReason: "conflicting_nonce" as const,
userOpInfo: removedUserOps[0]
}
}
Expand All @@ -113,7 +115,28 @@

if (removedUserOps.length > 0) {
return {
reason: "conflicting_deployment" as const,
conflictReason: "conflicting_deployment" as const,
userOpInfo: removedUserOps[0]
}
}
}
}

// Check for conflicting EIP-7702 auth.
if (userOp.eip7702Auth) {
const existingEip7702AuthHash = (await this.redis.hget(
this.eip7702AuthHashMap,
sender
)) as HexData32 | null

Check warning on line 130 in src/store/outstanding/redis.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=pimlicolabs_alto&issues=AZqnqu_j85D-l9bgMCd3&open=AZqnqu_j85D-l9bgMCd3&pullRequest=644

if (existingEip7702AuthHash) {
const removedUserOps = await this.remove([
existingEip7702AuthHash
])

if (removedUserOps.length > 0) {
return {
conflictReason: "conflicting_7702_auth" as const,
userOpInfo: removedUserOps[0]
}
}
Expand Down Expand Up @@ -156,6 +179,15 @@
if (isDeployment(userOp)) {
pipeline.hset(this.deploymentHashMap, userOp.sender, userOpHash)
}

// Add sender to eip7702Auth hash if this has eip7702Auth.
if (userOp.eip7702Auth) {
pipeline.hset(
this.eip7702AuthHashMap,
userOp.sender,
userOpHash
)
}
}

await pipeline.exec()
Expand Down Expand Up @@ -205,6 +237,11 @@
removalPipeline.hdel(this.deploymentHashMap, userOp.sender)
}

// Remove sender from eip7702Auth hash if this had eip7702Auth.
if (userOp.eip7702Auth) {
removalPipeline.hdel(this.eip7702AuthHashMap, userOp.sender)
}

removedUserOps.push(userOpInfo)
}

Expand Down
5 changes: 4 additions & 1 deletion src/store/outstanding/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { HexData32, UserOpInfo, UserOperation } from "@alto/types"

export type ConflictingOutstandingType =
| {
reason: "conflicting_nonce" | "conflicting_deployment"
conflictReason:
| "conflicting_nonce"
| "conflicting_deployment"
| "conflicting_7702_auth"
userOpInfo: UserOpInfo
}
| undefined
Expand Down