@aztec/p2p 0.0.1-commit.aada20e3 → 0.0.1-commit.b2a5d0dd1

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 (478) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +12 -11
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +55 -16
  5. package/dest/client/interface.d.ts +54 -34
  6. package/dest/client/interface.d.ts.map +1 -1
  7. package/dest/client/p2p_client.d.ts +41 -52
  8. package/dest/client/p2p_client.d.ts.map +1 -1
  9. package/dest/client/p2p_client.js +179 -226
  10. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +24 -11
  11. package/dest/config.d.ts +133 -92
  12. package/dest/config.d.ts.map +1 -1
  13. package/dest/config.js +109 -40
  14. package/dest/errors/p2p-service.error.d.ts +9 -0
  15. package/dest/errors/p2p-service.error.d.ts.map +1 -0
  16. package/dest/errors/p2p-service.error.js +10 -0
  17. package/dest/errors/tx-pool.error.d.ts +8 -0
  18. package/dest/errors/tx-pool.error.d.ts.map +1 -0
  19. package/dest/errors/tx-pool.error.js +9 -0
  20. package/dest/index.d.ts +2 -2
  21. package/dest/index.d.ts.map +1 -1
  22. package/dest/index.js +1 -1
  23. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +106 -88
  24. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  25. package/dest/mem_pools/attestation_pool/attestation_pool.js +448 -3
  26. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  27. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  28. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +353 -87
  29. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  30. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  31. package/dest/mem_pools/attestation_pool/index.js +1 -2
  32. package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -2
  33. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  34. package/dest/mem_pools/attestation_pool/mocks.js +2 -2
  35. package/dest/mem_pools/index.d.ts +3 -3
  36. package/dest/mem_pools/index.d.ts.map +1 -1
  37. package/dest/mem_pools/index.js +1 -1
  38. package/dest/mem_pools/instrumentation.d.ts +4 -2
  39. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  40. package/dest/mem_pools/instrumentation.js +16 -14
  41. package/dest/mem_pools/interface.d.ts +5 -5
  42. package/dest/mem_pools/interface.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +104 -0
  44. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
  45. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +251 -0
  46. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +3 -3
  47. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +18 -9
  49. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  50. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  51. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +7 -3
  52. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +3 -3
  53. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  54. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +12 -4
  55. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +3 -2
  56. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  57. package/dest/mem_pools/tx_pool_v2/eviction/index.js +2 -1
  58. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +16 -0
  59. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -0
  60. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +62 -0
  61. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +54 -5
  62. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  63. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +8 -0
  64. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +7 -5
  65. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +9 -7
  66. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
  67. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  68. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +14 -6
  69. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +4 -4
  70. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  71. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +16 -4
  72. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +3 -3
  73. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  74. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +3 -3
  75. package/dest/mem_pools/tx_pool_v2/index.d.ts +3 -2
  76. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  77. package/dest/mem_pools/tx_pool_v2/index.js +2 -1
  78. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts +15 -0
  79. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts.map +1 -0
  80. package/dest/mem_pools/tx_pool_v2/instrumentation.js +43 -0
  81. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +37 -12
  82. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  83. package/dest/mem_pools/tx_pool_v2/interfaces.js +5 -1
  84. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +81 -15
  85. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  86. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +147 -18
  87. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +108 -0
  88. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
  89. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +337 -0
  90. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +12 -5
  91. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  92. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +23 -6
  93. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +14 -5
  94. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  95. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +477 -594
  96. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +5 -2
  97. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  98. package/dest/msg_validators/attestation_validator/attestation_validator.js +20 -11
  99. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +5 -3
  100. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  101. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
  102. package/dest/msg_validators/clock_tolerance.d.ts +12 -1
  103. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  104. package/dest/msg_validators/clock_tolerance.js +54 -3
  105. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +7 -4
  106. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  107. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  108. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +7 -4
  109. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  110. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  111. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +15 -8
  112. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  113. package/dest/msg_validators/proposal_validator/proposal_validator.js +67 -47
  114. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +4 -4
  115. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  116. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  117. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
  118. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  119. package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
  120. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  121. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  122. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  123. package/dest/msg_validators/tx_validator/archive_cache.js +1 -1
  124. package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
  125. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  126. package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
  127. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  128. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  129. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  130. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  131. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  132. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  133. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
  134. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
  135. package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
  136. package/dest/msg_validators/tx_validator/factory.d.ts +133 -6
  137. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  138. package/dest/msg_validators/tx_validator/factory.js +247 -60
  139. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  140. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  141. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  142. package/dest/msg_validators/tx_validator/gas_validator.d.ts +99 -3
  143. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  144. package/dest/msg_validators/tx_validator/gas_validator.js +137 -53
  145. package/dest/msg_validators/tx_validator/index.d.ts +3 -1
  146. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  147. package/dest/msg_validators/tx_validator/index.js +2 -0
  148. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  149. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  150. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  151. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  152. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  153. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  154. package/dest/msg_validators/tx_validator/phases_validator.d.ts +22 -2
  155. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  156. package/dest/msg_validators/tx_validator/phases_validator.js +72 -24
  157. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
  158. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  159. package/dest/msg_validators/tx_validator/timestamp_validator.js +6 -6
  160. package/dest/services/data_store.d.ts +1 -1
  161. package/dest/services/data_store.d.ts.map +1 -1
  162. package/dest/services/data_store.js +5 -5
  163. package/dest/services/dummy_service.d.ts +17 -6
  164. package/dest/services/dummy_service.d.ts.map +1 -1
  165. package/dest/services/dummy_service.js +16 -5
  166. package/dest/services/encoding.d.ts +7 -3
  167. package/dest/services/encoding.d.ts.map +1 -1
  168. package/dest/services/encoding.js +18 -11
  169. package/dest/services/gossipsub/index.d.ts +3 -0
  170. package/dest/services/gossipsub/index.d.ts.map +1 -0
  171. package/dest/services/gossipsub/index.js +2 -0
  172. package/dest/services/gossipsub/scoring.d.ts +21 -3
  173. package/dest/services/gossipsub/scoring.d.ts.map +1 -1
  174. package/dest/services/gossipsub/scoring.js +24 -7
  175. package/dest/services/gossipsub/topic_score_params.d.ts +184 -0
  176. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -0
  177. package/dest/services/gossipsub/topic_score_params.js +363 -0
  178. package/dest/services/libp2p/instrumentation.d.ts +3 -1
  179. package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
  180. package/dest/services/libp2p/instrumentation.js +14 -0
  181. package/dest/services/libp2p/libp2p_service.d.ts +95 -51
  182. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  183. package/dest/services/libp2p/libp2p_service.js +566 -442
  184. package/dest/services/peer-manager/metrics.d.ts +3 -1
  185. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  186. package/dest/services/peer-manager/metrics.js +6 -0
  187. package/dest/services/peer-manager/peer_manager.d.ts +6 -2
  188. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  189. package/dest/services/peer-manager/peer_manager.js +39 -11
  190. package/dest/services/peer-manager/peer_scoring.d.ts +7 -2
  191. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  192. package/dest/services/peer-manager/peer_scoring.js +57 -12
  193. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +14 -10
  194. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  195. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +89 -112
  196. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +4 -7
  197. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  198. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +11 -13
  199. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  200. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +31 -46
  201. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +19 -11
  202. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  203. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +52 -15
  204. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  205. package/dest/services/reqresp/config.d.ts +3 -3
  206. package/dest/services/reqresp/config.d.ts.map +1 -1
  207. package/dest/services/reqresp/interface.d.ts +23 -9
  208. package/dest/services/reqresp/interface.d.ts.map +1 -1
  209. package/dest/services/reqresp/interface.js +23 -10
  210. package/dest/services/reqresp/metrics.d.ts +1 -1
  211. package/dest/services/reqresp/metrics.d.ts.map +1 -1
  212. package/dest/services/reqresp/metrics.js +0 -1
  213. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +7 -5
  214. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  215. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
  216. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +21 -10
  217. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  218. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +27 -11
  219. package/dest/services/reqresp/protocols/index.d.ts +1 -2
  220. package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
  221. package/dest/services/reqresp/protocols/index.js +0 -1
  222. package/dest/services/reqresp/protocols/tx.d.ts +7 -1
  223. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  224. package/dest/services/reqresp/protocols/tx.js +21 -3
  225. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
  226. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  227. package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
  228. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +1 -1
  229. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
  230. package/dest/services/reqresp/rate-limiter/rate_limits.js +0 -10
  231. package/dest/services/reqresp/reqresp.d.ts +4 -2
  232. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  233. package/dest/services/reqresp/reqresp.js +40 -15
  234. package/dest/services/service.d.ts +43 -4
  235. package/dest/services/service.d.ts.map +1 -1
  236. package/dest/services/tx_collection/config.d.ts +22 -4
  237. package/dest/services/tx_collection/config.d.ts.map +1 -1
  238. package/dest/services/tx_collection/config.js +49 -3
  239. package/dest/services/tx_collection/fast_tx_collection.d.ts +6 -8
  240. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  241. package/dest/services/tx_collection/fast_tx_collection.js +88 -88
  242. package/dest/services/tx_collection/file_store_tx_collection.d.ts +53 -0
  243. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
  244. package/dest/services/tx_collection/file_store_tx_collection.js +167 -0
  245. package/dest/services/tx_collection/file_store_tx_source.d.ts +38 -0
  246. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
  247. package/dest/services/tx_collection/file_store_tx_source.js +100 -0
  248. package/dest/services/tx_collection/index.d.ts +3 -2
  249. package/dest/services/tx_collection/index.d.ts.map +1 -1
  250. package/dest/services/tx_collection/index.js +1 -0
  251. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  252. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  253. package/dest/services/tx_collection/instrumentation.js +2 -1
  254. package/dest/services/tx_collection/proposal_tx_collector.d.ts +15 -15
  255. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  256. package/dest/services/tx_collection/proposal_tx_collector.js +6 -6
  257. package/dest/services/tx_collection/request_tracker.d.ts +53 -0
  258. package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
  259. package/dest/services/tx_collection/request_tracker.js +84 -0
  260. package/dest/services/tx_collection/slow_tx_collection.d.ts +7 -3
  261. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  262. package/dest/services/tx_collection/slow_tx_collection.js +60 -26
  263. package/dest/services/tx_collection/tx_collection.d.ts +23 -13
  264. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  265. package/dest/services/tx_collection/tx_collection.js +75 -3
  266. package/dest/services/tx_collection/tx_collection_sink.d.ts +18 -8
  267. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  268. package/dest/services/tx_collection/tx_collection_sink.js +26 -29
  269. package/dest/services/tx_collection/tx_source.d.ts +13 -7
  270. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  271. package/dest/services/tx_collection/tx_source.js +26 -7
  272. package/dest/services/tx_file_store/config.d.ts +1 -3
  273. package/dest/services/tx_file_store/config.d.ts.map +1 -1
  274. package/dest/services/tx_file_store/config.js +0 -4
  275. package/dest/services/tx_file_store/tx_file_store.d.ts +4 -3
  276. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  277. package/dest/services/tx_file_store/tx_file_store.js +9 -6
  278. package/dest/services/tx_provider.d.ts +4 -4
  279. package/dest/services/tx_provider.d.ts.map +1 -1
  280. package/dest/services/tx_provider.js +9 -8
  281. package/dest/test-helpers/make-test-p2p-clients.d.ts +7 -8
  282. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  283. package/dest/test-helpers/make-test-p2p-clients.js +4 -2
  284. package/dest/test-helpers/mock-pubsub.d.ts +40 -6
  285. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  286. package/dest/test-helpers/mock-pubsub.js +139 -13
  287. package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
  288. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  289. package/dest/test-helpers/reqresp-nodes.js +8 -5
  290. package/dest/test-helpers/testbench-utils.d.ts +43 -38
  291. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  292. package/dest/test-helpers/testbench-utils.js +150 -61
  293. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -2
  294. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  295. package/dest/testbench/p2p_client_testbench_worker.js +85 -28
  296. package/dest/testbench/worker_client_manager.d.ts +10 -1
  297. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  298. package/dest/testbench/worker_client_manager.js +55 -3
  299. package/dest/util.d.ts +3 -3
  300. package/dest/util.d.ts.map +1 -1
  301. package/package.json +14 -14
  302. package/src/client/factory.ts +110 -29
  303. package/src/client/interface.ts +65 -35
  304. package/src/client/p2p_client.ts +216 -272
  305. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +36 -15
  306. package/src/config.ts +172 -46
  307. package/src/errors/p2p-service.error.ts +11 -0
  308. package/src/errors/tx-pool.error.ts +12 -0
  309. package/src/index.ts +1 -1
  310. package/src/mem_pools/attestation_pool/attestation_pool.ts +501 -91
  311. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +442 -102
  312. package/src/mem_pools/attestation_pool/index.ts +9 -2
  313. package/src/mem_pools/attestation_pool/mocks.ts +2 -1
  314. package/src/mem_pools/index.ts +2 -2
  315. package/src/mem_pools/instrumentation.ts +17 -13
  316. package/src/mem_pools/interface.ts +4 -4
  317. package/src/mem_pools/tx_pool_v2/README.md +112 -17
  318. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +321 -0
  319. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +21 -8
  320. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +7 -3
  321. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +18 -4
  322. package/src/mem_pools/tx_pool_v2/eviction/index.ts +5 -0
  323. package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +65 -0
  324. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +59 -4
  325. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +5 -5
  326. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +8 -8
  327. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +14 -9
  328. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +33 -6
  329. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +4 -3
  330. package/src/mem_pools/tx_pool_v2/index.ts +2 -1
  331. package/src/mem_pools/tx_pool_v2/instrumentation.ts +69 -0
  332. package/src/mem_pools/tx_pool_v2/interfaces.ts +37 -12
  333. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +215 -26
  334. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +430 -0
  335. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +37 -8
  336. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +520 -678
  337. package/src/msg_validators/attestation_validator/README.md +49 -0
  338. package/src/msg_validators/attestation_validator/attestation_validator.ts +21 -9
  339. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +6 -3
  340. package/src/msg_validators/clock_tolerance.ts +72 -3
  341. package/src/msg_validators/proposal_validator/README.md +123 -0
  342. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +17 -4
  343. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +23 -7
  344. package/src/msg_validators/proposal_validator/proposal_validator.ts +79 -49
  345. package/src/msg_validators/tx_validator/README.md +127 -0
  346. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +5 -5
  347. package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
  348. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  349. package/src/msg_validators/tx_validator/archive_cache.ts +1 -1
  350. package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
  351. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  352. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  353. package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
  354. package/src/msg_validators/tx_validator/factory.ts +396 -78
  355. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  356. package/src/msg_validators/tx_validator/gas_validator.ts +199 -54
  357. package/src/msg_validators/tx_validator/index.ts +2 -0
  358. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  359. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  360. package/src/msg_validators/tx_validator/phases_validator.ts +82 -27
  361. package/src/msg_validators/tx_validator/timestamp_validator.ts +23 -18
  362. package/src/services/data_store.ts +5 -13
  363. package/src/services/dummy_service.ts +25 -7
  364. package/src/services/encoding.ts +18 -10
  365. package/src/services/gossipsub/README.md +641 -0
  366. package/src/services/gossipsub/index.ts +2 -0
  367. package/src/services/gossipsub/scoring.ts +29 -5
  368. package/src/services/gossipsub/topic_score_params.ts +519 -0
  369. package/src/services/libp2p/instrumentation.ts +14 -0
  370. package/src/services/libp2p/libp2p_service.ts +593 -477
  371. package/src/services/peer-manager/metrics.ts +7 -0
  372. package/src/services/peer-manager/peer_manager.ts +45 -11
  373. package/src/services/peer-manager/peer_scoring.ts +52 -5
  374. package/src/services/reqresp/README.md +229 -0
  375. package/src/services/reqresp/batch-tx-requester/README.md +53 -14
  376. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +89 -122
  377. package/src/services/reqresp/batch-tx-requester/interface.ts +3 -6
  378. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +30 -71
  379. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +68 -24
  380. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  381. package/src/services/reqresp/config.ts +2 -2
  382. package/src/services/reqresp/interface.ts +45 -10
  383. package/src/services/reqresp/metrics.ts +0 -1
  384. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +23 -14
  385. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +38 -15
  386. package/src/services/reqresp/protocols/index.ts +0 -1
  387. package/src/services/reqresp/protocols/tx.ts +23 -3
  388. package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
  389. package/src/services/reqresp/rate-limiter/rate_limits.ts +0 -10
  390. package/src/services/reqresp/reqresp.ts +53 -16
  391. package/src/services/service.ts +57 -3
  392. package/src/services/tx_collection/config.ts +74 -6
  393. package/src/services/tx_collection/fast_tx_collection.ts +94 -97
  394. package/src/services/tx_collection/file_store_tx_collection.ts +202 -0
  395. package/src/services/tx_collection/file_store_tx_source.ts +129 -0
  396. package/src/services/tx_collection/index.ts +2 -1
  397. package/src/services/tx_collection/instrumentation.ts +7 -1
  398. package/src/services/tx_collection/proposal_tx_collector.ts +21 -27
  399. package/src/services/tx_collection/request_tracker.ts +127 -0
  400. package/src/services/tx_collection/slow_tx_collection.ts +66 -33
  401. package/src/services/tx_collection/tx_collection.ts +114 -19
  402. package/src/services/tx_collection/tx_collection_sink.ts +30 -34
  403. package/src/services/tx_collection/tx_source.ts +28 -8
  404. package/src/services/tx_file_store/config.ts +0 -6
  405. package/src/services/tx_file_store/tx_file_store.ts +10 -8
  406. package/src/services/tx_provider.ts +10 -9
  407. package/src/test-helpers/make-test-p2p-clients.ts +6 -6
  408. package/src/test-helpers/mock-pubsub.ts +180 -14
  409. package/src/test-helpers/reqresp-nodes.ts +9 -9
  410. package/src/test-helpers/testbench-utils.ts +157 -74
  411. package/src/testbench/p2p_client_testbench_worker.ts +94 -31
  412. package/src/testbench/worker_client_manager.ts +68 -6
  413. package/src/util.ts +8 -2
  414. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  415. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  416. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  417. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  418. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  419. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  420. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +0 -125
  421. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +0 -1
  422. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +0 -596
  423. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +0 -32
  424. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +0 -1
  425. package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +0 -112
  426. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +0 -157
  427. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +0 -1
  428. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +0 -52
  429. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +0 -16
  430. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +0 -1
  431. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +0 -122
  432. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +0 -17
  433. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +0 -1
  434. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +0 -84
  435. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +0 -19
  436. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +0 -1
  437. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.js +0 -78
  438. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +0 -26
  439. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +0 -1
  440. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.js +0 -84
  441. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts +0 -25
  442. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts.map +0 -1
  443. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.js +0 -57
  444. package/dest/mem_pools/tx_pool/index.d.ts +0 -3
  445. package/dest/mem_pools/tx_pool/index.d.ts.map +0 -1
  446. package/dest/mem_pools/tx_pool/index.js +0 -2
  447. package/dest/mem_pools/tx_pool/priority.d.ts +0 -12
  448. package/dest/mem_pools/tx_pool/priority.d.ts.map +0 -1
  449. package/dest/mem_pools/tx_pool/priority.js +0 -15
  450. package/dest/mem_pools/tx_pool/tx_pool.d.ts +0 -127
  451. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +0 -1
  452. package/dest/mem_pools/tx_pool/tx_pool.js +0 -3
  453. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +0 -7
  454. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +0 -1
  455. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +0 -400
  456. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
  457. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  458. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
  459. package/dest/services/reqresp/protocols/block.d.ts +0 -9
  460. package/dest/services/reqresp/protocols/block.d.ts.map +0 -1
  461. package/dest/services/reqresp/protocols/block.js +0 -32
  462. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  463. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
  464. package/src/mem_pools/tx_pool/README.md +0 -270
  465. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +0 -746
  466. package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +0 -132
  467. package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +0 -208
  468. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +0 -162
  469. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +0 -104
  470. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.ts +0 -93
  471. package/src/mem_pools/tx_pool/eviction/low_priority_eviction_rule.ts +0 -106
  472. package/src/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.ts +0 -75
  473. package/src/mem_pools/tx_pool/index.ts +0 -2
  474. package/src/mem_pools/tx_pool/priority.ts +0 -20
  475. package/src/mem_pools/tx_pool/tx_pool.ts +0 -141
  476. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +0 -319
  477. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
  478. package/src/services/reqresp/protocols/block.ts +0 -37
