@aztec/p2p 4.0.0-devnet.2-patch.4 → 4.0.0-devnet.3-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 (212) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +4 -5
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +30 -28
  5. package/dest/client/interface.d.ts +8 -13
  6. package/dest/client/interface.d.ts.map +1 -1
  7. package/dest/client/p2p_client.d.ts +6 -13
  8. package/dest/client/p2p_client.d.ts.map +1 -1
  9. package/dest/client/p2p_client.js +22 -88
  10. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +2 -4
  11. package/dest/config.d.ts +29 -10
  12. package/dest/config.d.ts.map +1 -1
  13. package/dest/config.js +80 -31
  14. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  15. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  16. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  17. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  18. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  19. package/dest/mem_pools/tx_pool/priority.js +4 -4
  20. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  21. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  22. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -2
  26. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
  27. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
  29. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
  30. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
  32. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
  33. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +10 -6
  35. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
  36. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
  38. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
  39. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  41. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
  42. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/index.js +1 -1
  44. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +9 -5
  45. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  47. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +46 -8
  48. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  49. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +81 -17
  50. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  51. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  52. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +9 -10
  53. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +5 -3
  54. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  55. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  56. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -2
  57. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  58. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +179 -151
  59. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +6 -4
  60. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  61. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  62. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +6 -4
  63. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  64. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  65. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -8
  66. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  67. package/dest/msg_validators/proposal_validator/proposal_validator.js +48 -36
  68. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
  69. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  70. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  71. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
  72. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  73. package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
  74. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  75. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  76. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  77. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  78. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  79. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  80. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  81. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  82. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  83. package/dest/msg_validators/tx_validator/factory.d.ts +133 -6
  84. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  85. package/dest/msg_validators/tx_validator/factory.js +247 -60
  86. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  87. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  88. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  89. package/dest/msg_validators/tx_validator/gas_validator.d.ts +67 -3
  90. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  91. package/dest/msg_validators/tx_validator/gas_validator.js +104 -37
  92. package/dest/msg_validators/tx_validator/index.d.ts +3 -1
  93. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  94. package/dest/msg_validators/tx_validator/index.js +2 -0
  95. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  96. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  97. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  98. package/dest/msg_validators/tx_validator/phases_validator.d.ts +22 -2
  99. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  100. package/dest/msg_validators/tx_validator/phases_validator.js +72 -24
  101. package/dest/services/dummy_service.d.ts +4 -4
  102. package/dest/services/dummy_service.d.ts.map +1 -1
  103. package/dest/services/dummy_service.js +4 -4
  104. package/dest/services/encoding.d.ts +6 -2
  105. package/dest/services/encoding.d.ts.map +1 -1
  106. package/dest/services/encoding.js +14 -8
  107. package/dest/services/gossipsub/topic_score_params.d.ts +18 -6
  108. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  109. package/dest/services/gossipsub/topic_score_params.js +32 -10
  110. package/dest/services/libp2p/libp2p_service.d.ts +16 -13
  111. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  112. package/dest/services/libp2p/libp2p_service.js +97 -93
  113. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  114. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
  115. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  116. package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
  117. package/dest/services/reqresp/reqresp.d.ts +1 -1
  118. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  119. package/dest/services/reqresp/reqresp.js +16 -8
  120. package/dest/services/service.d.ts +5 -3
  121. package/dest/services/service.d.ts.map +1 -1
  122. package/dest/services/tx_collection/file_store_tx_source.d.ts +5 -4
  123. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  124. package/dest/services/tx_collection/file_store_tx_source.js +39 -29
  125. package/dest/services/tx_collection/tx_source.d.ts +6 -5
  126. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  127. package/dest/services/tx_collection/tx_source.js +9 -7
  128. package/dest/services/tx_provider.d.ts +3 -3
  129. package/dest/services/tx_provider.d.ts.map +1 -1
  130. package/dest/services/tx_provider.js +4 -4
  131. package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
  132. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  133. package/dest/test-helpers/make-test-p2p-clients.js +1 -2
  134. package/dest/test-helpers/mock-pubsub.d.ts +4 -4
  135. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  136. package/dest/test-helpers/mock-pubsub.js +8 -2
  137. package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
  138. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  139. package/dest/test-helpers/reqresp-nodes.js +2 -2
  140. package/dest/test-helpers/testbench-utils.d.ts +2 -2
  141. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  142. package/dest/test-helpers/testbench-utils.js +2 -1
  143. package/dest/testbench/p2p_client_testbench_worker.js +7 -6
  144. package/dest/testbench/worker_client_manager.d.ts +3 -1
  145. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  146. package/dest/testbench/worker_client_manager.js +4 -1
  147. package/dest/util.d.ts +2 -2
  148. package/dest/util.d.ts.map +1 -1
  149. package/package.json +14 -14
  150. package/src/client/factory.ts +49 -45
  151. package/src/client/interface.ts +8 -13
  152. package/src/client/p2p_client.ts +24 -117
  153. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +2 -3
  154. package/src/config.ts +115 -33
  155. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  156. package/src/mem_pools/tx_pool/priority.ts +4 -4
  157. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  158. package/src/mem_pools/tx_pool_v2/README.md +9 -1
  159. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -2
  160. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
  161. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
  162. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -2
  163. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +10 -6
  164. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
  165. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
  166. package/src/mem_pools/tx_pool_v2/index.ts +1 -1
  167. package/src/mem_pools/tx_pool_v2/interfaces.ts +9 -4
  168. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +113 -18
  169. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +11 -11
  170. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +14 -2
  171. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +188 -153
  172. package/src/msg_validators/attestation_validator/README.md +49 -0
  173. package/src/msg_validators/proposal_validator/README.md +123 -0
  174. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +14 -4
  175. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +20 -7
  176. package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -40
  177. package/src/msg_validators/tx_validator/README.md +119 -0
  178. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
  179. package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
  180. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  181. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  182. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  183. package/src/msg_validators/tx_validator/factory.ts +394 -78
  184. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  185. package/src/msg_validators/tx_validator/gas_validator.ts +123 -27
  186. package/src/msg_validators/tx_validator/index.ts +2 -0
  187. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  188. package/src/msg_validators/tx_validator/phases_validator.ts +82 -27
  189. package/src/services/dummy_service.ts +6 -6
  190. package/src/services/encoding.ts +14 -7
  191. package/src/services/gossipsub/README.md +29 -14
  192. package/src/services/gossipsub/topic_score_params.ts +49 -13
  193. package/src/services/libp2p/libp2p_service.ts +111 -101
  194. package/src/services/reqresp/README.md +229 -0
  195. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  196. package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
  197. package/src/services/reqresp/reqresp.ts +18 -10
  198. package/src/services/service.ts +11 -2
  199. package/src/services/tx_collection/file_store_tx_source.ts +43 -31
  200. package/src/services/tx_collection/tx_source.ts +8 -7
  201. package/src/services/tx_provider.ts +2 -2
  202. package/src/test-helpers/make-test-p2p-clients.ts +0 -2
  203. package/src/test-helpers/mock-pubsub.ts +13 -6
  204. package/src/test-helpers/reqresp-nodes.ts +2 -5
  205. package/src/test-helpers/testbench-utils.ts +2 -1
  206. package/src/testbench/p2p_client_testbench_worker.ts +3 -6
  207. package/src/testbench/worker_client_manager.ts +11 -4
  208. package/src/util.ts +7 -1
  209. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
  210. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  211. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
  212. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
