@aztec/p2p 0.0.1-commit.4eabbdb → 0.0.1-commit.5358163d3

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 (163) hide show
  1. package/dest/client/factory.d.ts +4 -5
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +15 -26
  4. package/dest/client/interface.d.ts +6 -13
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +5 -13
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +3 -58
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -2
  10. package/dest/config.d.ts +10 -14
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +25 -35
  13. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +1 -1
  14. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  15. package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -1
  16. package/dest/mem_pools/instrumentation.d.ts +4 -2
  17. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  18. package/dest/mem_pools/instrumentation.js +16 -14
  19. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
  20. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
  21. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  22. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
  23. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
  24. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
  25. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +10 -6
  27. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
  28. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/index.js +1 -1
  30. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -5
  31. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  32. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  33. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +26 -4
  34. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +48 -7
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +9 -10
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +2 -2
  40. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +3 -2
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  44. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +175 -145
  45. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +2 -1
  46. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  47. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +2 -1
  48. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  49. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +3 -1
  50. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  51. package/dest/msg_validators/proposal_validator/proposal_validator.js +10 -0
  52. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +2 -1
  53. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +1 -1
  54. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +166 -0
  55. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
  56. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  57. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  58. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
  59. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  60. package/dest/msg_validators/tx_validator/allowed_public_setup.js +25 -10
  61. package/dest/msg_validators/tx_validator/factory.d.ts +114 -6
  62. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  63. package/dest/msg_validators/tx_validator/factory.js +219 -58
  64. package/dest/msg_validators/tx_validator/gas_validator.d.ts +58 -3
  65. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  66. package/dest/msg_validators/tx_validator/gas_validator.js +73 -36
  67. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  68. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  69. package/dest/msg_validators/tx_validator/index.js +1 -0
  70. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  71. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  72. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  73. package/dest/msg_validators/tx_validator/phases_validator.d.ts +2 -2
  74. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  75. package/dest/msg_validators/tx_validator/phases_validator.js +25 -24
  76. package/dest/services/dummy_service.d.ts +2 -3
  77. package/dest/services/dummy_service.d.ts.map +1 -1
  78. package/dest/services/dummy_service.js +1 -4
  79. package/dest/services/encoding.d.ts +2 -2
  80. package/dest/services/encoding.d.ts.map +1 -1
  81. package/dest/services/encoding.js +7 -7
  82. package/dest/services/libp2p/libp2p_service.d.ts +15 -13
  83. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  84. package/dest/services/libp2p/libp2p_service.js +72 -83
  85. package/dest/services/peer-manager/metrics.d.ts +3 -1
  86. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  87. package/dest/services/peer-manager/metrics.js +6 -0
  88. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  89. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  90. package/dest/services/peer-manager/peer_manager.js +2 -1
  91. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
  92. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  93. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +14 -37
  94. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +17 -11
  95. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  96. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +49 -15
  97. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  98. package/dest/services/reqresp/reqresp.d.ts +1 -1
  99. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  100. package/dest/services/reqresp/reqresp.js +2 -1
  101. package/dest/services/service.d.ts +2 -2
  102. package/dest/services/service.d.ts.map +1 -1
  103. package/dest/services/tx_provider.d.ts +3 -3
  104. package/dest/services/tx_provider.d.ts.map +1 -1
  105. package/dest/services/tx_provider.js +4 -4
  106. package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
  107. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  108. package/dest/test-helpers/make-test-p2p-clients.js +1 -2
  109. package/dest/test-helpers/mock-pubsub.d.ts +2 -3
  110. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  111. package/dest/test-helpers/mock-pubsub.js +2 -2
  112. package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
  113. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  114. package/dest/test-helpers/reqresp-nodes.js +2 -2
  115. package/dest/test-helpers/testbench-utils.d.ts +2 -2
  116. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  117. package/dest/testbench/p2p_client_testbench_worker.js +5 -5
  118. package/package.json +14 -14
  119. package/src/client/factory.ts +22 -46
  120. package/src/client/interface.ts +5 -19
  121. package/src/client/p2p_client.ts +4 -88
  122. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -2
  123. package/src/config.ts +36 -42
  124. package/src/mem_pools/attestation_pool/attestation_pool.ts +5 -4
  125. package/src/mem_pools/instrumentation.ts +17 -13
  126. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
  127. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
  128. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -2
  129. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +10 -6
  130. package/src/mem_pools/tx_pool_v2/index.ts +1 -1
  131. package/src/mem_pools/tx_pool_v2/interfaces.ts +7 -4
  132. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +65 -10
  133. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +11 -11
  134. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +4 -1
  135. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +184 -148
  136. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +1 -1
  137. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +1 -1
  138. package/src/msg_validators/proposal_validator/proposal_validator.ts +15 -1
  139. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +144 -1
  140. package/src/msg_validators/tx_validator/README.md +115 -0
  141. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
  142. package/src/msg_validators/tx_validator/allowed_public_setup.ts +27 -13
  143. package/src/msg_validators/tx_validator/factory.ts +353 -77
  144. package/src/msg_validators/tx_validator/gas_validator.ts +90 -27
  145. package/src/msg_validators/tx_validator/index.ts +1 -0
  146. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  147. package/src/msg_validators/tx_validator/phases_validator.ts +25 -29
  148. package/src/services/dummy_service.ts +1 -5
  149. package/src/services/encoding.ts +5 -6
  150. package/src/services/libp2p/libp2p_service.ts +84 -92
  151. package/src/services/peer-manager/metrics.ts +7 -0
  152. package/src/services/peer-manager/peer_manager.ts +2 -1
  153. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +14 -42
  154. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +63 -24
  155. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  156. package/src/services/reqresp/reqresp.ts +3 -1
  157. package/src/services/service.ts +1 -1
  158. package/src/services/tx_provider.ts +2 -2
  159. package/src/test-helpers/make-test-p2p-clients.ts +0 -2
  160. package/src/test-helpers/mock-pubsub.ts +3 -6
  161. package/src/test-helpers/reqresp-nodes.ts +2 -5
  162. package/src/test-helpers/testbench-utils.ts +1 -1
  163. package/src/testbench/p2p_client_testbench_worker.ts +2 -6
