@aztec/p2p 4.0.0-devnet.1-patch.1 → 4.0.0-devnet.2-patch.1

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 (175) 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 +9 -5
  4. package/dest/client/interface.d.ts +8 -8
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +4 -6
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +46 -14
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +6 -5
  10. package/dest/config.d.ts +10 -1
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +5 -0
  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/attestation_pool/attestation_pool.d.ts +4 -2
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -0
  19. package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -2
  20. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/mocks.js +2 -2
  22. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +3 -3
  23. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +30 -13
  24. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +91 -20
  26. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +3 -3
  27. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +18 -9
  29. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  30. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +4 -1
  32. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +3 -3
  33. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +10 -4
  35. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +2 -2
  36. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/eviction/index.js +1 -1
  38. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +48 -5
  39. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +8 -0
  41. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +7 -5
  42. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +5 -3
  43. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +1 -1
  44. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  45. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +8 -4
  46. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +4 -4
  47. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +14 -4
  49. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +3 -3
  50. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  51. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  52. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts +15 -0
  53. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts.map +1 -0
  54. package/dest/mem_pools/tx_pool_v2/instrumentation.js +43 -0
  55. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +14 -2
  56. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  57. package/dest/mem_pools/tx_pool_v2/interfaces.js +3 -1
  58. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +9 -5
  59. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  60. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +30 -5
  61. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +12 -3
  62. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  63. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +27 -4
  64. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +8 -3
  65. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  66. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +11 -6
  67. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +12 -4
  68. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  69. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +164 -37
  70. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +2 -2
  71. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  72. package/dest/msg_validators/tx_validator/timestamp_validator.js +6 -6
  73. package/dest/services/encoding.d.ts +1 -1
  74. package/dest/services/encoding.d.ts.map +1 -1
  75. package/dest/services/encoding.js +2 -1
  76. package/dest/services/libp2p/libp2p_service.js +2 -2
  77. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -3
  78. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  79. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +5 -9
  80. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +2 -6
  81. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  82. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +10 -13
  83. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  84. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +25 -46
  85. package/dest/services/tx_collection/config.d.ts +13 -1
  86. package/dest/services/tx_collection/config.d.ts.map +1 -1
  87. package/dest/services/tx_collection/config.js +30 -0
  88. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -1
  89. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  90. package/dest/services/tx_collection/fast_tx_collection.js +39 -33
  91. package/dest/services/tx_collection/file_store_tx_collection.d.ts +38 -29
  92. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -1
  93. package/dest/services/tx_collection/file_store_tx_collection.js +126 -77
  94. package/dest/services/tx_collection/file_store_tx_source.d.ts +16 -6
  95. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  96. package/dest/services/tx_collection/file_store_tx_source.js +49 -16
  97. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  98. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  99. package/dest/services/tx_collection/instrumentation.js +2 -1
  100. package/dest/services/tx_collection/missing_txs_tracker.d.ts +32 -0
  101. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +1 -0
  102. package/dest/services/tx_collection/missing_txs_tracker.js +27 -0
  103. package/dest/services/tx_collection/proposal_tx_collector.d.ts +7 -6
  104. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  105. package/dest/services/tx_collection/proposal_tx_collector.js +5 -4
  106. package/dest/services/tx_collection/slow_tx_collection.d.ts +5 -3
  107. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  108. package/dest/services/tx_collection/slow_tx_collection.js +17 -12
  109. package/dest/services/tx_collection/tx_collection.d.ts +9 -6
  110. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  111. package/dest/services/tx_collection/tx_collection.js +26 -10
  112. package/dest/services/tx_collection/tx_collection_sink.d.ts +6 -5
  113. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  114. package/dest/services/tx_collection/tx_collection_sink.js +13 -22
  115. package/dest/services/tx_collection/tx_source.d.ts +8 -3
  116. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  117. package/dest/services/tx_collection/tx_source.js +19 -2
  118. package/dest/services/tx_file_store/tx_file_store.d.ts +3 -2
  119. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  120. package/dest/services/tx_file_store/tx_file_store.js +9 -6
  121. package/dest/test-helpers/testbench-utils.d.ts +7 -2
  122. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  123. package/dest/test-helpers/testbench-utils.js +7 -1
  124. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -2
  125. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  126. package/dest/testbench/p2p_client_testbench_worker.js +8 -8
  127. package/package.json +14 -14
  128. package/src/client/factory.ts +18 -3
  129. package/src/client/interface.ts +10 -8
  130. package/src/client/p2p_client.ts +52 -17
  131. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +18 -8
  132. package/src/config.ts +8 -0
  133. package/src/errors/tx-pool.error.ts +12 -0
  134. package/src/mem_pools/attestation_pool/attestation_pool.ts +8 -0
  135. package/src/mem_pools/attestation_pool/mocks.ts +2 -1
  136. package/src/mem_pools/tx_pool/README.md +1 -1
  137. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +3 -3
  138. package/src/mem_pools/tx_pool_v2/README.md +43 -27
  139. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +109 -22
  140. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +21 -8
  141. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +4 -1
  142. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +15 -4
  143. package/src/mem_pools/tx_pool_v2/eviction/index.ts +4 -0
  144. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +49 -4
  145. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +5 -5
  146. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +3 -3
  147. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +8 -7
  148. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +24 -6
  149. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +3 -3
  150. package/src/mem_pools/tx_pool_v2/instrumentation.ts +69 -0
  151. package/src/mem_pools/tx_pool_v2/interfaces.ts +14 -2
  152. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +44 -9
  153. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +32 -5
  154. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +17 -6
  155. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +186 -29
  156. package/src/msg_validators/tx_validator/timestamp_validator.ts +7 -7
  157. package/src/services/encoding.ts +2 -1
  158. package/src/services/libp2p/libp2p_service.ts +2 -2
  159. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +6 -6
  160. package/src/services/reqresp/batch-tx-requester/interface.ts +1 -5
  161. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +23 -71
  162. package/src/services/tx_collection/config.ts +42 -0
  163. package/src/services/tx_collection/fast_tx_collection.ts +51 -30
  164. package/src/services/tx_collection/file_store_tx_collection.ts +143 -93
  165. package/src/services/tx_collection/file_store_tx_source.ts +64 -17
  166. package/src/services/tx_collection/instrumentation.ts +7 -1
  167. package/src/services/tx_collection/missing_txs_tracker.ts +52 -0
  168. package/src/services/tx_collection/proposal_tx_collector.ts +8 -7
  169. package/src/services/tx_collection/slow_tx_collection.ts +17 -13
  170. package/src/services/tx_collection/tx_collection.ts +45 -14
  171. package/src/services/tx_collection/tx_collection_sink.ts +15 -29
  172. package/src/services/tx_collection/tx_source.ts +22 -3
  173. package/src/services/tx_file_store/tx_file_store.ts +6 -4
  174. package/src/test-helpers/testbench-utils.ts +10 -2
  175. package/src/testbench/p2p_client_testbench_worker.ts +20 -13
