diff --git a/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts b/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts index a6649b39bfad..a158c05540f7 100644 --- a/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts +++ b/yarn-project/end-to-end/src/e2e_mempool_limit.test.ts @@ -1,7 +1,6 @@ import { AztecAddress } from '@aztec/aztec.js/addresses'; import { NO_WAIT } from '@aztec/aztec.js/contracts'; import { TxStatus } from '@aztec/aztec.js/tx'; -import { retryUntil } from '@aztec/foundation/retry'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import type { AztecNode, AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; @@ -70,24 +69,9 @@ describe('e2e_mempool_limit', () => { expect.objectContaining({ status: TxStatus.PENDING }), ); - const txHash3 = await tx3.send({ wait: NO_WAIT }); - - const txDropped = await retryUntil( - async () => { - // one of the txs will be dropped. Which one is picked is somewhat random because all three will have the same fee - const receipts = await Promise.all([ - aztecNode.getTxReceipt(txHash1), - aztecNode.getTxReceipt(txHash2), - aztecNode.getTxReceipt(txHash3), - ]); - const numPending = receipts.reduce((count, r) => (r.status === TxStatus.PENDING ? count + 1 : count), 0); - return numPending < 3; - }, - 'Waiting for one of the txs to be evicted from the mempool', - 60, - 1, - ); - - expect(txDropped).toBe(true); + // tx3 should be rejected because pool is at capacity and its priority is not higher than existing txs + await expect(tx3.send({ wait: NO_WAIT })).rejects.toMatchObject({ + data: { code: 'LOW_PRIORITY_FEE' }, + }); }); }); diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 25200df67014..c4ffc3fa35e6 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -133,6 +133,29 @@ describe('P2P Client', () => { await client.stop(); }); + it('throws TxPoolError with structured reason when pool rejects tx', async () => { + await client.start(); + const tx1 = await mockTx(); + const txHashStr = tx1.getTxHash().toString(); + const errors = new Map(); + errors.set(txHashStr, { + code: 'LOW_PRIORITY_FEE', + message: 'Tx does not meet minimum priority fee', + minimumPriorityFee: 101n, + txPriorityFee: 50n, + }); + txPool.addPendingTxs.mockResolvedValueOnce({ + accepted: [], + ignored: [tx1.getTxHash()], + rejected: [], + errors, + }); + + await expect(client.sendTx(tx1)).rejects.toThrow('Tx does not meet minimum priority fee'); + expect(p2pService.propagate).not.toHaveBeenCalled(); + await client.stop(); + }); + it('rejects txs after being stopped', async () => { await client.start(); const tx1 = await mockTx(); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 4bcfa04539a3..4851b7bf7bbd 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -32,6 +32,7 @@ import type { PeerId } from '@libp2p/interface'; import type { ENR } from '@nethermindeth/enr'; import { type P2PConfig, getP2PDefaultConfig } from '../config.js'; +import { TxPoolError } from '../errors/tx-pool.error.js'; import type { AttestationPoolApi } from '../mem_pools/attestation_pool/attestation_pool.js'; import type { MemPools } from '../mem_pools/interface.js'; import type { TxPoolV2 } from '../mem_pools/tx_pool_v2/interfaces.js'; @@ -585,11 +586,19 @@ export class P2PClient const result = await this.txPool.addPendingTxs([tx], { feeComparisonOnly: true }); if (result.accepted.length === 1) { await this.p2pService.propagate(tx); - } else { - this.log.warn( - `Tx ${tx.getTxHash()} not propagated: accepted=${result.accepted.length} ignored=${result.ignored.length} rejected=${result.rejected.length}`, - ); + return; } + + const txHashStr = tx.getTxHash().toString(); + const reason = result.errors?.get(txHashStr); + if (reason) { + this.log.warn(`Tx ${txHashStr} not added to pool: ${reason.message}`); + throw new TxPoolError(reason); + } + + this.log.warn( + `Tx ${txHashStr} not propagated: accepted=${result.accepted.length} ignored=${result.ignored.length} rejected=${result.rejected.length}`, + ); } /** diff --git a/yarn-project/p2p/src/errors/tx-pool.error.ts b/yarn-project/p2p/src/errors/tx-pool.error.ts new file mode 100644 index 000000000000..1b59099757e4 --- /dev/null +++ b/yarn-project/p2p/src/errors/tx-pool.error.ts @@ -0,0 +1,12 @@ +import type { TxPoolRejectionError } from '../mem_pools/tx_pool_v2/eviction/interfaces.js'; + +/** Error thrown when a transaction is not added to the mempool. */ +export class TxPoolError extends Error { + public readonly data: TxPoolRejectionError; + + constructor(public readonly reason: TxPoolRejectionError) { + super(reason.message); + this.name = 'TxPoolError'; + this.data = reason; + } +} diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.test.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.test.ts index f7fd0d3252de..974f2e31b6c9 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.test.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.test.ts @@ -12,6 +12,7 @@ import { type PoolOperations, type PreAddPoolAccess, type PreAddRule, + TxPoolRejectionCode, } from './interfaces.js'; describe('EvictionManager', () => { @@ -226,10 +227,15 @@ describe('EvictionManager', () => { it('returns ignore result immediately when a rule says to ignore', async () => { const preAddRule2 = mock({ name: 'preAddRule2' }); + const testReason = { + code: 'NULLIFIER_CONFLICT' as const, + message: 'test reason', + conflictingTxHash: '0x9999', + }; preAddRule.check.mockResolvedValue({ shouldIgnore: true, txHashesToEvict: [], - reason: 'test reason', + reason: testReason, }); preAddRule2.check.mockResolvedValue({ shouldIgnore: false, @@ -243,7 +249,7 @@ describe('EvictionManager', () => { const result = await evictionManager.runPreAddRules(incomingMeta, poolAccess); expect(result.shouldIgnore).toBe(true); - expect(result.reason).toBe('test reason'); + expect(result.reason).toEqual(testReason); expect(preAddRule.check).toHaveBeenCalledTimes(1); // Second rule should not be called since first rule ignored expect(preAddRule2.check).not.toHaveBeenCalled(); @@ -351,7 +357,9 @@ describe('EvictionManager', () => { const result = await evictionManager.runPreAddRules(incomingMeta, poolAccess); expect(result.shouldIgnore).toBe(true); - expect(result.reason).toContain('failingRule'); + expect(result.reason).toBeDefined(); + expect(result.reason!.code).toBe(TxPoolRejectionCode.INTERNAL_ERROR); + expect(result.reason!.message).toContain('failingRule'); expect(result.txHashesToEvict).toHaveLength(0); // Second rule should not be called since first rule threw expect(preAddRule2.check).not.toHaveBeenCalled(); diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts index b2a8ad122011..f8245cf4614e 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts @@ -14,6 +14,7 @@ import { type PreAddResult, type PreAddRule, type TaggedEviction, + TxPoolRejectionCode, } from './interfaces.js'; /** @@ -78,7 +79,10 @@ export class EvictionManager { return { shouldIgnore: true, txHashesToEvict: [], - reason: `pre-add rule ${rule.name} error: ${err}`, + reason: { + code: TxPoolRejectionCode.INTERNAL_ERROR, + message: `Pre-add rule ${rule.name} error: ${err}`, + }, }; } } diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.test.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.test.ts index 1c7898a3491e..b103c0ef404f 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.test.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.test.ts @@ -1,6 +1,6 @@ import { type TxMetaData, stubTxMetaValidationData } from '../tx_metadata.js'; import { FeePayerBalancePreAddRule } from './fee_payer_balance_pre_add_rule.js'; -import type { PreAddPoolAccess } from './interfaces.js'; +import { type PreAddPoolAccess, TxPoolRejectionCode } from './interfaces.js'; describe('FeePayerBalancePreAddRule', () => { let rule: FeePayerBalancePreAddRule; @@ -63,7 +63,12 @@ describe('FeePayerBalancePreAddRule', () => { expect(result.shouldIgnore).toBe(true); expect(result.txHashesToEvict).toHaveLength(0); - expect(result.reason).toContain('insufficient balance'); + expect(result.reason).toBeDefined(); + expect(result.reason!.code).toBe(TxPoolRejectionCode.INSUFFICIENT_FEE_PAYER_BALANCE); + if (result.reason!.code === TxPoolRejectionCode.INSUFFICIENT_FEE_PAYER_BALANCE) { + expect(result.reason!.currentBalance).toBe(50n); + expect(result.reason!.feeLimit).toBe(100n); + } }); it('accepts tx when balance exactly equals fee limit', async () => { @@ -108,7 +113,8 @@ describe('FeePayerBalancePreAddRule', () => { const result = await rule.check(incomingMeta, poolAccess); expect(result.shouldIgnore).toBe(true); - expect(result.reason).toContain('insufficient balance'); + expect(result.reason).toBeDefined(); + expect(result.reason!.code).toBe(TxPoolRejectionCode.INSUFFICIENT_FEE_PAYER_BALANCE); }); it('evicts lower-priority existing tx when high-priority tx is added', async () => { @@ -263,7 +269,8 @@ describe('FeePayerBalancePreAddRule', () => { expect(result.shouldIgnore).toBe(true); expect(result.reason).toBeDefined(); - expect(result.reason).toContain('insufficient balance'); + expect(result.reason!.code).toBe(TxPoolRejectionCode.INSUFFICIENT_FEE_PAYER_BALANCE); + expect(result.reason!.message).toContain('insufficient balance'); }); }); }); diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts index daa5ce665cfc..0cdebb94948d 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts @@ -1,7 +1,13 @@ import { createLogger } from '@aztec/foundation/log'; import { type TxMetaData, comparePriority } from '../tx_metadata.js'; -import type { PreAddContext, PreAddPoolAccess, PreAddResult, PreAddRule } from './interfaces.js'; +import { + type PreAddContext, + type PreAddPoolAccess, + type PreAddResult, + type PreAddRule, + TxPoolRejectionCode, +} from './interfaces.js'; /** * Pre-add rule that checks if a fee payer has sufficient balance to cover the incoming transaction. @@ -78,7 +84,13 @@ export class FeePayerBalancePreAddRule implements PreAddRule { return { shouldIgnore: true, txHashesToEvict: [], - reason: `fee payer ${incomingMeta.feePayer} has insufficient balance`, + reason: { + code: TxPoolRejectionCode.INSUFFICIENT_FEE_PAYER_BALANCE, + message: `Fee payer ${incomingMeta.feePayer} has insufficient balance. Balance at transaction: ${available}, required: ${incomingMeta.feeLimit}`, + currentBalance: initialBalance, + availableBalance: available, + feeLimit: incomingMeta.feeLimit, + }, }; } else { // Existing tx cannot be covered after adding incoming - mark for eviction @@ -93,7 +105,6 @@ export class FeePayerBalancePreAddRule implements PreAddRule { return { shouldIgnore: true, txHashesToEvict: [], - reason: 'internal error: tx coverage not determined', }; } diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/index.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/index.ts index e084e02039d8..79abcdc12812 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/index.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/index.ts @@ -11,6 +11,8 @@ export { type PreAddResult, type PreAddRule, type TaggedEviction, + TxPoolRejectionCode, + type TxPoolRejectionError, } from './interfaces.js'; // Pre-add rules diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/interfaces.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/interfaces.ts index ab5af10718cf..32135758973d 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/interfaces.ts @@ -73,6 +73,36 @@ export interface TaggedEviction { readonly reason: string; } +/** + * Machine-readable rejection codes for pre-add rule rejections. + */ +export const TxPoolRejectionCode = { + LOW_PRIORITY_FEE: 'LOW_PRIORITY_FEE', + INSUFFICIENT_FEE_PAYER_BALANCE: 'INSUFFICIENT_FEE_PAYER_BALANCE', + NULLIFIER_CONFLICT: 'NULLIFIER_CONFLICT', + INTERNAL_ERROR: 'INTERNAL_ERROR', +} as const; + +export type TxPoolRejectionCode = (typeof TxPoolRejectionCode)[keyof typeof TxPoolRejectionCode]; + +/** Structured rejection reason returned by pre-add rules. */ +export type TxPoolRejectionError = + | { + code: typeof TxPoolRejectionCode.LOW_PRIORITY_FEE; + message: string; + minimumPriorityFee: bigint; + txPriorityFee: bigint; + } + | { + code: typeof TxPoolRejectionCode.INSUFFICIENT_FEE_PAYER_BALANCE; + message: string; + currentBalance: bigint; + availableBalance: bigint; + feeLimit: bigint; + } + | { code: typeof TxPoolRejectionCode.NULLIFIER_CONFLICT; message: string; conflictingTxHash: string } + | { code: typeof TxPoolRejectionCode.INTERNAL_ERROR; message: string }; + /** * Result of a pre-add check for a single transaction. */ @@ -84,7 +114,7 @@ export interface PreAddResult { /** Evictions tagged with the rule name that produced them. Populated by EvictionManager. */ readonly evictions?: TaggedEviction[]; /** Optional reason for ignoring */ - readonly reason?: string; + readonly reason?: TxPoolRejectionError; } /** Context passed to pre-add rules from addPendingTxs. */ diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.test.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.test.ts index e2fc58850110..2e61088b3b5b 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.test.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.test.ts @@ -1,5 +1,5 @@ import { type TxMetaData, comparePriority, stubTxMetaValidationData } from '../tx_metadata.js'; -import type { PreAddContext, PreAddPoolAccess } from './interfaces.js'; +import { type PreAddContext, type PreAddPoolAccess, TxPoolRejectionCode } from './interfaces.js'; import { LowPriorityPreAddRule } from './low_priority_pre_add_rule.js'; describe('LowPriorityPreAddRule', () => { @@ -101,7 +101,12 @@ describe('LowPriorityPreAddRule', () => { expect(result.shouldIgnore).toBe(true); expect(result.txHashesToEvict).toHaveLength(0); - expect(result.reason).toContain('lower priority'); + expect(result.reason).toBeDefined(); + expect(result.reason!.code).toBe(TxPoolRejectionCode.LOW_PRIORITY_FEE); + if (result.reason!.code === TxPoolRejectionCode.LOW_PRIORITY_FEE) { + expect(result.reason!.minimumPriorityFee).toBe(101n); + expect(result.reason!.txPriorityFee).toBe(50n); + } }); it('ignores tx when incoming has equal priority to lowest', async () => { diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts index fa5cc0360dcd..b4d5ef8382db 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts @@ -1,7 +1,14 @@ import { createLogger } from '@aztec/foundation/log'; import { type TxMetaData, comparePriority } from '../tx_metadata.js'; -import type { EvictionConfig, PreAddContext, PreAddPoolAccess, PreAddResult, PreAddRule } from './interfaces.js'; +import { + type EvictionConfig, + type PreAddContext, + type PreAddPoolAccess, + type PreAddResult, + type PreAddRule, + TxPoolRejectionCode, +} from './interfaces.js'; /** * Pre-add rule that checks if the pool is at capacity and handles low-priority eviction. @@ -66,7 +73,12 @@ export class LowPriorityPreAddRule implements PreAddRule { return Promise.resolve({ shouldIgnore: true, txHashesToEvict: [], - reason: `pool at capacity and tx has lower priority than existing transactions`, + reason: { + code: TxPoolRejectionCode.LOW_PRIORITY_FEE, + message: `Tx does not meet minimum priority fee. Required: ${lowestPriorityMeta.priorityFee + 1n}, got: ${incomingMeta.priorityFee}`, + minimumPriorityFee: lowestPriorityMeta.priorityFee + 1n, + txPriorityFee: incomingMeta.priorityFee, + }, }); } diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts index 05378999f704..9b638e13e83d 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts @@ -23,7 +23,7 @@ export class NullifierConflictRule implements PreAddRule { ); if (result.shouldIgnore) { - this.log.debug(`Ignoring tx ${incomingMeta.txHash}: ${result.reason}`); + this.log.debug(`Ignoring tx ${incomingMeta.txHash}: ${result.reason?.message}`); } return Promise.resolve(result); diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/interfaces.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/interfaces.ts index 908e42a1eee7..ca23965e117e 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/interfaces.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/interfaces.ts @@ -4,6 +4,7 @@ import type { L2Block, L2BlockId, L2BlockSource } from '@aztec/stdlib/block'; import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server'; import type { BlockHeader, Tx, TxHash, TxValidator } from '@aztec/stdlib/tx'; +import type { TxPoolRejectionError } from './eviction/interfaces.js'; import type { TxMetaData, TxState } from './tx_metadata.js'; /** @@ -17,6 +18,8 @@ export type AddTxsResult = { ignored: TxHash[]; /** Transactions rejected because they failed validation (e.g., invalid proof, expired timestamp) */ rejected: TxHash[]; + /** Optional rejection errors, only present when there are rejections with structured errors. */ + errors?: Map; }; /** diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.test.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.test.ts index 54123c98a798..9f3e076f9f29 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.test.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.test.ts @@ -1,5 +1,6 @@ import { mockTx } from '@aztec/stdlib/testing'; +import { TxPoolRejectionCode } from './eviction/interfaces.js'; import { type TxMetaData, buildTxMetaData, @@ -131,7 +132,11 @@ describe('TxMetaData', () => { expect(result.shouldIgnore).toBe(true); expect(result.txHashesToEvict).toEqual([]); - expect(result.reason).toContain(existing.txHash); + expect(result.reason).toBeDefined(); + expect(result.reason!.code).toBe(TxPoolRejectionCode.NULLIFIER_CONFLICT); + if (result.reason!.code === TxPoolRejectionCode.NULLIFIER_CONFLICT) { + expect(result.reason!.conflictingTxHash).toBe(existing.txHash); + } }); it('ignores incoming tx when existing has equal priority (tie goes to existing)', () => { diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.ts index 64b35b374401..61b12a5debf8 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_metadata.ts @@ -6,7 +6,7 @@ import type { Tx } from '@aztec/stdlib/tx'; import { getFeePayerBalanceDelta } from '../../msg_validators/tx_validator/fee_payer_balance.js'; import { getTxPriorityFee } from '../tx_pool/priority.js'; -import type { PreAddResult } from './eviction/interfaces.js'; +import { type PreAddResult, TxPoolRejectionCode } from './eviction/interfaces.js'; /** Validator-compatible data interface, mirroring the subset of PrivateKernelTailCircuitPublicInputs used by validators. */ export type TxMetaValidationData = { @@ -215,7 +215,11 @@ export function checkNullifierConflict( return { shouldIgnore: true, txHashesToEvict: [], - reason: `nullifier conflict with ${conflictingHashStr}`, + reason: { + code: TxPoolRejectionCode.NULLIFIER_CONFLICT, + message: `Nullifier conflict with existing tx ${conflictingHashStr}`, + conflictingTxHash: conflictingHashStr, + }, }; } } diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts index 73a16d061228..4b149185e63b 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts @@ -25,6 +25,8 @@ import { type PoolOperations, type PreAddContext, type PreAddPoolAccess, + TxPoolRejectionCode, + type TxPoolRejectionError, } from './eviction/index.js'; import { TxPoolV2Instrumentation } from './instrumentation.js'; import { @@ -182,6 +184,7 @@ export class TxPoolV2Impl { const accepted: TxHash[] = []; const ignored: TxHash[] = []; const rejected: TxHash[] = []; + const errors = new Map(); const acceptedPending = new Set(); const poolAccess = this.#createPreAddPoolAccess(); @@ -219,6 +222,7 @@ export class TxPoolV2Impl { poolAccess, acceptedPending, ignored, + errors, preAddContext, ); if (result.status === 'accepted') { @@ -252,7 +256,7 @@ export class TxPoolV2Impl { await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]); } - return { accepted, ignored, rejected }; + return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) }; } /** Validates and adds a regular pending tx. Returns status. */ @@ -262,6 +266,7 @@ export class TxPoolV2Impl { poolAccess: PreAddPoolAccess, acceptedPending: Set, ignored: TxHash[], + errors: Map, preAddContext?: PreAddContext, ): Promise<{ status: 'accepted' | 'ignored' | 'rejected' }> { const txHash = tx.getTxHash(); @@ -277,7 +282,10 @@ export class TxPoolV2Impl { const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess, preAddContext); if (preAddResult.shouldIgnore) { - this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason}`); + this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`); + if (preAddResult.reason && preAddResult.reason.code !== TxPoolRejectionCode.INTERNAL_ERROR) { + errors.set(txHashStr, preAddResult.reason); + } return { status: 'ignored' }; } @@ -965,7 +973,9 @@ export class TxPoolV2Impl { if (preAddResult.shouldIgnore) { // Transaction rejected - mark for deletion from DB rejected.push(meta.txHash); - this.#log.debug(`Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason}`); + this.#log.debug( + `Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason?.message ?? 'unknown reason'}`, + ); continue; }