@@ -45,6 +45,7 @@ import { TxPoolIndices } from './tx_pool_indices.js';
45
45
  export interface TxPoolV2Callbacks {
46
46
  onTxsAdded: (txs: Tx[], opts: { source?: string }) => void;
47
47
  onTxsRemoved: (txHashes: string[] | bigint[]) => void;
48
+ onTxsMined: (txHashes: string[]) => void;
48
49
  }
49
50
 
50
51
  /**
@@ -187,6 +188,30 @@ export class TxPoolV2Impl {
187
188
  const errors = new Map<string, TxPoolRejectionError>();
188
189
  const acceptedPending = new Set<string>();
189
190
 
191
+ // Phase 1: Pre-compute all throwable I/O outside the transaction.
192
+ // If any pre-computation throws, the entire call fails before mutations happen.
193
+ const precomputed = new Map<string, { meta: TxMetaData; minedBlockId: L2BlockId | undefined; isValid: boolean }>();
194
+
195
+ const validator = await this.#createTxValidator();
196
+
197
+ for (const tx of txs) {
198
+ const txHash = tx.getTxHash();
199
+ const txHashStr = txHash.toString();
200
+
201
+ const meta = await buildTxMetaData(tx);
202
+ const minedBlockId = await this.#getMinedBlockId(txHash);
203
+
204
+ // Validate non-mined txs (mined and pre-protected txs bypass validation inside the transaction)
205
+ let isValid = true;
206
+ if (!minedBlockId) {
207
+ isValid = await this.#validateMeta(meta, validator);
208
+ }
209
+
210
+ precomputed.set(txHashStr, { meta, minedBlockId, isValid });
211
+ }
212
+
213
+ // Phase 2: Apply mutations inside the transaction using only pre-computed results,
214
+ // in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
190
215
  const poolAccess = this.#createPreAddPoolAccess();
191
216
  const preAddContext: PreAddContext | undefined =
192
217
  opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
@@ -202,22 +227,25 @@ export class TxPoolV2Impl {
202
227
  continue;
203
228
  }
204
229
 
205
- // Check mined status first (applies to all paths)
206
- const minedBlockId = await this.#getMinedBlockId(txHash);
230
+ const { meta, minedBlockId, isValid } = precomputed.get(txHashStr)!;
207
231
  const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
208
232
 
209
233
  if (minedBlockId) {
210
234
  // Already mined - add directly (protection already set if pre-protected)
211
- await this.#addTx(tx, { mined: minedBlockId }, opts);
235
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
212
236
  accepted.push(txHash);
213
237
  } else if (preProtectedSlot !== undefined) {
214
238
  // Pre-protected and not mined - add as protected (bypass validation)
215
- await this.#addTx(tx, { protected: preProtectedSlot }, opts);
239
+ await this.#addTx(tx, { protected: preProtectedSlot }, opts, meta);
216
240
  accepted.push(txHash);
241
+ } else if (!isValid) {
242
+ // Failed pre-computed validation
243
+ rejected.push(txHash);
217
244
  } else {
218
- // Regular pending tx - validate and run pre-add rules
245
+ // Regular pending tx - run pre-add rules using pre-computed metadata
219
246
  const result = await this.#tryAddRegularPendingTx(
220
247
  tx,
248
+ meta,
221
249
  opts,
222
250
  poolAccess,
223
251
  acceptedPending,
@@ -227,13 +255,18 @@ export class TxPoolV2Impl {
227
255
  );
228
256
  if (result.status === 'accepted') {
229
257
  acceptedPending.add(txHashStr);
230
- } else if (result.status === 'rejected') {
231
- rejected.push(txHash);
232
258
  } else {
233
259
  ignored.push(txHash);
234
260
  }
235
261
  }
236
262
  }
263
+
264
+ // Run post-add eviction rules for pending txs (inside transaction for atomicity)
265
+ if (acceptedPending.size > 0) {
266
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
267
+ const uniqueFeePayers = new Set<string>(feePayers);
268
+ await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
269
+ }
237
270
  });
238
271
 
239
272
  // Build final accepted list for pending txs (excludes intra-batch evictions)
@@ -249,37 +282,24 @@ export class TxPoolV2Impl {
249
282
  this.#instrumentation.recordRejected(rejected.length);
250
283
  }
251
284
 
252
- // Run post-add eviction rules for pending txs
253
- if (acceptedPending.size > 0) {
254
- const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
255
- const uniqueFeePayers = new Set<string>(feePayers);
256
- await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
257
- }
258
-
259
285
  return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
260
286
  }
261
287
 
262
- /** Validates and adds a regular pending tx. Returns status. */
288
+ /** Adds a validated pending tx, running pre-add rules and evicting conflicts. */
263
289
  async #tryAddRegularPendingTx(