@@ -1,5 +1,6 @@
1
1
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { Logger } from '@aztec/foundation/log';
3
+ import type { DateProvider } from '@aztec/foundation/timer';
3
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
4
5
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
5
6
  import { computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
@@ -8,6 +9,7 @@ import type { L2Block, L2BlockId, L2BlockSource } from '@aztec/stdlib/block';
8
9
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
9
10
  import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
10
11
  import { BlockHeader, Tx, TxHash, type TxValidator } from '@aztec/stdlib/tx';
12
+ import type { TelemetryClient } from '@aztec/telemetry-client';
11
13
 
12
14
  import { TxArchive } from './archive/index.js';
13
15
  import { DeletedPool } from './deleted_pool.js';
@@ -21,8 +23,12 @@ import {
21
23
  LowPriorityPreAddRule,
22
24
  NullifierConflictRule,
23
25
  type PoolOperations,
26
+ type PreAddContext,
24
27
  type PreAddPoolAccess,
28
+ TxPoolRejectionCode,
29
+ type TxPoolRejectionError,
25
30
  } from './eviction/index.js';
31
+ import { TxPoolV2Instrumentation } from './instrumentation.js';
26
32
  import {
27
33
  type AddTxsResult,
28
34
  DEFAULT_TX_POOL_V2_CONFIG,
@@ -64,6 +70,9 @@ export class TxPoolV2Impl {
64
70
  #archive: TxArchive;
65
71
  #deletedPool: DeletedPool;
66
72
  #evictionManager: EvictionManager;
73
+ #dateProvider: DateProvider;
74
+ #instrumentation: TxPoolV2Instrumentation;
75
+ #evictedTxHashes: Set<string> = new Set();
67
76
  #log: Logger;
68
77
  #callbacks: TxPoolV2Callbacks;
69
78
 
@@ -72,7 +81,9 @@ export class TxPoolV2Impl {
72
81
  archiveStore: AztecAsyncKVStore,
73
82
  deps: TxPoolV2Dependencies,
74
83
  callbacks: TxPoolV2Callbacks,
84
+ telemetry: TelemetryClient,
75
85
  config: Partial<TxPoolV2Config> = {},
86
+ dateProvider: DateProvider,
76
87
  log: Logger,
77
88
  ) {
78
89
  this.#store = store;
@@ -85,6 +96,8 @@ export class TxPoolV2Impl {
85
96
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
86
97
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
87
98
  this.#deletedPool = new DeletedPool(store, this.#txsDB, log);
99
+ this.#dateProvider = dateProvider;
100
+ this.#instrumentation = new TxPoolV2Instrumentation(telemetry, () => this.#indices.getTotalMetadataBytes());
88
101
  this.#log = log;
89
102
  this.#callbacks = callbacks;
90
103
 
@@ -164,16 +177,19 @@ export class TxPoolV2Impl {
164
177
  await this.#txsDB.delete(txHashStr);
165
178
  }
166
179
  });
167
- this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`);
180
+ this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`, { txHashes: toDelete });
168
181
  }
169
182
 
170
- async addPendingTxs(txs: Tx[], opts: { source?: string }): Promise<AddTxsResult> {
183
+ async addPendingTxs(txs: Tx[], opts: { source?: string; feeComparisonOnly?: boolean }): Promise<AddTxsResult> {
171
184
  const accepted: TxHash[] = [];
172
185
  const ignored: TxHash[] = [];
173
186
  const rejected: TxHash[] = [];
187
+ const errors = new Map<string, TxPoolRejectionError>();
174
188
  const acceptedPending = new Set<string>();
175
189
 
176
190
  const poolAccess = this.#createPreAddPoolAccess();
191
+ const preAddContext: PreAddContext | undefined =
192
+ opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
177
193
 
178
194
  await this.#store.transactionAsync(async () => {
179
195
  for (const tx of txs) {
@@ -200,7 +216,15 @@ export class TxPoolV2Impl {
200
216
  accepted.push(txHash);
201
217
  } else {
202
218
  // Regular pending tx - validate and run pre-add rules
203
- 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
+ );
204
228
  if (result.status === 'accepted') {
205
229
  acceptedPending.add(txHashStr);
206
230
  } else if (result.status === 'rejected') {
@@ -217,6 +241,14 @@ export class TxPoolV2Impl {
217
241
  accepted.push(TxHash.fromString(txHashStr));
218
242
  }
219
243
 
244
+ // Record metrics
245
+ if (ignored.length > 0) {
246
+ this.#instrumentation.recordIgnored(ignored.length);
247
+ }
248
+ if (rejected.length > 0) {
249
+ this.#instrumentation.recordRejected(rejected.length);
250
+ }
251
+
220
252
  // Run post-add eviction rules for pending txs
221
253
  if (acceptedPending.size > 0) {
222
254
  const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
@@ -224,7 +256,7 @@ export class TxPoolV2Impl {
224
256
  await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
225
257
  }
226
258
 
227
- return { accepted, ignored, rejected };
259
+ return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
228
260
  }
229
261
 
230
262
  /** Validates and adds a regular pending tx. Returns status. */
@@ -234,6 +266,8 @@ export class TxPoolV2Impl {
234
266
  poolAccess: PreAddPoolAccess,
235
267
  acceptedPending: Set<string>,
236
268
  ignored: TxHash[],
269
+ errors: Map<string, TxPoolRejectionError>,
270
+ preAddContext?: PreAddContext,
237
271
  ): Promise<{ status: 'accepted' | 'ignored' | 'rejected' }> {
238
272
  const txHash = tx.getTxHash();
239
273
  const txHashStr = txHash.toString();
@@ -245,21 +279,40 @@ export class TxPoolV2Impl {
245
279
  }
246
280
 
247
281
  // Run pre-add rules
248
- const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
282
+ const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess, preAddContext);
249
283
 
250
284
  if (preAddResult.shouldIgnore) {
251
- 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
+ }
252
289
  return { status: 'ignored' };
253
290
  }
254
291
 
255
- // Evict conflicts
256
- for (const evictHashStr of preAddResult.txHashesToEvict) {
257
- await this.#deleteTx(evictHashStr);
258
- this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
259
- if (acceptedPending.has(evictHashStr)) {
260
- // Evicted tx was from this batch - mark as ignored in result
261
- acceptedPending.delete(evictHashStr);
262
- 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);
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
+ }
263
316
  }
264
317
  }
265
318
 
@@ -320,9 +373,11 @@ export class TxPoolV2Impl {
320
373
  });
321
374
  }
