@aztec/p2p 5.0.0-private.20260319 → 5.0.0-rc.2

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 (411) hide show
  1. package/README.md +4 -1
  2. package/dest/bootstrap/bootstrap.d.ts +1 -1
  3. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  4. package/dest/bootstrap/bootstrap.js +22 -6
  5. package/dest/client/factory.d.ts +4 -3
  6. package/dest/client/factory.d.ts.map +1 -1
  7. package/dest/client/factory.js +33 -24
  8. package/dest/client/interface.d.ts +43 -11
  9. package/dest/client/interface.d.ts.map +1 -1
  10. package/dest/client/p2p_client.d.ts +33 -19
  11. package/dest/client/p2p_client.d.ts.map +1 -1
  12. package/dest/client/p2p_client.js +100 -44
  13. package/dest/config.d.ts +137 -107
  14. package/dest/config.d.ts.map +1 -1
  15. package/dest/config.js +64 -23
  16. package/dest/errors/p2p-service.error.d.ts +9 -0
  17. package/dest/errors/p2p-service.error.d.ts.map +1 -0
  18. package/dest/errors/p2p-service.error.js +10 -0
  19. package/dest/errors/reqresp.error.d.ts +1 -20
  20. package/dest/errors/reqresp.error.d.ts.map +1 -1
  21. package/dest/errors/reqresp.error.js +0 -21
  22. package/dest/index.d.ts +1 -2
  23. package/dest/index.d.ts.map +1 -1
  24. package/dest/index.js +0 -1
  25. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +98 -56
  26. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  27. package/dest/mem_pools/attestation_pool/attestation_pool.js +265 -196
  28. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  29. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  30. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +175 -59
  31. package/dest/mem_pools/attestation_pool/mocks.d.ts +1 -1
  32. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  33. package/dest/mem_pools/attestation_pool/mocks.js +6 -4
  34. package/dest/mem_pools/index.d.ts +1 -2
  35. package/dest/mem_pools/index.d.ts.map +1 -1
  36. package/dest/mem_pools/instrumentation.d.ts +1 -1
  37. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  38. package/dest/mem_pools/instrumentation.js +17 -1
  39. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +1 -5
  40. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -1
  41. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +1 -7
  42. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -3
  43. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  44. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +6 -6
  45. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
  46. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +2 -1
  47. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool_v2/eviction/index.js +1 -0
  49. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +16 -0
  50. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -0
  51. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +62 -0
  52. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
  53. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +22 -5
  54. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  55. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +5 -2
  56. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  57. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +13 -6
  58. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +7 -1
  59. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  60. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +10 -0
  61. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +8 -3
  62. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  63. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +7 -4
  64. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +8 -3
  65. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  66. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +153 -111
  67. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +10 -3
  68. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  69. package/dest/msg_validators/attestation_validator/attestation_validator.js +38 -16
  70. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +7 -3
  71. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  72. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +4 -5
  73. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +8 -3
  74. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  75. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +2 -2
  76. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +8 -3
  77. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  78. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +8 -2
  79. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +25 -3
  80. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  81. package/dest/msg_validators/proposal_validator/proposal_validator.js +77 -14
  82. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +1 -1
  83. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  84. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +8 -15
  85. package/dest/msg_validators/tx_validator/allowed_public_setup.js +3 -3
  86. package/dest/msg_validators/tx_validator/archive_cache.js +1 -1
  87. package/dest/msg_validators/tx_validator/cached_tx_validator.d.ts +15 -0
  88. package/dest/msg_validators/tx_validator/cached_tx_validator.d.ts.map +1 -0
  89. package/dest/msg_validators/tx_validator/cached_tx_validator.js +19 -0
  90. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  91. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  92. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  93. package/dest/msg_validators/tx_validator/data_validator.d.ts +2 -1
  94. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  95. package/dest/msg_validators/tx_validator/data_validator.js +36 -2
  96. package/dest/msg_validators/tx_validator/factory.d.ts +14 -15
  97. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  98. package/dest/msg_validators/tx_validator/factory.js +36 -17
  99. package/dest/msg_validators/tx_validator/gas_validator.d.ts +47 -10
  100. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  101. package/dest/msg_validators/tx_validator/gas_validator.js +65 -48
  102. package/dest/msg_validators/tx_validator/index.d.ts +3 -1
  103. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  104. package/dest/msg_validators/tx_validator/index.js +2 -0
  105. package/dest/msg_validators/tx_validator/phases_validator.js +3 -3
  106. package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts +2 -1
  107. package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -1
  108. package/dest/msg_validators/tx_validator/tx_proof_validator.js +2 -0
  109. package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts +48 -0
  110. package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts.map +1 -0
  111. package/dest/msg_validators/tx_validator/tx_validation_cache.js +68 -0
  112. package/dest/services/data_store.d.ts +1 -1
  113. package/dest/services/data_store.d.ts.map +1 -1
  114. package/dest/services/data_store.js +5 -5
  115. package/dest/services/discv5/discV5_service.d.ts +13 -2
  116. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  117. package/dest/services/discv5/discV5_service.js +119 -23
  118. package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
  119. package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
  120. package/dest/services/discv5/persisted_enr_store.js +145 -0
  121. package/dest/services/dummy_service.d.ts +15 -15
  122. package/dest/services/dummy_service.d.ts.map +1 -1
  123. package/dest/services/dummy_service.js +15 -16
  124. package/dest/services/encoding.d.ts +8 -8
  125. package/dest/services/encoding.d.ts.map +1 -1
  126. package/dest/services/encoding.js +11 -15
  127. package/dest/services/gossipsub/topic_score_params.d.ts +6 -12
  128. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  129. package/dest/services/gossipsub/topic_score_params.js +4 -14
  130. package/dest/services/libp2p/instrumentation.d.ts +3 -1
  131. package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
  132. package/dest/services/libp2p/instrumentation.js +14 -0
  133. package/dest/services/libp2p/libp2p_service.d.ts +44 -39
  134. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  135. package/dest/services/libp2p/libp2p_service.js +383 -278
  136. package/dest/services/peer-manager/peer_manager.d.ts +20 -3
  137. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  138. package/dest/services/peer-manager/peer_manager.js +79 -23
  139. package/dest/services/peer-manager/peer_scoring.d.ts +34 -2
  140. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  141. package/dest/services/peer-manager/peer_scoring.js +102 -13
  142. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -8
  143. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  144. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +38 -44
  145. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +8 -5
  146. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  147. package/dest/services/reqresp/batch-tx-requester/tx_validator.d.ts +5 -14
  148. package/dest/services/reqresp/batch-tx-requester/tx_validator.d.ts.map +1 -1
  149. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +6 -20
  150. package/dest/services/reqresp/config.d.ts +3 -3
  151. package/dest/services/reqresp/config.d.ts.map +1 -1
  152. package/dest/services/reqresp/interface.d.ts +16 -18
  153. package/dest/services/reqresp/interface.d.ts.map +1 -1
  154. package/dest/services/reqresp/interface.js +10 -20
  155. package/dest/services/reqresp/metrics.d.ts +1 -1
  156. package/dest/services/reqresp/metrics.d.ts.map +1 -1
  157. package/dest/services/reqresp/metrics.js +0 -1
  158. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +4 -3
  159. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
  160. package/dest/services/reqresp/protocols/block_txs/bitvector.js +3 -2
  161. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +1 -1
  162. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  163. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +31 -28
  164. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +12 -7
  165. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  166. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +42 -24
  167. package/dest/services/reqresp/protocols/index.d.ts +1 -2
  168. package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
  169. package/dest/services/reqresp/protocols/index.js +0 -1
  170. package/dest/services/reqresp/protocols/tx.d.ts +1 -1
  171. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  172. package/dest/services/reqresp/protocols/tx.js +1 -3
  173. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
  174. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  175. package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
  176. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +1 -1
  177. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
  178. package/dest/services/reqresp/rate-limiter/rate_limits.js +0 -10
  179. package/dest/services/reqresp/reqresp.d.ts +7 -29
  180. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  181. package/dest/services/reqresp/reqresp.js +26 -207
  182. package/dest/services/service.d.ts +25 -12
  183. package/dest/services/service.d.ts.map +1 -1
  184. package/dest/services/tx_collection/config.d.ts +2 -23
  185. package/dest/services/tx_collection/config.d.ts.map +1 -1
  186. package/dest/services/tx_collection/config.js +2 -55
  187. package/dest/services/tx_collection/file_store_tx_collection.d.ts +12 -28
  188. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -1
  189. package/dest/services/tx_collection/file_store_tx_collection.js +43 -83
  190. package/dest/services/tx_collection/file_store_tx_source.d.ts +5 -4
  191. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  192. package/dest/services/tx_collection/file_store_tx_source.js +39 -29
  193. package/dest/services/tx_collection/index.d.ts +2 -3
  194. package/dest/services/tx_collection/index.d.ts.map +1 -1
  195. package/dest/services/tx_collection/index.js +0 -1
  196. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  197. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  198. package/dest/services/tx_collection/instrumentation.js +0 -2
  199. package/dest/services/tx_collection/tx_collection.d.ts +35 -51
  200. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  201. package/dest/services/tx_collection/tx_collection.js +275 -119
  202. package/dest/services/tx_collection/tx_collection_sink.d.ts +1 -1
  203. package/dest/services/tx_collection/tx_collection_sink.js +2 -2
  204. package/dest/services/tx_collection/tx_source.d.ts +6 -5
  205. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  206. package/dest/services/tx_collection/tx_source.js +13 -8
  207. package/dest/services/tx_file_store/tx_file_store.d.ts +1 -3
  208. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  209. package/dest/services/tx_file_store/tx_file_store.js +4 -14
  210. package/dest/services/tx_provider.d.ts +3 -1
  211. package/dest/services/tx_provider.d.ts.map +1 -1
  212. package/dest/services/tx_provider.js +3 -0
  213. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  214. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  215. package/dest/test-helpers/make-test-p2p-clients.js +5 -2
  216. package/dest/test-helpers/mock-pubsub.d.ts +23 -9
  217. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  218. package/dest/test-helpers/mock-pubsub.js +44 -44
  219. package/dest/test-helpers/reqresp-nodes.d.ts +4 -5
  220. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  221. package/dest/test-helpers/reqresp-nodes.js +16 -18
  222. package/dest/test-helpers/test_tx_provider.d.ts +3 -1
  223. package/dest/test-helpers/test_tx_provider.d.ts.map +1 -1
  224. package/dest/test-helpers/test_tx_provider.js +3 -0
  225. package/dest/test-helpers/testbench-utils.d.ts +14 -14
  226. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  227. package/dest/test-helpers/testbench-utils.js +25 -13
  228. package/dest/testbench/p2p_client_testbench_worker.d.ts +3 -5
  229. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  230. package/dest/testbench/p2p_client_testbench_worker.js +102 -41
  231. package/dest/testbench/worker_client_manager.d.ts +10 -6
  232. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  233. package/dest/testbench/worker_client_manager.js +51 -9
  234. package/dest/util.d.ts +11 -6
  235. package/dest/util.d.ts.map +1 -1
  236. package/dest/util.js +35 -14
  237. package/dest/versioning.d.ts +3 -6
  238. package/dest/versioning.d.ts.map +1 -1
  239. package/dest/versioning.js +3 -24
  240. package/package.json +16 -16
  241. package/src/bootstrap/bootstrap.ts +17 -3
  242. package/src/client/factory.ts +52 -24
  243. package/src/client/interface.ts +38 -17
  244. package/src/client/p2p_client.ts +118 -76
  245. package/src/client/test/{tx_proposal_collector/README.md → p2p_client.batch_tx_requester.bench.README.md} +23 -53
  246. package/src/config.ts +130 -31
  247. package/src/errors/p2p-service.error.ts +11 -0
  248. package/src/errors/reqresp.error.ts +0 -25
  249. package/src/index.ts +0 -1
  250. package/src/mem_pools/attestation_pool/attestation_pool.ts +319 -238
  251. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +198 -62
  252. package/src/mem_pools/attestation_pool/mocks.ts +13 -8
  253. package/src/mem_pools/index.ts +0 -3
  254. package/src/mem_pools/instrumentation.ts +5 -1
  255. package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +1 -9
  256. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +7 -7
  257. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
  258. package/src/mem_pools/tx_pool_v2/eviction/index.ts +1 -0
  259. package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +65 -0
  260. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +3 -3
  261. package/src/mem_pools/tx_pool_v2/interfaces.ts +18 -4
  262. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +20 -8
  263. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +12 -0
  264. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +8 -4
  265. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +163 -117
  266. package/src/msg_validators/attestation_validator/attestation_validator.ts +46 -15
  267. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +15 -7
  268. package/src/msg_validators/proposal_validator/README.md +1 -1
  269. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +15 -3
  270. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +26 -3
  271. package/src/msg_validators/proposal_validator/proposal_validator.ts +89 -11
  272. package/src/msg_validators/tx_validator/README.md +13 -3
  273. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -12
  274. package/src/msg_validators/tx_validator/allowed_public_setup.ts +3 -3
  275. package/src/msg_validators/tx_validator/archive_cache.ts +1 -1
  276. package/src/msg_validators/tx_validator/cached_tx_validator.ts +31 -0
  277. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  278. package/src/msg_validators/tx_validator/data_validator.ts +44 -1
  279. package/src/msg_validators/tx_validator/factory.ts +44 -20
  280. package/src/msg_validators/tx_validator/gas_validator.ts +99 -54
  281. package/src/msg_validators/tx_validator/index.ts +2 -0
  282. package/src/msg_validators/tx_validator/phases_validator.ts +3 -3
  283. package/src/msg_validators/tx_validator/tx_proof_validator.ts +2 -0
  284. package/src/msg_validators/tx_validator/tx_validation_cache.ts +101 -0
  285. package/src/services/data_store.ts +5 -13
  286. package/src/services/discv5/discV5_service.ts +126 -14
  287. package/src/services/discv5/persisted_enr_store.ts +158 -0
  288. package/src/services/dummy_service.ts +20 -39
  289. package/src/services/encoding.ts +11 -21
  290. package/src/services/gossipsub/README.md +33 -11
  291. package/src/services/gossipsub/topic_score_params.ts +9 -18
  292. package/src/services/libp2p/instrumentation.ts +14 -0
  293. package/src/services/libp2p/libp2p_service.ts +436 -311
  294. package/src/services/peer-manager/peer_manager.ts +86 -22
  295. package/src/services/peer-manager/peer_scoring.ts +102 -8
  296. package/src/services/reqresp/README.md +0 -14
  297. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +42 -52
  298. package/src/services/reqresp/batch-tx-requester/interface.ts +11 -4
  299. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +12 -25
  300. package/src/services/reqresp/config.ts +2 -2
  301. package/src/services/reqresp/interface.ts +21 -47
  302. package/src/services/reqresp/metrics.ts +0 -1
  303. package/src/services/reqresp/protocols/block_txs/bitvector.ts +3 -2
  304. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +34 -30
  305. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +47 -20
  306. package/src/services/reqresp/protocols/index.ts +0 -1
  307. package/src/services/reqresp/protocols/tx.ts +1 -3
  308. package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
  309. package/src/services/reqresp/rate-limiter/rate_limits.ts +0 -10
  310. package/src/services/reqresp/reqresp.ts +29 -252
  311. package/src/services/service.ts +31 -28
  312. package/src/services/tx_collection/config.ts +3 -80
  313. package/src/services/tx_collection/file_store_tx_collection.ts +54 -103
  314. package/src/services/tx_collection/file_store_tx_source.ts +43 -31
  315. package/src/services/tx_collection/index.ts +1 -6
  316. package/src/services/tx_collection/instrumentation.ts +1 -7
  317. package/src/services/tx_collection/tx_collection.ts +330 -173
  318. package/src/services/tx_collection/tx_collection_sink.ts +2 -2
  319. package/src/services/tx_collection/tx_source.ts +10 -8
  320. package/src/services/tx_file_store/tx_file_store.ts +5 -17
  321. package/src/services/tx_provider.ts +5 -0
  322. package/src/test-helpers/make-test-p2p-clients.ts +3 -0
  323. package/src/test-helpers/mock-pubsub.ts +46 -60
  324. package/src/test-helpers/reqresp-nodes.ts +12 -22
  325. package/src/test-helpers/test_tx_provider.ts +5 -0
  326. package/src/test-helpers/testbench-utils.ts +31 -26
  327. package/src/testbench/p2p_client_testbench_worker.ts +110 -55
  328. package/src/testbench/worker_client_manager.ts +59 -20
  329. package/src/util.ts +32 -17
  330. package/src/versioning.ts +3 -33
  331. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.d.ts +0 -2
  332. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.d.ts.map +0 -1
  333. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +0 -305
  334. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.d.ts +0 -73
  335. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.d.ts.map +0 -1
  336. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.js +0 -8
  337. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +0 -125
  338. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +0 -1
  339. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +0 -596
  340. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +0 -32
  341. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +0 -1
  342. package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +0 -112
  343. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +0 -157
  344. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +0 -1
  345. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +0 -52
  346. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +0 -16
  347. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +0 -1
  348. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +0 -123
  349. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +0 -17
  350. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +0 -1
  351. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +0 -84
  352. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +0 -19
  353. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +0 -1
  354. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.js +0 -78
  355. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +0 -26
  356. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +0 -1
  357. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.js +0 -84
  358. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts +0 -25
  359. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts.map +0 -1
  360. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.js +0 -57
  361. package/dest/mem_pools/tx_pool/index.d.ts +0 -3
  362. package/dest/mem_pools/tx_pool/index.d.ts.map +0 -1
  363. package/dest/mem_pools/tx_pool/index.js +0 -2
  364. package/dest/mem_pools/tx_pool/priority.d.ts +0 -12
  365. package/dest/mem_pools/tx_pool/priority.d.ts.map +0 -1
  366. package/dest/mem_pools/tx_pool/priority.js +0 -15
  367. package/dest/mem_pools/tx_pool/tx_pool.d.ts +0 -127
  368. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +0 -1
  369. package/dest/mem_pools/tx_pool/tx_pool.js +0 -3
  370. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +0 -7
  371. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +0 -1
  372. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +0 -402
  373. package/dest/msg_validators/clock_tolerance.d.ts +0 -21
  374. package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
  375. package/dest/msg_validators/clock_tolerance.js +0 -38
  376. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +0 -64
  377. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +0 -1
  378. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +0 -151
  379. package/dest/services/reqresp/protocols/block.d.ts +0 -9
  380. package/dest/services/reqresp/protocols/block.d.ts.map +0 -1
  381. package/dest/services/reqresp/protocols/block.js +0 -32
  382. package/dest/services/tx_collection/fast_tx_collection.d.ts +0 -51
  383. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +0 -1
  384. package/dest/services/tx_collection/fast_tx_collection.js +0 -311
  385. package/dest/services/tx_collection/proposal_tx_collector.d.ts +0 -48
  386. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +0 -1
  387. package/dest/services/tx_collection/proposal_tx_collector.js +0 -50
  388. package/dest/services/tx_collection/slow_tx_collection.d.ts +0 -57
  389. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +0 -1
  390. package/dest/services/tx_collection/slow_tx_collection.js +0 -211
  391. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +0 -344
  392. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.ts +0 -43
  393. package/src/mem_pools/tx_pool/README.md +0 -270
  394. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +0 -746
  395. package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +0 -132
  396. package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +0 -208
  397. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +0 -163
  398. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +0 -104
  399. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.ts +0 -93
  400. package/src/mem_pools/tx_pool/eviction/low_priority_eviction_rule.ts +0 -106
  401. package/src/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.ts +0 -75
  402. package/src/mem_pools/tx_pool/index.ts +0 -2
  403. package/src/mem_pools/tx_pool/priority.ts +0 -20
  404. package/src/mem_pools/tx_pool/tx_pool.ts +0 -141
  405. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +0 -321
  406. package/src/msg_validators/clock_tolerance.ts +0 -52
  407. package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +0 -161
  408. package/src/services/reqresp/protocols/block.ts +0 -37
  409. package/src/services/tx_collection/fast_tx_collection.ts +0 -361
  410. package/src/services/tx_collection/proposal_tx_collector.ts +0 -108
  411. package/src/services/tx_collection/slow_tx_collection.ts +0 -266