@@ -61,6 +61,7 @@ export class TxPoolV2Impl {
61
61
  #l2BlockSource: L2BlockSource;
62
62
  #worldStateSynchronizer: WorldStateSynchronizer;
63
63
  #createTxValidator: TxPoolV2Dependencies['createTxValidator'];
64
+ #checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
64
65
 
65
66
  // === In-Memory Indices ===
66
67
  #indices: TxPoolIndices = new TxPoolIndices();
@@ -92,6 +93,7 @@ export class TxPoolV2Impl {
92
93
  this.#l2BlockSource = deps.l2BlockSource;
93
94
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
94
95
  this.#createTxValidator = deps.createTxValidator;
96
+ this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
95
97
 
96
98
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
97
99
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
@@ -187,9 +189,35 @@ export class TxPoolV2Impl {
187
189
  const errors = new Map<string, TxPoolRejectionError>();
188
190
  const acceptedPending = new Set<string>();
189
191
 
192
+ // Phase 1: Pre-compute all throwable I/O outside the transaction.
193
+ // If any pre-computation throws, the entire call fails before mutations happen.
194
+ const precomputed = new Map<string, { meta: TxMetaData; minedBlockId: L2BlockId | undefined; isValid: boolean }>();
195
+
196
+ const validator = await this.#createTxValidator();
197
+
198
+ for (const tx of txs) {
199
+ const txHash = tx.getTxHash();
200
+ const txHashStr = txHash.toString();
201
+
202
+ const meta = await buildTxMetaData(tx);
203
+ const minedBlockId = await this.#getMinedBlockId(txHash);
204
+
205
+ // Validate non-mined txs (mined and pre-protected txs bypass validation inside the transaction)
206
+ let isValid = true;
207
+ if (!minedBlockId) {
208
+ isValid = await this.#validateMeta(meta, validator);
209
+ }
210
+
211
+ precomputed.set(txHashStr, { meta, minedBlockId, isValid });
212
+ }
213
+
214
+ // Phase 2: Apply mutations inside the transaction using only pre-computed results,
215
+ // in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
190
216
  const poolAccess = this.#createPreAddPoolAccess();
191
217
  const preAddContext: PreAddContext | undefined =
192
- opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
218
+ opts.feeComparisonOnly !== undefined
219
+ ? { feeComparisonOnly: opts.feeComparisonOnly, priceBumpPercentage: this.#config.priceBumpPercentage }
220
+ : undefined;
193
221
 
194
222
  await this.#store.transactionAsync(async () => {
195
223
  for (const tx of txs) {
@@ -202,22 +230,25 @@ export class TxPoolV2Impl {
202
230
  continue;
203
231
  }
204
232
 
205
- // Check mined status first (applies to all paths)
206
- const minedBlockId = await this.#getMinedBlockId(txHash);
233
+ const { meta, minedBlockId, isValid } = precomputed.get(txHashStr)!;
207
234
  const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
208
235
 
209
236
  if (minedBlockId) {
210
237
  // Already mined - add directly (protection already set if pre-protected)
211
- await this.#addTx(tx, { mined: minedBlockId }, opts);
238
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
212
239
  accepted.push(txHash);
213
240
  } else if (preProtectedSlot !== undefined) {
214
241
  // Pre-protected and not mined - add as protected (bypass validation)
215
- await this.#addTx(tx, { protected: preProtectedSlot }, opts);
242
+ await this.#addTx(tx, { protected: preProtectedSlot }, opts, meta);
216
243
  accepted.push(txHash);
244
+ } else if (!isValid) {
245
+ // Failed pre-computed validation
246
+ rejected.push(txHash);
217
247
  } else {
218
- // Regular pending tx - validate and run pre-add rules
248
+ // Regular pending tx - run pre-add rules using pre-computed metadata
219
249
  const result = await this.#tryAddRegularPendingTx(
220
250
  tx,
251
+ meta,
221
252
  opts,
222
253
  poolAccess,
223
254
  acceptedPending,
@@ -227,13 +258,18 @@ export class TxPoolV2Impl {
227
258
  );
228
259
  if (result.status === 'accepted') {
229
260
  acceptedPending.add(txHashStr);
230
- } else if (result.status === 'rejected') {
231
- rejected.push(txHash);
232
261
  } else {
233
262
  ignored.push(txHash);
234
263
  }
235
264
  }
236
265
  }
266
+
267
+ // Run post-add eviction rules for pending txs (inside transaction for atomicity)
268
+ if (acceptedPending.size > 0) {
269
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
270
+ const uniqueFeePayers = new Set<string>(feePayers);
271
+ await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
272
+ }
237
273
  });
238
274
 
239
275
  // Build final accepted list for pending txs (excludes intra-batch evictions)
@@ -249,37 +285,24 @@ export class TxPoolV2Impl {
249
285
  this.#instrumentation.recordRejected(rejected.length);
250
286
  }
251
287
 
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
288
  return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
260
289
  }
261
290
 
262
- /** Validates and adds a regular pending tx. Returns status. */
291
+ /** Adds a validated pending tx, running pre-add rules and evicting conflicts. */
263
292
  async #tryAddRegularPendingTx(
264
293
  tx: Tx,
294
+ precomputedMeta: TxMetaData,
265
295
  opts: { source?: string },
266
296
  poolAccess: PreAddPoolAccess,
267
297
  acceptedPending: Set<string>,
268
298
  ignored: TxHash[],
269
299
  errors: Map<string, TxPoolRejectionError>,
270
300
  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
- }
301
+ ): Promise<{ status: 'accepted' | 'ignored' }> {
302
+ const txHashStr = tx.getTxHash().toString();
280
303
 
281
304
  // Run pre-add rules
282
- const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess, preAddContext);
305
+ const preAddResult = await this.#evictionManager.runPreAddRules(precomputedMeta, poolAccess, preAddContext);
283
306
 