322
375
 
323
- protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
376
+ async protectTxs(txHashes: TxHash[], block: BlockHeader): Promise<TxHash[]> {
324
377
  const slotNumber = block.globalVariables.slotNumber;
325
378
  const missing: TxHash[] = [];
379
+ let softDeletedHits = 0;
380
+ let missingPreviouslyEvicted = 0;
326
381
 
327
382
  for (const txHash of txHashes) {
328
383
  const txHashStr = txHash.toString();
@@ -330,13 +385,44 @@ export class TxPoolV2Impl {
330
385
  if (this.#indices.has(txHashStr)) {
331
386
  // Update protection for existing tx
332
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++;
395
+ } else {
396
+ // Data missing despite soft-delete flag — treat as truly missing
397
+ this.#indices.setProtection(txHashStr, slotNumber);
398
+ missing.push(txHash);
399
+ }
333
400
  } else {
334
- // Pre-record protection for tx we don't have yet
401
+ // Truly missing — pre-record protection for tx we don't have yet
335
402
  this.#indices.setProtection(txHashStr, slotNumber);
336
403
  missing.push(txHash);
404
+ if (this.#evictedTxHashes.has(txHashStr)) {
405
+ missingPreviouslyEvicted++;
406
+ }
337
407
  }
338
408
  }
339
409
 
410
+ // Record metrics
411
+ if (softDeletedHits > 0) {
412
+ this.#instrumentation.recordSoftDeletedHits(softDeletedHits);
413
+ }
414
+ if (missing.length > 0) {
415
+ this.#log.debug(`protectTxs missing tx hashes: ${missing.map(h => h.toString()).join(', ')}`);
416
+ this.#instrumentation.recordMissingOnProtect(missing.length);
417
+ }
418
+ if (missingPreviouslyEvicted > 0) {
419
+ this.#instrumentation.recordMissingPreviouslyEvicted(missingPreviouslyEvicted);
420
+ }
421
+
422
+ this.#log.info(
423
+ `Protected ${txHashes.length} txs, missing: ${missing.length}, soft-deleted hits: ${softDeletedHits}`,
424
+ );
425
+
340
426
  return missing;