264
290
  tx: Tx,
291
+ precomputedMeta: TxMetaData,
265
292
  opts: { source?: string },
266
293
  poolAccess: PreAddPoolAccess,
267
294
  acceptedPending: Set<string>,
268
295
  ignored: TxHash[],
269
296
  errors: Map<string, TxPoolRejectionError>,
270
297
  preAddContext?: PreAddContext,
271
- ): Promise<{ status: 'accepted' | 'ignored' | 'rejected' }> {
272
- const txHash = tx.getTxHash();
273
- const txHashStr = txHash.toString();
274
-
275
- // Build metadata and validate using metadata
276
- const meta = await buildTxMetaData(tx);
277
- if (!(await this.#validateMeta(meta))) {
278
- return { status: 'rejected' };
279
- }
298
+ ): Promise<{ status: 'accepted' | 'ignored' }> {
299
+ const txHashStr = tx.getTxHash().toString();
280
300
 
281
301
  // Run pre-add rules
282
- const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess, preAddContext);
302
+ const preAddResult = await this.#evictionManager.runPreAddRules(precomputedMeta, poolAccess, preAddContext);
283
303
 
284
304
  if (preAddResult.shouldIgnore) {
285
305
  this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
@@ -316,12 +336,18 @@ export class TxPoolV2Impl {
316
336
  }
317
337
  }
318
338
 
339
+ // Randomly drop the transaction for testing purposes (report as accepted so it propagates)
340
+ if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
341
+ this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
342
+ return { status: 'accepted' };
343
+ }
344
+
319
345
  // Add the transaction