@@ -1,5 +1,6 @@
1
- import { SlotNumber } from '@aztec/foundation/branded-types';
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,20 +9,27 @@ 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';
15
+ import { DeletedPool } from './deleted_pool.js';
13
16
  import {
14
17
  EvictionManager,
15
18
  FeePayerBalanceEvictionRule,
16
19
  FeePayerBalancePreAddRule,
20
+ InsufficientFeePerGasEvictionRule,
17
21
  InvalidTxsAfterMiningRule,
18
22
  InvalidTxsAfterReorgRule,
19
23
  LowPriorityEvictionRule,
20
24
  LowPriorityPreAddRule,
21
25
  NullifierConflictRule,
22
26
  type PoolOperations,
27
+ type PreAddContext,
23
28
  type PreAddPoolAccess,
29
+ TxPoolRejectionCode,
30
+ type TxPoolRejectionError,
24
31
  } from './eviction/index.js';
32
+ import { TxPoolV2Instrumentation } from './instrumentation.js';
25
33
  import {
26
34
  type AddTxsResult,
27
35
  DEFAULT_TX_POOL_V2_CONFIG,
@@ -29,14 +37,8 @@ import {
29
37
  type TxPoolV2Config,
30
38
  type TxPoolV2Dependencies,
31
39
  } from './interfaces.js';
32
- import {
33
- type TxMetaData,
34
- type TxState,
35
- buildTxMetaData,
36
- checkNullifierConflict,
37
- compareFee,
38
- compareTxHash,
39
- } from './tx_metadata.js';
40
+ import { type TxMetaData, type TxState, buildTxMetaData, checkNullifierConflict } from './tx_metadata.js';
41
+ import { TxPoolIndices } from './tx_pool_indices.js';
40
42
 
41
43
  /**
42
44
  * Callbacks for the implementation to notify the outer class about events and metrics.
@@ -44,6 +46,7 @@ import {
44
46
  export interface TxPoolV2Callbacks {
45
47
  onTxsAdded: (txs: Tx[], opts: { source?: string }) => void;
46
48
  onTxsRemoved: (txHashes: string[] | bigint[]) => void;
49
+ onTxsMined: (txHashes: string[]) => void;
47
50
  }
48
51
 
49
52
  /**
@@ -59,27 +62,20 @@ export class TxPoolV2Impl {
59
62
  // === Dependencies ===
60
63
  #l2BlockSource: L2BlockSource;
61
64
  #worldStateSynchronizer: WorldStateSynchronizer;
62
- #pendingTxValidator: TxValidator<Tx>;
65
+ #createTxValidator: TxPoolV2Dependencies['createTxValidator'];
66
+ #checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
63
67
 
64
68
  // === In-Memory Indices ===
65
- /** Primary metadata store: txHash -> TxMetaData */
66
- #metadata: Map<string, TxMetaData> = new Map();
67
- /** Nullifier to txHash index (pending txs only) */
68
- #nullifierToTxHash: Map<string, string> = new Map();
69
- /** Fee payer to txHashes index (pending txs only) */
70
- #feePayerToTxHashes: Map<string, Set<string>> = new Map();
71
- /**
72
- * Pending txHashes grouped by priority fee.
73
- * Outer map: priorityFee -> Set of txHashes at that fee level.
74
- */
75
- #pendingByPriority: Map<bigint, Set<string>> = new Map();
76
- /** Protected transactions: txHash -> slotNumber. Includes txs we have and txs we expect to receive. */
77
- #protectedTransactions: Map<string, SlotNumber> = new Map();
69
+ #indices: TxPoolIndices = new TxPoolIndices();
78
70
 
79
71
  // === Config & Services ===
80
72
  #config: TxPoolV2Config;
81
73
  #archive: TxArchive;
74
+ #deletedPool: DeletedPool;
82
75
  #evictionManager: EvictionManager;
76
+ #dateProvider: DateProvider;
77
+ #instrumentation: TxPoolV2Instrumentation;
78
+ #evictedTxHashes: Set<string> = new Set();
83
79
  #log: Logger;
84
80
  #callbacks: TxPoolV2Callbacks;
85
81
 
@@ -88,7 +84,9 @@ export class TxPoolV2Impl {
88
84
  archiveStore: AztecAsyncKVStore,
89
85
  deps: TxPoolV2Dependencies,
90
86
  callbacks: TxPoolV2Callbacks,
87
+ telemetry: TelemetryClient,
91
88
  config: Partial<TxPoolV2Config> = {},
89
+ dateProvider: DateProvider,
92
90
  log: Logger,
93
91
  ) {
94
92
  this.#store = store;
@@ -96,10 +94,14 @@ export class TxPoolV2Impl {
96
94
 
97
95
  this.#l2BlockSource = deps.l2BlockSource;
98
96
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
99
- this.#pendingTxValidator = deps.pendingTxValidator;
97
+ this.#createTxValidator = deps.createTxValidator;
98
+ this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
100
99
 
101
100
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
102
101
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
102
+ this.#deletedPool = new DeletedPool(store, this.#txsDB, log);
103
+ this.#dateProvider = dateProvider;
104
+ this.#instrumentation = new TxPoolV2Instrumentation(telemetry, () => this.#indices.getTotalMetadataBytes());
103
105
  this.#log = log;
104
106
  this.#callbacks = callbacks;
105
107
 
@@ -115,6 +117,7 @@ export class TxPoolV2Impl {
115
117
 
116
118
  // Post-event eviction rules (run after events to check ALL pending txs)
117
119
  this.#evictionManager.registerRule(new InvalidTxsAfterMiningRule());
120
+ this.#evictionManager.registerRule(new InsufficientFeePerGasEvictionRule(deps.blockMinFeesProvider));
118
121
  this.#evictionManager.registerRule(new InvalidTxsAfterReorgRule(deps.worldStateSynchronizer));
119
122
  this.#evictionManager.registerRule(new FeePayerBalanceEvictionRule(deps.worldStateSynchronizer));
120
123
  // LowPriorityEvictionRule handles cases where txs become pending via prepareForSlot (unprotect)
@@ -134,26 +137,42 @@ export class TxPoolV2Impl {
134
137
  * by running pre-add rules to resolve nullifier conflicts, balance checks, and pool size limits.
135
138
  */
136
139
  async hydrateFromDatabase(): Promise<void> {
137
- // Step 1: Load all transactions from DB
140
+ // Step 0: Hydrate deleted pool state
141
+ await this.#deletedPool.hydrateFromDatabase();
142
+
143
+ // Step 1: Load all transactions from DB (excluding soft-deleted)
138
144
  const { loaded, errors: deserializationErrors } = await this.#loadAllTxsFromDb();
139
145
 
140
146
  // Step 2: Check mined status for each tx
141
147
  await this.#markMinedStatusBatch(loaded.map(l => l.meta));
142
148
 
143
149
  // Step 3: Partition by mined status
144
- const { mined, nonMined } = this.#partitionByMinedStatus(loaded);
150
+ const mined: TxMetaData[] = [];
151
+ const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
152
+ for (const entry of loaded) {
153
+ if (entry.meta.minedL2BlockId !== undefined) {
154
+ mined.push(entry.meta);
155
+ } else {
156
+ nonMined.push(entry);
157
+ }
158
+ }
145
159
 
146
160
  // Step 4: Validate non-mined transactions
147
- const { valid, invalid } = await this.#validateNonMinedTxs(nonMined);
161
+ const { valid, invalid } = await this.#revalidateMetadata(
162
+ nonMined.map(e => e.meta),
163
+ 'on startup',
164
+ );
148
165
 
149
166
  // Step 5: Populate mined indices (these don't need conflict resolution)
150
- this.#populateMinedIndices(mined);
167
+ for (const meta of mined) {
168
+ this.#indices.addMined(meta);
169
+ }
151
170
 
152
171
  // Step 6: Rebuild pending pool by running pre-add rules for each tx
153
172
  // This resolves nullifier conflicts, fee payer balance issues, and pool size limits
154
173
  const { rejected } = await this.#rebuildPendingPool(valid);
155
174
 
156
- // Step 7: Delete invalid and rejected txs from DB
175
+ // Step 7: Delete invalid and rejected txs from DB only (indices were never populated for these)
157
176
  const toDelete = [...deserializationErrors, ...invalid, ...rejected];
158
177
  if (toDelete.length === 0) {
159
178
  return;
@@ -163,17 +182,45 @@ export class TxPoolV2Impl {
163
182
  await this.#txsDB.delete(txHashStr);
164
183
  }
165
184
  });
166
- this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`);
185
+ this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`, { txHashes: toDelete });
167
186
  }
168
187
 
169
- async addPendingTxs(txs: Tx[], opts: { source?: string }): Promise<AddTxsResult> {
188
+ async addPendingTxs(txs: Tx[], opts: { source?: string; feeComparisonOnly?: boolean }): Promise<AddTxsResult> {
170
189
  const accepted: TxHash[] = [];
171
190
  const ignored: TxHash[] = [];
172
191
  const rejected: TxHash[] = [];
173
- const newlyAdded: Tx[] = [];
192
+ const errors = new Map<string, TxPoolRejectionError>();
174
193
  const acceptedPending = new Set<string>();
175
194
 
195
+ // Phase 1: Pre-compute all throwable I/O outside the transaction.
196
+ // If any pre-computation throws, the entire call fails before mutations happen.
197
+ const precomputed = new Map<string, { meta: TxMetaData; minedBlockId: L2BlockId | undefined; isValid: boolean }>();
198
+
199
+ const validator = await this.#createTxValidator();
200
+
201
+ for (const tx of txs) {
202
+ const txHash = tx.getTxHash();
203
+ const txHashStr = txHash.toString();
204
+
205
+ const meta = await buildTxMetaData(tx);
206
+ const minedBlockId = await this.#getMinedBlockId(txHash);
207
+
208
+ // Validate non-mined txs (mined and pre-protected txs bypass validation inside the transaction)
209
+ let isValid = true;
210
+ if (!minedBlockId) {
211
+ isValid = await this.#validateMeta(meta, validator);
212
+ }
213
+
214
+ precomputed.set(txHashStr, { meta, minedBlockId, isValid });
215
+ }
216
+
217
+ // Phase 2: Apply mutations inside the transaction using only pre-computed results,
218
+ // in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
176
219
  const poolAccess = this.#createPreAddPoolAccess();
220
+ const preAddContext: PreAddContext | undefined =
221
+ opts.feeComparisonOnly !== undefined
222
+ ? { feeComparisonOnly: opts.feeComparisonOnly, priceBumpPercentage: this.#config.priceBumpPercentage }
223
+ : undefined;
177
224
 
178
225
  await this.#store.transactionAsync(async () => {
179
226
  for (const tx of txs) {
@@ -181,38 +228,51 @@ export class TxPoolV2Impl {
181
228
  const txHashStr = txHash.toString();
182
229
 
183
230
  // Skip duplicates
184
- if (this.#isDuplicateTx(txHashStr)) {
231
+ if (this.#indices.has(txHashStr)) {
185
232
  ignored.push(txHash);
186
233
  continue;
187
234
  }
188
235
 
189
- // Check mined status first (applies to all paths)
190
- const minedBlockId = await this.#getMinedBlockId(txHash);
191
- const preProtectedSlot = this.#protectedTransactions.get(txHashStr);
236
+ const { meta, minedBlockId, isValid } = precomputed.get(txHashStr)!;
237
+ const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
192
238
 
193
239
  if (minedBlockId) {
194
240
  // Already mined - add directly (protection already set if pre-protected)
195
- await this.#addNewMinedTx(tx, minedBlockId);
241
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
196
242
  accepted.push(txHash);
197
- newlyAdded.push(tx);
198
243
  } else if (preProtectedSlot !== undefined) {
199
244
  // Pre-protected and not mined - add as protected (bypass validation)
200
- await this.#addNewProtectedTx(tx, preProtectedSlot);
245
+ await this.#addTx(tx, { protected: preProtectedSlot }, opts, meta);
201
246
  accepted.push(txHash);
202
- newlyAdded.push(tx);
247
+ } else if (!isValid) {
248
+ // Failed pre-computed validation
249
+ rejected.push(txHash);
203
250
  } else {
204
- // Regular pending tx - validate and run pre-add rules
205
- const result = await this.#tryAddRegularPendingTx(tx, poolAccess, acceptedPending, ignored);
251
+ // Regular pending tx - run pre-add rules using pre-computed metadata
252
+ const result = await this.#tryAddRegularPendingTx(
253
+ tx,
254
+ meta,
255
+ opts,
256
+ poolAccess,
257
+ acceptedPending,
258
+ ignored,
259
+ errors,
260
+ preAddContext,
261
+ );
206
262
  if (result.status === 'accepted') {
207
263
  acceptedPending.add(txHashStr);
208
- newlyAdded.push(tx);
209
- } else if (result.status === 'rejected') {
210
- rejected.push(txHash);
211
264
  } else {
212
265
  ignored.push(txHash);
213
266
  }
214
267
  }
215
268
  }
269
+
270
+ // Run post-add eviction rules for pending txs (inside transaction for atomicity)
271
+ if (acceptedPending.size > 0) {
272
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
273
+ const uniqueFeePayers = new Set<string>(feePayers);
274
+ await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
275
+ }
216
276
  });
217
277
 
218
278
  // Build final accepted list for pending txs (excludes intra-batch evictions)
@@ -220,164 +280,211 @@ export class TxPoolV2Impl {
220
280
  accepted.push(TxHash.fromString(txHashStr));
221
281
  }
222
282
 
223
- // Run post-add eviction rules for pending txs
224
- if (acceptedPending.size > 0) {
225
- const feePayers = Array.from(acceptedPending).map(txHash => this.#metadata.get(txHash)!.feePayer);
226
- const uniqueFeePayers = new Set<string>(feePayers);
227
- await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
283
+ // Record metrics
284
+ if (ignored.length > 0) {
285
+ this.#instrumentation.recordIgnored(ignored.length);
228
286
  }
229
-
230
- // Emit events
231
- if (newlyAdded.length > 0) {
232
- this.#callbacks.onTxsAdded(newlyAdded, opts);
287
+ if (rejected.length > 0) {
288
+ this.#instrumentation.recordRejected(rejected.length);
233
289
  }
234
290
 
235
- return { accepted, ignored, rejected };
291
+ return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
236
292
  }
237
293
 
238
- /** Validates and adds a regular pending tx. Returns status. */
294
+ /** Adds a validated pending tx, running pre-add rules and evicting conflicts. */
239
295
  async #tryAddRegularPendingTx(
240
296
  tx: Tx,
297
+ precomputedMeta: TxMetaData,
298
+ opts: { source?: string },
241
299
  poolAccess: PreAddPoolAccess,
242
300
  acceptedPending: Set<string>,
243
301
  ignored: TxHash[],
244
- ): Promise<{ status: 'accepted' | 'ignored' | 'rejected' }> {
245
- const txHash = tx.getTxHash();
246
- const txHashStr = txHash.toString();
302
+ errors: Map<string, TxPoolRejectionError>,
303
+ preAddContext?: PreAddContext,
304
+ ): Promise<{ status: 'accepted' | 'ignored' }> {
305
+ const txHashStr = tx.getTxHash().toString();
247
306
 
248
- // Validate transaction
249
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
250
- if (validationResult.result !== 'valid') {
251
- this.#log.info(`Rejecting tx ${txHashStr}: ${validationResult.reason?.join(', ')}`);
252
- return { status: 'rejected' };
253
- }
254
-
255
- // Build metadata and run pre-add rules
256
- const meta = await buildTxMetaData(tx);
257
- const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
307
+ // Run pre-add rules
308
+ const preAddResult = await this.#evictionManager.runPreAddRules(precomputedMeta, poolAccess, preAddContext);
258
309
 
259
310
  if (preAddResult.shouldIgnore) {
260
- this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason}`);
311
+ this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
312
+ if (preAddResult.reason && preAddResult.reason.code !== TxPoolRejectionCode.INTERNAL_ERROR) {
313
+ errors.set(txHashStr, preAddResult.reason);
314
+ }
261
315
  return { status: 'ignored' };
262
316
  }
263
317
 
264
- // Evict conflicts (tracking intra-batch evictions)
265
- for (const evictHashStr of preAddResult.txHashesToEvict) {
266
- await this.#deleteTx(evictHashStr);
267
- this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
268
- if (acceptedPending.has(evictHashStr)) {
269
- acceptedPending.delete(evictHashStr);
270
- ignored.push(TxHash.fromString(evictHashStr));
318
+ // Evict conflicts, grouped by rule name for metrics
319
+ if (preAddResult.evictions && preAddResult.evictions.length > 0) {
320
+ const byReason = new Map<string, string[]>();
321
+ for (const { txHash: evictHash, reason } of preAddResult.evictions) {
322
+ const group = byReason.get(reason);
323
+ if (group) {
324
+ group.push(evictHash);
325
+ } else {
326
+ byReason.set(reason, [evictHash]);
327
+ }
328
+ }
329
+ for (const [reason, hashes] of byReason) {
330
+ await this.#evictTxs(hashes, reason);
271
331
  }
332
+ for (const evictHashStr of preAddResult.txHashesToEvict) {
333
+ this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`, {
334
+ evictedTxHash: evictHashStr,
335
+ replacementTxHash: txHashStr,
336
+ });
337
+ if (acceptedPending.has(evictHashStr)) {
338
+ // Evicted tx was from this batch - mark as ignored in result
339
+ acceptedPending.delete(evictHashStr);
340
+ ignored.push(TxHash.fromString(evictHashStr));
341
+ }
342
+ }
343
+ }
344
+
345
+ // Randomly drop the transaction for testing purposes (report as accepted so it propagates)
346
+ if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
347
+ this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
348
+ return { status: 'accepted' };
272
349
  }
273
350
 
274
351
  // Add the transaction
275
- await this.#addNewPendingTx(tx);
352
+ await this.#addTx(tx, 'pending', opts, precomputedMeta);
276
353
  return { status: 'accepted' };
277
354
  }
278
355
 
279
- async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored' | 'rejected'> {
356
+ async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
280
357
  const txHashStr = tx.getTxHash().toString();
281
358
 
282
359
  // Check if already in pool
283
- if (this.#metadata.has(txHashStr)) {
360
+ if (this.#indices.has(txHashStr)) {
361
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} already in pool`);
284
362
  return 'ignored';
285
363
  }
286
364
 
287
- // Validate transaction
288
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
289
- if (validationResult.result !== 'valid') {
290
- return 'rejected';
291
- }
292
-
293
- // Build metadata and use pre-add rules
365
+ // Build metadata and check pre-add rules
294
366
  const meta = await buildTxMetaData(tx);
295
367
  const poolAccess = this.#createPreAddPoolAccess();
296
368
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
297
369
 
298
- return preAddResult.shouldIgnore ? 'ignored' : 'accepted';
370
+ if (preAddResult.shouldIgnore) {
371
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} ignored by pre-add rule`, {
372
+ reason: preAddResult.reason?.message ?? 'no reason provided',
373
+ });
374
+ return 'ignored';
375
+ }
376
+ return 'accepted';
299
377
  }
