@aztec/p2p 0.0.1-commit.4d79d1f2d → 0.0.1-commit.4d9804df

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