@aztec/p2p 0.0.1-commit.8f9871590 → 0.0.1-commit.934299a21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/dest/client/factory.d.ts +3 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +13 -23
  4. package/dest/client/interface.d.ts +9 -18
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +5 -16
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +40 -71
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +5 -5
  10. package/dest/config.d.ts +4 -6
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +0 -5
  13. package/dest/errors/tx-pool.error.d.ts +8 -0
  14. package/dest/errors/tx-pool.error.d.ts.map +1 -0
  15. package/dest/errors/tx-pool.error.js +9 -0
  16. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -1
  17. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  18. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +9 -0
  19. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +3 -3
  20. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -1
  21. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +18 -9
  22. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -3
  23. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +3 -3
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +10 -4
  26. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +2 -2
  27. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/index.js +1 -1
  29. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +48 -5
  30. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +8 -0
  32. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +2 -2
  33. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +5 -5
  34. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
  35. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  36. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +12 -6
  37. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +4 -4
  38. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  39. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +14 -4
  40. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +3 -3
  41. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  42. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  43. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts +15 -0
  44. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts.map +1 -0
  45. package/dest/mem_pools/tx_pool_v2/instrumentation.js +43 -0
  46. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +16 -6
  47. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool_v2/interfaces.js +3 -1
  49. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +12 -2
  50. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  51. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +37 -1
  52. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +5 -2
  53. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  54. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +12 -2
  55. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +6 -3
  56. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  57. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -5
  58. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +11 -5
  59. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  60. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +241 -130
  61. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
  62. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  63. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  64. package/dest/msg_validators/tx_validator/factory.d.ts +114 -6
  65. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  66. package/dest/msg_validators/tx_validator/factory.js +219 -58
  67. package/dest/msg_validators/tx_validator/gas_validator.d.ts +58 -3
  68. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  69. package/dest/msg_validators/tx_validator/gas_validator.js +73 -36
  70. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  71. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  72. package/dest/msg_validators/tx_validator/index.js +1 -0
  73. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  74. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  75. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  76. package/dest/services/dummy_service.d.ts +4 -4
  77. package/dest/services/dummy_service.d.ts.map +1 -1
  78. package/dest/services/dummy_service.js +4 -4
  79. package/dest/services/encoding.d.ts +1 -1
  80. package/dest/services/encoding.d.ts.map +1 -1
  81. package/dest/services/encoding.js +2 -1
  82. package/dest/services/gossipsub/topic_score_params.d.ts +18 -6
  83. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  84. package/dest/services/gossipsub/topic_score_params.js +32 -10
  85. package/dest/services/libp2p/libp2p_service.d.ts +11 -7
  86. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  87. package/dest/services/libp2p/libp2p_service.js +62 -71
  88. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -3
  89. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  90. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +19 -46
  91. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +2 -6
  92. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  93. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +10 -13
  94. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  95. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +25 -46
  96. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +17 -11
  97. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  98. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +49 -15
  99. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  100. package/dest/services/service.d.ts +5 -3
  101. package/dest/services/service.d.ts.map +1 -1
  102. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -1
  103. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  104. package/dest/services/tx_collection/fast_tx_collection.js +39 -33
  105. package/dest/services/tx_collection/file_store_tx_collection.d.ts +1 -1
  106. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -1
  107. package/dest/services/tx_collection/file_store_tx_collection.js +4 -2
  108. package/dest/services/tx_collection/file_store_tx_source.d.ts +15 -6
  109. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  110. package/dest/services/tx_collection/file_store_tx_source.js +47 -16
  111. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  112. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  113. package/dest/services/tx_collection/instrumentation.js +2 -1
  114. package/dest/services/tx_collection/missing_txs_tracker.d.ts +32 -0
  115. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +1 -0
  116. package/dest/services/tx_collection/missing_txs_tracker.js +27 -0
  117. package/dest/services/tx_collection/proposal_tx_collector.d.ts +7 -6
  118. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  119. package/dest/services/tx_collection/proposal_tx_collector.js +5 -4
  120. package/dest/services/tx_collection/slow_tx_collection.d.ts +2 -2
  121. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  122. package/dest/services/tx_collection/slow_tx_collection.js +10 -8
  123. package/dest/services/tx_collection/tx_collection.d.ts +5 -4
  124. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  125. package/dest/services/tx_collection/tx_collection_sink.d.ts +6 -5
  126. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  127. package/dest/services/tx_collection/tx_collection_sink.js +13 -22
  128. package/dest/services/tx_collection/tx_source.d.ts +8 -3
  129. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  130. package/dest/services/tx_collection/tx_source.js +19 -2
  131. package/dest/services/tx_file_store/tx_file_store.js +1 -1
  132. package/dest/services/tx_provider.d.ts +3 -3
  133. package/dest/services/tx_provider.d.ts.map +1 -1
  134. package/dest/services/tx_provider.js +4 -4
  135. package/dest/test-helpers/mock-pubsub.d.ts +3 -2
  136. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  137. package/dest/test-helpers/mock-pubsub.js +6 -0
  138. package/dest/test-helpers/testbench-utils.d.ts +6 -3
  139. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  140. package/dest/test-helpers/testbench-utils.js +1 -1
  141. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -2
  142. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  143. package/dest/testbench/p2p_client_testbench_worker.js +6 -6
  144. package/dest/util.d.ts +2 -2
  145. package/dest/util.d.ts.map +1 -1
  146. package/package.json +14 -14
  147. package/src/client/factory.ts +19 -35
  148. package/src/client/interface.ts +16 -19
  149. package/src/client/p2p_client.ts +46 -93
  150. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +18 -8
  151. package/src/config.ts +2 -10
  152. package/src/errors/tx-pool.error.ts +12 -0
  153. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +11 -0
  154. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +21 -8
  155. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -3
  156. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +15 -4
  157. package/src/mem_pools/tx_pool_v2/eviction/index.ts +4 -0
  158. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +49 -4
  159. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +2 -2
  160. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +5 -5
  161. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +12 -9
  162. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +24 -6
  163. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +3 -3
  164. package/src/mem_pools/tx_pool_v2/instrumentation.ts +69 -0
  165. package/src/mem_pools/tx_pool_v2/interfaces.ts +15 -6
  166. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +46 -2
  167. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +14 -3
  168. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +12 -7
  169. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +264 -125
  170. package/src/msg_validators/tx_validator/README.md +115 -0
  171. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
  172. package/src/msg_validators/tx_validator/factory.ts +353 -77
  173. package/src/msg_validators/tx_validator/gas_validator.ts +90 -27
  174. package/src/msg_validators/tx_validator/index.ts +1 -0
  175. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  176. package/src/services/dummy_service.ts +6 -6
  177. package/src/services/encoding.ts +2 -1
  178. package/src/services/gossipsub/README.md +29 -14
  179. package/src/services/gossipsub/topic_score_params.ts +49 -13
  180. package/src/services/libp2p/libp2p_service.ts +75 -79
  181. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +20 -48
  182. package/src/services/reqresp/batch-tx-requester/interface.ts +1 -5
  183. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +23 -71
  184. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +63 -24
  185. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  186. package/src/services/service.ts +11 -2
  187. package/src/services/tx_collection/fast_tx_collection.ts +51 -30
  188. package/src/services/tx_collection/file_store_tx_collection.ts +7 -3
  189. package/src/services/tx_collection/file_store_tx_source.ts +61 -17
  190. package/src/services/tx_collection/instrumentation.ts +7 -1
  191. package/src/services/tx_collection/missing_txs_tracker.ts +52 -0
  192. package/src/services/tx_collection/proposal_tx_collector.ts +8 -7
  193. package/src/services/tx_collection/slow_tx_collection.ts +8 -9
  194. package/src/services/tx_collection/tx_collection.ts +4 -3
  195. package/src/services/tx_collection/tx_collection_sink.ts +15 -29
  196. package/src/services/tx_collection/tx_source.ts +22 -3
  197. package/src/services/tx_file_store/tx_file_store.ts +1 -1
  198. package/src/services/tx_provider.ts +2 -2
  199. package/src/test-helpers/mock-pubsub.ts +10 -0
  200. package/src/test-helpers/testbench-utils.ts +3 -3
  201. package/src/testbench/p2p_client_testbench_worker.ts +18 -11
  202. package/src/util.ts +7 -1