300
378
 
301
379
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
302
380
  const slotNumber = block.globalVariables.slotNumber;
303
- const newlyAdded: Tx[] = [];
381
+
382
+ // Precompute setup-call allow-list flags outside the store transaction
383
+ const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
304
384
 
305
385
  await this.#store.transactionAsync(async () => {
306
- for (const tx of txs) {
386
+ for (let i = 0; i < txs.length; i++) {
387
+ const tx = txs[i];
307
388
  const txHash = tx.getTxHash();
308
389
  const txHashStr = txHash.toString();
309
- const isNew = !this.#metadata.has(txHashStr);
390
+ const isNew = !this.#indices.has(txHashStr);
310
391
  const minedBlockId = await this.#getMinedBlockId(txHash);
311
392
 
312
393
  if (isNew) {
313
- // New tx - add as mined or protected
394
+ const meta = await buildTxMetaData(tx, allowedFlags[i]);
395
+ // New tx - add as mined or protected (callback emitted by #addTx)
314
396
  if (minedBlockId) {
315
- await this.#addNewMinedTx(tx, minedBlockId);
316
- this.#protectedTransactions.set(txHashStr, slotNumber);
397
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
398
+ this.#indices.setProtection(txHashStr, slotNumber);
317
399
  } else {
318
- await this.#addNewProtectedTx(tx, slotNumber);
400
+ await this.#addTx(tx, { protected: slotNumber }, opts, meta);
319
401
  }
320
- newlyAdded.push(tx);
321
402
  } else {
322
403
  // Existing tx - update protection and mined status
323
- this.#updateProtection(txHashStr, slotNumber);
404
+ this.#indices.updateProtection(txHashStr, slotNumber);
324
405
  if (minedBlockId) {
325
- this.#markAsMined(this.#metadata.get(txHashStr)!, minedBlockId);
406
+ const meta = this.#indices.getMetadata(txHashStr)!;
407
+ this.#indices.markAsMined(meta, minedBlockId);
326
408
  }
327
409
  }
328
410
  }
329
411
  });