341
427
  }
342
428
 
@@ -393,6 +479,9 @@ export class TxPoolV2Impl {
393
479
  }
394
480
 
395
481
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
482
+ // Step 0: Clean up slot-deleted txs from previous slots
483
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
484
+
396
485
  // Step 1: Find expired protected txs
397
486
  const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
398
487
 
@@ -402,6 +491,7 @@ export class TxPoolV2Impl {
402
491
  // Step 3: Filter to only txs that have metadata and are not mined
403
492
  const txsToRestore = this.#indices.filterRestorable(expiredProtected);
404
493
  if (txsToRestore.length === 0) {
494
+ this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
405
495
  return;
406
496
  }
407
497
 
@@ -413,8 +503,9 @@ export class TxPoolV2Impl {
413
503
  // Step 5: Resolve nullifier conflicts and add winners to pending indices
414
504
  const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
415
505
 
416
- // Step 6: Delete invalid and evicted txs
417
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
506
+ // Step 6: Delete invalid txs and evict conflict losers
507
+ await this.#deleteTxsBatch(invalid);
508
+ await this.#evictTxs(toEvict, 'NullifierConflict');
418
509
 
419
510
  // Step 7: Run eviction rules (enforce pool size limit)
420
511
  if (added.length > 0) {
@@ -427,7 +518,7 @@ export class TxPoolV2Impl {
427
518
  }
428
519
  }
429
520
 
430
- async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
521
+ async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
431
522
  // Step 1: Find transactions mined after the prune point
432
523
  const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
433
524
  if (txsToUnmine.length === 0) {
@@ -452,17 +543,33 @@ export class TxPoolV2Impl {
452
543
  this.#indices.markAsUnmined(meta);
453
544
  }
454
545
 
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
+ }
555
+
455
556
  // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
456
557
  const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
457
558
 
458
- // Step 4: Validate for pending pool
559
+ // Step 5: Validate for pending pool
459
560
  const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
460
561
 
461
562
  // Step 6: Resolve nullifier conflicts and add winners to pending indices
462
563
  const { toEvict } = this.#applyNullifierConflictResolution(valid);
463
564
 
464
- // Step 7: Delete invalid and evicted txs
465
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
565
+ // Step 7: Delete invalid txs and evict conflict losers
566
+ await this.#deleteTxsBatch(invalid);
567
+ await this.#evictTxs(toEvict, 'NullifierConflict');
568
+
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
+ );
466
573
 
467
574
  // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
468
575
  // This handles cases like existing pending txs with invalid fee payer balances
@@ -473,7 +580,7 @@ export class TxPoolV2Impl {
473
580
  // Delete failed txs
474
581
  await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
475
582
 
476
- this.#log.info(`Deleted ${txHashes.length} failed txs`);
583
+ this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
477
584
  }
478
585
 
479
586
  async handleFinalizedBlock(block: BlockHeader): Promise<void> {
@@ -505,7 +612,9 @@ export class TxPoolV2Impl {
505
612
  }
506
613
 
507
614
  if (minedTxsToFinalize.length > 0) {
508
- this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`);
615
+ this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
616
+ txHashes: minedTxsToFinalize,
617
+ });
509
618
  }
510
619
  }
511
620
 
@@ -549,6 +658,13 @@ export class TxPoolV2Impl {
549
658
  return [...this.#indices.iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
550
659
  }
551
660
 
661
+ getEligiblePendingTxHashes(): TxHash[] {
662
+ const maxReceivedAt = this.#dateProvider.now() - this.#config.minTxPoolAgeMs;
663
+ return [...this.#indices.iterateEligiblePendingByPriority('desc', maxReceivedAt)].map(hash =>
664
+ TxHash.fromString(hash),
665
+ );
666
+ }
667
+
552
668
  getPendingTxCount(): number {
553
669
  return this.#indices.getPendingTxCount();
554
670
  }
@@ -593,6 +709,9 @@ export class TxPoolV2Impl {
593
709
  this.#config.archivedTxLimit = config.archivedTxLimit;
594
710
  this.#archive.updateLimit(config.archivedTxLimit);
595
711
  }
712
+ if (config.minTxPoolAgeMs !== undefined) {
713
+ this.#config.minTxPoolAgeMs = config.minTxPoolAgeMs;
714
+ }
596
715
  // Update eviction rules with new config
597
716
  this.#evictionManager.updateConfig(config);
598
717
  }
@@ -610,8 +729,17 @@ export class TxPoolV2Impl {
610
729
 
611
730
  // === Metrics ===
612
731
 
613
- countTxs(): { pending: number; protected: number; mined: number } {
614
- return this.#indices.countTxs();
732
+ countTxs(): {
733
+ pending: number;
734
+ protected: number;
735
+ mined: number;
736
+ softDeleted: number;
737
+ totalMetadataBytes: number;
738
+ } {
739
+ return {
740
+ ...this.#indices.countTxs(),
741
+ softDeleted: this.#deletedPool.getSoftDeletedCount(),
742
+ };
615
743
  }
616
744
 
617
745
  // ============================================================================
@@ -629,8 +757,10 @@ export class TxPoolV2Impl {
629
757
  ): Promise<TxMetaData> {
630
758
  const txHashStr = tx.getTxHash().toString();
631
759
  const meta = await buildTxMetaData(tx);
760
+ meta.receivedAt = this.#dateProvider.now();
632
761
 
633
762
  await this.#txsDB.set(txHashStr, tx.toBuffer());
763
+ await this.#deletedPool.clearSoftDeleted(txHashStr);
634
764
  this.#callbacks.onTxsAdded([tx], opts);
635
765
 
636
766
  if (state === 'pending') {
@@ -643,9 +773,11 @@ export class TxPoolV2Impl {
643
773
  }
644
774
 
645
775
  const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
646
- this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
776
+ this.#log.debug(`Added tx ${txHashStr} as ${stateStr}`, {
647
777
  eventName: 'tx-added-to-pool',
778
+ txHash: txHashStr,
648
779
  state: stateStr,
780
+ source: opts.source,
649
781
  });
650
782
 
651
783
  return meta;
@@ -673,6 +805,29 @@ export class TxPoolV2Impl {
673
805
  }
674
806
  }
675
807
 
808
+ /** Evicts transactions: records eviction metric with reason, caches hashes, then deletes. */
809
+ async #evictTxs(txHashes: string[], reason: string): Promise<void> {
810
+ if (txHashes.length === 0) {
811
+ return;
812
+ }
813
+ this.#instrumentation.recordEvictions(txHashes.length, reason);
814
+ for (const txHashStr of txHashes) {
815
+ this.#log.debug(`Evicting tx ${txHashStr}`, { txHash: txHashStr, reason });
816
+ this.#addToEvictedCache(txHashStr);
817
+ }
818
+ await this.#deleteTxsBatch(txHashes);
819
+ }
820
+
821
+ /** Adds a tx hash to the bounded evicted cache, evicting the oldest entry if at capacity. */
822
+ #addToEvictedCache(txHashStr: string): void {
823
+ if (this.#evictedTxHashes.size >= this.#config.evictedTxCacheSize) {
824
+ // FIFO eviction: remove the first (oldest) entry
825
+ const oldest = this.#evictedTxHashes.values().next().value!;
826
+ this.#evictedTxHashes.delete(oldest);
827
+ }
828
+ this.#evictedTxHashes.add(txHashStr);
829
+ }
830
+
676
831
  // ============================================================================
677
832
  // PRIVATE HELPERS - Validation & Conflict Resolution
678
833
  // ============================================================================
@@ -828,7 +983,9 @@ export class TxPoolV2Impl {
828
983
  if (preAddResult.shouldIgnore) {
829
984
  // Transaction rejected - mark for deletion from DB
830
985
  rejected.push(meta.txHash);
831
- this.#log.debug(`Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason}`);
986
+ this.#log.debug(
987
+ `Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason?.message ?? 'unknown reason'}`,
988
+ );
832
989
  continue;
833
990
  }
834
991
 
@@ -864,7 +1021,7 @@ export class TxPoolV2Impl {
864
1021
  getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
865
1022
  getPendingTxCount: () => this.#indices.getPendingTxCount(),
866
1023
  getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
867
- deleteTxs: (txHashes: string[]) => this.#deleteTxsBatch(txHashes),
1024
+ deleteTxs: (txHashes: string[], reason?: string) => this.#evictTxs(txHashes, reason ?? 'unknown'),
868
1025
  };
869
1026
  }
870
1027
 
@@ -1,13 +1,13 @@
1
1
  import type { BlockNumber } from '@aztec/foundation/branded-types';
2
2
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
- import { TX_ERROR_INVALID_INCLUDE_BY_TIMESTAMP, type TxValidationResult, type TxValidator } from '@aztec/stdlib/tx';
3
+ import { TX_ERROR_INVALID_EXPIRATION_TIMESTAMP, type TxValidationResult, type TxValidator } from '@aztec/stdlib/tx';
4
4
  import type { UInt64 } from '@aztec/stdlib/types';
5
5
 
6
6
  /** Structural interface for timestamp validation. */
7
7
  export interface HasTimestampData {
8
8
  txHash: { toString(): string };
9
9
  data: {
10
- includeByTimestamp: bigint;
10
+ expirationTimestamp: bigint;
11
11
  constants: {
12
12
  anchorBlockHeader: {
13
13
  globalVariables: {
@@ -35,21 +35,21 @@ export class TimestampTxValidator<T extends HasTimestampData> implements TxValid
35
35
  }
36
36
 
37
37
  validateTx(tx: T): Promise<TxValidationResult> {
38
- const includeByTimestamp = tx.data.includeByTimestamp;
39
- // If building block 1, we skip the expiration check. For details on why see the `validate_include_by_timestamp`
38
+ const expirationTimestamp = tx.data.expirationTimestamp;
39
+ // If building block 1, we skip the expiration check. For details on why see the `validate_expiration_timestamp`
40
40
  // function in `noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/validation_requests.nr`.
41
41
  const buildingBlock1 = this.values.blockNumber === 1;
42
42
 
43
- if (!buildingBlock1 && includeByTimestamp < this.values.timestamp) {
43
+ if (!buildingBlock1 && expirationTimestamp < this.values.timestamp) {
44
44
  if (tx.data.constants.anchorBlockHeader.globalVariables.blockNumber === 0) {
45
45
  this.#log.warn(
46
46
  `A tx built against a genesis block failed to be included in block 1 which is the only block in which txs built against a genesis block are allowed to be included.`,
47
47
  );
48
48
  }
49
49
  this.#log.verbose(
50
- `Rejecting tx ${tx.txHash} for low expiration timestamp. Tx expiration timestamp: ${includeByTimestamp}, timestamp: ${this.values.timestamp}.`,
50
+ `Rejecting tx ${tx.txHash} for low expiration timestamp. Tx expiration timestamp: ${expirationTimestamp}, timestamp: ${this.values.timestamp}.`,
51
51
  );
52
- return Promise.resolve({ result: 'invalid', reason: [TX_ERROR_INVALID_INCLUDE_BY_TIMESTAMP] });
52
+ return Promise.resolve({ result: 'invalid', reason: [TX_ERROR_INVALID_EXPIRATION_TIMESTAMP] });
53
53
  } else {
54
54
  return Promise.resolve({ result: 'valid' });
55
55
  }
@@ -58,7 +58,8 @@ const DefaultMaxSizesKb: Record<TopicType, number> = {
58
58
  // Proposals may carry some tx objects, so we allow a larger size capped at 10mb
59
59
  // Note this may not be enough for carrying all tx objects in a block
60
60
  [TopicType.block_proposal]: 1024 * 10,
61
- // TODO(palla/mbps): Check size for checkpoint proposal
61
+ // Checkpoint proposals carry almost the same data as a block proposal (see the lastBlockProposal)
62
+ // Only diff is an additional header, which is pretty small compared to the 10mb limit
62
63
  [TopicType.checkpoint_proposal]: 1024 * 10,
63
64
  };
64
65
 
@@ -1544,7 +1544,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1544
1544
  protected async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
1545
1545
  const currentBlockNumber = await this.archiver.getBlockNumber();
1546
1546
 
1547
- // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1547
+ // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1548
1548
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1549
1549
  const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
1550
1550
 
@@ -1599,7 +1599,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1599
1599
  public async validate(txs: Tx[]): Promise<void> {
1600
1600
  const currentBlockNumber = await this.archiver.getBlockNumber();
1601
1601
 
1602
- // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1602
+ // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1603
1603
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1604
1604
  const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
1605
1605
 
@@ -10,6 +10,7 @@ import { Tx, TxArray, TxHash } from '@aztec/stdlib/tx';
10
10
  import type { PeerId } from '@libp2p/interface';
11
11
  import { peerIdFromString } from '@libp2p/peer-id';
12
12
 
13
+ import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
13
14
  import { ReqRespSubProtocol } from '.././interface.js';
14
15
  import { BlockTxsRequest, BlockTxsResponse, type BlockTxsSource } from '.././protocols/index.js';
15
16
  import { ReqRespStatus } from '.././status.js';
@@ -20,7 +21,7 @@ import {
20
21
  DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE,
21
22
  } from './config.js';
22
23
  import type { BatchTxRequesterLibP2PService, BatchTxRequesterOptions, ITxMetadataCollection } from './interface.js';
23
- import { MissingTxMetadata, MissingTxMetadataCollection } from './missing_txs.js';
24
+ import { MissingTxMetadataCollection } from './missing_txs.js';
24
25
  import { type IPeerCollection, PeerCollection } from './peer_collection.js';
25
26
  import { BatchRequestTxValidator, type IBatchRequestTxValidator } from './tx_validator.js';
26
27
 
@@ -60,7 +61,7 @@ export class BatchTxRequester {
60
61
  private readonly txBatchSize: number;
61
62
 
62
63
  constructor(
63
- missingTxs: TxHash[],
64
+ missingTxsTracker: IMissingTxsTracker,
64
65
  blockTxsSource: BlockTxsSource,
65
66
  pinnedPeer: PeerId | undefined,
66
67
  timeoutMs: number,
@@ -99,8 +100,7 @@ export class BatchTxRequester {
99
100
  this.p2pService.peerScoring,
100
101
  );
101
102
  }
102
- const entries: Array<[string, MissingTxMetadata]> = missingTxs.map(h => [h.toString(), new MissingTxMetadata(h)]);
103
- this.txsMetadata = new MissingTxMetadataCollection(entries, this.txBatchSize);
103
+ this.txsMetadata = new MissingTxMetadataCollection(missingTxsTracker, this.txBatchSize);
104
104
  this.smartRequesterSemaphore = this.opts.semaphore ?? new Semaphore(0);
105
105
  }
106
106
 
@@ -661,7 +661,7 @@ export class BatchTxRequester {
661
661
  /*
662
662
  * @returns true if all missing txs have been fetched */
663
663
  private fetchedAllTxs() {
664
- return Array.from(this.txsMetadata.values()).every(tx => tx.fetched);
664
+ return this.txsMetadata.getMissingTxHashes().size == 0;
665
665
  }
666
666
 
667
667
  /*
@@ -679,7 +679,7 @@ export class BatchTxRequester {
679
679
  this.unlockSmartRequesterSemaphores();
680
680
  }
681
681
 
682
- return aborted || this.txsMetadata.size === 0 || this.fetchedAllTxs() || this.dateProvider.now() > this.deadline;
682
+ return aborted || this.fetchedAllTxs() || this.dateProvider.now() > this.deadline;
683
683
  }
684
684
 
685
685
  /*
@@ -6,7 +6,6 @@ import type { PeerId } from '@libp2p/interface';
6
6
 
7
7
  import type { ConnectionSampler } from '../connection-sampler/connection_sampler.js';
8
8
  import type { ReqRespInterface } from '../interface.js';
9
- import type { MissingTxMetadata } from './missing_txs.js';
10
9
  import type { IPeerCollection } from './peer_collection.js';
11
10
  import type { BatchRequestTxValidatorConfig, IBatchRequestTxValidator } from './tx_validator.js';
12
11
 
@@ -15,18 +14,15 @@ export interface IPeerPenalizer {
15
14
  }
16
15
 
17
16
  export interface ITxMetadataCollection {
18
- size: number;
19
- values(): IterableIterator<MissingTxMetadata>;
20
17
  getMissingTxHashes(): Set<string>;
18
+ markFetched(peerId: PeerId, tx: Tx): boolean;
21
19
  getTxsToRequestFromThePeer(peer: PeerId): TxHash[];
22
20
  markRequested(txHash: TxHash): void;
23
21
  markInFlightBySmartPeer(txHash: TxHash): void;
24
22
  markNotInFlightBySmartPeer(txHash: TxHash): void;
25
23
  alreadyFetched(txHash: TxHash): boolean;
26
24
  // Returns true if tx was marked as fetched, false if it was already marked as fetched
27
- markFetched(peerId: PeerId, tx: Tx): boolean;
28
25
  markPeerHas(peerId: PeerId, txHashes: TxHash[]): void;
29
- getFetchedTxs(): Tx[];
30
26
  }
31
27
 
32
28
  /**