@aztec/p2p 0.0.1-commit.993d240 → 0.0.1-commit.9a89641

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 (201) hide show
  1. package/README.md +7 -6
  2. package/dest/bootstrap/bootstrap.d.ts +1 -1
  3. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  4. package/dest/bootstrap/bootstrap.js +20 -12
  5. package/dest/client/factory.d.ts +2 -2
  6. package/dest/client/factory.d.ts.map +1 -1
  7. package/dest/client/factory.js +10 -5
  8. package/dest/client/interface.d.ts +40 -9
  9. package/dest/client/interface.d.ts.map +1 -1
  10. package/dest/client/p2p_client.d.ts +23 -12
  11. package/dest/client/p2p_client.d.ts.map +1 -1
  12. package/dest/client/p2p_client.js +38 -35
  13. package/dest/config.d.ts +19 -13
  14. package/dest/config.d.ts.map +1 -1
  15. package/dest/config.js +27 -26
  16. package/dest/errors/reqresp.error.d.ts +13 -1
  17. package/dest/errors/reqresp.error.d.ts.map +1 -1
  18. package/dest/errors/reqresp.error.js +14 -0
  19. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
  20. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/attestation_pool.js +9 -2
  22. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  23. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +43 -0
  25. package/dest/mem_pools/instrumentation.d.ts +9 -4
  26. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  27. package/dest/mem_pools/instrumentation.js +11 -8
  28. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +1 -5
  29. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +1 -7
  31. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -3
  32. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  33. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +6 -6
  34. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  35. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  36. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -3
  37. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
  38. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  39. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +3 -1
  40. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +1 -1
  41. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -1
  42. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +3 -1
  43. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts +1 -1
  44. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -1
  45. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +3 -4
  46. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts +1 -1
  47. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +4 -5
  49. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +1 -1
  50. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  51. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +6 -5
  52. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +30 -5
  53. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  54. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  55. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +15 -1
  56. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  57. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +31 -0
  58. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +9 -3
  59. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  60. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +19 -8
  61. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +17 -4
  62. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  63. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +133 -34
  64. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +6 -5
  65. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  66. package/dest/msg_validators/attestation_validator/attestation_validator.js +24 -26
  67. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -4
  68. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  69. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
  70. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +4 -3
  71. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  72. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +2 -2
  73. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +4 -3
  74. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  75. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +8 -2
  76. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +19 -5
  77. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  78. package/dest/msg_validators/proposal_validator/proposal_validator.js +66 -30
  79. package/dest/msg_validators/tx_validator/factory.d.ts +9 -11
  80. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  81. package/dest/msg_validators/tx_validator/factory.js +15 -6
  82. package/dest/msg_validators/tx_validator/gas_validator.d.ts +12 -7
  83. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  84. package/dest/msg_validators/tx_validator/gas_validator.js +15 -15
  85. package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
  86. package/dest/msg_validators/tx_validator/timestamp_validator.js +1 -1
  87. package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts +2 -2
  88. package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts.map +1 -1
  89. package/dest/msg_validators/tx_validator/tx_validation_cache.js +6 -7
  90. package/dest/services/discv5/discV5_service.d.ts +12 -2
  91. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  92. package/dest/services/discv5/discV5_service.js +84 -15
  93. package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
  94. package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
  95. package/dest/services/discv5/persisted_enr_store.js +145 -0
  96. package/dest/services/dummy_service.d.ts +6 -2
  97. package/dest/services/dummy_service.d.ts.map +1 -1
  98. package/dest/services/dummy_service.js +3 -0
  99. package/dest/services/encoding.d.ts +19 -6
  100. package/dest/services/encoding.d.ts.map +1 -1
  101. package/dest/services/encoding.js +25 -15
  102. package/dest/services/gossipsub/topic_score_params.d.ts +6 -23
  103. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  104. package/dest/services/gossipsub/topic_score_params.js +4 -31
  105. package/dest/services/libp2p/libp2p_service.d.ts +12 -3
  106. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  107. package/dest/services/libp2p/libp2p_service.js +204 -114
  108. package/dest/services/peer-manager/peer_manager.d.ts +15 -2
  109. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  110. package/dest/services/peer-manager/peer_manager.js +46 -18
  111. package/dest/services/peer-manager/peer_scoring.d.ts +28 -1
  112. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  113. package/dest/services/peer-manager/peer_scoring.js +70 -3
  114. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -8
  115. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  116. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +21 -39
  117. package/dest/services/reqresp/config.d.ts +13 -1
  118. package/dest/services/reqresp/config.d.ts.map +1 -1
  119. package/dest/services/reqresp/config.js +11 -0
  120. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +4 -3
  121. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
  122. package/dest/services/reqresp/protocols/block_txs/bitvector.js +3 -2
  123. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +1 -1
  124. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  125. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +29 -28
  126. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +12 -7
  127. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  128. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +42 -24
  129. package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
  130. package/dest/services/reqresp/reqresp.d.ts +1 -1
  131. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  132. package/dest/services/reqresp/reqresp.js +34 -3
  133. package/dest/services/service.d.ts +21 -1
  134. package/dest/services/service.d.ts.map +1 -1
  135. package/dest/services/tx_collection/tx_source.d.ts +1 -1
  136. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  137. package/dest/services/tx_collection/tx_source.js +4 -1
  138. package/dest/test-helpers/testbench-utils.d.ts +4 -1
  139. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  140. package/dest/test-helpers/testbench-utils.js +11 -3
  141. package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
  142. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  143. package/dest/testbench/p2p_client_testbench_worker.js +19 -2
  144. package/package.json +15 -17
  145. package/src/bootstrap/bootstrap.ts +15 -9
  146. package/src/client/factory.ts +9 -5
  147. package/src/client/interface.ts +33 -7
  148. package/src/client/p2p_client.ts +46 -48
  149. package/src/config.ts +50 -38
  150. package/src/errors/reqresp.error.ts +19 -0
  151. package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
  152. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
  153. package/src/mem_pools/instrumentation.ts +13 -11
  154. package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +1 -9
  155. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +7 -7
  156. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -4
  157. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -2
  158. package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +3 -1
  159. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +1 -3
  160. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -4
  161. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +5 -6
  162. package/src/mem_pools/tx_pool_v2/interfaces.ts +28 -4
  163. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +35 -0
  164. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +22 -8
  165. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +158 -33
  166. package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -26
  167. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -3
  168. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -2
  169. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +10 -2
  170. package/src/msg_validators/proposal_validator/proposal_validator.ts +69 -30
  171. package/src/msg_validators/tx_validator/README.md +2 -0
  172. package/src/msg_validators/tx_validator/factory.ts +19 -13
  173. package/src/msg_validators/tx_validator/gas_validator.ts +16 -20
  174. package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
  175. package/src/msg_validators/tx_validator/timestamp_validator.ts +1 -1
  176. package/src/msg_validators/tx_validator/tx_validation_cache.ts +6 -7
  177. package/src/services/discv5/discV5_service.ts +88 -9
  178. package/src/services/discv5/persisted_enr_store.ts +158 -0
  179. package/src/services/dummy_service.ts +6 -0
  180. package/src/services/encoding.ts +28 -20
  181. package/src/services/gossipsub/README.md +33 -11
  182. package/src/services/gossipsub/topic_score_params.ts +9 -50
  183. package/src/services/libp2p/libp2p_service.ts +234 -125
  184. package/src/services/peer-manager/peer_manager.ts +48 -17
  185. package/src/services/peer-manager/peer_scoring.ts +75 -3
  186. package/src/services/reqresp/README.md +2 -23
  187. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +21 -45
  188. package/src/services/reqresp/config.ts +17 -0
  189. package/src/services/reqresp/protocols/block_txs/bitvector.ts +3 -2
  190. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +33 -31
  191. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +47 -20
  192. package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
  193. package/src/services/reqresp/reqresp.ts +42 -2
  194. package/src/services/service.ts +23 -0
  195. package/src/services/tx_collection/tx_source.ts +2 -1
  196. package/src/test-helpers/testbench-utils.ts +13 -2
  197. package/src/testbench/p2p_client_testbench_worker.ts +22 -2
  198. package/dest/msg_validators/clock_tolerance.d.ts +0 -32
  199. package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
  200. package/dest/msg_validators/clock_tolerance.js +0 -95
  201. package/src/msg_validators/clock_tolerance.ts +0 -127