284
307
  if (preAddResult.shouldIgnore) {
285
308
  this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
@@ -317,11 +340,11 @@ export class TxPoolV2Impl {
317
340
  }
318
341
 
319
342
  // Add the transaction
320
- await this.#addTx(tx, 'pending', opts);
343
+ await this.#addTx(tx, 'pending', opts, precomputedMeta);
321
344
  return { status: 'accepted' };
322
345
  }
323
346
 
324
- async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored' | 'rejected'> {
347
+ async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
325
348
  const txHashStr = tx.getTxHash().toString();
326
349
 
327
350
  // Check if already in pool
@@ -329,14 +352,8 @@ export class TxPoolV2Impl {
329
352
  return 'ignored';
330
353
  }
331
354
 
332
- // Build metadata and validate using metadata
355
+ // Build metadata and check pre-add rules
333
356
  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
357
  const poolAccess = this.#createPreAddPoolAccess();
341
358
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
342
359
 
@@ -346,20 +363,25 @@ export class TxPoolV2Impl {
346
363
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
347
364
  const slotNumber = block.globalVariables.slotNumber;
348
365
 
366
+ // Precompute setup-call allow-list flags outside the store transaction
367
+ const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
368
+
349
369
  await this.#store.transactionAsync(async () => {
350
- for (const tx of txs) {
370
+ for (let i = 0; i < txs.length; i++) {
371
+ const tx = txs[i];
351
372
  const txHash = tx.getTxHash();
352
373
  const txHashStr = txHash.toString();
353
374
  const isNew = !this.#indices.has(txHashStr);
354
375
  const minedBlockId = await this.#getMinedBlockId(txHash);
355
376
 
356
377
  if (isNew) {
378
+ const meta = await buildTxMetaData(tx, allowedFlags[i]);
357
379
  // New tx - add as mined or protected (callback emitted by #addTx)
358
380
  if (minedBlockId) {
359
- await this.#addTx(tx, { mined: minedBlockId }, opts);
381
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
360
382
  this.#indices.setProtection(txHashStr, slotNumber);
361
383
  } else {
362
- await this.#addTx(tx, { protected: slotNumber }, opts);
384
+ await this.#addTx(tx, { protected: slotNumber }, opts, meta);
363
385
  }
364
386
  } else {
365
387
  // Existing tx - update protection and mined status
@@ -379,33 +401,35 @@ export class TxPoolV2Impl {
379
401
  let softDeletedHits = 0;
380
402
  let missingPreviouslyEvicted = 0;
381
403
 
382
- for (const txHash of txHashes) {
383
- const txHashStr = txHash.toString();
404
+ await this.#store.transactionAsync(async () => {
405
+ for (const txHash of txHashes) {
406
+ const txHashStr = txHash.toString();
384
407
 
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++;
408
+ if (this.#indices.has(txHashStr)) {
409
+ // Update protection for existing tx
410
+ this.#indices.updateProtection(txHashStr, slotNumber);
411
+ } else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
412
+ // Resurrect soft-deleted tx as protected
413
+ const buffer = await this.#txsDB.getAsync(txHashStr);
414
+ if (buffer) {
415
+ const tx = Tx.fromBuffer(buffer);
416
+ await this.#addTx(tx, { protected: slotNumber });
417
+ softDeletedHits++;
418
+ } else {
419
+ // Data missing despite soft-delete flag — treat as truly missing
420
+ this.#indices.setProtection(txHashStr, slotNumber);
421
+ missing.push(txHash);
422
+ }
395
423
  } else {
396
- // Data missing despite soft-delete flag treat as truly missing
424
+ // Truly missing pre-record protection for tx we don't have yet
397
425
  this.#indices.setProtection(txHashStr, slotNumber);
398
426
  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++;
427
+ if (this.#evictedTxHashes.has(txHashStr)) {
428
+ missingPreviouslyEvicted++;
429
+ }
406
430
  }
407
431
  }
408
- }
432
+ });
409
433
 
410
434
  // Record metrics
411
435
  if (softDeletedHits > 0) {
@@ -466,56 +490,60 @@ export class TxPoolV2Impl {
466
490
  }
467
491
  }
468
492
 
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
- }
493
+ await this.#store.transactionAsync(async () => {
494
+ // Step 4: Mark txs as mined (only those we have in the pool)
495
+ for (const meta of found) {
496
+ this.#indices.markAsMined(meta, blockId);
497
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
498
+ }
474
499
 
475
- // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
476
- await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
500
+ // Step 5: Run post-event eviction rules (inside transaction for atomicity)
501
+ await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
502
+ });
477
503
 