320
- await this.#addTx(tx, 'pending', opts);
346
+ await this.#addTx(tx, 'pending', opts, precomputedMeta);
321
347
  return { status: 'accepted' };
322
348
  }
323
349
 
324
- async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored' | 'rejected'> {
350
+ async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
325
351
  const txHashStr = tx.getTxHash().toString();
326
352
 
327
353
  // Check if already in pool
@@ -329,14 +355,8 @@ export class TxPoolV2Impl {
329
355
  return 'ignored';
330
356
  }
331
357
 
332
- // Build metadata and validate using metadata
358
+ // Build metadata and check pre-add rules
333
359
  const meta = await buildTxMetaData(tx);
334
- const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
335
- if (validationResult !== true) {
336
- return 'rejected';
337
- }
338
-
339
- // Use pre-add rules
340
360
  const poolAccess = this.#createPreAddPoolAccess();
341
361
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
342
362
 
@@ -379,33 +399,35 @@ export class TxPoolV2Impl {
379
399
  let softDeletedHits = 0;
380
400
  let missingPreviouslyEvicted = 0;
381
401
 
382
- for (const txHash of txHashes) {
383
- const txHashStr = txHash.toString();
402
+ await this.#store.transactionAsync(async () => {
403
+ for (const txHash of txHashes) {
404
+ const txHashStr = txHash.toString();
384
405
 
385
- if (this.#indices.has(txHashStr)) {
386
- // Update protection for existing tx
387
- this.#indices.updateProtection(txHashStr, slotNumber);
388
- } else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
389
- // Resurrect soft-deleted tx as protected
390
- const buffer = await this.#txsDB.getAsync(txHashStr);
391
- if (buffer) {
392
- const tx = Tx.fromBuffer(buffer);
393
- await this.#addTx(tx, { protected: slotNumber });
394
- softDeletedHits++;
406
+ if (this.#indices.has(txHashStr)) {
407
+ // Update protection for existing tx
408
+ this.#indices.updateProtection(txHashStr, slotNumber);
409
+ } else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
410
+ // Resurrect soft-deleted tx as protected
411
+ const buffer = await this.#txsDB.getAsync(txHashStr);
412
+ if (buffer) {
413
+ const tx = Tx.fromBuffer(buffer);
414
+ await this.#addTx(tx, { protected: slotNumber });
415
+ softDeletedHits++;
416
+ } else {
417
+ // Data missing despite soft-delete flag — treat as truly missing
418
+ this.#indices.setProtection(txHashStr, slotNumber);
419
+ missing.push(txHash);
420
+ }
395
421
  } else {
396
- // Data missing despite soft-delete flag treat as truly missing
422
+ // Truly missing pre-record protection for tx we don't have yet
397
423
  this.#indices.setProtection(txHashStr, slotNumber);
398
424
  missing.push(txHash);
399
- }
400
- } else {
401
- // Truly missing — pre-record protection for tx we don't have yet
402
- this.#indices.setProtection(txHashStr, slotNumber);
403
- missing.push(txHash);
404
- if (this.#evictedTxHashes.has(txHashStr)) {
405
- missingPreviouslyEvicted++;
425
+ if (this.#evictedTxHashes.has(txHashStr)) {
426
+ missingPreviouslyEvicted++;
427
+ }
406
428
  }
407
429
  }
408
- }
430
+ });
409
431
 
410
432
  // Record metrics