package/src/config.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  SecretValue,
4
4
  bigintConfigHelper,
5
5
  booleanConfigHelper,
6
+ floatConfigHelper,
6
7
  getConfigFromMappings,
7
8
  getDefaultConfig,
8
9
  numberConfigHelper,
@@ -42,34 +43,26 @@ export interface P2PConfig
42
43
  TxFileStoreConfig,
43
44
  Pick<
44
45
  SequencerConfig,
45
- | 'blockDurationMs'
46
46
  | 'expectedBlockProposalsPerSlot'
47
- | 'l1PublishingTime'
48
47
  | 'maxTxsPerBlock'
49
- | 'attestationPropagationTime'
48
+ | 'checkpointProposalSyncGraceSeconds'
50
49
  | 'maxBlocksPerCheckpoint'
51
- > {
50
+ >,
51
+ // `blockDurationMs` is optional on the loose `SequencerConfig` but is always populated for p2p via
52
+ // the shared `numberConfigHelper(3000)` mapping, so it is required here.
53
+ Required<Pick<SequencerConfig, 'blockDurationMs'>> {
52
54
  /** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
53
55
  validateMaxTxsPerBlock?: number;
54
56
 
55
57
  /** Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. */
56
58
  validateMaxTxsPerCheckpoint?: number;
57
59
 
58
- /** Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. */
59
- validateMaxL2BlockGas?: number;
60
-
61
- /** Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. */
62
- validateMaxDABlockGas?: number;
63
-
64
60
  /** A flag dictating whether the P2P subsystem should be enabled. */
65
61
  p2pEnabled: boolean;
66
62
 
67
63
  /** The frequency in which to check for new L2 blocks. */
68
64
  blockCheckIntervalMS: number;
69
65
 
70
- /** The frequency in which to check for new L2 slots. */
71
- slotCheckIntervalMS: number;
72
-
73
66
  /** The number of blocks to fetch in a single batch. */
74
67
  blockRequestBatchSize: number;
75
68
 
@@ -150,6 +143,13 @@ export interface P2PConfig
150
143
  /** How long to keep message IDs in the seen cache (ms). */
151
144
  gossipsubSeenTTL: number;
152
145
 
146
+ /**
147
+ * Maximum clock-disparity tolerance (ms) applied to proposal/attestation gossip receive windows. Both
148
+ * ends of each acceptance window are widened by this much so peers are not penalized for messages valid
149
+ * when sent but arriving slightly early or late due to clock skew (Ethereum's MAXIMUM_GOSSIP_CLOCK_DISPARITY).
150
+ */
151
+ maxGossipClockDisparityMs: number;
152
+
153
153
  /** The 'age' (in # of L2 blocks) of a processed tx after which we heavily penalize a peer for re-sending it. */
154
154
  doubleSpendSeverePeerPenaltyWindow: number;
155
155
 
@@ -165,6 +165,9 @@ export interface P2PConfig
165
165
  /** The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors. */
166
166
  peerPenaltyValues: number[];
167
167
 
168
+ /** How long (in seconds) a peer is banned for once its score drops below the ban threshold. */
169
+ peerBanDurationSeconds: number;
170
+
168
171
  /** Limit of transactions to archive in the tx pool. Once the archived tx limit is reached, the oldest archived txs will be purged. */
169
172
  archivedTxLimit: number;
170
173
 
@@ -241,6 +244,12 @@ export interface P2PConfig
241
244
  /** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
242
245
  priceBumpPercentage: bigint;
243
246
 
247
+ /**
248
+ * Number of slots behind the finalized tip to keep finalized txs for before deleting them. 0 deletes
249
+ * at the finalized tip (default).
250
+ */
251
+ keepFinalizedTxsForSlots: number;
252
+
244
253
  /** Drop incoming block and checkpoint proposals at the libp2p dispatch layer (for testing only) */
245
254
  skipIncomingProposals?: boolean;
246
255
 
@@ -278,16 +287,6 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
278
287
  'Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set.',
279
288
  ...optionalNumberConfigHelper(),
280
289
  },
281
- validateMaxL2BlockGas: {
282
- env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
283
- description: 'Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected.',
284
- ...optionalNumberConfigHelper(),
285
- },
286
- validateMaxDABlockGas: {
287
- env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
288
- description: 'Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected.',
289
- ...optionalNumberConfigHelper(),
290
- },
291
290
  p2pEnabled: {
292
291
  env: 'P2P_ENABLED',
293
292
  description: 'A flag dictating whether the P2P subsystem should be enabled.',
@@ -303,11 +302,6 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
303
302
  description: 'The frequency in which to check for new L2 blocks.',
304
303
  ...numberConfigHelper(100),
305
304
  },
306
- slotCheckIntervalMS: {
307
- env: 'P2P_SLOT_CHECK_INTERVAL_MS',
308
- description: 'The frequency in which to check for new L2 slots.',
309
- ...numberConfigHelper(1000),
310
- },
311
305
  debugDisableColocationPenalty: {
312
306
  env: 'DEBUG_P2P_DISABLE_COLOCATION_PENALTY',
313
307
  description: 'DEBUG: Disable colocation penalty - NEVER set to true in production',
@@ -439,20 +433,26 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
439
433
  description: 'How long to keep message IDs in the seen cache.',
440
434
  ...numberConfigHelper(20 * 60 * 1000),
441
435
  },
436
+ maxGossipClockDisparityMs: {
437
+ env: 'P2P_MAX_GOSSIP_CLOCK_DISPARITY_MS',
438
+ description:
439
+ 'Maximum clock-disparity tolerance (ms) applied to both ends of proposal/attestation gossip receive windows.',
440
+ ...numberConfigHelper(500),
441
+ },
442
442
  gossipsubTxTopicWeight: {
443
443
  env: 'P2P_GOSSIPSUB_TX_TOPIC_WEIGHT',
444
444
  description: 'The weight of the tx topic for the gossipsub protocol.',
445
- ...numberConfigHelper(1),
445
+ ...floatConfigHelper(1),
446
446
  },
447
447
  gossipsubTxInvalidMessageDeliveriesWeight: {
448
448
  env: 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT',
449
449
  description: 'The weight of the tx invalid message deliveries for the gossipsub protocol.',
450
- ...numberConfigHelper(-20),
450
+ ...floatConfigHelper(-20),
451
451
  },
452
452
  gossipsubTxInvalidMessageDeliveriesDecay: {
453
453
  env: 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY',
454
454
  description: 'Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1.',
455
- ...numberConfigHelper(0.5),
455
+ ...floatConfigHelper(0.5),
456
456
  },
457
457
  peerPenaltyValues: {
458
458
  env: 'P2P_PEER_PENALTY_VALUES',
@@ -461,6 +461,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
461
461
  'The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors.',
462
462
  defaultValue: [2, 10, 50],
463
463
  },
464
+ peerBanDurationSeconds: {
465
+ env: 'P2P_PEER_BAN_DURATION_SECONDS',
466
+ description: 'How long (in seconds) a peer is banned for once its score drops below the ban threshold.',
467
+ ...numberConfigHelper(24 * 60 * 60),
468
+ },
464
469
  doubleSpendSeverePeerPenaltyWindow: {
465
470
  env: 'P2P_DOUBLE_SPEND_SEVERE_PEER_PENALTY_WINDOW',
466
471
  description: 'The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it.',
@@ -563,11 +568,6 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
563
568
  description: 'Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY',
564
569
  ...booleanConfigHelper(false),
565
570
  },
566
- l1PublishingTime: {
567
- env: 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT',
568
- description: 'How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot).',
569
- ...optionalNumberConfigHelper(),
570
- },
571
571
  fishermanMode: {
572
572
  env: 'FISHERMAN_MODE',
573
573
  description:
@@ -615,7 +615,19 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
615
615
  'Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more.',
616
616
  ...bigintConfigHelper(10n),
617
617
  },
618
- ...sharedSequencerConfigMappings,
618
+ keepFinalizedTxsForSlots: {
619
+ env: 'P2P_KEEP_FINALIZED_TXS_FOR_SLOTS',
620
+ description:
621
+ 'Number of slots behind the finalized tip to keep finalized txs for before deleting them. 0 deletes at the finalized tip.',
622
+ ...numberConfigHelper(0),
623
+ },
624
+ ...pickConfigMappings(sharedSequencerConfigMappings, [
625
+ 'expectedBlockProposalsPerSlot',
626
+ 'maxTxsPerBlock',
627
+ 'checkpointProposalSyncGraceSeconds',
628
+ 'maxBlocksPerCheckpoint',
629
+ 'blockDurationMs',
630
+ ]),
619
631
  ...p2pReqRespConfigMappings,
620
632
  ...batchTxRequesterConfigMappings,
621
633
  ...chainConfigMappings,
@@ -741,7 +753,7 @@ export function parseAllowList(value: string): AllowedElement[] {
741
753
 
742
754
  if (typeString === 'I') {
743
755
  entries.push({
744
- address: AztecAddress.fromString(identifierString),
756
+ address: AztecAddress.fromStringUnsafe(identifierString),
745
757
  selector,
746
758
  ...flags,
747
759
  });
@@ -8,3 +8,22 @@ export class IndividualReqRespTimeoutError extends Error {
8
8
  super(`Request to peer timed out`);
9
9
  }
10
10
  }
11
+
12
+ /** Oversized reqresp request error
13
+ *
14
+ * Thrown locally (before dialing) when a request payload does not fit in a single muxer frame. Such a request would
15
+ * reach the responder split across multiple chunks, which are never reassembled into one request.
16
+ * @category Errors
17
+ */
18
+ export class OversizedReqRespRequestError extends Error {
19
+ constructor(
20
+ public readonly subProtocol: string,
21
+ public readonly payloadSizeBytes: number,
22
+ public readonly maxSizeBytes: number,
23
+ ) {
24
+ super(
25
+ `Request payload of ${payloadSizeBytes} bytes for ${subProtocol} exceeds the ${maxSizeBytes} byte limit of a single muxer frame`,
26
+ );
27
+ this.name = 'OversizedReqRespRequestError';
28
+ }
29
+ }
@@ -43,6 +43,7 @@ export type AttestationPoolApi = Pick<
43
43
  | 'getProposalsForSlot'
44
44
  | 'tryAddCheckpointProposal'
45
45
  | 'getCheckpointProposal'
46
+ | 'hasCheckpointProposalForSlot'
46
47
  | 'addOwnCheckpointAttestations'
47
48
  | 'tryAddCheckpointAttestation'
48
49
  | 'deleteOlderThan'
@@ -403,6 +404,11 @@ export class AttestationPool {
403
404
  return undefined;
404
405
  }
405
406
 
407
+ /** Returns whether any checkpoint proposal is retained for the given slot. */
408
+ public async hasCheckpointProposalForSlot(slot: SlotNumber): Promise<boolean> {
409
+ return (await this.getCheckpointProposal(slot)) !== undefined;
410
+ }
411
+
406
412
  /**
407
413
  * Adds own checkpoint attestations to the pool.
408
414
  * Skips per-signer cap and equivocation tracking; the caller is trusted.
@@ -428,7 +434,8 @@ export class AttestationPool {
428
434
  const ownKey = this.getSlotSignerKey(slotNumber, address);
429
435
  const payloadHash = attestation.getPayloadHash();
430
436
 
431
- await this.attestationPerSlotAndSigner.set(ownKey, attestation.toBuffer());
437
+ // Store the signature in canonical (v ∈ {27, 28}) form; see tryAddCheckpointAttestation.
438
+ await this.attestationPerSlotAndSigner.set(ownKey, attestation.withNormalizedSignature().toBuffer());
432
439
  this.metrics.trackMempoolItemAdded(ownKey);
433
440
 
434
441
  // Track our own payload hash so that an equivocating attestation from another
@@ -608,7 +615,10 @@ export class AttestationPool {
608
615
  // equivocations are detected via the multimap but their bytes are not retained.
609
616
  const alreadyHasStored = await this.attestationPerSlotAndSigner.hasAsync(slotSignerKey);
610
617
  if (!alreadyHasStored) {
611
- await this.attestationPerSlotAndSigner.set(slotSignerKey, attestation.toBuffer());
618
+ // Store the signature in canonical (v ∈ {27, 28}) form. `sender` recovered above, so the
619
+ // signature is non-empty and safe to normalize; this keeps a yParity-encoded (v = 0/1) variant
620
+ // from a malicious peer or an in-flight byte mutation out of the L1 bundle downstream.
621
+ await this.attestationPerSlotAndSigner.set(slotSignerKey, attestation.withNormalizedSignature().toBuffer());
612
622
  this.metrics.trackMempoolItemAdded(slotSignerKey);
613
623
  }
614
624
 
@@ -1,7 +1,9 @@
1
1
  import { IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
- import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore } from '@aztec/stdlib/p2p';
4
+ import { Signature } from '@aztec/foundation/eth-signature';
5
+ import type { BlockProposal, CheckpointProposalCore } from '@aztec/stdlib/p2p';
6
+ import { CheckpointAttestation } from '@aztec/stdlib/p2p';
5
7
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
6
8
  import {
7
9
  makeBlockHeader,
@@ -62,7 +64,44 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
62
64
  expect(a1Buffer).toEqual(expect.arrayContaining(a2Buffer));
63
65
  };
64
66
 
67
+ /**
68
+ * Rewrites the attester signature into yParity form (27 -> 0, 28 -> 1), keeping (r, s). The recovery
69
+ * bit is unchanged so it still recovers to the same signer, mimicking a peer that gossips (or mutates
70
+ * in flight) a signature with a non-canonical recovery byte.
71
+ */
72
+ const toYParityForm = (attestation: CheckpointAttestation): CheckpointAttestation => {
73
+ const sig = attestation.signature;
74
+ const yParityV = sig.v === 27 ? 0 : sig.v === 28 ? 1 : sig.v;
75
+ return new CheckpointAttestation(
76
+ attestation.payload,
77
+ new Signature(sig.r, sig.s, yParityV),
78
+ attestation.proposerSignature,
79
+ );
80
+ };
81
+
65
82
  describe('CheckpointAttestation', () => {
83
+ it('normalizes yParity (v=0/v=1) attester signatures on ingress from both gossip and own keys', async () => {
84
+ const slotNumber = 421;
85
+ const canonical = createCheckpointAttestationsForSlot(slotNumber);
86
+ const payloadHash = canonical[0].getPayloadHash();
87
+ const expectedSenders = new Set([canonical[0].getSender()!.toString(), canonical[1].getSender()!.toString()]);
88
+
89
+ const gossiped = toYParityForm(canonical[0]);
90
+ expect([0, 1]).toContain(gossiped.signature.v);
91
+ await ap.tryAddCheckpointAttestation(gossiped);
92
+
93
+ const own = toYParityForm(canonical[1]);
94
+ expect([0, 1]).toContain(own.signature.v);
95
+ await ap.addOwnCheckpointAttestations([own]);
96
+
97
+ const retrieved = await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(slotNumber), payloadHash);
98
+ expect(retrieved.length).toBe(2);
99
+ for (const attestation of retrieved) {
100
+ expect([27, 28]).toContain(attestation.signature.v);
101
+ expect(expectedSenders.has(attestation.getSender()!.toString())).toBe(true);
102
+ }
103
+ });
104
+
66
105
  it('should add attestations to pool', async () => {
67
106
  const slotNumber = 420;
68
107
  const archive = Fr.random();
@@ -1,5 +1,4 @@
1
1
  import type { Gossipable } from '@aztec/stdlib/p2p';
2
- import type { Tx } from '@aztec/stdlib/tx';
3
2
  import {
4
3
  Attributes,
5
4
  type BatchObservableResult,
@@ -117,16 +116,6 @@ export class PoolInstrumentation<PoolObject extends Gossipable> {
117
116
  this.addObjectCounter.add(count);
118
117
  }
119
118
 
120
- public transactionsAdded(transactions: Tx[]) {
121
- transactions.forEach(tx => this.trackMempoolItemAdded(tx.txHash.toBigInt()));
122
- }
123
-
124
- public transactionsRemoved(hashes: Iterable<bigint> | Iterable<string>) {
125
- for (const hash of hashes) {
126
- this.trackMempoolItemRemoved(BigInt(hash));
127
- }
128
- }
129
-
130
119
  public trackMempoolItemAdded(key: bigint | string): void {
131
120
  this.mempoolItemAddedTimestamp.set(key, Date.now());
132
121
  }
@@ -142,6 +131,19 @@ export class PoolInstrumentation<PoolObject extends Gossipable> {
142
131
  }
143
132
  }
144
133
 
134
+ /**
135
+ * Records a pre-computed pending-to-mined delay directly, bypassing the
136
+ * `mempoolItemAddedTimestamp` map. Used by the tx pool, which derives the
137
+ * delay from the persisted `receivedAt` on tx metadata at the mined
138
+ * transition — more accurate than the add/remove map (which also fires on
139
+ * eviction) and resilient to the caller not having tracked the add.
140
+ */
141
+ public recordMinedDelay(delayMs: number): void {
142
+ if (delayMs > 0) {
143
+ this.minedDelay.record(delayMs);
144
+ }
145
+ }
146
+
145
147
  private observeStats = async (observer: BatchObservableResult) => {
146
148
  const { itemCount } = await this.poolStats();
147
149
  if (typeof itemCount === 'number') {
@@ -1,6 +1,5 @@
1
1
  import { type Logger, createLogger } from '@aztec/foundation/log';
2
2
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
3
- import { ChonkProof } from '@aztec/stdlib/proofs';
4
3
  import { Tx, TxHash } from '@aztec/stdlib/tx';
5
4
 
6
5
  /**
@@ -70,7 +69,7 @@ export class TxArchive {
70
69
  }
71
70
 
72
71
  // Archive the transaction with stripped proof
73
- const archivedTx = this.stripProof(tx);
72
+ const archivedTx = tx.withoutProof();
74
73
  const txHash = tx.getTxHash().toString();
75
74
  await this.#txs.set(txHash, archivedTx.toBuffer());
76
75
  await this.#indices.set(headIdx, txHash);
@@ -101,13 +100,6 @@ export class TxArchive {
101
100
  return head - tail;
102
101
  }
103
102
 
104
- /**
105
- * Strips the proof from a transaction for archival.
106
- */
107
- private stripProof(tx: Tx): Tx {
108
- return new Tx(tx.txHash, tx.data, ChonkProof.empty(), tx.contractClassLogFields, tx.publicFunctionCalldata);
109
- }
110
-
111
103
  private async getHeadIndex(): Promise<number> {
112
104
  const entry = await this.#indices.entriesAsync({ limit: 1, reverse: true }).next();
113
105
  return (entry.value?.[0] ?? -1) + 1;
@@ -52,8 +52,8 @@ export class DeletedPool {
52
52
  /** Persisted map: txHash -> DeletedTxState (serialized) - for prune-based soft deletions */
53
53
  #deletedTxsDB: AztecAsyncMap<string, Buffer>;
54
54
 
55
- /** Reference to the main txs database for hard deletion */
56
- #txsDB: AztecAsyncMap<string, Buffer>;
55
+ /** Hard-deletes a tx's stored data (tx blob and proof) from the pool's stores. */
56
+ #deleteTxData: (txHash: string) => Promise<void>;
57
57
 
58
58
  /** In-memory state for transactions from pruned blocks */
59
59
  #state: Map<string, DeletedTxState> = new Map();
@@ -69,10 +69,10 @@ export class DeletedPool {
69
69
 
70
70
  #log: Logger;
71
71
 
72
- constructor(store: AztecAsyncKVStore, txsDB: AztecAsyncMap<string, Buffer>, log: Logger) {
72
+ constructor(store: AztecAsyncKVStore, deleteTxData: (txHash: string) => Promise<void>, log: Logger) {
73
73
  this.#deletedTxsDB = store.openMap('deleted_txs');
74
74
  this.#slotDeletedDB = store.openSet('slot_deleted_txs');
75
- this.#txsDB = txsDB;
75
+ this.#deleteTxData = deleteTxData;
76
76
  this.#log = log;
77
77
  }
78
78
 
@@ -100,7 +100,7 @@ export class DeletedPool {
100
100
  // Slot-deleted txs are stale after restart - hard-delete them all
101
101
  let slotDeletedCount = 0;
102
102
  for await (const txHash of this.#slotDeletedDB.entriesAsync()) {
103
- await this.#txsDB.delete(txHash);
103
+ await this.#deleteTxData(txHash);
104
104
  await this.#slotDeletedDB.delete(txHash);
105
105
  slotDeletedCount++;
106
106
  }
@@ -234,7 +234,7 @@ export class DeletedPool {
234
234
  for (const txHash of toHardDelete) {
235
235
  this.#state.delete(txHash);
236
236
  await this.#deletedTxsDB.delete(txHash);
237
- await this.#txsDB.delete(txHash);
237
+ await this.#deleteTxData(txHash);
238
238
  }
239
239
 
240
240
  this.#log.debug(`Finalized ${toHardDelete.length} txs from pruned blocks at block ${finalizedBlockNumber}`, {
@@ -266,7 +266,7 @@ export class DeletedPool {
266
266
  for (const txHash of toHardDelete) {
267
267
  this.#slotDeletedTxs.delete(txHash);
268
268
  await this.#slotDeletedDB.delete(txHash);
269
- await this.#txsDB.delete(txHash);
269
+ await this.#deleteTxData(txHash);
270
270
  }
271
271
 
272
272
  this.#log.debug(
@@ -69,9 +69,7 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
69
69
 
70
70
  if (txsToEvict.length > 0) {
71
71
  await pool.deleteTxs(txsToEvict, this.name);
72
- this.log.debug(`Evicted ${txsToEvict.length} txs due to insufficient fee payer balance`, {
73
- txHashes: txsToEvict,
74
- });
72
+ this.log.info(`Evicted ${txsToEvict.length} txs due to insufficient fee payer balance`, { txsToEvict });
75
73
  }
76
74
 
77
75
  return {
@@ -86,7 +84,7 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
86
84
  publicStateSource: DatabasePublicStateSource,
87
85
  pool: PoolOperations,
88
86
  ): Promise<string[]> {
89
- const feePayer = AztecAddress.fromString(feePayerStr);
87
+ const feePayer = AztecAddress.fromStringUnsafe(feePayerStr);
90
88
  const initialBalance = (
91
89
  await publicStateSource.storageRead(
92
90
  ProtocolContractAddress.FeeJuice,
@@ -112,8 +112,9 @@ export class FeePayerBalancePreAddRule implements PreAddRule {
112
112
  }
113
113
 
114
114
  if (txsToEvict.length > 0) {
115
- this.log.debug(
116
- `Accepting tx ${incomingMeta.txHash}, evicting ${txsToEvict.length} lower-priority txs due to fee payer balance`,
115
+ this.log.info(
116
+ `Accepting tx ${incomingMeta.txHash} and evicting ${txsToEvict.length} lower-priority txs due to fee payer balance`,
117
+ { txsToEvict },
117
118
  );
118
119
  }
119
120
 
@@ -43,7 +43,9 @@ export class InsufficientFeePerGasEvictionRule implements EvictionRule {
43
43
  }
44
44
 
45
45
  if (txsToEvict.length > 0) {
46
- this.log.info(`Evicted ${txsToEvict.length} txs with insufficient fee per gas after block mined`);
46
+ this.log.info(`Evicted ${txsToEvict.length} txs with insufficient fee per gas after block mined`, {
47
+ txsToEvict,
48
+ });
47
49
  await pool.deleteTxs(txsToEvict, this.name);
48
50
  }
49
51
 
@@ -50,12 +50,10 @@ export class InvalidTxsAfterMiningRule implements EvictionRule {
50
50
  }
51
51
 
52
52
  if (txsToEvict.length > 0) {
53
- this.log.info(`Evicted ${txsToEvict.length} invalid txs after block mined`);
53
+ this.log.info(`Evicted ${txsToEvict.length} invalid txs after block mined`, { txsToEvict });
54
54
  await pool.deleteTxs(txsToEvict, this.name);
55
55
  }
56
56
 
57
- this.log.debug(`Evicted ${txsToEvict.length} invalid txs after block mined`, { txHashes: txsToEvict });
58
-
59
57
  return {
60
58
  reason: 'block_mined_invalid_txs',
61
59
  success: true,
@@ -72,17 +72,15 @@ export class InvalidTxsAfterReorgRule implements EvictionRule {
72
72
  }
73
73
 
74
74
  if (txsToEvict.length > 0) {
75
- this.log.info(`Evicting ${txsToEvict.length} txs from pool due to referencing pruned blocks`);
75
+ this.log.info(`Evicting ${txsToEvict.length} txs from pool referencing pruned blocks`, { txsToEvict });
76
76
  await pool.deleteTxs(txsToEvict, this.name);
77
77
  }
78
78
 
79
79
  const keptCount = pendingTxs.length - txsToEvict.length;
80
80
  if (keptCount > 0) {
81
- this.log.verbose(`Kept ${keptCount} txs that did not reference pruned blocks`);
81
+ this.log.debug(`Kept ${keptCount} txs that did not reference pruned blocks`);
82
82
  }
83
83
 
84
- this.log.debug(`Evicted ${txsToEvict.length} invalid txs after reorg`, { txHashes: txsToEvict });
85
-
86
84
  return {
87
85
  reason: 'reorg_invalid_txs',
88
86
  success: true,
@@ -56,17 +56,16 @@ export class LowPriorityEvictionRule implements EvictionRule {
56
56
  if (context.event === EvictionEvent.TXS_ADDED) {
57
57
  const toEvictSet = new Set(txsToEvict);
58
58
  const numNewTxsEvicted = context.newTxHashes.filter(newTxHash => toEvictSet.has(newTxHash)).length;
59
- this.log.info(`Evicted ${txsToEvict.length} low priority txs, including ${numNewTxsEvicted} newly added txs`);
59
+ this.log.info(
60
+ `Evicted ${txsToEvict.length} low priority txs, including ${numNewTxsEvicted} newly added txs`,
61
+ { txsToEvict },
62
+ );
60
63
  } else {
61
- this.log.info(`Evicted ${txsToEvict.length} low priority txs after chain prune`);
64
+ this.log.info(`Evicted ${txsToEvict.length} low priority txs after chain prune`, { txsToEvict });
62
65
  }
63
66
  await pool.deleteTxs(txsToEvict, this.name);
64
67
  }
65
68
 
66
- this.log.debug(`Evicted ${txsToEvict.length} low priority txs`, {
67
- txHashes: txsToEvict,
68
- });
69
-
70
69
  return {
71
70
  reason: 'low_priority',
72
71
  success: true,
@@ -49,6 +49,11 @@ export type TxPoolV2Config = {
49
49
  dropTransactionsProbability: number;
50
50
  /** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
51
51
  priceBumpPercentage: bigint;
52
+ /**
53
+ * Number of slots behind the finalized tip to keep finalized txs for before deleting them. 0 deletes
54
+ * at the finalized tip (default).
55
+ */
56
+ keepFinalizedTxsForSlots: number;
52
57
  };
53
58
 
54
59
  /**
@@ -61,6 +66,7 @@ export const DEFAULT_TX_POOL_V2_CONFIG: TxPoolV2Config = {
61
66
  evictedTxCacheSize: 10_000,
62
67
  dropTransactionsProbability: 0,
63
68
  priceBumpPercentage: 10n,
69
+ keepFinalizedTxsForSlots: 0,
64
70
  };
65
71
 
66
72
  /**
@@ -170,6 +176,17 @@ export interface TxPoolV2 extends TypedEventEmitter<TxPoolV2Events> {
170
176
  */
171
177
  prepareForSlot(slotNumber: SlotNumber): Promise<void>;
172
178
 
179
+ /**
180
+ * Releases the protections a failed block proposal created and restores the txs to pending.
181
+ * Only clears protection entries still recorded at exactly the given slot: a tx that another,
182
+ * still-live proposal raised to a higher slot via {@link protectTxs} keeps its protection, and
183
+ * mined txs (which carry no protection entry) are left untouched. Restored txs are re-validated
184
+ * and resolved against nullifier conflicts before re-entering the pending indices.
185
+ * @param txHashes - Hashes of the proposal's txs to release.
186
+ * @param slotNumber - The slot the failed proposal targeted; protection is released only for this slot.
187
+ */
188
+ unprotectTxs(txHashes: TxHash[], slotNumber: SlotNumber): Promise<void>;
189
+
173
190
  /**
174
191
  * Handles pruned blocks during a reorg.
175
192
  * Un-mines all transactions mined in blocks beyond the given latest block
@@ -192,11 +209,11 @@ export interface TxPoolV2 extends TypedEventEmitter<TxPoolV2Events> {
192
209
  */
193
210
  handleFinalizedBlock(block: BlockHeader): Promise<void>;
194
211
 
195
- /** Gets a transaction by its hash */
196
- getTxByHash(txHash: TxHash): Promise<Tx | undefined>;
212
+ /** Gets a transaction by its hash. Set `includeProof: false` to skip loading the proof from the DB. */
213
+ getTxByHash(txHash: TxHash, opts?: { includeProof?: boolean }): Promise<Tx | undefined>;
197
214
 
198
- /** Gets multiple transactions by their hashes */
199
- getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]>;
215
+ /** Gets multiple transactions by their hashes. Set `includeProof: false` to skip loading proofs from the DB. */
216
+ getTxsByHash(txHashes: TxHash[], opts?: { includeProof?: boolean }): Promise<(Tx | undefined)[]>;
200
217
 
201
218
  /** Checks if transactions exist in the pool */
202
219
  hasTxs(txHashes: TxHash[]): Promise<boolean[]>;
@@ -213,6 +230,13 @@ export interface TxPoolV2 extends TypedEventEmitter<TxPoolV2Events> {
213
230
  /** Gets the count of pending transactions */
214
231
  getPendingTxCount(): Promise<number>;
215
232
 
233
+ /**
234
+ * Returns whether at least `minCount` pending transactions are old enough per minTxPoolAgeMs to be eligible
235
+ * for block building. Stops scanning once the threshold is reached, so it is cheaper than counting all
236
+ * eligible txs when only a few are needed.
237
+ */
238
+ hasEligiblePendingTxs(minCount: number): Promise<boolean>;
239
+
216
240
  /** Gets mined transaction hashes with their block IDs */
217
241
  getMinedTxHashes(): Promise<[TxHash, L2BlockId][]>;
218
242
 
@@ -131,6 +131,9 @@ export class TxPoolIndices {
131
131
  meta.minedL2BlockId = blockId;
132
132
  // Safe to call unconditionally - removeFromPendingIndices is idempotent
133
133
  this.#removeFromPendingIndices(meta);
134
+ // A mined tx supersedes any protection: drop the stale entry so it can't linger in the map and
135
+ // be matched by later protection scans.
136
+ this.#protectedTransactions.delete(meta.txHash);
134
137
  }
135
138
 
136
139
  /** Clears the mined status from a transaction */
@@ -225,6 +228,29 @@ export class TxPoolIndices {
225
228
  return this.#pendingByPriority.length;
226
229
  }
227
230
 
231
+ /**
232
+ * Returns whether at least `minCount` pending transactions were received at or before maxReceivedAt,
233
+ * i.e. are old enough to be eligible for block building. Stops as soon as the threshold is met, so it
234
+ * does not scan the whole pool when only a handful of eligible txs are needed. The total pending count
235
+ * is an upper bound on the eligible count, so a pool with fewer than `minCount` pending can never
236
+ * satisfy the threshold and short-circuits without scanning.
237
+ */
238
+ hasEligiblePendingTxs(maxReceivedAt: number, minCount: number): boolean {
239
+ if (minCount <= 0) {
240
+ return true;
241
+ }
242
+ if (this.#pendingByPriority.length < minCount) {
243
+ return false;
244
+ }
245
+ let count = 0;
246
+ for (const _ of this.iterateEligiblePendingByPriority('desc', maxReceivedAt)) {
247
+ if (++count >= minCount) {
248
+ return true;
249
+ }
250
+ }
251
+ return false;
252
+ }
253
+
228
254
  /** Gets the lowest priority pending transaction hashes (up to limit) */
229
255
  getLowestPriorityPending(limit: number): string[] {
230
256
  if (limit <= 0) {
@@ -316,6 +342,15 @@ export class TxPoolIndices {
316
342
  return result;
317
343
  }
318
344
 
345
+ /**
346
+ * From the given hashes, returns those whose protection is recorded at exactly the given slot.
347
+ * Used to release the protections a single block proposal created without disturbing entries a
348
+ * later proposal raised to a higher slot via updateProtection.
349
+ */
350
+ findProtectedTxsAtSlot(txHashes: string[], slotNumber: SlotNumber): string[] {
351
+ return txHashes.filter(txHash => this.#protectedTransactions.get(txHash) === slotNumber);
352
+ }
353
+
319
354
  /** Filters out transactions that are currently protected */
320
355
  filterUnprotected(txs: TxMetaData[]): TxMetaData[] {
321
356
  return txs.filter(meta => !this.#protectedTransactions.has(meta.txHash));