330
-
331
- if (newlyAdded.length > 0) {
332
- this.#callbacks.onTxsAdded(newlyAdded, opts);
333
- }
334
412
  }
335
413
 
336
- protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
414
+ async protectTxs(txHashes: TxHash[], block: BlockHeader): Promise<TxHash[]> {
337
415
  const slotNumber = block.globalVariables.slotNumber;
338
416
  const missing: TxHash[] = [];
417
+ let softDeletedHits = 0;
418
+ let missingPreviouslyEvicted = 0;
339
419
 
340
- for (const txHash of txHashes) {
341
- const txHashStr = txHash.toString();
420
+ await this.#store.transactionAsync(async () => {
421
+ for (const txHash of txHashes) {
422
+ const txHashStr = txHash.toString();
342
423
 
343
- if (this.#metadata.has(txHashStr)) {
344
- // Step 1a: Update protection for existing tx
345
- this.#updateProtection(txHashStr, slotNumber);
346
- } else {
347
- // Step 1b: Pre-record protection for tx we don't have yet
348
- this.#protectedTransactions.set(txHashStr, slotNumber);
349
- missing.push(txHash);
424
+ if (this.#indices.has(txHashStr)) {
425
+ // Update protection for existing tx
426
+ this.#indices.updateProtection(txHashStr, slotNumber);
427
+ } else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
428
+ // Resurrect soft-deleted tx as protected
429
+ const buffer = await this.#txsDB.getAsync(txHashStr);
430
+ if (buffer) {
431
+ const tx = Tx.fromBuffer(buffer);
432
+ await this.#addTx(tx, { protected: slotNumber });
433
+ softDeletedHits++;
434
+ } else {
435
+ // Data missing despite soft-delete flag — treat as truly missing
436
+ this.#indices.setProtection(txHashStr, slotNumber);
437
+ missing.push(txHash);
438
+ }
439
+ } else {
440
+ // Truly missing — pre-record protection for tx we don't have yet
441
+ this.#indices.setProtection(txHashStr, slotNumber);
442
+ missing.push(txHash);
443
+ if (this.#evictedTxHashes.has(txHashStr)) {
444
+ missingPreviouslyEvicted++;
445
+ }
446
+ }
350
447
  }
448
+ });
449
+
450
+ // Record metrics
451
+ if (softDeletedHits > 0) {
452
+ this.#instrumentation.recordSoftDeletedHits(softDeletedHits);
453
+ }
454
+ if (missing.length > 0) {
455
+ this.#log.debug(`protectTxs missing tx hashes: ${missing.map(h => h.toString()).join(', ')}`);
456
+ this.#instrumentation.recordMissingOnProtect(missing.length);
457
+ }
458
+ if (missingPreviouslyEvicted > 0) {
459
+ this.#instrumentation.recordMissingPreviouslyEvicted(missingPreviouslyEvicted);
351
460
  }
352
461
 
462
+ this.#log.info(
463
+ `Protected ${txHashes.length} txs, missing: ${missing.length}, soft-deleted hits: ${softDeletedHits}`,
464
+ );
465
+
353
466
  return missing;
354
467
  }
355
468
 
356
469
  async addMinedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
357
470
  // Step 1: Build block ID
358
471
  const blockId = await this.#buildBlockId(block);
359
- const newlyAdded: Tx[] = [];
360
472
 
361
473
  await this.#store.transactionAsync(async () => {
362
474
  for (const tx of txs) {
363
475
  const txHashStr = tx.getTxHash().toString();
364
- const existingMeta = this.#metadata.get(txHashStr);
476
+ const existingMeta = this.#indices.getMetadata(txHashStr);
365
477
 
366
478
  if (existingMeta) {
367
- // Step 2a: Mark existing tx as mined
368
- this.#markAsMined(existingMeta, blockId);
479
+ // Mark existing tx as mined
480
+ this.#indices.markAsMined(existingMeta, blockId);
369
481
  } else {
370
- // Step 2b: Add new mined tx
371
- await this.#addNewMinedTx(tx, blockId);
372
- newlyAdded.push(tx);
482
+ // Add new mined tx (callback emitted by #addTx)
483
+ await this.#addTx(tx, { mined: blockId }, opts);
373
484
  }
485
+ await this.#deletedPool.clearIfMinedHigher(txHashStr, blockId.number);
374
486
  }