@@ -9,6 +9,7 @@ import type { L2Block, L2BlockId, L2BlockSource } from '@aztec/stdlib/block';
9
9
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
10
10
  import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
11
11
  import { BlockHeader, Tx, TxHash, type TxValidator } from '@aztec/stdlib/tx';
12
+ import type { TelemetryClient } from '@aztec/telemetry-client';
12
13
 
13
14
  import { TxArchive } from './archive/index.js';
14
15
  import { DeletedPool } from './deleted_pool.js';
@@ -22,8 +23,12 @@ import {
22
23
  LowPriorityPreAddRule,
23
24
  NullifierConflictRule,
24
25
  type PoolOperations,
26
+ type PreAddContext,
25
27
  type PreAddPoolAccess,
28
+ TxPoolRejectionCode,
29
+ type TxPoolRejectionError,
26
30
  } from './eviction/index.js';
31
+ import { TxPoolV2Instrumentation } from './instrumentation.js';
27
32
  import {
28
33
  type AddTxsResult,
29
34
  DEFAULT_TX_POOL_V2_CONFIG,
@@ -66,6 +71,8 @@ export class TxPoolV2Impl {
66
71
  #deletedPool: DeletedPool;
67
72
  #evictionManager: EvictionManager;
68
73
  #dateProvider: DateProvider;
74
+ #instrumentation: TxPoolV2Instrumentation;
75
+ #evictedTxHashes: Set<string> = new Set();
69
76
  #log: Logger;
70
77
  #callbacks: TxPoolV2Callbacks;
71
78
 
@@ -74,6 +81,7 @@ export class TxPoolV2Impl {
74
81
  archiveStore: AztecAsyncKVStore,
75
82
  deps: TxPoolV2Dependencies,
76
83
  callbacks: TxPoolV2Callbacks,
84
+ telemetry: TelemetryClient,
77
85
  config: Partial<TxPoolV2Config> = {},
78
86
  dateProvider: DateProvider,
79
87
  log: Logger,
@@ -89,6 +97,7 @@ export class TxPoolV2Impl {
89
97
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
90
98
  this.#deletedPool = new DeletedPool(store, this.#txsDB, log);
91
99
  this.#dateProvider = dateProvider;
100
+ this.#instrumentation = new TxPoolV2Instrumentation(telemetry, () => this.#indices.getTotalMetadataBytes());
92
101
  this.#log = log;
93
102
  this.#callbacks = callbacks;
94
103
 
@@ -171,13 +180,16 @@ export class TxPoolV2Impl {
171
180
  this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`, { txHashes: toDelete });
172
181
  }
173
182
 
174
- async addPendingTxs(txs: Tx[], opts: { source?: string }): Promise<AddTxsResult> {
183
+ async addPendingTxs(txs: Tx[], opts: { source?: string; feeComparisonOnly?: boolean }): Promise<AddTxsResult> {
175
184
  const accepted: TxHash[] = [];
176
185
  const ignored: TxHash[] = [];
177
186
  const rejected: TxHash[] = [];
187
+ const errors = new Map<string, TxPoolRejectionError>();
178
188
  const acceptedPending = new Set<string>();
179
189
 
180
190
  const poolAccess = this.#createPreAddPoolAccess();
191
+ const preAddContext: PreAddContext | undefined =
192
+ opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
181
193
 
182
194
  await this.#store.transactionAsync(async () => {
183
195
  for (const tx of txs) {
@@ -204,7 +216,15 @@ export class TxPoolV2Impl {
204
216
  accepted.push(txHash);
205
217
  } else {
206
218
  // Regular pending tx - validate and run pre-add rules
207
- const result = await this.#tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored);
219
+ const result = await this.#tryAddRegularPendingTx(
220
+ tx,
221
+ opts,
222
+ poolAccess,
223
+ acceptedPending,
224
+ ignored,
225
+ errors,
226
+ preAddContext,
227
+ );
208
228
  if (result.status === 'accepted') {
209
229
  acceptedPending.add(txHashStr);
210
230
  } else if (result.status === 'rejected') {
@@ -214,6 +234,13 @@ export class TxPoolV2Impl {
214
234
  }
215
235
  }
216
236
  }
237
+
238
+ // Run post-add eviction rules for pending txs (inside transaction for atomicity)
239
+ if (acceptedPending.size > 0) {
240
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
241
+ const uniqueFeePayers = new Set<string>(feePayers);
242
+ await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
243
+ }
217
244
  });
218
245
 
219
246
  // Build final accepted list for pending txs (excludes intra-batch evictions)
@@ -221,14 +248,15 @@ export class TxPoolV2Impl {
221
248
  accepted.push(TxHash.fromString(txHashStr));
222
249
  }
223
250
 
224
- // Run post-add eviction rules for pending txs
225
- if (acceptedPending.size > 0) {
226
- const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
227
- const uniqueFeePayers = new Set<string>(feePayers);
228
- await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
251
+ // Record metrics
252
+ if (ignored.length > 0) {
253
+ this.#instrumentation.recordIgnored(ignored.length);
254
+ }
255
+ if (rejected.length > 0) {
256
+ this.#instrumentation.recordRejected(rejected.length);
229
257
  }
230
258
 
231
- return { accepted, ignored, rejected };
259
+ return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
232
260
  }
233
261
 
234
262
  /** Validates and adds a regular pending tx. Returns status. */
@@ -238,6 +266,8 @@ export class TxPoolV2Impl {
238
266
  poolAccess: PreAddPoolAccess,
239
267
  acceptedPending: Set<string>,
240
268
  ignored: TxHash[],
269
+ errors: Map<string, TxPoolRejectionError>,
270
+ preAddContext?: PreAddContext,
241
271
  ): Promise<{ status: 'accepted' | 'ignored' | 'rejected' }> {
242
272
  const txHash = tx.getTxHash();
243
273
  const txHashStr = txHash.toString();
@@ -249,25 +279,47 @@ export class TxPoolV2Impl {
249
279
  }
250
280
 
251
281
  // Run pre-add rules
252
- const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
282
+ const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess, preAddContext);
253
283
 
254
284
  if (preAddResult.shouldIgnore) {
255
- this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason}`);
285
+ this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
286
+ if (preAddResult.reason && preAddResult.reason.code !== TxPoolRejectionCode.INTERNAL_ERROR) {
287
+ errors.set(txHashStr, preAddResult.reason);
288
+ }
256
289
  return { status: 'ignored' };
257
290
  }
258
291
 
259
- // Evict conflicts
260
- for (const evictHashStr of preAddResult.txHashesToEvict) {
261
- await this.#deleteTx(evictHashStr);
262
- this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`, {
263
- evictedTxHash: evictHashStr,
264
- replacementTxHash: txHashStr,
265
- });
266
- if (acceptedPending.has(evictHashStr)) {
267
- // Evicted tx was from this batch - mark as ignored in result
268
- acceptedPending.delete(evictHashStr);
269
- ignored.push(TxHash.fromString(evictHashStr));
292
+ // Evict conflicts, grouped by rule name for metrics
293
+ if (preAddResult.evictions && preAddResult.evictions.length > 0) {
294
+ const byReason = new Map<string, string[]>();
295
+ for (const { txHash: evictHash, reason } of preAddResult.evictions) {
296
+ const group = byReason.get(reason);
297
+ if (group) {
298
+ group.push(evictHash);
299
+ } else {
300
+ byReason.set(reason, [evictHash]);
301
+ }
302
+ }
303
+ for (const [reason, hashes] of byReason) {
304
+ await this.#evictTxs(hashes, reason);
270
305
  }
306
+ for (const evictHashStr of preAddResult.txHashesToEvict) {
307
+ this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`, {
308
+ evictedTxHash: evictHashStr,
309
+ replacementTxHash: txHashStr,
310
+ });
311
+ if (acceptedPending.has(evictHashStr)) {
312
+ // Evicted tx was from this batch - mark as ignored in result
313
+ acceptedPending.delete(evictHashStr);
314
+ ignored.push(TxHash.fromString(evictHashStr));
315
+ }
316
+ }
317
+ }
318
+
319
+ // Randomly drop the transaction for testing purposes (report as accepted so it propagates)
320
+ if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
321
+ this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
322
+ return { status: 'accepted' };
271
323
  }
