From b58ce54d0b4e7afd6bb644788281f9caad60708c Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:21:08 +0100 Subject: [PATCH 1/7] initial commit --- src/executor/executor.ts | 100 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 13faf9b9..c1dc69d3 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -30,6 +30,7 @@ import { isVersion06, maxBigInt, parseViemError, + scaleBigIntByPercent, toPackedUserOperation } from "@alto/utils" // biome-ignore lint/style/noNamespaceImport: explicitly make it clear when sentry is used @@ -46,7 +47,9 @@ import { type Chain, type PublicClient, type Transport, - type WalletClient + type WalletClient, + type Hex, + TransactionExecutionError } from "viem" import { createCompressedCalldata, @@ -55,6 +58,10 @@ import { simulatedOpsToResults, type CompressedFilterOpsAndEstimateGasParams } from "./utils" +import type { + PrepareTransactionRequestRequest, + SendTransactionErrorType +} from "viem" export interface GasEstimateResult { preverificationGas: bigint @@ -514,6 +521,91 @@ export class Executor { await Promise.all(promises) } + async sendHandleOpsTransaction( + userOps: PackedUserOperation[], + entryPoint: Address, + opts: + | { + gasPrice: bigint + maxFeePerGas?: undefined + maxPriorityFeePerGas?: undefined + account: Account + gas: bigint + nonce: number + } + | { + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + gasPrice?: undefined + account: Account + gas: bigint + nonce: number + } + ) { + let request: PrepareTransactionRequestRequest + + try { + request = await this.walletClient.prepareTransactionRequest({ + to: entryPoint, + data: encodeFunctionData({ + abi: EntryPointV07Abi, + functionName: "handleOps", + args: [userOps, opts.account.address] + }), + ...opts, + nonce: opts.nonce - 1 + }) + } catch (e: unknown) { + throw new Error("Failed to generate transactionRequest") + } + + let attempts = 0 + let transactionHash: Hex | undefined + const maxAttempts = 3 + + // Try sending the transaction and updating if there is an error + while (attempts < maxAttempts) { + try { + transactionHash = + await this.walletClient.sendTransaction(request) + + break + } catch (e: unknown) { + const error = e as SendTransactionErrorType + + if (error instanceof TransactionExecutionError) { + const cause = error.cause + + if (cause instanceof NonceTooLowError) { + this.logger.warn("Nonce too low, retrying") + request.nonce += 1 + } + + if (cause instanceof IntrinsicGasTooLowError) { + this.logger.warn("Intrinsic gas too low, retrying") + request.gas = scaleBigIntByPercent(request.gas, 150) + } + } + + attempts++ + if (attempts === maxAttempts) { + throw error + } + + this.logger.warn(`Attempt ${attempts} failed. Retrying...`) + } + } + + console.log("sent: ", transactionHash) + + // needed for TS + if (!transactionHash) { + throw new Error("Transaction hash not assigned") + } + + return transactionHash as Hex + } + async bundle( entryPoint: Address, ops: UserOperation[] @@ -696,8 +788,9 @@ export class Executor { ) ) as PackedUserOperation[] - transactionHash = await ep.write.handleOps( - [userOps, wallet.address], + transactionHash = await this.sendHandleOpsTransaction( + userOps, + entryPoint, opts ) @@ -708,6 +801,7 @@ export class Executor { ) }) } catch (err: unknown) { + console.log(err) const e = parseViemError(err) if (e instanceof InsufficientFundsError) { childLogger.error( From 1131ba7439481b9e7c2d44f267edb5a14c494bd5 Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:40:24 +0100 Subject: [PATCH 2/7] remove try/catch around prepareTransactionRequest --- src/executor/executor.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/executor/executor.ts b/src/executor/executor.ts index c1dc69d3..02e468cf 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -542,28 +542,21 @@ export class Executor { nonce: number } ) { - let request: PrepareTransactionRequestRequest - - try { - request = await this.walletClient.prepareTransactionRequest({ - to: entryPoint, - data: encodeFunctionData({ - abi: EntryPointV07Abi, - functionName: "handleOps", - args: [userOps, opts.account.address] - }), - ...opts, - nonce: opts.nonce - 1 - }) - } catch (e: unknown) { - throw new Error("Failed to generate transactionRequest") - } + const request = await this.walletClient.prepareTransactionRequest({ + to: entryPoint, + data: encodeFunctionData({ + abi: EntryPointV07Abi, + functionName: "handleOps", + args: [userOps, opts.account.address] + }), + ...opts + }) let attempts = 0 let transactionHash: Hex | undefined const maxAttempts = 3 - // Try sending the transaction and updating if there is an error + // Try sending the transaction and updating relevant fields if there is an error. while (attempts < maxAttempts) { try { transactionHash = @@ -596,8 +589,6 @@ export class Executor { } } - console.log("sent: ", transactionHash) - // needed for TS if (!transactionHash) { throw new Error("Transaction hash not assigned") From ba373d41ce39192f542379347e4936d0453e9a2c Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:42:39 +0100 Subject: [PATCH 3/7] remove unused imports --- src/executor/executor.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 02e468cf..3288fffe 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -58,10 +58,7 @@ import { simulatedOpsToResults, type CompressedFilterOpsAndEstimateGasParams } from "./utils" -import type { - PrepareTransactionRequestRequest, - SendTransactionErrorType -} from "viem" +import type { SendTransactionErrorType } from "viem" export interface GasEstimateResult { preverificationGas: bigint From 2d849186334c038bc131b1bfe84f9d56d107ee62 Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:45:04 +0100 Subject: [PATCH 4/7] add checks for v0.6 --- src/executor/executor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 3288fffe..64dca93c 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -520,6 +520,7 @@ export class Executor { async sendHandleOpsTransaction( userOps: PackedUserOperation[], + isUserOpVersion06: boolean, entryPoint: Address, opts: | { @@ -542,7 +543,7 @@ export class Executor { const request = await this.walletClient.prepareTransactionRequest({ to: entryPoint, data: encodeFunctionData({ - abi: EntryPointV07Abi, + abi: isUserOpVersion06 ? EntryPointV06Abi : EntryPointV07Abi, functionName: "handleOps", args: [userOps, opts.account.address] }), @@ -778,6 +779,7 @@ export class Executor { transactionHash = await this.sendHandleOpsTransaction( userOps, + isUserOpVersion06, entryPoint, opts ) From 4e74abafc4c0ba0385816054baf2119b25f9e21f Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:35:21 +0100 Subject: [PATCH 5/7] remove console log --- src/executor/executor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 64dca93c..c3471fd1 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -791,7 +791,6 @@ export class Executor { ) }) } catch (err: unknown) { - console.log(err) const e = parseViemError(err) if (e instanceof InsufficientFundsError) { childLogger.error( From 179fb4e83ece9a8818943b263597a17e2976608b Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:43:50 +0100 Subject: [PATCH 6/7] recognize TransactionExecutionErrors --- src/utils/validation.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 1e83ac66..6b7beed0 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -700,6 +700,9 @@ export function parseViemError(err: unknown) { if (e instanceof EstimateGasExecutionError) { return e } + if (e instanceof TransactionExecutionError) { + return e + } return } return From e76804b8eae8a10b448b8766b497152c20b957ed Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:38:56 +0100 Subject: [PATCH 7/7] sync with main + handle tx underpriced errors --- src/executor/executor.ts | 35 +++++++++++++++++------------------ src/utils/validation.ts | 5 +++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 2f0278c4..20b15db0 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -36,7 +36,6 @@ import { import * as sentry from "@sentry/node" import { Mutex } from "async-mutex" import { - type Account, FeeCapTooLowError, InsufficientFundsError, IntrinsicGasTooLowError, @@ -44,10 +43,6 @@ import { encodeFunctionData, getContract, type Account, - type Chain, - type PublicClient, - type Transport, - type WalletClient, type Hex, TransactionExecutionError } from "viem" @@ -528,15 +523,18 @@ export class Executor { nonce: number } ) { - const request = await this.walletClient.prepareTransactionRequest({ - to: entryPoint, - data: encodeFunctionData({ - abi: isUserOpVersion06 ? EntryPointV06Abi : EntryPointV07Abi, - functionName: "handleOps", - args: [userOps, opts.account.address] - }), - ...opts - }) + const request = + await this.config.walletClient.prepareTransactionRequest({ + to: entryPoint, + data: encodeFunctionData({ + abi: isUserOpVersion06 + ? EntryPointV06Abi + : EntryPointV07Abi, + functionName: "handleOps", + args: [userOps, opts.account.address] + }), + ...opts + }) let attempts = 0 let transactionHash: Hex | undefined @@ -546,11 +544,12 @@ export class Executor { while (attempts < maxAttempts) { try { transactionHash = - await this.walletClient.sendTransaction(request) + await this.config.walletClient.sendTransaction(request) break } catch (e: unknown) { const error = e as SendTransactionErrorType + let isErrorHandled = false if (error instanceof TransactionExecutionError) { const cause = error.cause @@ -558,20 +557,20 @@ export class Executor { if (cause instanceof NonceTooLowError) { this.logger.warn("Nonce too low, retrying") request.nonce += 1 + isErrorHandled = true } if (cause instanceof IntrinsicGasTooLowError) { this.logger.warn("Intrinsic gas too low, retrying") request.gas = scaleBigIntByPercent(request.gas, 150) + isErrorHandled = true } } attempts++ - if (attempts === maxAttempts) { + if (attempts === maxAttempts || !isErrorHandled) { throw error } - - this.logger.warn(`Attempt ${attempts} failed. Retrying...`) } } diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 98ac6fd2..15d040ad 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -28,7 +28,8 @@ import { getContract, serializeTransaction, toBytes, - toFunctionSelector + toFunctionSelector, + InternalRpcError } from "viem" import { base, baseGoerli, baseSepolia } from "viem/chains" import { maxBigInt, minBigInt, scaleBigIntByPercent } from "./bigInt" @@ -706,7 +707,7 @@ export function parseViemError(err: unknown) { if (e instanceof EstimateGasExecutionError) { return e } - if (e instanceof TransactionExecutionError) { + if (e instanceof InternalRpcError) { return e } return