375
487
  });
376
-
377
- // Step 3: Emit events for newly added txs
378
- if (newlyAdded.length > 0) {
379
- this.#callbacks.onTxsAdded(newlyAdded, opts);
380
- }
381
488
  }
382
489
 
383
490
  async handleMinedBlock(block: L2Block): Promise<void> {
@@ -392,61 +499,76 @@ export class TxPoolV2Impl {
392
499
  const feePayers: string[] = [];
393
500
  const found: TxMetaData[] = [];
394
501
  for (const txHash of txHashes) {
395
- const meta = this.#metadata.get(txHash.toString());
502
+ const meta = this.#indices.getMetadata(txHash.toString());
396
503
  if (meta) {
397
504
  feePayers.push(meta.feePayer);
398
505
  found.push(meta);
399
506
  }
400
507
  }
401
508
 
402
- // Step 4: Mark txs as mined (only those we have in the pool)
403
- this.#markTxsAsMined(found, blockId);
509
+ await this.#store.transactionAsync(async () => {
510
+ // Step 4: Mark txs as mined (only those we have in the pool)
511
+ for (const meta of found) {
512
+ this.#indices.markAsMined(meta, blockId);
513
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
514
+ }
404
515
 
405
- // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
406
- await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
516
+ // Step 5: Run post-event eviction rules (inside transaction for atomicity)
517
+ await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
518
+ });
519
+
520
+ if (found.length > 0) {
521
+ this.#callbacks.onTxsMined(found.map(m => m.txHash));
522
+ }
407
523
 
408
- this.#callbacks.onTxsRemoved(txHashes.map(h => h.toBigInt()));
409
524
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
410
525
  }
411
526
 
412
527
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
413
- // Step 1: Find expired protected txs
414
- const expiredProtected = this.#findExpiredProtectedTxs(slotNumber);
528
+ await this.#store.transactionAsync(async () => {
529
+ // Step 0: Clean up slot-deleted txs from previous slots
530
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
415
531
 
416
- // Step 2: Clear protection for all expired entries (including those without metadata)
417
- this.#clearProtection(expiredProtected);
532
+ // Step 1: Find expired protected txs
533
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
418
534
 
419
- // Step 3: Filter to only txs that have metadata and are not mined
420
- const txsToRestore = this.#filterRestorable(expiredProtected);
421
- if (txsToRestore.length === 0) {
422
- return;
423
- }
535
+ // Step 2: Clear protection for all expired entries (including those without metadata)
536
+ this.#indices.clearProtection(expiredProtected);
537
+
538
+ // Step 3: Filter to only txs that have metadata and are not mined
539
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
540
+ if (txsToRestore.length === 0) {
541
+ this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
542
+ return;
543
+ }
424
544
 
425
- this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
545
+ this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
426
546
 
427
- // Step 4: Validate for pending pool
428
- const { valid, invalid } = await this.#validateForPending(txsToRestore);
547
+ // Step 4: Validate for pending pool
548
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
429
549
 
430
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
431
- const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
550
+ // Step 5: Resolve nullifier conflicts and add winners to pending indices
551
+ const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
432
552
 
433
- // Step 6: Delete invalid and evicted txs
434
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
553
+ // Step 6: Delete invalid txs and evict conflict losers
554
+ await this.#deleteTxsBatch(invalid);
555
+ await this.#evictTxs(toEvict, 'NullifierConflict');
435
556
 
436
- // Step 7: Run eviction rules (enforce pool size limit)
437
- if (added.length > 0) {
438
- const feePayers = added.map(meta => meta.feePayer);
439
- const uniqueFeePayers = new Set<string>(feePayers);
440
- await this.#evictionManager.evictAfterNewTxs(
441
- added.map(m => m.txHash),
442
- [...uniqueFeePayers],
443
- );
444
- }
557
+ // Step 7: Run eviction rules (enforce pool size limit)
558
+ if (added.length > 0) {
559
+ const feePayers = added.map(meta => meta.feePayer);
560
+ const uniqueFeePayers = new Set<string>(feePayers);
561
+ await this.#evictionManager.evictAfterNewTxs(
562
+ added.map(m => m.txHash),
563
+ [...uniqueFeePayers],
564
+ );
565
+ }
566
+ });
445
567
  }
446
568
 
447
- async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
569
+ async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
448
570
  // Step 1: Find transactions mined after the prune point
449
- const txsToUnmine = this.#findTxsMinedAfter(latestBlock.number);
571
+ const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
450
572
  if (txsToUnmine.length === 0) {
451
573
  this.#log.debug(`No transactions to un-mine for prune to block ${latestBlock.number}`);
452
574
  return;
@@ -454,62 +576,99 @@ export class TxPoolV2Impl {
454
576
 
455
577
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
456
578
 
457
- // Step 2: Unmine - clear mined status from metadata
458
- this.#unmineTxs(txsToUnmine);
579
+ await this.#store.transactionAsync(async () => {
580
+ // Step 2: Mark ALL un-mined txs with their original mined block number
581
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
582
+ // when their original mined block is finalized
583
+ await this.#deletedPool.markFromPrunedBlock(
584
+ txsToUnmine.map(m => ({
585
+ txHash: m.txHash,
586
+ minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
587
+ })),
588
+ );
459
589
 
460
- // Step 3: Filter out protected txs (they'll be handled by prepareForSlot)
461
- const unprotectedTxs = this.#filterUnprotected(txsToUnmine);
590
+ // Step 3: Unmine - clear mined status from metadata
591
+ for (const meta of txsToUnmine) {
592
+ this.#indices.markAsUnmined(meta);
593
+ }
462
594
 
463
- // Step 4: Validate for pending pool
464
- const { valid, invalid } = await this.#validateForPending(unprotectedTxs);
595
+ // If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
596
+ if (options?.deleteAllTxs) {
597
+ const allTxHashes = txsToUnmine.map(m => m.txHash);
598
+ await this.#deleteTxsBatch(allTxHashes);
599
+ this.#log.info(
600
+ `Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
601
+ );
602
+ return;
603
+ }
465
604
 
466
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
467
- const { toEvict } = this.#applyNullifierConflictResolution(valid);
605
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
606
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
468
607
 
469
- // Step 6: Delete invalid and evicted txs
470
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
608
+ // Step 5: Validate for pending pool
609
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
471
610
 
472
- // Step 7: Run eviction rules for ALL pending txs (not just restored ones)
473
- // This handles cases like existing pending txs with invalid fee payer balances
474
- await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
611
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
612
+ const { toEvict } = this.#applyNullifierConflictResolution(valid);
613
+
614
+ // Step 7: Delete invalid txs and evict conflict losers
615
+ await this.#deleteTxsBatch(invalid);
616
+ await this.#evictTxs(toEvict, 'NullifierConflict');
617
+
618
+ this.#log.info(
619
+ `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
620
+ { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
621
+ );
622
+
623
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
624
+ // This handles cases like existing pending txs with invalid fee payer balances
625
+ await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
626
+ });
475
627
  }
476
628
 
477
629
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
478
- // Step 1: Delete failed txs
479
- await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
630
+ await this.#store.transactionAsync(async () => {
631
+ await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
632
+ });
480
633
 
481
- this.#log.info(`Deleted ${txHashes.length} failed txs`);
634
+ this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
482
635
  }
483
636
 
484
637
  async handleFinalizedBlock(block: BlockHeader): Promise<void> {
485
638
  const blockNumber = block.globalVariables.blockNumber;
486
639
 
487
- // Step 1: Find txs mined at or before finalized block
488
- const txsToFinalize = this.#findTxsMinedAtOrBefore(blockNumber);
489
- if (txsToFinalize.length === 0) {
490
- return;
491
- }
640
+ // Step 1: Find mined txs at or before finalized block
641
+ const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
492
642
 
493
- // Step 2: Collect txs for archiving (before deletion)
494
- const txsToArchive: Tx[] = [];
495
- if (this.#archive.isEnabled()) {
496
- for (const txHashStr of txsToFinalize) {
497
- const buffer = await this.#txsDB.getAsync(txHashStr);
498
- if (buffer) {
499
- txsToArchive.push(Tx.fromBuffer(buffer));
643
+ await this.#store.transactionAsync(async () => {
644
+ // Step 2: Collect mined txs for archiving (before deletion)
645
+ const txsToArchive: Tx[] = [];
646
+ if (this.#archive.isEnabled()) {
647
+ for (const txHashStr of minedTxsToFinalize) {
648
+ const buffer = await this.#txsDB.getAsync(txHashStr);
649
+ if (buffer) {
650
+ txsToArchive.push(Tx.fromBuffer(buffer));
651
+ }
500
652
  }
501
653
  }
502
- }
503
654
 
504
- // Step 3: Delete from active pool
505
- await this.#deleteTxsBatch(txsToFinalize);
655
+ // Step 3: Delete mined txs from active pool
656
+ await this.#deleteTxsBatch(minedTxsToFinalize);
506
657
 
507
- // Step 4: Archive
508
- if (txsToArchive.length > 0) {
509
- await this.#archive.archiveTxs(txsToArchive);
510
- }
658
+ // Step 4: Finalize soft-deleted txs
659
+ await this.#deletedPool.finalizeBlock(blockNumber);
660
+
661
+ // Step 5: Archive mined txs
662
+ if (txsToArchive.length > 0) {
663
+ await this.#archive.archiveTxs(txsToArchive);
664
+ }
665
+ });
511
666
 
512
- this.#log.info(`Finalized ${txsToFinalize.length} txs from blocks up to ${blockNumber}`);
667
+ if (minedTxsToFinalize.length > 0) {
668
+ this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
669
+ txHashes: minedTxsToFinalize,
670
+ });
671
+ }
513
672
  }
514
673
 
515
674
  // === Query Methods ===
@@ -529,42 +688,47 @@ export class TxPoolV2Impl {
529
688
  }
530
689
 