272
324
 
273
325
  // Add the transaction
@@ -275,7 +327,7 @@ export class TxPoolV2Impl {
275
327
  return { status: 'accepted' };
276
328
  }
277
329
 
278
- async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored' | 'rejected'> {
330
+ async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
279
331
  const txHashStr = tx.getTxHash().toString();
280
332
 
281
333
  // Check if already in pool
@@ -283,14 +335,8 @@ export class TxPoolV2Impl {
283
335
  return 'ignored';
284
336
  }
285
337
 
286
- // Build metadata and validate using metadata
338
+ // Build metadata and check pre-add rules
287
339
  const meta = await buildTxMetaData(tx);
288
- const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
289
- if (validationResult !== true) {
290
- return 'rejected';
291
- }
292
-
293
- // Use pre-add rules
294
340
  const poolAccess = this.#createPreAddPoolAccess();
295
341
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
296
342
 
@@ -327,22 +373,57 @@ export class TxPoolV2Impl {
327
373
  });
328
374
  }
329
375
 
330
- protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
376
+ async protectTxs(txHashes: TxHash[], block: BlockHeader): Promise<TxHash[]> {
331
377
  const slotNumber = block.globalVariables.slotNumber;
332
378
  const missing: TxHash[] = [];
379
+ let softDeletedHits = 0;
380
+ let missingPreviouslyEvicted = 0;
333
381
 
334
- for (const txHash of txHashes) {
335
- const txHashStr = txHash.toString();
382
+ await this.#store.transactionAsync(async () => {
383
+ for (const txHash of txHashes) {
384
+ const txHashStr = txHash.toString();
336
385
 
337
- if (this.#indices.has(txHashStr)) {
338
- // Update protection for existing tx
339
- this.#indices.updateProtection(txHashStr, slotNumber);
340
- } else {
341
- // Pre-record protection for tx we don't have yet
342
- this.#indices.setProtection(txHashStr, slotNumber);
343
- missing.push(txHash);
386
+ if (this.#indices.has(txHashStr)) {
387
+ // Update protection for existing tx
388
+ this.#indices.updateProtection(txHashStr, slotNumber);
389
+ } else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
390
+ // Resurrect soft-deleted tx as protected
391
+ const buffer = await this.#txsDB.getAsync(txHashStr);
392
+ if (buffer) {
393
+ const tx = Tx.fromBuffer(buffer);
394
+ await this.#addTx(tx, { protected: slotNumber });
395
+ softDeletedHits++;
396
+ } else {
397
+ // Data missing despite soft-delete flag — treat as truly missing
398
+ this.#indices.setProtection(txHashStr, slotNumber);
399
+ missing.push(txHash);
400
+ }
401
+ } else {
402
+ // Truly missing — pre-record protection for tx we don't have yet
403
+ this.#indices.setProtection(txHashStr, slotNumber);
404
+ missing.push(txHash);
405
+ if (this.#evictedTxHashes.has(txHashStr)) {
406
+ missingPreviouslyEvicted++;
407
+ }
408
+ }
344
409
  }
410
+ });
411
+
412
+ // Record metrics
413
+ if (softDeletedHits > 0) {
414
+ this.#instrumentation.recordSoftDeletedHits(softDeletedHits);
345
415
  }
