diff --git a/src/cli/setupServer.ts b/src/cli/setupServer.ts index 281b0f58..5b086198 100644 --- a/src/cli/setupServer.ts +++ b/src/cli/setupServer.ts @@ -116,6 +116,7 @@ const getCompressionHandler = async ( } const getExecutor = ({ + mempool, config, senderManager, reputationManager, @@ -124,6 +125,7 @@ const getExecutor = ({ gasPriceManager, eventManager }: { + mempool: MemoryMempool config: AltoConfig senderManager: SenderManager reputationManager: InterfaceReputationManager @@ -133,6 +135,7 @@ const getExecutor = ({ eventManager: EventManager }): Executor => { return new Executor({ + mempool, config, senderManager, reputationManager, @@ -299,6 +302,7 @@ export const setupServer = async ({ }) const executor = getExecutor({ + mempool, config, senderManager, reputationManager, diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 3a478c12..78b9052b 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -7,7 +7,7 @@ import type { EventManager, GasPriceManager } from "@alto/handlers" -import type { InterfaceReputationManager } from "@alto/mempool" +import type { InterfaceReputationManager, MemoryMempool } from "@alto/mempool" import { type Address, type BundleResult, @@ -87,10 +87,12 @@ export class Executor { compressionHandler: CompressionHandler | null gasPriceManager: GasPriceManager mutex: Mutex + mempool: MemoryMempool eventManager: EventManager constructor({ config, + mempool, senderManager, reputationManager, metrics, @@ -99,6 +101,7 @@ export class Executor { eventManager }: { config: AltoConfig + mempool: MemoryMempool senderManager: SenderManager reputationManager: InterfaceReputationManager metrics: Metrics @@ -107,6 +110,7 @@ export class Executor { eventManager: EventManager }) { this.config = config + this.mempool = mempool this.senderManager = senderManager this.reputationManager = reputationManager this.logger = config.getLogger( @@ -540,6 +544,7 @@ export class Executor { ...opts }) + let isTransactionUnderPriced = false let attempts = 0 let transactionHash: Hex | undefined const maxAttempts = 3 @@ -552,11 +557,13 @@ export class Executor { break } catch (e: unknown) { + isTransactionUnderPriced = false let isErrorHandled = false if (e instanceof BaseError) { if (isTransactionUnderpricedError(e)) { this.logger.warn("Transaction underpriced, retrying") + request.maxFeePerGas = scaleBigIntByPercent( request.maxFeePerGas, 150 @@ -566,6 +573,7 @@ export class Executor { 150 ) isErrorHandled = true + isTransactionUnderPriced = true } } @@ -612,6 +620,13 @@ export class Executor { } } + if (isTransactionUnderPriced) { + await this.handleTransactionUnderPriced({ + nonce: request.nonce, + executor: request.from + }) + } + // needed for TS if (!transactionHash) { throw new Error("Transaction hash not assigned") @@ -620,6 +635,33 @@ export class Executor { return transactionHash as Hex } + // Occurs when tx was sent with conflicting nonce, we want to resubmit all conflicting ops + async handleTransactionUnderPriced({ + nonce, + executor + }: { nonce: number; executor: Address }) { + const submitted = this.mempool.dumpSubmittedOps() + + const conflictingOps = submitted + .filter((submitted) => { + const tx = submitted.transactionInfo + + return ( + tx.executor.address === executor && + tx.transactionRequest.nonce === nonce + ) + }) + .map(({ userOperation }) => userOperation) + + conflictingOps.map((op) => { + this.logger.info( + `Resubmitting ${op.userOperationHash} due to transaction underpriced` + ) + this.mempool.removeSubmitted(op.userOperationHash) + this.mempool.add(op.mempoolUserOperation, op.entryPoint) + }) + } + async bundle( entryPoint: Address, ops: UserOperation[]