478
504
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
479
505
  }
480
506
 
481
507
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
482
- // Step 0: Clean up slot-deleted txs from previous slots
483
- await this.#deletedPool.cleanupSlotDeleted(slotNumber);
508
+ await this.#store.transactionAsync(async () => {
509
+ // Step 0: Clean up slot-deleted txs from previous slots
510
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
484
511
 
485
- // Step 1: Find expired protected txs
486
- const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
512
+ // Step 1: Find expired protected txs
513
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
487
514
 
488
- // Step 2: Clear protection for all expired entries (including those without metadata)
489
- this.#indices.clearProtection(expiredProtected);
515
+ // Step 2: Clear protection for all expired entries (including those without metadata)
516
+ this.#indices.clearProtection(expiredProtected);
490
517
 
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
- }
518
+ // Step 3: Filter to only txs that have metadata and are not mined
519
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
520
+ if (txsToRestore.length === 0) {
521
+ this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
522
+ return;
523
+ }
497
524
 
498
- this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
525
+ this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
499
526
 
500
- // Step 4: Validate for pending pool
501
- const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
527
+ // Step 4: Validate for pending pool
528
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
502
529
 
503
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
504
- const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
530
+ // Step 5: Resolve nullifier conflicts and add winners to pending indices
531
+ const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
505
532
 