416
+ if (missing.length > 0) {
417
+ this.#log.debug(`protectTxs missing tx hashes: ${missing.map(h => h.toString()).join(', ')}`);
418
+ this.#instrumentation.recordMissingOnProtect(missing.length);
419
+ }
420
+ if (missingPreviouslyEvicted > 0) {
421
+ this.#instrumentation.recordMissingPreviouslyEvicted(missingPreviouslyEvicted);
422
+ }
423
+
424
+ this.#log.info(
425
+ `Protected ${txHashes.length} txs, missing: ${missing.length}, soft-deleted hits: ${softDeletedHits}`,
426
+ );
346
427
 
347
428
  return missing;
348
429
  }
@@ -387,57 +468,63 @@ export class TxPoolV2Impl {
387
468
  }
388
469
  }
389
470
 
390
- // Step 4: Mark txs as mined (only those we have in the pool)
391
- for (const meta of found) {
392
- this.#indices.markAsMined(meta, blockId);
393
- await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
394
- }
471
+ await this.#store.transactionAsync(async () => {
472
+ // Step 4: Mark txs as mined (only those we have in the pool)
473
+ for (const meta of found) {
474
+ this.#indices.markAsMined(meta, blockId);
475
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
476
+ }
395
477
 
396
- // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
397
- await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
478
+ // Step 5: Run post-event eviction rules (inside transaction for atomicity)
479
+ await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
480
+ });
398
481
 
