@aztec/p2p 0.0.1-commit.43c09e3f → 0.0.1-commit.4d3c002

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