506
- // Step 6: Delete invalid txs and evict conflict losers
507
- await this.#deleteTxsBatch(invalid);
508
- await this.#evictTxs(toEvict, 'NullifierConflict');
533
+ // Step 6: Delete invalid txs and evict conflict losers
534
+ await this.#deleteTxsBatch(invalid);
535
+ await this.#evictTxs(toEvict, 'NullifierConflict');
509
536
 
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
- }
537
+ // Step 7: Run eviction rules (enforce pool size limit)
538
+ if (added.length > 0) {
539
+ const feePayers = added.map(meta => meta.feePayer);
540
+ const uniqueFeePayers = new Set<string>(feePayers);
541
+ await this.#evictionManager.evictAfterNewTxs(
542
+ added.map(m => m.txHash),
543
+ [...uniqueFeePayers],
544
+ );
545
+ }
546
+ });
519
547
  }
520
548
 
521
549
  async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
@@ -528,57 +556,60 @@ export class TxPoolV2Impl {
528
556
 
529
557
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
530
558
 
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
- );
559
+ await this.#store.transactionAsync(async () => {
560
+ // Step 2: Mark ALL un-mined txs with their original mined block number
561
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
562
+ // when their original mined block is finalized
563
+ await this.#deletedPool.markFromPrunedBlock(
564
+ txsToUnmine.map(m => ({
565
+ txHash: m.txHash,
566
+ minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
567
+ })),
568
+ );
540
569
 