411
433
  if (softDeletedHits > 0) {
@@ -466,56 +488,64 @@ export class TxPoolV2Impl {
466
488
  }
467
489
  }
468
490
 
469
- // Step 4: Mark txs as mined (only those we have in the pool)
470
- for (const meta of found) {
471
- this.#indices.markAsMined(meta, blockId);
472
- await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
473
- }
491
+ await this.#store.transactionAsync(async () => {
492
+ // Step 4: Mark txs as mined (only those we have in the pool)
493
+ for (const meta of found) {
494
+ this.#indices.markAsMined(meta, blockId);
495
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
496
+ }
474
497
 
475
- // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
476
- await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
498
+ // Step 5: Run post-event eviction rules (inside transaction for atomicity)
499
+ await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
500
+ });
501
+
502
+ if (found.length > 0) {
503
+ this.#callbacks.onTxsMined(found.map(m => m.txHash));
504
+ }
477
505
 
478
506
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
479
507
  }
480
508
 
481
509
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
482
- // Step 0: Clean up slot-deleted txs from previous slots
483
- await this.#deletedPool.cleanupSlotDeleted(slotNumber);
510
+ await this.#store.transactionAsync(async () => {
511
+ // Step 0: Clean up slot-deleted txs from previous slots
512
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
484
513
 
485
- // Step 1: Find expired protected txs
486
- const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
514
+ // Step 1: Find expired protected txs
515
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
487
516
 
488
- // Step 2: Clear protection for all expired entries (including those without metadata)
489
- this.#indices.clearProtection(expiredProtected);
517
+ // Step 2: Clear protection for all expired entries (including those without metadata)
518
+ this.#indices.clearProtection(expiredProtected);
490
519
 
491
- // Step 3: Filter to only txs that have metadata and are not mined
492
- const txsToRestore = this.#indices.filterRestorable(expiredProtected);
493
- if (txsToRestore.length === 0) {
494
- this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
495
- return;
496
- }
520
+ // Step 3: Filter to only txs that have metadata and are not mined
521
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
522
+ if (txsToRestore.length === 0) {
523
+ this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
524
+ return;
525
+ }
497
526
 
498
- this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
527
+ this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
499
528
 
500
- // Step 4: Validate for pending pool
501
- const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
529
+ // Step 4: Validate for pending pool
530
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
502
531
 
503
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
504
- const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
532
+ // Step 5: Resolve nullifier conflicts and add winners to pending indices
533
+ const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
505
534
 
506
- // Step 6: Delete invalid txs and evict conflict losers
507
- await this.#deleteTxsBatch(invalid);
508
- await this.#evictTxs(toEvict, 'NullifierConflict');
535
+ // Step 6: Delete invalid txs and evict conflict losers
536
+ await this.#deleteTxsBatch(invalid);
537
+ await this.#evictTxs(toEvict, 'NullifierConflict');
509
538
 
510
- // Step 7: Run eviction rules (enforce pool size limit)
511
- if (added.length > 0) {
512
- const feePayers = added.map(meta => meta.feePayer);
513
- const uniqueFeePayers = new Set<string>(feePayers);
514
- await this.#evictionManager.evictAfterNewTxs(
515
- added.map(m => m.txHash),
516
- [...uniqueFeePayers],
517
- );
518
- }
539
+ // Step 7: Run eviction rules (enforce pool size limit)
540
+ if (added.length > 0) {
541
+ const feePayers = added.map(meta => meta.feePayer);
542
+ const uniqueFeePayers = new Set<string>(feePayers);
543
+ await this.#evictionManager.evictAfterNewTxs(
544
+ added.map(m => m.txHash),
545
+ [...uniqueFeePayers],
546
+ );
547
+ }
548
+ });
519
549
  }
520
550
 
521
551
  async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