@@ -1,5 +1,4 @@
1
- import { IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
2
- import { Fr } from '@aztec/foundation/curves/bn254';
1
+ import type { BlockProposalHash, CheckpointProposalHash, SlotNumber } from '@aztec/foundation/branded-types';
3
2
  import { toArray } from '@aztec/foundation/iterable';
4
3
  import { createLogger } from '@aztec/foundation/log';
5
4
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store';
@@ -15,17 +14,22 @@ import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instru
15
14
 
16
15
  /** Result of trying to add an item (proposal or attestation) to the pool */
17
16
  export type TryAddResult = {
18
- /** Whether the item was added */
17
+ /** Whether the item was accepted into pool state. False when it already existed, was invalid, or hit a cap. */
19
18
  added: boolean;
20
- /** Whether the exact item already existed */
19
+ /** Whether the exact signed payload (matched by payload hash) already existed in the pool. */
21
20
  alreadyExists: boolean;
22
- /** Count of items for the position. Meaning varies by method:
23
- * - tryAddBlockProposal: proposals at (slot, indexWithinCheckpoint)
24
- * - tryAddCheckpointProposal: proposals at slot
25
- * - tryAddCheckpointAttestation: attestations by this signer for this slot */
21
+ /** Number of distinct signed-payload hashes seen for the position. Meaning varies by method:
22
+ * - tryAddBlockProposal: distinct payload hashes at (slot, indexWithinCheckpoint)
23
+ * - tryAddCheckpointProposal: distinct payload hashes at slot
24
+ * - tryAddCheckpointAttestation: distinct payload hashes by this signer for this slot */
26
25
  count: number;
27
26
  };
28
27
 
28
+ export type ProposalsForSlot = {
29
+ blockProposals: BlockProposal[];
30
+ checkpointProposals: CheckpointProposalCore[];
31
+ };
32
+
29
33
  export const MAX_CHECKPOINT_PROPOSALS_PER_SLOT = 2;
30
34
  export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
31
35
  /** Maximum attestations a single signer can make per slot before being rejected. */
@@ -35,9 +39,11 @@ export const MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER = 2;
35
39
  export type AttestationPoolApi = Pick<
36
40
  AttestationPool,
37
41
  | 'tryAddBlockProposal'
38
- | 'getBlockProposal'
42
+ | 'getBlockProposalByArchive'
43
+ | 'getProposalsForSlot'
39
44
  | 'tryAddCheckpointProposal'
40
45
  | 'getCheckpointProposal'
46
+ | 'hasCheckpointProposalForSlot'
41
47
  | 'addOwnCheckpointAttestations'
42
48
  | 'tryAddCheckpointAttestation'
43
49
  | 'deleteOlderThan'
@@ -52,31 +58,45 @@ export type AttestationPoolApi = Pick<
52
58
  *
53
59
  * Attestations and proposals observed via the p2p network are stored for requests
54
60
  * from the validator to produce a block, or to serve to other peers.
61
+ *
62
+ * Equivocation detection: distinct *signed payload hashes* arriving at the same
63
+ * position are tracked in the matching index multimap so the equivocation count
64
+ * reaches 2 even when archive collides on `feeAssetPriceModifier` variants.
65
+ * Proposal bytes are retained per accepted payload hash, up to the same equivocation
66
+ * caps, for slashing watchers that need signed P2P proposals.
55
67
  */
56
68
  export class AttestationPool {
57
69
  private metrics: PoolInstrumentation<CheckpointAttestation>;
58
70
 
59
- // Checkpoint attestations from attestation key (slot-proposalId-signer) to serialized CheckpointAttestation
60
- // Keys are lexicographically sortable allowing range queries by slot or by (slot, proposalId)
61
- private checkpointAttestations: AztecAsyncMap<string, Buffer>;
71
+ // Checkpoint attestations from `${paddedSlot}-${signer}` to serialized CheckpointAttestation.
72
+ // Stores the first attestation seen per (slot, signer); subsequent distinct payload
73
+ // hashes from the same signer are tracked only in `attestationHashesPerSlotAndSigner`
74
+ // for equivocation detection.
75
+ private attestationPerSlotAndSigner: AztecAsyncMap<string, Buffer>;
62
76
 
63
- // Checkpoint proposals from proposal archive to serialized CheckpointProposal
64
- private checkpointProposals: AztecAsyncMap<string, Buffer>;
77
+ // Distinct payload hashes seen per (slot, signer) for tracking attestation equivocations.
78
+ // Key: `${paddedSlot}-${signerAddress}`, Value: CheckpointProposalHash (`0x`-prefixed hex)
79
+ private attestationHashesPerSlotAndSigner: AztecAsyncMultiMap<string, CheckpointProposalHash>;
65
80
 
66
- // Checkpoint proposals indexed by slot for querying all proposals in a slot
67
- // Key: slot number, Value: proposal archive strings
68
- private checkpointProposalsForSlot: AztecAsyncMultiMap<number, string>;
81
+ // Checkpoint proposals from `${paddedSlot}-${payloadHash}` to serialized CheckpointProposalCore.
82
+ // Stores every accepted distinct payload up to MAX_CHECKPOINT_PROPOSALS_PER_SLOT.
83
+ private checkpointProposalsPerSlotAndHash: AztecAsyncMap<string, Buffer>;
69
84
 
70
- // Block proposals from proposal archive to serialized BlockProposal
71
- private blockProposals: AztecAsyncMap<string, Buffer>;
85
+ // Distinct payload hashes seen per slot. Hash collision = duplicate.
86
+ // Hash count reaching 2 = equivocation.
87
+ // Key: slot number, Value: CheckpointProposalHash (`0x`-prefixed hex)
88
+ private checkpointProposalHashesPerSlot: AztecAsyncMultiMap<number, CheckpointProposalHash>;
72
89
 
73
- // Block proposals indexed by slot and index-within-checkpoint for duplicate detection
74
- // Key: (slot << 10) | indexWithinCheckpoint, Value: archive string
75
- private blockProposalsForSlotAndIndex: AztecAsyncMultiMap<number, string>;
90
+ // Block proposals from `${paddedSlot}-${paddedIndex}-${payloadHash}` to serialized BlockProposal.
91
+ // Stores every accepted distinct payload up to MAX_BLOCK_PROPOSALS_PER_POSITION.
92
+ private blockProposalsPerSlotIndexAndHash: AztecAsyncMap<string, Buffer>;
76
93
 
77
- // Checkpoint attestations indexed by (slot, signer) for tracking attestations per (slot, signer) for duplicate detection
78
- // Key: `${Fr(slot).toString()}-${signerAddress}` string (padded for lexicographic ordering), Value: `proposalId` strings
79
- private checkpointAttestationsPerSlotAndSigner: AztecAsyncMultiMap<string, string>;
94
+ // Distinct payload hashes seen per (slot, indexWithinCheckpoint).
95
+ // Key: slot * (1 << INDEX_BITS) + indexWithinCheckpoint, Value: BlockProposalHash (`0x`-prefixed hex)
96
+ private blockProposalHashesPerSlotAndIndex: AztecAsyncMultiMap<number, BlockProposalHash>;
97
+
98
+ // Secondary index from archive root to all retained block proposal keys.
99
+ private blockProposalKeysPerArchive: AztecAsyncMultiMap<string, string>;
80
100
 
81
101
  constructor(
82
102
  private store: AztecAsyncKVStore,
@@ -84,128 +104,159 @@ export class AttestationPool {
84
104
  private log = createLogger('aztec:attestation_pool'),
85
105
  ) {
86
106
  // Initialize block proposal storage
87
- this.blockProposals = store.openMap('proposals');
88
- this.blockProposalsForSlotAndIndex = store.openMultiMap('block_proposals_for_slot_and_index');
107
+ this.blockProposalsPerSlotIndexAndHash = store.openMap('block_proposals_by_slot_index_and_hash');
108
+ this.blockProposalHashesPerSlotAndIndex = store.openMultiMap('block_proposals_for_slot_and_index');
109
+ this.blockProposalKeysPerArchive = store.openMultiMap('block_proposals_by_archive');
89
110
 
90
111
  // Initialize checkpoint attestations storage
91
- this.checkpointAttestations = store.openMap('checkpoint_attestations');
92
- this.checkpointAttestationsPerSlotAndSigner = store.openMultiMap('checkpoint_attestations_per_slot_and_signer');
112
+ this.attestationPerSlotAndSigner = store.openMap('checkpoint_attestations');
113
+ this.attestationHashesPerSlotAndSigner = store.openMultiMap('checkpoint_attestations_per_slot_and_signer');
93
114
 
94
115
  // Initialize checkpoint proposal storage
95
- this.checkpointProposals = store.openMap('checkpoint_proposals');
96
- this.checkpointProposalsForSlot = store.openMultiMap('checkpoint_proposals_for_slot');
116
+ this.checkpointProposalsPerSlotAndHash = store.openMap('checkpoint_proposals_by_slot_and_hash');
117
+ this.checkpointProposalHashesPerSlot = store.openMultiMap('checkpoint_proposals_for_slot');
97
118
 
98
119
  this.metrics = new PoolInstrumentation(telemetry, PoolName.ATTESTATION_POOL, this.poolStats);
99
120
  }
100
121
 
101
122
  private poolStats: PoolStatsCallback = async () => {
102
123
  return {
103
- itemCount: await this.checkpointAttestations.sizeAsync(),
124
+ itemCount: await this.attestationPerSlotAndSigner.sizeAsync(),
104
125
  };
105
126
  };
106
127
 
107
128
  /** Returns whether the pool is empty. */
108
129
  public async isEmpty(): Promise<boolean> {
109
- for await (const _ of this.checkpointAttestations.entriesAsync()) {
110
- return false;
111
- }
112
- for await (const _ of this.blockProposals.entriesAsync()) {
113
- return false;
114
- }
115
- return true;
130
+ const [attestationCount, blockProposalCount, checkpointProposalCount] = await Promise.all([
131
+ this.attestationPerSlotAndSigner.sizeAsync(),
132
+ this.blockProposalsPerSlotIndexAndHash.sizeAsync(),
133
+ this.checkpointProposalsPerSlotAndHash.sizeAsync(),
134
+ ]);
135
+
136
+ return attestationCount === 0 && blockProposalCount === 0 && checkpointProposalCount === 0;
137
+ }
138
+
139
+ /** Number of bits reserved for indexWithinCheckpoint in position keys. */
140
+ private static readonly INDEX_BITS = 10;
141
+ /** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */
142
+ private static readonly MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1;
143
+ /** Decimal digits used to left-pad slot numbers in string keys.
144
+ * 10 digits ≈ 3500 years at 36 s/slot, leaving ample headroom. */
145
+ private static readonly SLOT_PAD_DIGITS = 10;
146
+
147
+ /** Fixed-width decimal slot string for use in composite string keys. */
148
+ private slotPaddedKey(slot: SlotNumber | number): string {
149
+ return slot.toString().padStart(AttestationPool.SLOT_PAD_DIGITS, '0');
116
150
  }
117
151
 
118
- private getProposalKey(slot: number | bigint | Fr | string, proposalId: Fr | string | Buffer): string {
119
- const slotStr = typeof slot === 'string' ? slot : new Fr(slot).toString();
120
- const proposalIdStr =
121
- typeof proposalId === 'string'
122
- ? proposalId
123
- : Buffer.isBuffer(proposalId)
124
- ? Fr.fromBuffer(proposalId).toString()
125
- : proposalId.toString();
152
+ /** Fixed-width decimal index string for use in composite string keys. */
153
+ private indexPaddedKey(indexWithinCheckpoint: number): string {
154
+ return indexWithinCheckpoint.toString().padStart(4, '0');
155
+ }
126
156
 
127
- return `${slotStr}-${proposalIdStr}`;
157
+ /** Key for retained block proposals. */
158
+ private getBlockProposalKey(
159
+ slot: SlotNumber | number,
160
+ indexWithinCheckpoint: number,
161
+ payloadHash: BlockProposalHash,
162
+ ): string {
163
+ return `${this.slotPaddedKey(slot)}-${this.indexPaddedKey(indexWithinCheckpoint)}-${payloadHash}`;
128
164
  }
129
165
 
130
- private getAttestationKey(slot: number | bigint | Fr | string, proposalId: Fr | string, address: string): string {
131
- return `${this.getProposalKey(slot, proposalId)}-${address}`;
166
+ /** Range bounds for all retained block proposals in a slot. */
167
+ private getBlockProposalKeyRangeForSlot(slot: SlotNumber): { start: string; end: string } {
168
+ return { start: `${this.slotPaddedKey(slot)}-`, end: `${this.slotPaddedKey(slot + 1)}-` };
132
169
  }
133
170
 
134
- /** Returns range bounds for querying all attestations for a given slot. */
135
- private getAttestationKeyRangeForSlot(slot: SlotNumber): { start: string; end: string } {
136
- const slotStr = new Fr(slot).toString();
137
- return { start: `${slotStr}-`, end: `${slotStr}-Z` }; // 'Z' sorts after any hex character
171
+ /** Key for retained checkpoint proposals. */
172
+ private getCheckpointProposalKey(slot: SlotNumber | number, payloadHash: CheckpointProposalHash): string {
173
+ return `${this.slotPaddedKey(slot)}-${payloadHash}`;
138
174
  }
139
175
 
140
- /** Returns range bounds for querying all attestations for a given (slot, proposalId). */
141
- private getAttestationKeyRangeForProposal(slot: SlotNumber, proposalId: string): { start: string; end: string } {
142
- const proposalKey = this.getProposalKey(slot, proposalId);
143
- return { start: `${proposalKey}-`, end: `${proposalKey}-Z` };
176
+ /** Range bounds for all retained checkpoint proposals in a slot. */
177
+ private getCheckpointProposalKeyRangeForSlot(slot: SlotNumber): { start: string; end: string } {
178
+ return { start: `${this.slotPaddedKey(slot)}-`, end: `${this.slotPaddedKey(slot + 1)}-` };
144
179
  }
145
180
 
146
- /** Creates a key for the per-signer-per-slot attestation index. Uses padded slot for lexicographic ordering. */
181
+ /** Key for the per-(slot, signer) attestation main store and equivocation index. */
147
182
  private getSlotSignerKey(slot: SlotNumber, signerAddress: string): string {
148
- const slotStr = new Fr(slot).toString();
149
- return `${slotStr}-${signerAddress}`;
183
+ return `${this.slotPaddedKey(slot)}-${signerAddress}`;
150
184
  }
151
185
 
152
- /** Number of bits reserved for indexWithinCheckpoint in position keys. */
153
- private static readonly INDEX_BITS = 10;
154
- /** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */
155
- private static readonly MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1;
186
+ /**
187
+ * Returns range bounds for querying all attestations for a given slot.
188
+ * Fixed-width padding ensures the slot prefix sorts cleanly, so using the next
189
+ * slot's prefix as the upper bound captures exactly the current slot's entries.
190
+ */
191
+ private getAttestationKeyRangeForSlot(slot: SlotNumber): { start: string; end: string } {
192
+ return { start: `${this.slotPaddedKey(slot)}-`, end: `${this.slotPaddedKey(slot + 1)}-` };
193
+ }
156
194
 
157
- /** Creates a position key for block proposals: (slot << 10) | indexWithinCheckpoint. */
195
+ /** Creates a position key for block proposals: slot * 1024 + indexWithinCheckpoint.
196
+ * Uses multiplication instead of bit-shift to avoid 32-bit signed integer overflow
197
+ * (bit-shift overflows after slot ~2^21, roughly 278 days of uptime). */
158
198
  private getBlockPositionKey(slot: number, indexWithinCheckpoint: number): number {
159
199
  if (indexWithinCheckpoint > AttestationPool.MAX_INDEX) {
160
200
  throw new Error(
161
201
  `Value for indexWithinCheckpoint ${indexWithinCheckpoint} exceeds maximum ${AttestationPool.MAX_INDEX}`,
162
202
  );
163
203
  }
164
- return (slot << AttestationPool.INDEX_BITS) | indexWithinCheckpoint;
204
+ return slot * (1 << AttestationPool.INDEX_BITS) + indexWithinCheckpoint;
205
+ }
206
+
207
+ /** Returns true if the multimap already contains the given value for the given key. */
208
+ private async multimapHasValue<TKey extends number | string, TValue extends string>(
209
+ map: AztecAsyncMultiMap<TKey, TValue>,
210
+ key: TKey,
211
+ value: TValue,
212
+ ): Promise<boolean> {
213
+ const values = await toArray(map.getValuesAsync(key));
214
+ return values.includes(value);
165
215
  }
166
216
 
167
217
  /**
168
218
  * Attempts to add a block proposal to the pool.
169
219
  *
170
- * This method performs validation and addition in a single call:
171
- * - Checks if the proposal already exists (returns alreadyExists: true if so)
172
- * - Checks if the position has reached the proposal cap (returns added: false if so)
173
- * - Adds the proposal if validation passes
220
+ * - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the
221
+ * exact same signed payload returns `alreadyExists: true`.
222
+ * - Distinct payload hashes at the same `(slot, indexWithinCheckpoint)` are tracked
223
+ * in the equivocation index and retained up to the cap.
174
224
  *
175
225
  * @param blockProposal - The block proposal to add
176
226
  * @returns Result indicating whether the proposal was added and duplicate detection info
177
227
  */
178
228
  public async tryAddBlockProposal(blockProposal: BlockProposal): Promise<TryAddResult> {
179
229
  return await this.store.transactionAsync(async () => {
180
- const proposalId = blockProposal.archive.toString();
181
-
182
- // Check if already exists
183
- const alreadyExists = await this.blockProposals.hasAsync(proposalId);
184
- if (alreadyExists) {
185
- const count = await this.getBlockProposalCountForPosition(
186
- blockProposal.slotNumber,
187
- blockProposal.indexWithinCheckpoint,
188
- );
230
+ const positionKey = this.getBlockPositionKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint);
231
+ const payloadHash = blockProposal.getPayloadHash();
232
+
233
+ // Hash already tracked => exact same signed payload was already received.
234
+ if (await this.multimapHasValue(this.blockProposalHashesPerSlotAndIndex, positionKey, payloadHash)) {
235
+ const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey);
189
236
  return { added: false, alreadyExists: true, count };
190
237
  }
191
238
 
192
- // Get current count for position and check cap, do not add if exceeded
193
- const count = await this.getBlockProposalCountForPosition(
194
- blockProposal.slotNumber,
195
- blockProposal.indexWithinCheckpoint,
196
- );
197
-
239
+ // Cap reached for this position (no more new payload hashes accepted).
240
+ const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey);
198
241
  if (count >= MAX_BLOCK_PROPOSALS_PER_POSITION) {
199
242
  return { added: false, alreadyExists: false, count };
200
243
  }
201
244
 
202
- // Add the proposal
203
- await this.addBlockProposal(blockProposal);
245
+ // Track the new payload hash for equivocation detection.
246
+ await this.blockProposalHashesPerSlotAndIndex.set(positionKey, payloadHash);
247
+ const proposalKey = this.getBlockProposalKey(
248
+ blockProposal.slotNumber,
249
+ blockProposal.indexWithinCheckpoint,
250
+ payloadHash,
251
+ );
252
+ await this.blockProposalsPerSlotIndexAndHash.set(proposalKey, blockProposal.withoutSignedTxs().toBuffer());
253
+ await this.blockProposalKeysPerArchive.set(blockProposal.archive.toString(), proposalKey);
204
254
 
205
255
  this.log.debug(
206
256
  `Added block proposal for slot ${blockProposal.slotNumber} and index ${blockProposal.indexWithinCheckpoint}`,
207
257
  {
208
- proposalId,
258
+ archive: blockProposal.archive.toString(),
259
+ payloadHash,
209
260
  slotNumber: blockProposal.slotNumber,
210
261
  indexWithinCheckpoint: blockProposal.indexWithinCheckpoint,
211
262
  },
@@ -215,60 +266,77 @@ export class AttestationPool {
215
266
  });
216
267
  }
217
268
 
218
- /** Gets the count of block proposals for a given position (slot, indexWithinCheckpoint). */
219
- private getBlockProposalCountForPosition(
220
- slot: SlotNumber,
221
- indexWithinCheckpoint: IndexWithinCheckpoint,
222
- ): Promise<number> {
223
- const positionKey = this.getBlockPositionKey(slot, indexWithinCheckpoint);
224
- return this.blockProposalsForSlotAndIndex.getValueCountAsync(positionKey);
225
- }
226
-
227
- /** Internal method - must be called within a transaction. */
228
- private async addBlockProposal(blockProposal: BlockProposal): Promise<void> {
229
- const proposalId = blockProposal.archive.toString();
230
- // Strip signedTxs before storing to avoid persisting full tx data
231
- await this.blockProposals.set(proposalId, blockProposal.withoutSignedTxs().toBuffer());
232
-
233
- // Index by slot and position for duplicate detection
234
- const positionKey = this.getBlockPositionKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint);
235
- await this.blockProposalsForSlotAndIndex.set(positionKey, proposalId);
236
- }
237
-
238
269
  /**
239
- * Get block proposal by its ID.
270
+ * Get block proposal by archive root.
240
271
  *
241
- * @param id - The ID of the block proposal to retrieve. The ID is proposal.payload.archive
272
+ * Resolves the archive root through the archive index and returns the first
273
+ * retained proposal for that archive. This lookup is used by block-txs req/resp,
274
+ * where any retained proposal for the requested archive gives the tx hash list.
242
275
  *
243
- * @return The block proposal if it exists, otherwise undefined.
276
+ * @param archiveRoot - The archive root to look up
277
+ * @return The block proposal if it exists and its archive matches, otherwise undefined.
244
278
  */
245
- public async getBlockProposal(id: string): Promise<BlockProposal | undefined> {
246
- const buffer = await this.blockProposals.getAsync(id);
247
- try {
248
- if (buffer && buffer.length > 0) {
249
- return BlockProposal.fromBuffer(buffer);
279
+ public async getBlockProposalByArchive(archiveRoot: string): Promise<BlockProposal | undefined> {
280
+ for await (const proposalKey of this.blockProposalKeysPerArchive.getValuesAsync(archiveRoot)) {
281
+ const buffer = await this.blockProposalsPerSlotIndexAndHash.getAsync(proposalKey);
282
+ if (!buffer || buffer.length === 0) {
283
+ continue;
284
+ }
285
+ try {
286
+ const proposal = BlockProposal.fromBuffer(buffer);
287
+ if (proposal.archive.toString() === archiveRoot) {
288
+ return proposal;
289
+ }
290
+ } catch {
291
+ continue;
250
292
  }
251
- } catch {
252
- return undefined;
253
293
  }
254
-
255
294
  return undefined;
256
295
  }
257
296
 
297
+ /** Returns retained signed proposals for a slot. */
298
+ public async getProposalsForSlot(slot: SlotNumber): Promise<ProposalsForSlot> {
299
+ const blockProposals: BlockProposal[] = [];
300
+ const checkpointProposals: CheckpointProposalCore[] = [];
301
+
302
+ for await (const [_, buffer] of this.blockProposalsPerSlotIndexAndHash.entriesAsync(
303
+ this.getBlockProposalKeyRangeForSlot(slot),
304
+ )) {
305
+ try {
306
+ blockProposals.push(BlockProposal.fromBuffer(buffer));
307
+ } catch {
308
+ continue;
309
+ }
310
+ }
311
+
312
+ for await (const [_, buffer] of this.checkpointProposalsPerSlotAndHash.entriesAsync(
313
+ this.getCheckpointProposalKeyRangeForSlot(slot),
314
+ )) {
315
+ try {
316
+ checkpointProposals.push(CheckpointProposal.fromBuffer(buffer));
317
+ } catch {
318
+ continue;
319
+ }
320
+ }
321
+
322
+ return { blockProposals, checkpointProposals };
323
+ }
324
+
258
325
  /** Checks if any block proposals exist for a given slot (at index 0). */
259
326
  public async hasBlockProposalsForSlot(slot: SlotNumber): Promise<boolean> {
260
327
  const positionKey = this.getBlockPositionKey(slot, 0);
261
- const count = await this.blockProposalsForSlotAndIndex.getValueCountAsync(positionKey);
328
+ const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey);
262
329
  return count > 0;
263
330
  }
264
331
 
265
332
  /**
266
333
  * Attempts to add a checkpoint proposal to the pool.
267
334
  *
268
- * This method performs validation and addition in a single call:
269
- * - Checks if the proposal already exists (returns alreadyExists: true if so)
270
- * - Checks if the slot has reached the proposal cap (returns added: false if so)
271
- * - Adds the proposal if validation passes
335
+ * - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the
336
+ * exact same signed payload returns `alreadyExists: true`.
337
+ * - Distinct payload hashes at the same slot are tracked in the equivocation index.
338
+ * Distinct payload bytes are retained up to the same cap so slashing watchers
339
+ * can recover signed proposals.
272
340
  *
273
341
  * Note: This method only handles the CheckpointProposalCore. If the original
274
342
  * CheckpointProposal contains a lastBlock, the caller should extract it via
@@ -279,73 +347,77 @@ export class AttestationPool {
279
347
  */
280
348
  public async tryAddCheckpointProposal(proposal: CheckpointProposalCore): Promise<TryAddResult> {
281
349
  return await this.store.transactionAsync(async () => {
282
- const proposalId = proposal.archive.toString();
350
+ const slot = proposal.slotNumber;
351
+ const payloadHash = proposal.getPayloadHash();
283
352
 
284
- // Check if already exists
285
- const alreadyExists = await this.checkpointProposals.hasAsync(proposalId);
286
- if (alreadyExists) {
287
- const count = await this.checkpointProposalsForSlot.getValueCountAsync(proposal.slotNumber);
353
+ if (await this.multimapHasValue(this.checkpointProposalHashesPerSlot, slot, payloadHash)) {
354
+ const count = await this.checkpointProposalHashesPerSlot.getValueCountAsync(slot);
288
355
  return { added: false, alreadyExists: true, count };
289
356
  }
290
357
 
291
- // Get current count for slot and check cap
292
- const count = await this.checkpointProposalsForSlot.getValueCountAsync(proposal.slotNumber);
358
+ const count = await this.checkpointProposalHashesPerSlot.getValueCountAsync(slot);
293
359
  if (count >= MAX_CHECKPOINT_PROPOSALS_PER_SLOT) {
294
360
  return { added: false, alreadyExists: false, count };
295
361
  }
296
362
 
297
- // Add the proposal if cap not exceeded
298
- await this.addCheckpointProposal(proposal);
363
+ // Track the new payload hash for equivocation detection.
364
+ await this.checkpointProposalHashesPerSlot.set(slot, payloadHash);
365
+ await this.checkpointProposalsPerSlotAndHash.set(
366
+ this.getCheckpointProposalKey(slot, payloadHash),
367
+ proposal.toBuffer(),
368
+ );
299
369
 
300
- this.log.debug(`Added checkpoint proposal for slot ${proposal.slotNumber}`, {
301
- proposalId,
302
- slotNumber: proposal.slotNumber,
370
+ this.log.debug(`Added checkpoint proposal for slot ${slot}`, {
371
+ archive: proposal.archive.toString(),
372
+ payloadHash,
373
+ slotNumber: slot,
303
374
  });
304
375
 
305
376
  return { added: true, alreadyExists: false, count: count + 1 };
306
377
  });
307
378
  }
308
379
 
309
- /** Internal method - must be called within a transaction. */
310
- private async addCheckpointProposal(proposal: CheckpointProposalCore): Promise<void> {
311
- const slotKey = proposal.slotNumber;
312
- const proposalId = proposal.archive.toString();
313
-
314
- await this.checkpointProposalsForSlot.set(slotKey, proposalId);
315
- await this.checkpointProposals.set(proposalId, proposal.toBuffer());
316
- }
317
-
318
380
  /**
319
- * Get checkpoint proposal by its ID.
381
+ * Get a retained checkpoint proposal stored for the given slot.
382
+ * If multiple proposals were retained for an equivocation, returns the lowest
383
+ * payload hash deterministically.
320
384
  *
321
385
  * Returns a CheckpointProposalCore (without lastBlock info) since the lastBlock
322
386
  * is extracted and stored separately as a BlockProposal when added.
323
387
  *
324
- * @param id - The ID of the checkpoint proposal to retrieve (proposal.archive)
325
- * @return The checkpoint proposal core if it exists, otherwise undefined.
388
+ * @param slot - The slot to look up
389
+ * @return The checkpoint proposal core if one is stored, otherwise undefined.
326
390
  */
327
- public async getCheckpointProposal(id: string): Promise<CheckpointProposalCore | undefined> {
328
- const buffer = await this.checkpointProposals.getAsync(id);
329
- try {
330
- if (buffer && buffer.length > 0) {
331
- return CheckpointProposal.fromBuffer(buffer);
391
+ public async getCheckpointProposal(slot: SlotNumber): Promise<CheckpointProposalCore | undefined> {
392
+ for await (const [_, buffer] of this.checkpointProposalsPerSlotAndHash.entriesAsync(
393
+ this.getCheckpointProposalKeyRangeForSlot(slot),
394
+ )) {
395
+ try {
396
+ if (buffer && buffer.length > 0) {
397
+ return CheckpointProposal.fromBuffer(buffer);
398
+ }
399
+ } catch {
400
+ continue;
332
401
  }
333
- } catch {
334
- return undefined;
335
402
  }
336
403
 
337
404
  return undefined;
338
405
  }
339
406
 
407
+ /** Returns whether any checkpoint proposal is retained for the given slot. */
408
+ public async hasCheckpointProposalForSlot(slot: SlotNumber): Promise<boolean> {
409
+ return (await this.getCheckpointProposal(slot)) !== undefined;
410
+ }
411
+
340
412
  /**
341
413
  * Adds own checkpoint attestations to the pool.
342
- * Skips validations on number of checkpoint attestations stored for the given slot.
414
+ * Skips per-signer cap and equivocation tracking; the caller is trusted.
415
+ * Each (slot, signer) gets a single stored attestation; later additions overwrite.
343
416
  */
344
417
  public async addOwnCheckpointAttestations(attestations: CheckpointAttestation[]): Promise<void> {
345
418
  await this.store.transactionAsync(async () => {
346
419
  for (const attestation of attestations) {
347
420
  const slotNumber = attestation.payload.header.slotNumber;
348
- const proposalId = attestation.archive;
349
421
  const sender = attestation.getSender();
350
422
 
351
423
  // Skip attestations with invalid signatures
@@ -353,22 +425,30 @@ export class AttestationPool {
353
425
  this.log.warn(`Skipping own checkpoint attestation with invalid signature for slot ${slotNumber}`, {
354
426
  signature: attestation.signature.toString(),
355
427
  slotNumber,
356
- proposalId,
428
+ archive: attestation.archive.toString(),
357
429
  });
358
430
  continue;
359
431
  }
360
432
 
361
433
  const address = sender.toString();
362
- const ownKey = this.getAttestationKey(slotNumber, proposalId, address);
434
+ const ownKey = this.getSlotSignerKey(slotNumber, address);
435
+ const payloadHash = attestation.getPayloadHash();
363
436
 
364
- await this.checkpointAttestations.set(ownKey, attestation.toBuffer());
437
+ await this.attestationPerSlotAndSigner.set(ownKey, attestation.toBuffer());
365
438
  this.metrics.trackMempoolItemAdded(ownKey);
366
439
 
440
+ // Track our own payload hash so that an equivocating attestation from another
441
+ // peer at the same (slot, signer) is detected as a duplicate.
442
+ if (!(await this.multimapHasValue(this.attestationHashesPerSlotAndSigner, ownKey, payloadHash))) {
443
+ await this.attestationHashesPerSlotAndSigner.set(ownKey, payloadHash);
444
+ }
445
+
367
446
  this.log.debug(`Added own checkpoint attestation for slot ${slotNumber} from ${address}`, {
368
447
  signature: attestation.signature.toString(),
369
448
  slotNumber,
370
449
  address,
371
- proposalId,
450
+ archive: attestation.archive.toString(),
451
+ payloadHash,
372
452
  });
373
453
  }
374
454
  });
@@ -377,6 +457,10 @@ export class AttestationPool {
377
457
  /**
378
458
  * Get all checkpoint attestations for a given slot.
379
459
  *
460
+ * Returns one attestation per (slot, signer) — the first seen for each signer.
461
+ * Later equivocating attestations from the same signer are tracked in the index
462
+ * but their bytes are not retained.
463
+ *
380
464
  * @param slot - The slot to query
381
465
  * @return CheckpointAttestations
382
466
  */
@@ -384,7 +468,7 @@ export class AttestationPool {
384
468
  const range = this.getAttestationKeyRangeForSlot(slot);
385
469
  const attestations: CheckpointAttestation[] = [];
386
470
 
387
- for await (const [_, buf] of this.checkpointAttestations.entriesAsync(range)) {
471
+ for await (const [_, buf] of this.attestationPerSlotAndSigner.entriesAsync(range)) {
388
472
  attestations.push(CheckpointAttestation.fromBuffer(buf));
389
473
  }
390
474
 
@@ -392,24 +476,19 @@ export class AttestationPool {
392
476
  }
393
477
 
394
478
  /**
395
- * Get checkpoint attestations for slot and given proposal.
479
+ * Get checkpoint attestations for a slot whose signed payload matches the given
480
+ * proposal payload hash.
396
481
  *
397
482
  * @param slot - The slot to query
398
- * @param proposalId - The proposal to query
399
- * @return CheckpointAttestations
483
+ * @param proposalPayloadHash - Hex-encoded keccak256 of the target proposal's signed payload
484
+ * @return CheckpointAttestations whose `getPayloadHash()` matches `proposalPayloadHash`
400
485
  */
401
486
  public async getCheckpointAttestationsForSlotAndProposal(
402
487
  slot: SlotNumber,
403
- proposalId: string,
488
+ proposalPayloadHash: CheckpointProposalHash,
404
489
  ): Promise<CheckpointAttestation[]> {
405
- const range = this.getAttestationKeyRangeForProposal(slot, proposalId);
406
- const attestations: CheckpointAttestation[] = [];
407
-
408
- for await (const [_, buf] of this.checkpointAttestations.entriesAsync(range)) {
409
- attestations.push(CheckpointAttestation.fromBuffer(buf));
410
- }
411
-
412
- return attestations;
490
+ const all = await this.getCheckpointAttestationsForSlot(slot);
491
+ return all.filter(att => att.getPayloadHash() === proposalPayloadHash);
413
492
  }
414
493
 
415
494
  /**
@@ -423,43 +502,51 @@ export class AttestationPool {
423
502
  let numberOfBlockProposals = 0;
424
503
 
425
504
  await this.store.transactionAsync(async () => {
426
- // Delete checkpoint attestations with slot < oldestSlot
427
- // Attestation keys start with Fr(slot).toString(), so we use end bound of Fr(oldestSlot).toString()
428
- const attestationEndKey = new Fr(oldestSlot).toString();
429
- for await (const key of this.checkpointAttestations.keysAsync({ end: attestationEndKey })) {
430
- await this.checkpointAttestations.delete(key);
505
+ const oldestSlotPadded = this.slotPaddedKey(oldestSlot);
506
+
507
+ // Delete checkpoint attestations whose key < `${oldestSlotPadded}-`. Fixed-width
508
+ // decimal padding means the slot prefix sorts strictly before any key at that slot.
509
+ for await (const key of this.attestationPerSlotAndSigner.keysAsync({ end: `${oldestSlotPadded}-` })) {
510
+ await this.attestationPerSlotAndSigner.delete(key);
431
511
  this.metrics.trackMempoolItemRemoved(key);
432
512
  numberOfAttestations++;
433
513
  }
434
514
 
435
- // Clean up per-signer-per-slot index. Keys are formatted as `${Fr(slot).toString()}-${signerAddress}`.
436
- // Since Fr pads to fixed-width hex, Fr(oldestSlot) is lexicographically greater than any key with
437
- // a smaller slot (even with the signer suffix), so using it as the exclusive end bound is correct.
438
- const slotSignerEndKey = new Fr(oldestSlot).toString();
439
- for await (const key of this.checkpointAttestationsPerSlotAndSigner.keysAsync({ end: slotSignerEndKey })) {
440
- await this.checkpointAttestationsPerSlotAndSigner.delete(key);
515
+ // Clean up per-signer-per-slot index using the same end bound.
516
+ for await (const key of this.attestationHashesPerSlotAndSigner.keysAsync({ end: `${oldestSlotPadded}-` })) {
517
+ await this.attestationHashesPerSlotAndSigner.delete(key);
441
518
  }
442
519
 
443
- // Delete checkpoint proposals for slots < oldestSlot, using checkpointProposalsForSlot as index
444
- for await (const slot of this.checkpointProposalsForSlot.keysAsync({ end: oldestSlot })) {
445
- const proposalIds = await toArray(this.checkpointProposalsForSlot.getValuesAsync(slot));
446
- for (const proposalId of proposalIds) {
447
- await this.checkpointProposals.delete(proposalId);
448
- numberOfCheckpointProposals++;
449
- }
450
- await this.checkpointProposalsForSlot.delete(slot);
520
+ // Delete checkpoint proposals for slots < oldestSlot.
521
+ for await (const slot of this.checkpointProposalHashesPerSlot.keysAsync({ end: oldestSlot })) {
522
+ await this.checkpointProposalHashesPerSlot.delete(slot);
451
523
  }
452
524
 
453
- // Delete block proposals for slots < oldestSlot, using blockProposalsForSlotAndIndex as index
454
- // Key format: (slot << INDEX_BITS) | indexWithinCheckpoint
455
- const blockPositionEndKey = oldestSlot << AttestationPool.INDEX_BITS;
456
- for await (const positionKey of this.blockProposalsForSlotAndIndex.keysAsync({ end: blockPositionEndKey })) {
457
- const proposalIds = await toArray(this.blockProposalsForSlotAndIndex.getValuesAsync(positionKey));
458
- for (const proposalId of proposalIds) {
459
- await this.blockProposals.delete(proposalId);
460
- numberOfBlockProposals++;
525
+ for await (const key of this.checkpointProposalsPerSlotAndHash.keysAsync({
526
+ end: `${oldestSlotPadded}-`,
527
+ })) {
528
+ await this.checkpointProposalsPerSlotAndHash.delete(key);
529
+ numberOfCheckpointProposals++;
530
+ }
531
+
532
+ // Delete block proposals for slots < oldestSlot, using blockProposalHashesPerSlotAndIndex as index.
533
+ // Key format: slot * (1 << INDEX_BITS) + indexWithinCheckpoint
534
+ const blockPositionEndKey = oldestSlot * (1 << AttestationPool.INDEX_BITS);
535
+ for await (const positionKey of this.blockProposalHashesPerSlotAndIndex.keysAsync({ end: blockPositionEndKey })) {
536
+ await this.blockProposalHashesPerSlotAndIndex.delete(positionKey);
537
+ }
538
+
539
+ for await (const [key, buffer] of this.blockProposalsPerSlotIndexAndHash.entriesAsync({
540
+ end: `${oldestSlotPadded}-`,
541
+ })) {
542
+ try {
543
+ const proposal = BlockProposal.fromBuffer(buffer);
544
+ await this.blockProposalKeysPerArchive.deleteValue(proposal.archive.toString(), key);
545
+ } catch {
546
+ // ignore decode errors when cleaning up
461
547
  }
462
- await this.blockProposalsForSlotAndIndex.delete(positionKey);
548
+ await this.blockProposalsPerSlotIndexAndHash.delete(key);
549
+ numberOfBlockProposals++;
463
550
  }
464
551
  });
465
552
 
@@ -474,18 +561,19 @@ export class AttestationPool {
474
561
  /**
475
562
  * Attempts to add a checkpoint attestation to the pool.
476
563
  *
477
- * This method performs validation and addition in a single call:
478
- * - Checks if the attestation already exists (returns alreadyExists: true if so)
479
- * - Checks if this signer has reached the per-signer attestation cap for this slot
480
- * - Adds the attestation if validation passes
564
+ * - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the
565
+ * exact same signed payload from the same signer returns `alreadyExists: true`.
566
+ * - Distinct payload hashes from the same (slot, signer) are tracked in the
567
+ * equivocation index. The first one's bytes are stored; later distinct hashes
568
+ * bump `count` so libp2p can fire its duplicate callback.
481
569
  *
482
570
  * @param attestation - The checkpoint attestation to add
483
- * @returns Result indicating whether the attestation was added, existence info, and count of
484
- * attestations by this signer for this slot (for equivocation detection)
571
+ * @returns Result indicating whether the attestation was added, existence info,
572
+ * and number of distinct payload hashes by this signer for this slot
573
+ * (for equivocation detection).
485
574
  */
486
575
  public async tryAddCheckpointAttestation(attestation: CheckpointAttestation): Promise<TryAddResult> {
487
576
  const slotNumber = attestation.payload.header.slotNumber;
488
- const proposalId = attestation.archive.toString();
489
577
  const sender = attestation.getSender();
490
578
 
491
579
  if (!sender) {
@@ -493,28 +581,23 @@ export class AttestationPool {
493
581
  }
494
582
 
495
583
  const signerAddress = sender.toString();
584
+ const slotSignerKey = this.getSlotSignerKey(slotNumber, signerAddress);
585
+ const payloadHash = attestation.getPayloadHash();
496
586
 
497
587
  return await this.store.transactionAsync(async () => {
498
- const key = this.getAttestationKey(slotNumber, proposalId, signerAddress);
499
- const alreadyExists = await this.checkpointAttestations.hasAsync(key);
500
-
501
- // Get count of attestations by this signer for this slot (for duplicate detection)
502
- const signerAttestationCount = await this.getSignerAttestationCountForSlot(slotNumber, signerAddress);
503
-
504
- if (alreadyExists) {
505
- return {
506
- added: false,
507
- alreadyExists: true,
508
- count: signerAttestationCount,
509
- };
588
+ if (await this.multimapHasValue(this.attestationHashesPerSlotAndSigner, slotSignerKey, payloadHash)) {
589
+ const count = await this.attestationHashesPerSlotAndSigner.getValueCountAsync(slotSignerKey);
590
+ return { added: false, alreadyExists: true, count };
510
591
  }
511
592
 
512
- // Check if this signer has exceeded the per-signer cap for this slot
593
+ const signerAttestationCount = await this.attestationHashesPerSlotAndSigner.getValueCountAsync(slotSignerKey);
594
+
513
595
  if (signerAttestationCount >= MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER) {
514
596
  this.log.debug(`Rejecting attestation: signer ${signerAddress} exceeded per-slot cap for slot ${slotNumber}`, {
515
597
  slotNumber,
516
598
  signerAddress,
517
- proposalId,
599
+ archive: attestation.archive.toString(),
600
+ payloadHash,
518
601
  signerAttestationCount,
519
602
  });
520
603
  return {
@@ -524,22 +607,26 @@ export class AttestationPool {
524
607
  };
525
608
  }
526
609
 
527
- // Add the attestation
528
- await this.checkpointAttestations.set(key, attestation.toBuffer());
529
- this.metrics.trackMempoolItemAdded(key);
610
+ // Track the new payload hash for equivocation detection.
611
+ await this.attestationHashesPerSlotAndSigner.set(slotSignerKey, payloadHash);
530
612
 
531
- // Track this attestation in the per-signer-per-slot index for duplicate detection
532
- const slotSignerKey = this.getSlotSignerKey(slotNumber, signerAddress);
533
- await this.checkpointAttestationsPerSlotAndSigner.set(slotSignerKey, proposalId);
613
+ // Only the first distinct payload at (slot, signer) is stored; later
614
+ // equivocations are detected via the multimap but their bytes are not retained.
615
+ const alreadyHasStored = await this.attestationPerSlotAndSigner.hasAsync(slotSignerKey);
616
+ if (!alreadyHasStored) {
617
+ await this.attestationPerSlotAndSigner.set(slotSignerKey, attestation.toBuffer());
618
+ this.metrics.trackMempoolItemAdded(slotSignerKey);
619
+ }
534
620
 
535
621
  this.log.debug(`Added checkpoint attestation for slot ${slotNumber} from ${signerAddress}`, {
536
622
  signature: attestation.signature.toString(),
537
623
  slotNumber,
538
624
  address: signerAddress,
539
- proposalId,
625
+ archive: attestation.archive.toString(),
626
+ payloadHash,
627
+ stored: !alreadyHasStored,
540
628
  });
541
629
 
542
- // Return the new count
543
630
  return {
544
631
  added: true,
545
632
  alreadyExists: false,
@@ -547,12 +634,6 @@ export class AttestationPool {
547
634
  };
548
635
  });
549
636
  }
550
-
551
- /** Gets the count of attestations by a specific signer for a given slot. */
552
- private async getSignerAttestationCountForSlot(slot: SlotNumber, signerAddress: string): Promise<number> {
553
- const slotSignerKey = this.getSlotSignerKey(slot, signerAddress);
554
- return await this.checkpointAttestationsPerSlotAndSigner.getValueCountAsync(slotSignerKey);
555
- }
556
637
  }
557
638
 
558
639
  /** Creates an AttestationPool backed by a temporary store for testing. */