541
- // Step 3: Unmine - clear mined status from metadata
542
- for (const meta of txsToUnmine) {
543
- this.#indices.markAsUnmined(meta);
544
- }
570
+ // Step 3: Unmine - clear mined status from metadata
571
+ for (const meta of txsToUnmine) {
572
+ this.#indices.markAsUnmined(meta);
573
+ }
545
574
 
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
- }
575
+ // If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
576
+ if (options?.deleteAllTxs) {
577
+ const allTxHashes = txsToUnmine.map(m => m.txHash);
578
+ await this.#deleteTxsBatch(allTxHashes);
579
+ this.#log.info(
580
+ `Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
581
+ );
582
+ return;
583
+ }
555
584
 
556
- // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
557
- const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
585
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
586
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
558
587
 
559
- // Step 5: Validate for pending pool
560
- const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
588
+ // Step 5: Validate for pending pool
589
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
561
590
 
562
- // Step 6: Resolve nullifier conflicts and add winners to pending indices
563
- const { toEvict } = this.#applyNullifierConflictResolution(valid);
591
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
592
+ const { toEvict } = this.#applyNullifierConflictResolution(valid);
564
593
 
565
- // Step 7: Delete invalid txs and evict conflict losers
566
- await this.#deleteTxsBatch(invalid);
567
- await this.#evictTxs(toEvict, 'NullifierConflict');
594
+ // Step 7: Delete invalid txs and evict conflict losers
595
+ await this.#deleteTxsBatch(invalid);
596
+ await this.#evictTxs(toEvict, 'NullifierConflict');
568
597
 
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
- );
598
+ this.#log.info(
599
+ `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
600
+ { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
601
+ );
573
602
 
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);
603
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
604
+ // This handles cases like existing pending txs with invalid fee payer balances
605
+ await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
606
+ });
577
607
  }
578
608
 
579
609
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
580
- // Delete failed txs
581
- await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
610
+ await this.#store.transactionAsync(async () => {
611
+ await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
612
+ });
582
613
 
583
614
  this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
584
615
  }
@@ -589,27 +620,29 @@ export class TxPoolV2Impl {
589
620
  // Step 1: Find mined txs at or before finalized block
590
621
  const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
591
622
 
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));
623
+ await this.#store.transactionAsync(async () => {
624
+ // Step 2: Collect mined txs for archiving (before deletion)
625
+ const txsToArchive: Tx[] = [];
626
+ if (this.#archive.isEnabled()) {
627
+ for (const txHashStr of minedTxsToFinalize) {
628
+ const buffer = await this.#txsDB.getAsync(txHashStr);
629
+ if (buffer) {
630
+ txsToArchive.push(Tx.fromBuffer(buffer));
631
+ }
599
632
  }
600
633
  }
601
- }
602
634
 
603
- // Step 3: Delete mined txs from active pool
604
- await this.#deleteTxsBatch(minedTxsToFinalize);
635
+ // Step 3: Delete mined txs from active pool
636
+ await this.#deleteTxsBatch(minedTxsToFinalize);
605
637
 
606
- // Step 4: Finalize soft-deleted txs
607
- await this.#deletedPool.finalizeBlock(blockNumber);
638
+ // Step 4: Finalize soft-deleted txs
639
+ await this.#deletedPool.finalizeBlock(blockNumber);
608
640
 
609
- // Step 5: Archive mined txs
610
- if (txsToArchive.length > 0) {
611
- await this.#archive.archiveTxs(txsToArchive);
612
- }
641
+ // Step 5: Archive mined txs
642
+ if (txsToArchive.length > 0) {
643
+ await this.#archive.archiveTxs(txsToArchive);
644
+ }
645
+ });
613
646
 