@@ -528,57 +558,60 @@ export class TxPoolV2Impl {
528
558
 
529
559
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
530
560
 
531
- // Step 2: Mark ALL un-mined txs with their original mined block number
532
- // This ensures they get soft-deleted if removed later, and only hard-deleted
533
- // when their original mined block is finalized
534
- await this.#deletedPool.markFromPrunedBlock(
535
- txsToUnmine.map(m => ({
536
- txHash: m.txHash,
537
- minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
538
- })),
539
- );
561
+ await this.#store.transactionAsync(async () => {
562
+ // Step 2: Mark ALL un-mined txs with their original mined block number
563
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
564
+ // when their original mined block is finalized
565
+ await this.#deletedPool.markFromPrunedBlock(
566
+ txsToUnmine.map(m => ({
567
+ txHash: m.txHash,
568
+ minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
569
+ })),
570
+ );
540
571
 
541
- // Step 3: Unmine - clear mined status from metadata
542
- for (const meta of txsToUnmine) {
543
- this.#indices.markAsUnmined(meta);
544
- }
572
+ // Step 3: Unmine - clear mined status from metadata
573
+ for (const meta of txsToUnmine) {
574
+ this.#indices.markAsUnmined(meta);
575
+ }
545
576
 
546
- // If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
547
- if (options?.deleteAllTxs) {
548
- const allTxHashes = txsToUnmine.map(m => m.txHash);
549
- await this.#deleteTxsBatch(allTxHashes);
550
- this.#log.info(
551
- `Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
552
- );
553
- return;
554
- }
577
+ // If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
578
+ if (options?.deleteAllTxs) {
579
+ const allTxHashes = txsToUnmine.map(m => m.txHash);
580
+ await this.#deleteTxsBatch(allTxHashes);
581
+ this.#log.info(
582
+ `Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
583
+ );
584
+ return;
585
+ }
555
586
 
556
- // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
557
- const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
587
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
588
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
558
589
 
559
- // Step 5: Validate for pending pool
560
- const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
590
+ // Step 5: Validate for pending pool
591
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
561
592
 
562
- // Step 6: Resolve nullifier conflicts and add winners to pending indices
563
- const { toEvict } = this.#applyNullifierConflictResolution(valid);
593
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
594
+ const { toEvict } = this.#applyNullifierConflictResolution(valid);
564
595
 
565
- // Step 7: Delete invalid txs and evict conflict losers
566
- await this.#deleteTxsBatch(invalid);
567
- await this.#evictTxs(toEvict, 'NullifierConflict');
596
+ // Step 7: Delete invalid txs and evict conflict losers
597
+ await this.#deleteTxsBatch(invalid);
598
+ await this.#evictTxs(toEvict, 'NullifierConflict');
568
599
 
569
- this.#log.info(
570
- `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
571
- { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
572
- );
600
+ this.#log.info(
601
+ `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
602
+ { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
603
+ );
573
604
 
574
- // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
575
- // This handles cases like existing pending txs with invalid fee payer balances
576
- await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
605
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
606
+ // This handles cases like existing pending txs with invalid fee payer balances
607
+ await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
608
+ });
577
609
  }
578
610
 
579
611
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
580
- // Delete failed txs
581
- await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
612
+ await this.#store.transactionAsync(async () => {
613
+ await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
614
+ });
582
615
 
583
616
  this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
584
617
  }