399
482
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
400
483
  }
401
484
 
402
485
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
403
- // Step 0: Clean up slot-deleted txs from previous slots
404
- await this.#deletedPool.cleanupSlotDeleted(slotNumber);
486
+ await this.#store.transactionAsync(async () => {
487
+ // Step 0: Clean up slot-deleted txs from previous slots
488
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
405
489
 
406
- // Step 1: Find expired protected txs
407
- const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
490
+ // Step 1: Find expired protected txs
491
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
408
492
 
409
- // Step 2: Clear protection for all expired entries (including those without metadata)
410
- this.#indices.clearProtection(expiredProtected);
493
+ // Step 2: Clear protection for all expired entries (including those without metadata)
494
+ this.#indices.clearProtection(expiredProtected);
411
495
 
412
- // Step 3: Filter to only txs that have metadata and are not mined
413
- const txsToRestore = this.#indices.filterRestorable(expiredProtected);
414
- if (txsToRestore.length === 0) {
415
- return;
416
- }
496
+ // Step 3: Filter to only txs that have metadata and are not mined
497
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
498
+ if (txsToRestore.length === 0) {
499
+ this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
500
+ return;
501
+ }
417
502
 
418
- this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
503
+ this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
419
504
 
420
- // Step 4: Validate for pending pool
421
- const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
505
+ // Step 4: Validate for pending pool
506
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
422
507
 
