diff --git a/src/cli/config/options.ts b/src/cli/config/options.ts index 6e1e5e86..6c3079f4 100644 --- a/src/cli/config/options.ts +++ b/src/cli/config/options.ts @@ -469,7 +469,7 @@ export const debugOptions: CliCommandOptions = { "Should the bundler deploy the simulations contract on startup", type: "boolean", require: true, - default: false + default: true }, tenderly: { description: "RPC url follows the tenderly format", diff --git a/src/cli/deploySimulationsContract.ts b/src/cli/deploySimulationsContract.ts index da950b4f..9b9be004 100644 --- a/src/cli/deploySimulationsContract.ts +++ b/src/cli/deploySimulationsContract.ts @@ -24,6 +24,16 @@ export const deploySimulationsContract = async ({ ) } + if (args.entrypointSimulationContract) { + const simulations = args.entrypointSimulationContract + const simulationsCode = await publicClient.getCode({ + address: simulations + }) + if (simulationsCode !== undefined && simulationsCode !== "0x") { + return args.entrypointSimulationContract + } + } + const walletClient = createWalletClient({ transport: http(args.rpcUrl), account: utilityPrivateKey diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 78b9052b..b4b255e7 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -21,7 +21,8 @@ import { type UserOperationV06, type UserOperationV07, type UserOperationWithHash, - deriveUserOperation + deriveUserOperation, + GasPriceParameters } from "@alto/types" import type { Logger, Metrics } from "@alto/utils" import { @@ -152,8 +153,15 @@ export class Executor { ): Promise { const newRequest = { ...transactionInfo.transactionRequest } - const gasPriceParameters = - await this.gasPriceManager.getNetworkGasPrice() + let gasPriceParameters + try { + gasPriceParameters = + await this.gasPriceManager.tryGetNetworkGasPrice() + } catch (err) { + this.logger.error({ error: err }, "Failed to get network gas price") + this.markWalletProcessed(transactionInfo.executor) + return { status: "failed" } + } newRequest.maxFeePerGas = maxBigInt( gasPriceParameters.maxFeePerGas, @@ -495,15 +503,23 @@ export class Executor { const wallets = Array.from(allWallets) - const gasPrice = await this.gasPriceManager.getNetworkGasPrice() + const gasPrice = await this.gasPriceManager.tryGetNetworkGasPrice() + const promises = wallets.map((wallet) => { - flushStuckTransaction( - this.config.publicClient, - this.config.walletClient, - wallet, - gasPrice.maxFeePerGas * 5n, - this.logger - ) + try { + flushStuckTransaction( + this.config.publicClient, + this.config.walletClient, + wallet, + gasPrice.maxFeePerGas * 5n, + this.logger + ) + } catch (e) { + this.logger.error( + { error: e }, + "error flushing stuck transaction" + ) + } }) await Promise.all(promises) @@ -709,15 +725,35 @@ export class Executor { }) childLogger.debug("bundling user operation") - const gasPriceParameters = - await this.gasPriceManager.getNetworkGasPrice() - childLogger.debug({ gasPriceParameters }, "got gas price") - - const nonce = await this.config.publicClient.getTransactionCount({ - address: wallet.address, - blockTag: "pending" - }) - childLogger.trace({ nonce }, "got nonce") + // These calls can throw, so we try/catch them to mark wallet as processed in event of error. + let nonce: number + let gasPriceParameters: GasPriceParameters + try { + ;[gasPriceParameters, nonce] = await Promise.all([ + this.gasPriceManager.tryGetNetworkGasPrice(), + this.config.publicClient.getTransactionCount({ + address: wallet.address, + blockTag: "pending" + }) + ]) + } catch (err) { + childLogger.error( + { error: err }, + "Failed to get parameters for bundling" + ) + this.markWalletProcessed(wallet) + return opsWithHashes.map((owh) => { + return { + status: "resubmit", + info: { + entryPoint, + userOpHash: owh.userOperationHash, + userOperation: owh.mempoolUserOperation, + reason: "Failed to get parameters for bundling" + } + } + }) + } const callContext: DefaultFilterOpsAndEstimateGasParams = { ep, @@ -989,15 +1025,39 @@ export class Executor { }) childLogger.debug("bundling compressed user operation") - const gasPriceParameters = - await this.gasPriceManager.getNetworkGasPrice() - childLogger.debug({ gasPriceParameters }, "got gas price") - - const nonce = await this.config.publicClient.getTransactionCount({ - address: wallet.address, - blockTag: "pending" - }) - childLogger.trace({ nonce }, "got nonce") + let nonce: number + let gasPriceParameters: GasPriceParameters + try { + ;[gasPriceParameters, nonce] = await Promise.all([ + this.gasPriceManager.tryGetNetworkGasPrice(), + this.config.publicClient.getTransactionCount({ + address: wallet.address, + blockTag: "pending" + }) + ]) + } catch (err) { + childLogger.error( + { error: err }, + "Failed to get parameters for bundling" + ) + this.markWalletProcessed(wallet) + return compressedOps.map((compressedOp) => { + const userOpHash = getUserOperationHash( + compressedOp.inflatedOp, + entryPoint, + this.config.walletClient.chain.id + ) + return { + status: "resubmit", + info: { + entryPoint, + userOpHash, + userOperation: compressedOp, + reason: "Failed to get parameters for bundling" + } + } + }) + } const callContext: CompressedFilterOpsAndEstimateGasParams = { publicClient: this.config.publicClient, diff --git a/src/executor/executorManager.ts b/src/executor/executorManager.ts index 6fcf22b6..a41f47f7 100644 --- a/src/executor/executorManager.ts +++ b/src/executor/executorManager.ts @@ -776,7 +776,7 @@ export class ExecutorManager { // for all still not included check if needs to be replaced (based on gas price) const gasPriceParameters = - await this.gasPriceManager.getNetworkGasPrice() + await this.gasPriceManager.tryGetNetworkGasPrice() this.logger.trace( { gasPriceParameters }, "fetched gas price parameters" diff --git a/src/executor/senderManager.ts b/src/executor/senderManager.ts index 35dfa07a..73103f69 100644 --- a/src/executor/senderManager.ts +++ b/src/executor/senderManager.ts @@ -127,18 +127,19 @@ export class SenderManager { }, "utility wallet has insufficient balance to refill wallets" ) - throw new Error( - `utility wallet ${ - this.utilityAccount.address - } has insufficient balance ${formatEther( - utilityWalletBalance - )} < ${formatEther(totalBalanceMissing)}` - ) + return + // throw new Error( + // `utility wallet ${ + // this.utilityAccount.address + // } has insufficient balance ${formatEther( + // utilityWalletBalance + // )} < ${formatEther(totalBalanceMissing)}` + // ) } if (Object.keys(balancesMissing).length > 0) { const { maxFeePerGas, maxPriorityFeePerGas } = - await this.gasPriceManager.getNetworkGasPrice() + await this.gasPriceManager.tryGetNetworkGasPrice() if (this.config.refillHelperContract) { const instructions = [] diff --git a/src/handlers/gasPriceManager.ts b/src/handlers/gasPriceManager.ts index 546bd0d9..c933c25a 100644 --- a/src/handlers/gasPriceManager.ts +++ b/src/handlers/gasPriceManager.ts @@ -71,7 +71,7 @@ export class GasPriceManager { this.updateBaseFee() } - this.updateGasPrice() + this.tryUpdateGasPrice() }, this.config.gasPriceRefreshInterval * 1000) } @@ -81,7 +81,7 @@ export class GasPriceManager { public init() { return Promise.all([ - this.updateGasPrice(), + this.tryUpdateGasPrice(), this.config.legacyTransactions === false ? this.updateBaseFee() : Promise.resolve() @@ -296,6 +296,7 @@ export class GasPriceManager { return { maxFeePerGas, maxPriorityFeePerGas } } + // This method throws if it can't get a valid RPC response. private async innerGetGasPrice(): Promise { let maxFeePerGas = 0n let maxPriorityFeePerGas = 0n @@ -386,7 +387,8 @@ export class GasPriceManager { return baseFee } - private async updateGasPrice(): Promise { + // This method throws if it can't get a valid RPC response. + private async tryUpdateGasPrice(): Promise { const gasPrice = await this.innerGetGasPrice() this.maxFeePerGasQueue.saveValue(gasPrice.maxFeePerGas) @@ -397,7 +399,7 @@ export class GasPriceManager { public async getGasPrice(): Promise { if (this.config.gasPriceRefreshInterval === 0) { - return await this.updateGasPrice() + return await this.tryUpdateGasPrice() } const maxFeePerGas = this.maxFeePerGasQueue.getLatestValue() @@ -414,7 +416,8 @@ export class GasPriceManager { } } - public async getNetworkGasPrice(): Promise { + // This method throws if it can't get a valid RPC response. + public async tryGetNetworkGasPrice(): Promise { return await this.innerGetGasPrice() }