@@ -589,27 +622,29 @@ export class TxPoolV2Impl {
589
622
  // Step 1: Find mined txs at or before finalized block
590
623
  const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
591
624
 
592
- // Step 2: Collect mined txs for archiving (before deletion)
593
- const txsToArchive: Tx[] = [];
594
- if (this.#archive.isEnabled()) {
595
- for (const txHashStr of minedTxsToFinalize) {
596
- const buffer = await this.#txsDB.getAsync(txHashStr);
597
- if (buffer) {
598
- txsToArchive.push(Tx.fromBuffer(buffer));
625
+ await this.#store.transactionAsync(async () => {
626
+ // Step 2: Collect mined txs for archiving (before deletion)
627
+ const txsToArchive: Tx[] = [];
628
+ if (this.#archive.isEnabled()) {
629
+ for (const txHashStr of minedTxsToFinalize) {
630
+ const buffer = await this.#txsDB.getAsync(txHashStr);
631
+ if (buffer) {
632
+ txsToArchive.push(Tx.fromBuffer(buffer));
633
+ }
599
634
  }
600
635
  }
601
- }
602
636
 
603
- // Step 3: Delete mined txs from active pool
604
- await this.#deleteTxsBatch(minedTxsToFinalize);
637
+ // Step 3: Delete mined txs from active pool
638
+ await this.#deleteTxsBatch(minedTxsToFinalize);
605
639
 
606
- // Step 4: Finalize soft-deleted txs
607
- await this.#deletedPool.finalizeBlock(blockNumber);
640
+ // Step 4: Finalize soft-deleted txs
641
+ await this.#deletedPool.finalizeBlock(blockNumber);
608
642
 
609
- // Step 5: Archive mined txs
610
- if (txsToArchive.length > 0) {
611
- await this.#archive.archiveTxs(txsToArchive);
612
- }
643
+ // Step 5: Archive mined txs
644
+ if (txsToArchive.length > 0) {
645
+ await this.#archive.archiveTxs(txsToArchive);
646
+ }
647
+ });
613
648
 
614
649
  if (minedTxsToFinalize.length > 0) {
615
650
  this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
@@ -754,9 +789,10 @@ export class TxPoolV2Impl {
754
789
  tx: Tx,
755
790
  state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
756
791
  opts: { source?: string } = {},
792
+ precomputedMeta?: TxMetaData,
757
793
  ): Promise<TxMetaData> {
758
794
  const txHashStr = tx.getTxHash().toString();
759
- const meta = await buildTxMetaData(tx);
795
+ const meta = precomputedMeta ?? (await buildTxMetaData(tx));
760
796
  meta.receivedAt = this.#dateProvider.now();
761
797
 
762
798
  await this.#txsDB.set(txHashStr, tx.toBuffer());
@@ -4,7 +4,7 @@ import type { BlockProposal, P2PValidator } from '@aztec/stdlib/p2p';
4
4
  import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
5
5
 
6
6
  export class BlockProposalValidator extends ProposalValidator<BlockProposal> implements P2PValidator<BlockProposal> {
7
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }) {
7
+ constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
8
8
  super(epochCache, opts, 'p2p:block_proposal_validator');
9
9
  }
10
10
  }
@@ -7,7 +7,7 @@ export class CheckpointProposalValidator
7
7
  extends ProposalValidator<CheckpointProposal>
8
8
  implements P2PValidator<CheckpointProposal>
9
9
  {
10
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }) {
10
+ constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
11
11
  super(epochCache, opts, 'p2p:checkpoint_proposal_validator');
12
12
  }
13
13
  }
@@ -9,10 +9,16 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
9
9
  protected epochCache: EpochCacheInterface;
10
10
  protected logger: Logger;
11
11
  protected txsPermitted: boolean;
12
+ protected maxTxsPerBlock?: number;
12
13
 
13
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }, loggerName: string) {
14
+ constructor(
15
+ epochCache: EpochCacheInterface,
16
+ opts: { txsPermitted: boolean; maxTxsPerBlock?: number },
17
+ loggerName: string,
18
+ ) {
14
19
  this.epochCache = epochCache;
15
20
  this.txsPermitted = opts.txsPermitted;
21
+ this.maxTxsPerBlock = opts.maxTxsPerBlock;
16
22
  this.logger = createLogger(loggerName);
17
23
  }
18
24
 
@@ -47,6 +53,14 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
47
53
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
48
54
  }
49
55
 
56
+ // Max txs per block check
57
+ if (this.maxTxsPerBlock !== undefined && proposal.txHashes.length > this.maxTxsPerBlock) {
58
+ this.logger.warn(
59
+ `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when max is ${this.maxTxsPerBlock}`,
60
+ );
61
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
62
+ }
63
+
50
64
  // Embedded txs must be listed in txHashes
51
65
  const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
52
66
  const missingTxHashes =