423
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
424
- const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
508
+ // Step 5: Resolve nullifier conflicts and add winners to pending indices
509
+ const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
425
510
 
426
- // Step 6: Delete invalid and evicted txs
427
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
511
+ // Step 6: Delete invalid txs and evict conflict losers
512
+ await this.#deleteTxsBatch(invalid);
513
+ await this.#evictTxs(toEvict, 'NullifierConflict');
428
514
 
429
- // Step 7: Run eviction rules (enforce pool size limit)
430
- if (added.length > 0) {
431
- const feePayers = added.map(meta => meta.feePayer);
432
- const uniqueFeePayers = new Set<string>(feePayers);
433
- await this.#evictionManager.evictAfterNewTxs(
434
- added.map(m => m.txHash),
435
- [...uniqueFeePayers],
436
- );
437
- }
515
+ // Step 7: Run eviction rules (enforce pool size limit)
516
+ if (added.length > 0) {
517
+ const feePayers = added.map(meta => meta.feePayer);
518
+ const uniqueFeePayers = new Set<string>(feePayers);
519
+ await this.#evictionManager.evictAfterNewTxs(
520
+ added.map(m => m.txHash),
521
+ [...uniqueFeePayers],
522
+ );
523
+ }
524
+ });
438
525
  }
439
526
 
440
- async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
527
+ async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
441
528
  // Step 1: Find transactions mined after the prune point
442
529
  const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