531
690
  hasTxs(txHashes: TxHash[]): boolean[] {
532
- return txHashes.map(h => this.#metadata.has(h.toString()));
691
+ return txHashes.map(h => {
692
+ const hashStr = h.toString();
693
+ return this.#indices.has(hashStr) || this.#deletedPool.isSoftDeleted(hashStr);
694
+ });
533
695
  }
534
696
 
535
697
  getTxStatus(txHash: TxHash): TxState | undefined {
536
- const meta = this.#metadata.get(txHash.toString());
537
- if (!meta) {
538
- return undefined;
698
+ const txHashStr = txHash.toString();
699
+ const meta = this.#indices.getMetadata(txHashStr);
700
+ if (meta) {
701
+ return this.#indices.getTxState(meta);
702
+ }
703
+ // Check if soft-deleted
704
+ if (this.#deletedPool.isSoftDeleted(txHashStr)) {
705
+ return 'deleted';
539
706
  }
540
- return this.#getTxState(meta);
707
+ return undefined;
541
708
  }
542
709
 
543
710
  getPendingTxHashes(): TxHash[] {
544
- return [...this.#iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
711
+ return [...this.#indices.iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
712
+ }
713
+
714
+ getEligiblePendingTxHashes(): TxHash[] {
715
+ const maxReceivedAt = this.#dateProvider.now() - this.#config.minTxPoolAgeMs;
716
+ return [...this.#indices.iterateEligiblePendingByPriority('desc', maxReceivedAt)].map(hash =>
717
+ TxHash.fromString(hash),
718
+ );
545
719
  }
546
720
 
547
721
  getPendingTxCount(): number {
548
- let count = 0;
549
- for (const hashes of this.#pendingByPriority.values()) {
550
- count += hashes.size;
551
- }
552
- return count;
722
+ return this.#indices.getPendingTxCount();
553
723
  }
554
724
 
555
725
  getMinedTxHashes(): [TxHash, L2BlockId][] {
556
- const result: [TxHash, L2BlockId][] = [];
557
- for (const [txHash, meta] of this.#metadata) {
558
- if (meta.minedL2BlockId !== undefined) {
559
- result.push([TxHash.fromString(txHash), meta.minedL2BlockId]);
560
- }
561
- }
562
- return result;
726
+ return this.#indices.getMinedTxs().map(([hash, blockId]) => [TxHash.fromString(hash), blockId]);
563
727
  }
564
728
 
565
729
  getMinedTxCount(): number {
566
730
  let count = 0;
567
- for (const meta of this.#metadata.values()) {
731
+ for (const [, meta] of this.#indices.iterateMetadata()) {
568
732
  if (meta.minedL2BlockId !== undefined) {
569
733
  count++;
570
734
  }
@@ -573,11 +737,11 @@ export class TxPoolV2Impl {
573
737
  }
574
738
 
575
739
  isEmpty(): boolean {
576
- return this.#metadata.size === 0;
740
+ return this.#indices.isEmpty();
577
741
  }
578
742
 
579
743
  getTxCount(): number {
580
- return this.#metadata.size;
744
+ return this.#indices.getTxCount();
581
745
  }
582
746
 
583
747
  getArchivedTxByHash(txHash: TxHash): Promise<Tx | undefined> {
@@ -585,18 +749,7 @@ export class TxPoolV2Impl {
585
749
  }
586
750
 
587
751
  getLowestPriorityPending(limit: number): TxHash[] {
588
- if (limit <= 0) {
589
- return [];
590
- }
591
-
592
- const result: TxHash[] = [];
593
- for (const hash of this.#iteratePendingByPriority('asc')) {
594
- result.push(TxHash.fromString(hash));
595
- if (result.length >= limit) {
596
- break;
597
- }
598
- }
599
- return result;
752
+ return this.#indices.getLowestPriorityPending(limit).map(h => TxHash.fromString(h));
600
753
  }
601
754
 
602
755
  // === Configuration ===
@@ -609,6 +762,9 @@ export class TxPoolV2Impl {
609
762
  this.#config.archivedTxLimit = config.archivedTxLimit;
610
763
  this.#archive.updateLimit(config.archivedTxLimit);
611
764
  }
765
+ if (config.minTxPoolAgeMs !== undefined) {
766
+ this.#config.minTxPoolAgeMs = config.minTxPoolAgeMs;
767
+ }
612
768
  // Update eviction rules with new config
613
769
  this.#evictionManager.updateConfig(config);
614
770
  }
@@ -617,159 +773,146 @@ export class TxPoolV2Impl {
617
773
 
618
774
  getPoolReadAccess(): PoolReadAccess {
619
775
  return {
620
- getMetadata: (txHash: string) => this.#metadata.get(txHash),
621
- getTxHashByNullifier: (nullifier: string) => this.#nullifierToTxHash.get(nullifier),
622
- getTxHashesByFeePayer: (feePayer: string) => this.#feePayerToTxHashes.get(feePayer),
623
- getPendingTxCount: () => this.getPendingTxCount(),
776
+ getMetadata: (txHash: string) => this.#indices.getMetadata(txHash),
777
+ getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
778
+ getTxHashesByFeePayer: (feePayer: string) => this.#indices.getTxHashesByFeePayer(feePayer),
779
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
624
780
  };
625
781
  }
626
782
 
627
783
  // === Metrics ===
628
784
 
629
- countTxs(): { pending: number; protected: number; mined: number } {
630
- let pending = 0;
631
- let protected_ = 0;
632
- let mined = 0;
633
-
634
- for (const meta of this.#metadata.values()) {
635
- const state = this.#getTxState(meta);
636
- if (state === 'pending') {
637
- pending++;
638
- } else if (state === 'protected') {
639
- protected_++;
640
- } else if (state === 'mined') {
641
- mined++;
642
- }
643
- }
644
-
645
- return { pending, protected: protected_, mined };
785
+ countTxs(): {
786
+ pending: number;
787
+ protected: number;
788
+ mined: number;
789
+ softDeleted: number;
790
+ totalMetadataBytes: number;
791
+ } {
792
+ return {
793
+ ...this.#indices.countTxs(),
794
+ softDeleted: this.#deletedPool.getSoftDeletedCount(),
795
+ };
646
796
  }
647
797
 
648
798
  // ============================================================================
649
- // PRIVATE QUERY IMPLEMENTATIONS
799
+ // PRIVATE HELPERS - Transaction Management
650
800
  // ============================================================================
651
801
 
652
802
  /**
653
- * Derives the transaction state from its metadata and protection status.
654
- * A transaction is:
655
- * - 'mined' if it has a minedL2BlockId
656
- * - 'protected' if it's in the protectedTransactions map (but not mined)
657
- * - 'pending' otherwise
803
+ * Adds a new transaction to the pool with the specified state.
804
+ * Emits onTxsAdded callback immediately after DB write.
658
805
  */
659
- #getTxState(meta: TxMetaData): TxState {
660
- if (meta.minedL2BlockId !== undefined) {
661
- return 'mined';
662
- } else if (this.#protectedTransactions.has(meta.txHash)) {
663
- return 'protected';
806
+ async #addTx(
807
+ tx: Tx,
808
+ state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
809
+ opts: { source?: string } = {},
810
+ precomputedMeta?: TxMetaData,
811
+ ): Promise<TxMetaData> {
812
+ const txHashStr = tx.getTxHash().toString();
813
+ const meta = precomputedMeta ?? (await buildTxMetaData(tx));
814
+ meta.receivedAt = this.#dateProvider.now();
815
+
816
+ await this.#txsDB.set(txHashStr, tx.toBuffer());
817
+ await this.#deletedPool.clearSoftDeleted(txHashStr);
818
+ this.#callbacks.onTxsAdded([tx], opts);
819
+
820
+ if (state === 'pending') {
821
+ this.#indices.addPending(meta);
822
+ } else if ('protected' in state) {
823
+ this.#indices.addProtected(meta, state.protected);
664
824
  } else {
665
- return 'pending';
825
+ meta.minedL2BlockId = state.mined;
826
+ this.#indices.addMined(meta);
666
827
  }
828
+
829
+ const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
830
+ this.#log.debug(`Added tx ${txHashStr} as ${stateStr}`, {
831
+ eventName: 'tx-added-to-pool',
832
+ txHash: txHashStr,
833
+ state: stateStr,
834
+ source: opts.source,
835
+ });
836
+
837
+ return meta;
667
838
  }
668
839
 
669
840
  /**
670
- * Iterates pending transaction hashes in priority order.
671
- * @param order - 'desc' for highest priority first, 'asc' for lowest priority first
841
+ * Deletes a transaction from both indices and DB.
842
+ * Emits onTxsRemoved callback immediately after DB delete.
672
843
  */
673
- *#iteratePendingByPriority(order: 'asc' | 'desc'): Generator<string> {
674
- // Use shared comparators, negating for descending order
675
- const feeCompareFn =
676
- order === 'desc' ? (a: bigint, b: bigint) => compareFee(b, a) : (a: bigint, b: bigint) => compareFee(a, b);
677
- const hashCompareFn =
678
- order === 'desc' ? (a: string, b: string) => compareTxHash(b, a) : (a: string, b: string) => compareTxHash(a, b);
679
-
680
- const sortedFees = [...this.#pendingByPriority.keys()].sort(feeCompareFn);
681
-
682
- for (const fee of sortedFees) {
683
- const hashesAtFee = this.#pendingByPriority.get(fee)!;
684
- const sortedHashes = [...hashesAtFee].sort(hashCompareFn);
685
- for (const hash of sortedHashes) {
686
- yield hash;
687
- }
688
- }
844
+ /**
845
+ * Deletes a transaction from the pool.
846
+ * Delegates to DeletedPool which decides soft vs hard delete based on whether
847
+ * the tx is from a pruned block.
848
+ */
849
+ async #deleteTx(txHashStr: string): Promise<void> {
850
+ this.#indices.remove(txHashStr);
851
+ this.#callbacks.onTxsRemoved([txHashStr]);
852
+ await this.#deletedPool.deleteTx(txHashStr);
689
853
  }
690
854
 
691
- // ============================================================================
692
- // HELPER FUNCTIONS - Pipeline Step Functions
693
- // ============================================================================
694
-
695
- // --- Finding & Filtering Steps ---
696
-
697
- /** Finds all transactions mined in blocks after the given block number */
698
- #findTxsMinedAfter(blockNumber: number): TxMetaData[] {
699
- const result: TxMetaData[] = [];
700
- for (const meta of this.#metadata.values()) {
701
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number > blockNumber) {
702
- result.push(meta);
703
- }
855
+ /** Deletes a batch of transactions, emitting callbacks individually for each. */
856
+ async #deleteTxsBatch(txHashes: string[]): Promise<void> {
857
+ for (const txHashStr of txHashes) {
858
+ await this.#deleteTx(txHashStr);
704
859
  }
705
- return result;
706
860
  }
707
861
 
708
- /** Finds tx hashes mined at or before the given block number */
709
- #findTxsMinedAtOrBefore(blockNumber: number): string[] {
710
- const result: string[] = [];
711
- for (const [txHashStr, meta] of this.#metadata) {
712
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number <= blockNumber) {
713
- result.push(txHashStr);
714
- }
862
+ /** Evicts transactions: records eviction metric with reason, caches hashes, then deletes. */
863
+ async #evictTxs(txHashes: string[], reason: string): Promise<void> {
864
+ if (txHashes.length === 0) {
865
+ return;
866
+ }
867
+ this.#instrumentation.recordEvictions(txHashes.length, reason);
868
+ for (const txHashStr of txHashes) {
869
+ this.#log.debug(`Evicting tx ${txHashStr}`, { txHash: txHashStr, reason });
870
+ this.#addToEvictedCache(txHashStr);
715
871
  }
716
- return result;
872
+ await this.#deleteTxsBatch(txHashes);
717
873
  }
718
874
 
719
- /** Finds protected tx hashes from slots earlier than the given slot number */
720
- #findExpiredProtectedTxs(slotNumber: SlotNumber): string[] {
721
- const result: string[] = [];
722
- for (const [txHashStr, protectedSlot] of this.#protectedTransactions) {
723
- if (protectedSlot < slotNumber) {
724
- result.push(txHashStr);
725
- }
875
+ /** Adds a tx hash to the bounded evicted cache, evicting the oldest entry if at capacity. */
876
+ #addToEvictedCache(txHashStr: string): void {
877
+ if (this.#evictedTxHashes.size >= this.#config.evictedTxCacheSize) {
878
+ // FIFO eviction: remove the first (oldest) entry
879
+ const oldest = this.#evictedTxHashes.values().next().value!;
880
+ this.#evictedTxHashes.delete(oldest);
726
881
  }
727
- return result;
882
+ this.#evictedTxHashes.add(txHashStr);
728
883
  }
729
884
 
730
- /** Filters out transactions that are currently protected */
731
- #filterUnprotected(txs: TxMetaData[]): TxMetaData[] {
732
- return txs.filter(meta => !this.#protectedTransactions.has(meta.txHash));
733
- }
885
+ // ============================================================================
886
+ // PRIVATE HELPERS - Validation & Conflict Resolution
887
+ // ============================================================================
734
888
 
735
- /** Filters to transactions that have metadata and are not mined */
736
- #filterRestorable(txHashes: string[]): TxMetaData[] {
737
- const result: TxMetaData[] = [];
738
- for (const txHashStr of txHashes) {
739
- const meta = this.#metadata.get(txHashStr);
740
- if (meta && meta.minedL2BlockId === undefined) {
741
- result.push(meta);
742
- }
889
+ /** Validates transaction metadata, returning true if valid */
890
+ async #validateMeta(meta: TxMetaData, validator?: TxValidator<TxMetaData>, context?: string): Promise<boolean> {
891
+ const txValidator = validator ?? (await this.#createTxValidator());
892
+ const result = await txValidator.validateTx(meta);
893
+ if (result.result !== 'valid') {
894
+ const contextStr = context ? ` ${context}` : '';
895
+ this.#log.info(`Tx ${meta.txHash}${contextStr} failed validation: ${result.reason?.join(', ')}`);
896
+ return false;
743
897
  }
744
- return result;
898
+ return true;
745
899
  }
746
900
 
747
- // --- Validation & Conflict Resolution Steps ---
748
-
749
- /** Validates transactions for pending pool, returning valid and invalid groups */
750
- async #validateForPending(txs: TxMetaData[]): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
901
+ /** Validates metadata directly */
902
+ async #revalidateMetadata(
903
+ metas: TxMetaData[],
904
+ context?: string,
905
+ ): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
751
906
  const valid: TxMetaData[] = [];
752
907
  const invalid: string[] = [];
753
-
754
- for (const meta of txs) {
755
- const buffer = await this.#txsDB.getAsync(meta.txHash);
756
- if (!buffer) {
757
- this.#log.warn(`Tx ${meta.txHash} not found in DB during validation`);
758
- invalid.push(meta.txHash);
759
- continue;
760
- }
761
-
762
- const tx = Tx.fromBuffer(buffer);
763
- const result = await this.#pendingTxValidator.validateTx(tx);
764
-
765
- if (result.result === 'valid') {
908
+ const validator = await this.#createTxValidator();
909
+ for (const meta of metas) {
910
+ if (await this.#validateMeta(meta, validator, context)) {
766
911
  valid.push(meta);
767
912
  } else {
768
- this.#log.info(`Tx ${meta.txHash} failed validation: ${result.reason?.join(', ')}`);
769
913
  invalid.push(meta.txHash);
770
914
  }
771
915
  }
772
-
773
916
  return { valid, invalid };
774
917
  }
775
918
 
@@ -785,8 +928,8 @@ export class TxPoolV2Impl {
785
928
  for (const meta of txs) {
786
929
  const conflict = checkNullifierConflict(
787
930
  meta,
788
- nullifier => this.#nullifierToTxHash.get(nullifier),
789
- txHash => this.#metadata.get(txHash),
931
+ nullifier => this.#indices.getTxHashByNullifier(nullifier),
932
+ txHash => this.#indices.getMetadata(txHash),
790
933
  );
791
934
  if (conflict.shouldIgnore) {
792
935
  // Lower priority than existing - don't add, mark for deletion
@@ -796,13 +939,13 @@ export class TxPoolV2Impl {
796
939
  toEvict.push(...conflict.txHashesToEvict);
797
940
  // Remove evicted from indices immediately for subsequent checks
798
941
  for (const evictHash of conflict.txHashesToEvict) {
799
- const evictMeta = this.#metadata.get(evictHash);
942
+ const evictMeta = this.#indices.getMetadata(evictHash);
800
943
  if (evictMeta) {
801
- this.#removeFromPendingIndices(evictMeta);
944
+ this.#indices.removeFromPendingIndices(evictMeta);
802
945
  }
803
946
  }
804
947
  // Add to pending indices immediately so subsequent txs in the batch see this tx
805
- this.#addToPendingIndices(meta);
948
+ this.#indices.addToPendingIndices(meta);
806
949
  added.push(meta);
807
950
  }
808
951
  }
@@ -810,43 +953,10 @@ export class TxPoolV2Impl {
810
953
  return { added, toEvict };
811
954
  }
812
955
 
813
- // --- State Transition Steps ---
814
-
815
- /** Clears the mined status from transactions, returning them for further processing */
816
- #unmineTxs(txs: TxMetaData[]): TxMetaData[] {
817
- for (const meta of txs) {
818
- meta.minedL2BlockId = undefined;
819
- }
820
- return txs;
821
- }
822
-
823
- /** Removes protection from tx hashes and clears them from the protected map */
824
- #clearProtection(txHashes: string[]): void {
825
- for (const txHashStr of txHashes) {
826
- this.#protectedTransactions.delete(txHashStr);
827
- }
828
- }
829
-
830
- // --- Batch Operation Steps ---
831
-
832
- /** Deletes a batch of transactions permanently */
833
- async #deleteTxsBatch(txHashes: string[]): Promise<void> {
834
- if (txHashes.length === 0) {
835
- return;
836
- }
837
-
838
- await this.#store.transactionAsync(async () => {
839
- for (const txHashStr of txHashes) {
840
- await this.#deleteTx(txHashStr);
841
- }
842
- });
843
-
844
- this.#callbacks.onTxsRemoved(txHashes);
845
- }
846
-
847
- // --- Block & Tx Info Steps ---
956
+ // ============================================================================
957
+ // PRIVATE HELPERS - Block & Hydration
958
+ // ============================================================================
848
959
 
849
- /** Builds a block ID from a block header */
850
960
  async #buildBlockId(block: BlockHeader): Promise<L2BlockId> {
851
961
  return {
852
962
  number: block.globalVariables.blockNumber,
@@ -866,50 +976,6 @@ export class TxPoolV2Impl {
866
976
  };
867
977
  }
868
978
 
869
- /** Marks a batch of transactions as mined */
870
- #markTxsAsMined(metas: TxMetaData[], blockId: L2BlockId): void {
871
- for (const meta of metas) {
872
- this.#markAsMined(meta, blockId);
873
- }
874
- }
875
-
876
- // --- Add Transaction Steps ---
877
-
878
- /** Persists a transaction to the database */
879
- async #persistTx(txHashStr: string, tx: Tx): Promise<void> {
880
- await this.#txsDB.set(txHashStr, tx.toBuffer());
881
- }
882
-
883
- /** Adds a new transaction as protected, returning its metadata */
884
- async #addNewProtectedTx(tx: Tx, slotNumber: SlotNumber): Promise<TxMetaData> {
885
- const txHashStr = tx.getTxHash().toString();
886
- const meta = await buildTxMetaData(tx);
887
-
888
- this.#protectedTransactions.set(txHashStr, slotNumber);
889
- await this.#persistTx(txHashStr, tx);
890
- this.#metadata.set(txHashStr, meta);
891
- // Don't add to pending indices since it's protected
892
-
893
- this.#log.verbose(`Added protected tx ${txHashStr} for slot ${slotNumber}`);
894
- return meta;
895
- }
896
-
897
- /** Adds a new transaction as mined, returning its metadata */
898
- async #addNewMinedTx(tx: Tx, blockId: L2BlockId): Promise<TxMetaData> {
899
- const txHashStr = tx.getTxHash().toString();
900
- const meta = await buildTxMetaData(tx);
901
- meta.minedL2BlockId = blockId;
902
-
903
- await this.#persistTx(txHashStr, tx);
904
- this.#metadata.set(txHashStr, meta);
905
- // Don't add to pending indices since it's mined
906
-
907
- this.#log.verbose(`Added mined tx ${txHashStr} from block ${blockId.number}`);
908
- return meta;
909
- }
910
-
911
- // --- Hydration Steps ---
912
-
913
979
  /** Loads all transactions from the database, returning loaded txs and deserialization errors */