614
647
  if (minedTxsToFinalize.length > 0) {
615
648
  this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
@@ -754,9 +787,10 @@ export class TxPoolV2Impl {
754
787
  tx: Tx,
755
788
  state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
756
789
  opts: { source?: string } = {},
790
+ precomputedMeta?: TxMetaData,
757
791
  ): Promise<TxMetaData> {
758
792
  const txHashStr = tx.getTxHash().toString();
759
- const meta = await buildTxMetaData(tx);
793
+ const meta = precomputedMeta ?? (await buildTxMetaData(tx));
760
794
  meta.receivedAt = this.#dateProvider.now();
761
795
 
762
796
  await this.#txsDB.set(txHashStr, tx.toBuffer());
@@ -938,7 +972,8 @@ export class TxPoolV2Impl {
938
972
 
939
973
  try {
940
974
  const tx = Tx.fromBuffer(buffer);
941
- const meta = await buildTxMetaData(tx);
975
+ const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
976
+ const meta = await buildTxMetaData(tx, allowedSetupCalls);
942
977
  loaded.push({ tx, meta });
943
978
  } catch (err) {
944
979
  this.#log.warn(`Failed to deserialize tx ${txHashStr}, deleting`, { err });
@@ -0,0 +1,49 @@
1
+ # Attestation Validation
2
+
3
+ This module validates `CheckpointAttestation` gossipsub messages. Attestations are signatures from committee members endorsing a checkpoint proposal.
4
+
5
+ **Topic**: `checkpoint_attestation` | **Snappy size limit**: 5 KB
6
+
7
+ ## Stage 1: AttestationValidator (Gossipsub Validation)
8
+
9
+ | # | Rule | Consequence | Severity | File |
10
+ |---|------|-------------|----------|------|
11
+ | 1 | **Slot timeliness**: `currentSlot` or `nextSlot`. Previous slot within 500ms: IGNORE. Older: REJECT. | REJECT or IGNORE | HighToleranceError | `attestation_validator.ts` |
12
+ | 2 | **Attester signature**: `getSender()` must recover valid address | REJECT | LowToleranceError | same |
13
+ | 3 | **Attester in committee**: recovered address in committee for slot | REJECT | HighToleranceError | same |
14
+ | 4 | **Proposer exists**: `getProposerAttesterAddressInSlot` must return defined | REJECT | HighToleranceError | same |
15
+ | 5 | **Proposer signature**: `getProposer()` must recover valid address | REJECT | LowToleranceError | same |
16
+ | 6 | **Proposer matches expected**: recovered proposer = expected for slot | REJECT | HighToleranceError | same |
17
+ | 7 | **NoCommitteeError**: committee unavailable | REJECT | LowToleranceError | same |
18
+
19
+ **Fisherman mode extension** (`FishermanAttestationValidator`): if a checkpoint proposal for the same archive exists in pool, the attestation's `ConsensusPayload` must `.equals()` the stored proposal's payload. On mismatch: REJECT + LowToleranceError.
20
+
21
+ ## Stage 2: Pool Admission
22
+
23
+ | # | Rule | Consequence |
24
+ |---|------|-------------|
25
+ | 8 | Sender recoverable (pool-side) | Silent drop |
26
+ | 9 | Not a duplicate (same slot + proposalId + signer) | IGNORE |
27
+ | 10 | Per-signer cap: `MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER` = 3 | IGNORE |
28
+
29
+ Own attestations added via `addOwnCheckpointAttestations` bypass the per-signer cap.
30
+
31
+ ## Stage 3: Equivocation Detection
32
+
33
+ When a signer's attestation count for a slot reaches exactly 2 (different proposals): `duplicateAttestationCallback` fires -> `WANT_TO_SLASH_EVENT` with `OffenseType.DUPLICATE_ATTESTATION`. Attestation still ACCEPTED and rebroadcast. Callback fires once (not again at count 3+).
34
+
35
+ ## Validation at L1 Checkpoint Submission (Archiver)
36
+
37
+ | Rule | Consequence | File |
38
+ |------|-------------|------|
39
+ | Each attestation must have recoverable signature (or address-only is allowed but does not count toward quorum) | Checkpoint rejected as invalid | `archiver/src/modules/validation.ts` |
40
+ | Attestation at index `i` must correspond to committee member at index `i` | Checkpoint rejected as invalid | same |
41
+ | Valid attestation count >= floor(committee * 2/3) + 1 | Checkpoint rejected as invalid | same |
42
+ | No committee / escape hatch open | Accepted unconditionally | same |
43
+
44
+ Note: `skipValidateCheckpointAttestations` config flag bypasses all archiver attestation validation.
45
+
46
+ ## Gossipsub Topic Scoring
47
+
48
+ P3 enabled with expected messages per slot = `targetCommitteeSize`. Conservative threshold (30% of convergence value). Max P3 penalty = -34 per topic.
49
+