Skip to content

Commit

Permalink
Fix/replacement logic flow (#378)
Browse files Browse the repository at this point in the history
* fix replacement logic flow

* add retry logic to resubmissions

* fix build

---------

Co-authored-by: mouseless <[email protected]>
  • Loading branch information
mouseless0x and mouseless0x authored Dec 13, 2024
1 parent ba16716 commit 03b5574
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 104 deletions.
198 changes: 96 additions & 102 deletions src/executor/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
type PackedUserOperation,
type TransactionInfo,
type UserOperation,
type UserOperationV06,
type UserOperationV07,
type UserOperationWithHash,
deriveUserOperation,
Expand Down Expand Up @@ -66,6 +65,18 @@ export interface GasEstimateResult {
callGasLimit: bigint
}

export type HandleOpsTxParam =
| {
type: "default"
ops: PackedUserOperation[]
isUserOpVersion06: boolean
entryPoint: Address
}
| {
type: "compressed"
compressedOps: CompressedUserOperation[]
}

export type ReplaceTransactionResult =
| {
status: "replaced"
Expand Down Expand Up @@ -333,6 +344,7 @@ export class Executor {
newRequest.gas = maxBigInt(newRequest.gas, gasLimit)

// update calldata to include only ops that pass simulation
let txParam: HandleOpsTxParam
if (transactionInfo.transactionType === "default") {
const isUserOpVersion06 = opsWithHashes.reduce(
(acc, op) => {
Expand All @@ -351,39 +363,29 @@ export class Executor {
)
)

newRequest.data = isUserOpVersion06
? encodeFunctionData({
abi: EntryPointV06Abi,
functionName: "handleOps",
args: [
opsToBundle.map(
(opInfo) =>
opInfo.mempoolUserOperation as UserOperationV06
),
transactionInfo.executor.address
]
})
: encodeFunctionData({
abi: EntryPointV07Abi,
functionName: "handleOps",
args: [
opsToBundle.map((opInfo) =>
toPackedUserOperation(
opInfo.mempoolUserOperation as UserOperationV07
)
),
transactionInfo.executor.address
]
})
} else if (transactionInfo.transactionType === "compressed") {
const userOps = opsToBundle.map((op) =>
isUserOpVersion06
? op.mempoolUserOperation
: toPackedUserOperation(
op.mempoolUserOperation as UserOperationV07
)
) as PackedUserOperation[]

txParam = {
type: "default",
isUserOpVersion06,
ops: userOps,
entryPoint: transactionInfo.entryPoint
}
} else {
const compressedOps = opsToBundle.map(
(opInfo) =>
opInfo.mempoolUserOperation as CompressedUserOperation
)
newRequest.data = createCompressedCalldata(
compressedOps,
this.getCompressionHandler().perOpInflatorId
)
({ mempoolUserOperation }) => mempoolUserOperation
) as CompressedUserOperation[]

txParam = {
type: "compressed",
compressedOps: compressedOps
}
}

try {
Expand All @@ -402,25 +404,30 @@ export class Executor {
"replacing transaction"
)

const txHash = await this.config.walletClient.sendTransaction(
this.config.legacyTransactions
const txHash = await this.sendHandleOpsTransaction({
txParam,
opts: this.config.legacyTransactions
? {
...newRequest,
account: newRequest.account,
gasPrice: newRequest.maxFeePerGas,
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
type: "legacy",
accessList: undefined
gas: newRequest.gas,
nonce: newRequest.nonce
}
: newRequest
)
: {
account: newRequest.account,
maxFeePerGas: newRequest.maxFeePerGas,
maxPriorityFeePerGas: newRequest.maxPriorityFeePerGas,
gas: newRequest.gas,
nonce: newRequest.nonce
}
})

opsToBundle.map((opToBundle) => {
const op = deriveUserOperation(opToBundle.mempoolUserOperation)
opsToBundle.map(({ entryPoint, mempoolUserOperation }) => {
const op = deriveUserOperation(mempoolUserOperation)
const chainId = this.config.publicClient.chain?.id
const opHash = getUserOperationHash(
op,
opToBundle.entryPoint,
entryPoint,
chainId as number
)

Expand Down Expand Up @@ -489,6 +496,7 @@ export class Executor {

childLogger.warn({ error: e }, "error replacing transaction")
this.markWalletProcessed(transactionInfo.executor)

return { status: "failed" }
}
}
Expand Down Expand Up @@ -525,10 +533,11 @@ export class Executor {
await Promise.all(promises)
}

async sendHandleOpsTransaction(
userOps: PackedUserOperation[],
isUserOpVersion06: boolean,
entryPoint: Address,
async sendHandleOpsTransaction({
txParam,
opts
}: {
txParam: HandleOpsTxParam
opts:
| {
gasPrice: bigint
Expand All @@ -546,17 +555,32 @@ export class Executor {
gas: bigint
nonce: number
}
) {
}) {
let data: Hex
let to: Address

if (txParam.type === "default") {
const { isUserOpVersion06, ops, entryPoint } = txParam
data = encodeFunctionData({
abi: isUserOpVersion06 ? EntryPointV06Abi : EntryPointV07Abi,
functionName: "handleOps",
args: [ops, opts.account.address]
})
to = entryPoint
} else {
const { compressedOps } = txParam
const compressionHandler = this.getCompressionHandler()
data = createCompressedCalldata(
compressedOps,
compressionHandler.perOpInflatorId
)
to = compressionHandler.bundleBulkerAddress
}

const request =
await this.config.walletClient.prepareTransactionRequest({
to: entryPoint,
data: encodeFunctionData({
abi: isUserOpVersion06
? EntryPointV06Abi
: EntryPointV07Abi,
functionName: "handleOps",
args: [userOps, opts.account.address]
}),
to,
data,
...opts
})

Expand Down Expand Up @@ -881,12 +905,15 @@ export class Executor {
)
) as PackedUserOperation[]

transactionHash = await this.sendHandleOpsTransaction(
userOps,
isUserOpVersion06,
entryPoint,
transactionHash = await this.sendHandleOpsTransaction({
txParam: {
type: "default",
ops: userOps,
isUserOpVersion06,
entryPoint
},
opts
)
})

opsWithHashToBundle.map(({ userOperationHash }) => {
this.eventManager.emitSubmitted(
Expand Down Expand Up @@ -953,30 +980,6 @@ export class Executor {
transactionRequest: {
account: wallet,
to: ep.address,
data: isUserOpVersion06
? encodeFunctionData({
abi: ep.abi,
functionName: "handleOps",
args: [
opsWithHashToBundle.map(
(owh) =>
owh.mempoolUserOperation as UserOperationV06
),
wallet.address
]
})
: encodeFunctionData({
abi: ep.abi,
functionName: "handleOps",
args: [
opsWithHashToBundle.map((owh) =>
toPackedUserOperation(
owh.mempoolUserOperation as UserOperationV07
)
),
wallet.address
]
}),
gas: gasLimit,
chain: this.config.walletClient.chain,
maxFeePerGas: gasPriceParameters.maxFeePerGas,
Expand Down Expand Up @@ -1156,17 +1159,12 @@ export class Executor {
}
)

// need to use sendTransaction to target BundleBulker's fallback
transactionHash = await this.config.walletClient.sendTransaction({
account: wallet,
to: compressionHandler.bundleBulkerAddress,
data: createCompressedCalldata(
compressedOpsToBundle,
compressionHandler.perOpInflatorId
),
gas: gasLimit,
nonce: nonce,
...gasOptions
transactionHash = await this.sendHandleOpsTransaction({
txParam: {
type: "compressed",
compressedOps: compressedOpsToBundle
},
opts: { ...gasOptions, gas: gasLimit, account: wallet, nonce }
})

opsToBundle.map(({ userOperationHash }) => {
Expand Down Expand Up @@ -1215,10 +1213,6 @@ export class Executor {
previousTransactionHashes: [],
transactionRequest: {
to: compressionHandler.bundleBulkerAddress,
data: createCompressedCalldata(
compressedOps,
compressionHandler.perOpInflatorId
),
gas: gasLimit,
account: wallet,
chain: this.config.walletClient.chain,
Expand Down
24 changes: 24 additions & 0 deletions src/executor/executorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,15 +823,37 @@ export class ExecutorManager {
reason: string
): Promise<void> {
let replaceResult: ReplaceTransactionResult | undefined = undefined

try {
replaceResult = await this.executor.replaceTransaction(txInfo)
} finally {
this.metrics.replacedTransactions
.labels({ reason, status: replaceResult?.status || "failed" })
.inc()
}

if (replaceResult.status === "failed") {
txInfo.userOperationInfos.map((opInfo) => {
const userOperation = deriveUserOperation(
opInfo.mempoolUserOperation
)

this.eventManager.emitDropped(
opInfo.userOperationHash,
"Failed to replace transaction"
)

this.logger.warn(
{
userOperation: JSON.stringify(userOperation, (_k, v) =>
typeof v === "bigint" ? v.toString() : v
),
userOpHash: opInfo.userOperationHash,
reason
},
"user operation rejected"
)

this.mempool.removeSubmitted(opInfo.userOperationHash)
})

Expand All @@ -842,6 +864,7 @@ export class ExecutorManager {

return
}

if (replaceResult.status === "potentially_already_included") {
this.logger.info(
{ oldTxHash: txInfo.transactionHash, reason },
Expand Down Expand Up @@ -872,6 +895,7 @@ export class ExecutorManager {
.map((ni) => ni.userOperationHash)
.includes(info.userOperationHash)
)

const matchingOps = txInfo.userOperationInfos.filter((info) =>
newTxInfo.userOperationInfos
.map((ni) => ni.userOperationHash)
Expand Down
3 changes: 1 addition & 2 deletions src/types/mempool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Address, Chain, Hex } from "viem"
import type { Address, Chain } from "viem"
import type { Account } from "viem/accounts"
import type { CompressedUserOperation, HexData32, UserOperation } from "."

Expand Down Expand Up @@ -33,7 +33,6 @@ export type TransactionInfo = {
transactionRequest: {
account: Account
to: Address
data: Hex
gas: bigint
chain: Chain
maxFeePerGas: bigint
Expand Down

0 comments on commit 03b5574

Please sign in to comment.