914
980
  async #loadAllTxsFromDb(): Promise<{
915
981
  loaded: { tx: Tx; meta: TxMetaData }[];
@@ -919,9 +985,15 @@ export class TxPoolV2Impl {
919
985
  const errors: string[] = [];
920
986
 
921
987
  for await (const [txHashStr, buffer] of this.#txsDB.entriesAsync()) {
988
+ // Skip soft-deleted transactions - they stay in DB but not in indices
989
+ if (this.#deletedPool.isSoftDeleted(txHashStr)) {
990
+ continue;
991
+ }
992
+
922
993
  try {
923
994
  const tx = Tx.fromBuffer(buffer);
924
- const meta = await buildTxMetaData(tx);
995
+ const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
996
+ const meta = await buildTxMetaData(tx, allowedSetupCalls);
925
997
  loaded.push({ tx, meta });
926
998
  } catch (err) {
927
999
  this.#log.warn(`Failed to deserialize tx ${txHashStr}, deleting`, { err });
@@ -949,50 +1021,6 @@ export class TxPoolV2Impl {
949
1021
  }
950
1022
  }
951
1023
 
952
- /** Partitions transactions by mined status */
953
- #partitionByMinedStatus(txs: { tx: Tx; meta: TxMetaData }[]): {
954
- mined: TxMetaData[];
955
- nonMined: { tx: Tx; meta: TxMetaData }[];
956
- } {
957
- const mined: TxMetaData[] = [];
958
- const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
959
-
960
- for (const entry of txs) {
961
- if (entry.meta.minedL2BlockId !== undefined) {
962
- mined.push(entry.meta);
963
- } else {
964
- nonMined.push(entry);
965
- }
966
- }
967
-
968
- return { mined, nonMined };
969
- }
970
-
971
- /** Validates non-mined transactions, returning valid metadata and invalid hashes */
972
- async #validateNonMinedTxs(txs: { tx: Tx; meta: TxMetaData }[]): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
973
- const valid: TxMetaData[] = [];
974
- const invalid: string[] = [];
975
-
976
- for (const { tx, meta } of txs) {
977
- const result = await this.#pendingTxValidator.validateTx(tx);
978
- if (result.result === 'valid') {
979
- valid.push(meta);
980
- } else {
981
- this.#log.info(`Removing invalid tx ${meta.txHash} on startup: ${result.reason?.join(', ')}`);
982
- invalid.push(meta.txHash);
983
- }
984
- }
985
-
986
- return { valid, invalid };
987
- }
988
-
989
- /** Populates metadata index for mined transactions */
990
- #populateMinedIndices(metas: TxMetaData[]): void {
991
- for (const meta of metas) {
992
- this.#metadata.set(meta.txHash, meta);
993
- }
994
- }
995
-
996
1024
  /**
997
1025
  * Rebuilds the pending pool by processing each tx through pre-add rules.
998
1026
  * Starts with an empty pending pool and adds txs one by one, resolving conflicts.
@@ -1010,24 +1038,26 @@ export class TxPoolV2Impl {
1010
1038
  if (preAddResult.shouldIgnore) {
1011
1039
  // Transaction rejected - mark for deletion from DB
1012
1040
  rejected.push(meta.txHash);
1013
- this.#log.debug(`Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason}`);
1041
+ this.#log.debug(
1042
+ `Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason?.message ?? 'unknown reason'}`,
1043
+ );
1014
1044
  continue;
1015
1045
  }
1016
1046
 
1017
1047
  // Evict any conflicting txs identified by pre-add rules
1018
1048
  for (const evictHashStr of preAddResult.txHashesToEvict) {
1019
- const evictMeta = this.#metadata.get(evictHashStr);
1049
+ const evictMeta = this.#indices.getMetadata(evictHashStr);
1020
1050
  if (evictMeta) {
1021
- this.#removeFromPendingIndices(evictMeta);
1022
- this.#metadata.delete(evictHashStr);
1051
+ this.#indices.removeFromPendingIndices(evictMeta);
1052
+ this.#indices.remove(evictHashStr);
1023
1053
  rejected.push(evictHashStr);
1024
1054
  accepted.delete(evictHashStr);
1025
1055
  this.#log.debug(`Evicted tx ${evictHashStr} during rebuild due to conflict with ${meta.txHash}`);
1026
1056
  }
1027
1057
  }
1028
1058
 
1029
- // Add to metadata and pending indices
1030
- this.#addToIndices(meta);
1059
+ // Add to indices
1060
+ this.#indices.addPending(meta);
1031
1061
  accepted.add(meta.txHash);
1032
1062
  }
1033
1063
 
@@ -1035,207 +1065,32 @@ export class TxPoolV2Impl {
1035
1065
  return { accepted: [...accepted], rejected };
1036
1066
  }
1037
1067
 
1038
- // --- Add Pending Tx Steps ---
1039
-
1040
- /** Checks if a tx is a duplicate (already in pool) */
1041
- #isDuplicateTx(txHashStr: string): boolean {
1042
- return this.#metadata.has(txHashStr);
1043
- }
1044
-
1045
- /** Adds a new pending tx to the pool, returning its metadata */
1046
- async #addNewPendingTx(tx: Tx): Promise<TxMetaData> {
1047
- const txHashStr = tx.getTxHash().toString();
1048
- const meta = await buildTxMetaData(tx);
1049
-
1050
- await this.#persistTx(txHashStr, tx);
1051
- this.#addToIndices(meta);
1052
-
1053
- this.#log.verbose(`Added tx ${txHashStr} to pool`, {
1054
- eventName: 'tx-added-to-pool',
1055
- state: this.#getTxState(meta),
1056
- });
1057
-
1058
- return meta;
1059
- }
1060
-
1061
1068
  // ============================================================================