443
530
  if (txsToUnmine.length === 0) {
@@ -447,46 +534,60 @@ export class TxPoolV2Impl {
447
534
 
448
535
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
449
536
 
450
- // Step 2: Mark ALL un-mined txs with their original mined block number
451
- // This ensures they get soft-deleted if removed later, and only hard-deleted
452
- // when their original mined block is finalized
453
- await this.#deletedPool.markFromPrunedBlock(
454
- txsToUnmine.map(m => ({
455
- txHash: m.txHash,
456
- minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
457
- })),
458
- );
537
+ await this.#store.transactionAsync(async () => {
538
+ // Step 2: Mark ALL un-mined txs with their original mined block number
539
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
540
+ // when their original mined block is finalized
541
+ await this.#deletedPool.markFromPrunedBlock(
542
+ txsToUnmine.map(m => ({
543
+ txHash: m.txHash,
544
+ minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
545
+ })),
546
+ );
459
547
 
460
- // Step 3: Unmine - clear mined status from metadata
461
- for (const meta of txsToUnmine) {
462
- this.#indices.markAsUnmined(meta);
463
- }
548
+ // Step 3: Unmine - clear mined status from metadata
549
+ for (const meta of txsToUnmine) {
550
+ this.#indices.markAsUnmined(meta);
551
+ }
552
+
553
+ // If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
554
+ if (options?.deleteAllTxs) {
555
+ const allTxHashes = txsToUnmine.map(m => m.txHash);
556
+ await this.#deleteTxsBatch(allTxHashes);
557
+ this.#log.info(
558
+ `Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
559
+ );
560
+ return;
561
+ }
464
562
 
465
- // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
466
- const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
563
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
564
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
467
565
 
468
- // Step 4: Validate for pending pool
469
- const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
566
+ // Step 5: Validate for pending pool
567
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
470
568
 
471
- // Step 6: Resolve nullifier conflicts and add winners to pending indices
472
- const { toEvict } = this.#applyNullifierConflictResolution(valid);
569
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
570
+ const { toEvict } = this.#applyNullifierConflictResolution(valid);
473
571
 
474
- // Step 7: Delete invalid and evicted txs
475
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
572
+ // Step 7: Delete invalid txs and evict conflict losers
573
+ await this.#deleteTxsBatch(invalid);
574
+ await this.#evictTxs(toEvict, 'NullifierConflict');
476
575
 
477
- this.#log.info(
478
- `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
479
- { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
480
- );
576
+ this.#log.info(
577
+ `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
578
+ { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
579
+ );
481
580
 
482
- // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
483
- // This handles cases like existing pending txs with invalid fee payer balances
484
- await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
581
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
582
+ // This handles cases like existing pending txs with invalid fee payer balances
583
+ await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
584
+ });
485
585
  }
486
586
 
487
587
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
488
- // Delete failed txs
489
- await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
588
+ await this.#store.transactionAsync(async () => {
589
+ await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
590
+ });
490
591
 
491
592
  this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
492
593
  }
@@ -497,27 +598,29 @@ export class TxPoolV2Impl {
497
598
  // Step 1: Find mined txs at or before finalized block
498
599
  const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
499
600
 
500
- // Step 2: Collect mined txs for archiving (before deletion)
501
- const txsToArchive: Tx[] = [];
502
- if (this.#archive.isEnabled()) {
503
- for (const txHashStr of minedTxsToFinalize) {
504
- const buffer = await this.#txsDB.getAsync(txHashStr);
505
- if (buffer) {
506
- txsToArchive.push(Tx.fromBuffer(buffer));
601
+ await this.#store.transactionAsync(async () => {
602
+ // Step 2: Collect mined txs for archiving (before deletion)
603
+ const txsToArchive: Tx[] = [];
604
+ if (this.#archive.isEnabled()) {
605
+ for (const txHashStr of minedTxsToFinalize) {
606
+ const buffer = await this.#txsDB.getAsync(txHashStr);
607
+ if (buffer) {
608
+ txsToArchive.push(Tx.fromBuffer(buffer));
609
+ }
507
610
  }
508
611
  }
509
- }
510
612
 
511
- // Step 3: Delete mined txs from active pool
512
- await this.#deleteTxsBatch(minedTxsToFinalize);
613
+ // Step 3: Delete mined txs from active pool
614
+ await this.#deleteTxsBatch(minedTxsToFinalize);
513
615
 
514
- // Step 4: Finalize soft-deleted txs
515
- await this.#deletedPool.finalizeBlock(blockNumber);
616
+ // Step 4: Finalize soft-deleted txs
617
+ await this.#deletedPool.finalizeBlock(blockNumber);
516
618
 
517
- // Step 5: Archive mined txs
518
- if (txsToArchive.length > 0) {
519
- await this.#archive.archiveTxs(txsToArchive);
520
- }
619
+ // Step 5: Archive mined txs
620
+ if (txsToArchive.length > 0) {
621
+ await this.#archive.archiveTxs(txsToArchive);
622
+ }
623
+ });
521
624
 
522
625
  if (minedTxsToFinalize.length > 0) {
523
626
  this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
@@ -637,8 +740,17 @@ export class TxPoolV2Impl {
637
740
 
638
741
  // === Metrics ===
639
742
 
640
- countTxs(): { pending: number; protected: number; mined: number } {
641
- return this.#indices.countTxs();
743
+ countTxs(): {
744
+ pending: number;
745
+ protected: number;
746
+ mined: number;
747
+ softDeleted: number;
748
+ totalMetadataBytes: number;
749
+ } {
750
+ return {
751
+ ...this.#indices.countTxs(),
752
+ softDeleted: this.#deletedPool.getSoftDeletedCount(),
753
+ };
642
754
  }
643
755
 
644
756
  // ============================================================================
@@ -672,9 +784,11 @@ export class TxPoolV2Impl {
672
784
  }
673
785
 
674
786
  const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
675
- this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
787
+ this.#log.debug(`Added tx ${txHashStr} as ${stateStr}`, {
676
788
  eventName: 'tx-added-to-pool',
789
+ txHash: txHashStr,
677
790
  state: stateStr,
791
+ source: opts.source,
678
792
  });
679
793
 
680
794
  return meta;
@@ -702,6 +816,29 @@ export class TxPoolV2Impl {
702
816
  }
703
817
  }
704
818
 
819
+ /** Evicts transactions: records eviction metric with reason, caches hashes, then deletes. */
820
+ async #evictTxs(txHashes: string[], reason: string): Promise<void> {
821
+ if (txHashes.length === 0) {
822
+ return;
823
+ }
824
+ this.#instrumentation.recordEvictions(txHashes.length, reason);
825
+ for (const txHashStr of txHashes) {
826
+ this.#log.debug(`Evicting tx ${txHashStr}`, { txHash: txHashStr, reason });
827
+ this.#addToEvictedCache(txHashStr);
828
+ }
829
+ await this.#deleteTxsBatch(txHashes);
830
+ }
831
+
832
+ /** Adds a tx hash to the bounded evicted cache, evicting the oldest entry if at capacity. */
833
+ #addToEvictedCache(txHashStr: string): void {
834
+ if (this.#evictedTxHashes.size >= this.#config.evictedTxCacheSize) {
835
+ // FIFO eviction: remove the first (oldest) entry
836
+ const oldest = this.#evictedTxHashes.values().next().value!;
837
+ this.#evictedTxHashes.delete(oldest);
838
+ }
839
+ this.#evictedTxHashes.add(txHashStr);
840
+ }
841
+
705
842
  // ============================================================================
706
843
  // PRIVATE HELPERS - Validation & Conflict Resolution
707
844
  // ============================================================================
@@ -857,7 +994,9 @@ export class TxPoolV2Impl {
857
994
  if (preAddResult.shouldIgnore) {
858
995
  // Transaction rejected - mark for deletion from DB
859
996
  rejected.push(meta.txHash);
860
- this.#log.debug(`Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason}`);
997
+ this.#log.debug(
998
+ `Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason?.message ?? 'unknown reason'}`,
999
+ );
861
1000
  continue;
862
1001
  }
863
1002
 
@@ -893,7 +1032,7 @@ export class TxPoolV2Impl {
893
1032
  getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
894
1033
  getPendingTxCount: () => this.#indices.getPendingTxCount(),
895
1034
  getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
896
- deleteTxs: (txHashes: string[]) => this.#deleteTxsBatch(txHashes),
1035
+ deleteTxs: (txHashes: string[], reason?: string) => this.#evictTxs(txHashes, reason ?? 'unknown'),
897
1036
  };
898
1037
  }
899
1038