1062
- // HELPER FUNCTIONS - Index Management
1069
+ // PRIVATE HELPERS - Pool Access Adapters
1063
1070
  // ============================================================================
1064
1071
 
1065
- #addToIndices(meta: TxMetaData): void {
1066
- this.#metadata.set(meta.txHash, meta);
1067
-
1068
- if (this.#getTxState(meta) === 'pending') {
1069
- this.#addToPendingIndices(meta);
1070
- }
1071
- // Protected and mined txs don't go into pending indices
1072
- }
1073
-
1074
- #addToPendingIndices(meta: TxMetaData): void {
1075
- // Add to nullifier index
1076
- for (const nullifier of meta.nullifiers) {
1077
- this.#nullifierToTxHash.set(nullifier, meta.txHash);
1078
- }
1079
-
1080
- // Add to fee payer index
1081
- let feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
1082
- if (!feePayerSet) {
1083
- feePayerSet = new Set();
1084
- this.#feePayerToTxHashes.set(meta.feePayer, feePayerSet);
1085
- }
1086
- feePayerSet.add(meta.txHash);
1087
-
1088
- // Add to priority bucket
1089
- let prioritySet = this.#pendingByPriority.get(meta.priorityFee);
1090
- if (!prioritySet) {
1091
- prioritySet = new Set();
1092
- this.#pendingByPriority.set(meta.priorityFee, prioritySet);
1093
- }
1094
- prioritySet.add(meta.txHash);
1095
- }
1096
-
1097
- #removeFromPendingIndices(meta: TxMetaData): void {
1098
- // Remove from nullifier index
1099
- for (const nullifier of meta.nullifiers) {
1100
- this.#nullifierToTxHash.delete(nullifier);
1101
- }
1102
-
1103
- // Remove from fee payer index
1104
- const feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
1105
- if (feePayerSet) {
1106
- feePayerSet.delete(meta.txHash);
1107
- if (feePayerSet.size === 0) {
1108
- this.#feePayerToTxHashes.delete(meta.feePayer);
1109
- }
1110
- }
1111
-
1112
- // Remove from priority map
1113
- const hashSet = this.#pendingByPriority.get(meta.priorityFee);
1114
- if (hashSet) {
1115
- hashSet.delete(meta.txHash);
1116
- if (hashSet.size === 0) {
1117
- this.#pendingByPriority.delete(meta.priorityFee);
1118
- }
1119
- }
1120
- }
1121
-
1122
- #updateProtection(txHashStr: string, slotNumber: SlotNumber): void {
1123
- const currentSlot = this.#protectedTransactions.get(txHashStr);
1124
-
1125
- // Only update if not already protected at an equal or later slot
1126
- if (currentSlot !== undefined && currentSlot >= slotNumber) {
1127
- return;
1128
- }
1129
-
1130
- // Remove from pending indices if transitioning from pending to protected
1131
- if (currentSlot === undefined) {
1132
- const meta = this.#metadata.get(txHashStr);
1133
- if (meta) {
1134
- this.#removeFromPendingIndices(meta);
1135
- }
1136
- }
1137
-
1138
- this.#protectedTransactions.set(txHashStr, slotNumber);
1139
- }
1140
-
1141
- #markAsMined(meta: TxMetaData, blockId: L2BlockId): void {
1142
- meta.minedL2BlockId = blockId;
1143
- // Safe to call unconditionally - removeFromPendingIndices is idempotent
1144
- this.#removeFromPendingIndices(meta);
1145
- }
1146
-
1147
- async #deleteTx(txHashStr: string): Promise<void> {
1148
- const meta = this.#metadata.get(txHashStr);
1149
- if (!meta) {
1150
- return;
1151
- }
1152
-
1153
- // Remove from all indices
1154
- this.#metadata.delete(txHashStr);
1155
- this.#protectedTransactions.delete(txHashStr);
1156
- this.#removeFromPendingIndices(meta);
1157
-
1158
- // Remove from persistence
1159
- await this.#txsDB.delete(txHashStr);
1160
- }
1161
-
1162
- // ============================================================================
1163
- // HELPER FUNCTIONS - Adapters
1164
- // ============================================================================
1165
-
1166
- /** Gets all pending transactions for a given fee payer. */
1167
- #getFeePayerPendingTxs(feePayer: string): TxMetaData[] {
1168
- const txHashes = this.#feePayerToTxHashes.get(feePayer);
1169
- if (!txHashes) {
1170
- return [];
1171
- }
1172
- const result: TxMetaData[] = [];
1173
- for (const txHashStr of txHashes) {
1174
- const meta = this.#metadata.get(txHashStr);
1175
- if (meta && this.#getTxState(meta) === 'pending') {
1176
- result.push(meta);
1177
- }
1178
- }
1179
- return result;
1180
- }
1181
-
1182
- /**
1183
- * Creates a PoolOperations adapter for use with the eviction manager.
1184
- */
1185
1072
  #createPoolOperations(): PoolOperations {
1186
1073
  return {
1187
- getPendingTxs: (): TxMetaData[] => {
1188
- const result: TxMetaData[] = [];
1189
- for (const hashSet of this.#pendingByPriority.values()) {
1190
- for (const txHashStr of hashSet) {
1191
- const meta = this.#metadata.get(txHashStr);
1192
- if (meta) {
1193
- result.push(meta);
1194
- }
1195
- }
1196
- }
1197
- return result;
1198
- },
1199
- getPendingFeePayers: (): string[] => {
1200
- return Array.from(this.#feePayerToTxHashes.keys());
1201
- },
1202
- getFeePayerPendingTxs: (feePayer: string): TxMetaData[] => {
1203
- return this.#getFeePayerPendingTxs(feePayer);
1204
- },
1205
- getPendingTxCount: (): number => {
1206
- return this.getPendingTxCount();
1207
- },
1208
- getLowestPriorityPending: (limit: number): string[] => {
1209
- return this.getLowestPriorityPending(limit).map(h => h.toString());
1210
- },
1211
- deleteTxs: async (txHashes: string[]): Promise<void> => {
1212
- await this.#store.transactionAsync(async () => {
1213
- for (const txHashStr of txHashes) {
1214
- await this.#deleteTx(txHashStr);
1215
- }
1216
- });
1217
- this.#callbacks.onTxsRemoved(txHashes);
1218
- },
1074
+ getPendingTxs: () => this.#indices.getPendingTxs(),
1075
+ getPendingFeePayers: () => this.#indices.getPendingFeePayers(),
1076
+ getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
1077
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
1078
+ getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
1079
+ deleteTxs: (txHashes: string[], reason?: string) => this.#evictTxs(txHashes, reason ?? 'unknown'),
1219
1080
  };
1220
1081
  }
1221
1082
 
1222
- /**
1223
- * Creates a PreAddPoolAccess adapter for use with pre-add eviction rules.
1224
- * All methods work with strings and TxMetaData for efficiency.
1225
- */
1226
1083
  #createPreAddPoolAccess(): PreAddPoolAccess {
1227
1084
  return {
1228
- getMetadata: (txHashStr: string): TxMetaData | undefined => {
1229
- const meta = this.#metadata.get(txHashStr);
1230
- if (!meta || this.#getTxState(meta) !== 'pending') {
1085
+ getMetadata: (txHashStr: string) => {
1086
+ const meta = this.#indices.getMetadata(txHashStr);
1087
+ if (!meta || this.#indices.getTxState(meta) !== 'pending') {
1231
1088
  return undefined;
1232
1089
  }
1233
1090
  return meta;
1234
1091
  },
1235
- getTxHashByNullifier: (nullifier: string): string | undefined => {
1236
- return this.#nullifierToTxHash.get(nullifier);
1237
- },
1238
- getFeePayerBalance: async (feePayer: string): Promise<bigint> => {
1092
+ getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
1093
+ getFeePayerBalance: async (feePayer: string) => {
1239
1094
  const db = this.#worldStateSynchronizer.getCommitted();
1240
1095
  const publicStateSource = new DatabasePublicStateSource(db);
1241
1096
  const balance = await publicStateSource.storageRead(
@@ -1244,22 +1099,9 @@ export class TxPoolV2Impl {
1244
1099
  );
1245
1100
  return balance.toBigInt();
1246
1101
  },
1247
- getFeePayerPendingTxs: (feePayer: string): TxMetaData[] => {
1248
- return this.#getFeePayerPendingTxs(feePayer);
1249
- },
1250
- getPendingTxCount: (): number => {
1251
- return this.getPendingTxCount();
1252
- },
1253
- getLowestPriorityPendingTx: (): TxMetaData | undefined => {
1254
- // Iterate in ascending order to find the lowest priority
1255
- for (const txHashStr of this.#iteratePendingByPriority('asc')) {
1256
- const meta = this.#metadata.get(txHashStr);
1257
- if (meta) {
1258
- return meta;
1259
- }
1260
- }
1261
- return undefined;
1262
- },
1102
+ getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
1103
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
1104
+ getLowestPriorityPendingTx: () => this.#indices.getLowestPriorityPendingTx(),
1263
1105
  };
1264
1106
  }
1265
1107
  }