@aztec/p2p 3.0.0-canary.a9708bd → 3.0.0-devnet.2-patch.1

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 (294) hide show
  1. package/dest/bootstrap/bootstrap.d.ts +1 -1
  2. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  3. package/dest/bootstrap/bootstrap.js +14 -4
  4. package/dest/client/factory.d.ts +2 -1
  5. package/dest/client/factory.d.ts.map +1 -1
  6. package/dest/client/factory.js +8 -3
  7. package/dest/client/index.d.ts +1 -1
  8. package/dest/client/interface.d.ts +8 -6
  9. package/dest/client/interface.d.ts.map +1 -1
  10. package/dest/client/p2p_client.d.ts +11 -34
  11. package/dest/client/p2p_client.d.ts.map +1 -1
  12. package/dest/client/p2p_client.js +68 -46
  13. package/dest/config.d.ts +65 -56
  14. package/dest/config.d.ts.map +1 -1
  15. package/dest/config.js +21 -5
  16. package/dest/enr/generate-enr.d.ts +2 -2
  17. package/dest/enr/generate-enr.d.ts.map +1 -1
  18. package/dest/enr/generate-enr.js +1 -1
  19. package/dest/enr/index.d.ts +1 -1
  20. package/dest/errors/attestation-pool.error.d.ts +7 -0
  21. package/dest/errors/attestation-pool.error.d.ts.map +1 -0
  22. package/dest/errors/attestation-pool.error.js +12 -0
  23. package/dest/errors/reqresp.error.d.ts +1 -1
  24. package/dest/errors/reqresp.error.d.ts.map +1 -1
  25. package/dest/index.d.ts +1 -1
  26. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +43 -6
  27. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  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 +72 -46
  31. package/dest/mem_pools/attestation_pool/index.d.ts +1 -1
  32. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +15 -6
  33. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  34. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +73 -18
  35. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +13 -6
  36. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  37. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +69 -11
  38. package/dest/mem_pools/attestation_pool/mocks.d.ts +226 -5
  39. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  40. package/dest/mem_pools/attestation_pool/mocks.js +9 -7
  41. package/dest/mem_pools/index.d.ts +1 -1
  42. package/dest/mem_pools/instrumentation.d.ts +3 -1
  43. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  44. package/dest/mem_pools/instrumentation.js +11 -2
  45. package/dest/mem_pools/interface.d.ts +1 -1
  46. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +17 -40
  47. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +103 -44
  49. package/dest/mem_pools/tx_pool/index.d.ts +1 -1
  50. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +19 -6
  51. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
  52. package/dest/mem_pools/tx_pool/memory_tx_pool.js +88 -9
  53. package/dest/mem_pools/tx_pool/priority.d.ts +1 -1
  54. package/dest/mem_pools/tx_pool/tx_pool.d.ts +21 -6
  55. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
  56. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  57. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  58. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +132 -7
  59. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +4 -2
  60. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  61. package/dest/msg_validators/attestation_validator/attestation_validator.js +32 -5
  62. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +20 -0
  63. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -0
  64. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +67 -0
  65. package/dest/msg_validators/attestation_validator/index.d.ts +2 -1
  66. package/dest/msg_validators/attestation_validator/index.d.ts.map +1 -1
  67. package/dest/msg_validators/attestation_validator/index.js +1 -0
  68. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +5 -2
  69. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -1
  70. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +56 -9
  71. package/dest/msg_validators/block_proposal_validator/index.d.ts +1 -1
  72. package/dest/msg_validators/index.d.ts +1 -1
  73. package/dest/msg_validators/msg_seen_validator/msg_seen_validator.d.ts +1 -1
  74. package/dest/msg_validators/msg_seen_validator/msg_seen_validator.d.ts.map +1 -1
  75. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +1 -1
  76. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  77. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +1 -1
  78. package/dest/msg_validators/tx_validator/archive_cache.d.ts +2 -2
  79. package/dest/msg_validators/tx_validator/archive_cache.d.ts.map +1 -1
  80. package/dest/msg_validators/tx_validator/block_header_validator.d.ts +2 -2
  81. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  82. package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
  83. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  84. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  85. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +1 -1
  86. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
  87. package/dest/msg_validators/tx_validator/double_spend_validator.js +1 -1
  88. package/dest/msg_validators/tx_validator/factory.d.ts +4 -3
  89. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  90. package/dest/msg_validators/tx_validator/factory.js +11 -5
  91. package/dest/msg_validators/tx_validator/gas_validator.d.ts +1 -1
  92. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  93. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  94. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  95. package/dest/msg_validators/tx_validator/index.js +1 -0
  96. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +3 -6
  97. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  98. package/dest/msg_validators/tx_validator/metadata_validator.js +6 -24
  99. package/dest/msg_validators/tx_validator/phases_validator.d.ts +1 -1
  100. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  101. package/dest/msg_validators/tx_validator/phases_validator.js +3 -1
  102. package/dest/msg_validators/tx_validator/test_utils.d.ts +2 -2
  103. package/dest/msg_validators/tx_validator/test_utils.d.ts.map +1 -1
  104. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +13 -0
  105. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -0
  106. package/dest/msg_validators/tx_validator/timestamp_validator.js +32 -0
  107. package/dest/msg_validators/tx_validator/tx_permitted_validator.d.ts +1 -1
  108. package/dest/msg_validators/tx_validator/tx_permitted_validator.d.ts.map +1 -1
  109. package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts +1 -1
  110. package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -1
  111. package/dest/services/data_store.d.ts +1 -1
  112. package/dest/services/data_store.d.ts.map +1 -1
  113. package/dest/services/discv5/discV5_service.d.ts +3 -3
  114. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  115. package/dest/services/discv5/discV5_service.js +2 -2
  116. package/dest/services/dummy_service.d.ts +2 -2
  117. package/dest/services/dummy_service.d.ts.map +1 -1
  118. package/dest/services/encoding.d.ts +25 -4
  119. package/dest/services/encoding.d.ts.map +1 -1
  120. package/dest/services/encoding.js +74 -6
  121. package/dest/services/gossipsub/scoring.d.ts +1 -1
  122. package/dest/services/index.d.ts +1 -1
  123. package/dest/services/libp2p/instrumentation.d.ts +3 -1
  124. package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
  125. package/dest/services/libp2p/instrumentation.js +9 -2
  126. package/dest/services/libp2p/libp2p_service.d.ts +29 -69
  127. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  128. package/dest/services/libp2p/libp2p_service.js +375 -133
  129. package/dest/services/peer-manager/interface.d.ts +1 -1
  130. package/dest/services/peer-manager/metrics.d.ts +3 -1
  131. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  132. package/dest/services/peer-manager/metrics.js +11 -0
  133. package/dest/services/peer-manager/peer_manager.d.ts +1 -32
  134. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  135. package/dest/services/peer-manager/peer_manager.js +27 -12
  136. package/dest/services/peer-manager/peer_scoring.d.ts +7 -2
  137. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  138. package/dest/services/peer-manager/peer_scoring.js +40 -2
  139. package/dest/services/reqresp/config.d.ts +1 -1
  140. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +1 -1
  141. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +1 -1
  142. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +1 -4
  143. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
  144. package/dest/services/reqresp/index.d.ts +1 -1
  145. package/dest/services/reqresp/interface.d.ts +2 -11
  146. package/dest/services/reqresp/interface.d.ts.map +1 -1
  147. package/dest/services/reqresp/interface.js +1 -18
  148. package/dest/services/reqresp/metrics.d.ts +1 -1
  149. package/dest/services/reqresp/metrics.d.ts.map +1 -1
  150. package/dest/services/reqresp/protocols/auth.d.ts +2 -2
  151. package/dest/services/reqresp/protocols/auth.d.ts.map +1 -1
  152. package/dest/services/reqresp/protocols/auth.js +2 -2
  153. package/dest/services/reqresp/protocols/block.d.ts +1 -1
  154. package/dest/services/reqresp/protocols/block.d.ts.map +1 -1
  155. package/dest/services/reqresp/protocols/block.js +3 -2
  156. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +1 -1
  157. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
  158. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +1 -1
  159. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +4 -6
  160. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  161. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +1 -1
  162. package/dest/services/reqresp/protocols/block_txs/index.d.ts +1 -1
  163. package/dest/services/reqresp/protocols/goodbye.d.ts +1 -1
  164. package/dest/services/reqresp/protocols/goodbye.d.ts.map +1 -1
  165. package/dest/services/reqresp/protocols/index.d.ts +1 -1
  166. package/dest/services/reqresp/protocols/ping.d.ts +1 -1
  167. package/dest/services/reqresp/protocols/status.d.ts +6 -5
  168. package/dest/services/reqresp/protocols/status.d.ts.map +1 -1
  169. package/dest/services/reqresp/protocols/status.js +4 -3
  170. package/dest/services/reqresp/protocols/tx.d.ts +1 -1
  171. package/dest/services/reqresp/rate-limiter/index.d.ts +1 -1
  172. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +2 -2
  173. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  174. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +1 -1
  175. package/dest/services/reqresp/reqresp.d.ts +1 -41
  176. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  177. package/dest/services/reqresp/reqresp.js +14 -10
  178. package/dest/services/reqresp/status.d.ts +2 -2
  179. package/dest/services/reqresp/status.d.ts.map +1 -1
  180. package/dest/services/service.d.ts +2 -2
  181. package/dest/services/service.d.ts.map +1 -1
  182. package/dest/services/tx_collection/config.d.ts +1 -1
  183. package/dest/services/tx_collection/fast_tx_collection.d.ts +4 -9
  184. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  185. package/dest/services/tx_collection/fast_tx_collection.js +6 -1
  186. package/dest/services/tx_collection/index.d.ts +1 -1
  187. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  188. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  189. package/dest/services/tx_collection/slow_tx_collection.d.ts +6 -7
  190. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  191. package/dest/services/tx_collection/slow_tx_collection.js +2 -1
  192. package/dest/services/tx_collection/tx_collection.d.ts +12 -11
  193. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  194. package/dest/services/tx_collection/tx_collection.js +3 -2
  195. package/dest/services/tx_collection/tx_collection_sink.d.ts +3 -3
  196. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  197. package/dest/services/tx_collection/tx_collection_sink.js +34 -4
  198. package/dest/services/tx_collection/tx_source.d.ts +1 -1
  199. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  200. package/dest/services/tx_collection/tx_source.js +2 -2
  201. package/dest/services/tx_provider.d.ts +5 -4
  202. package/dest/services/tx_provider.d.ts.map +1 -1
  203. package/dest/services/tx_provider.js +8 -4
  204. package/dest/services/tx_provider_instrumentation.d.ts +1 -1
  205. package/dest/services/tx_provider_instrumentation.d.ts.map +1 -1
  206. package/dest/test-helpers/generate-peer-id-private-keys.d.ts +1 -1
  207. package/dest/test-helpers/get-ports.d.ts +1 -1
  208. package/dest/test-helpers/get-ports.d.ts.map +1 -1
  209. package/dest/test-helpers/index.d.ts +1 -1
  210. package/dest/test-helpers/make-enrs.d.ts +1 -1
  211. package/dest/test-helpers/make-enrs.js +1 -1
  212. package/dest/test-helpers/make-test-p2p-clients.d.ts +2 -2
  213. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  214. package/dest/test-helpers/mock-pubsub.d.ts +4 -4
  215. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  216. package/dest/test-helpers/mock-tx-helpers.d.ts +12 -0
  217. package/dest/test-helpers/mock-tx-helpers.d.ts.map +1 -0
  218. package/dest/test-helpers/mock-tx-helpers.js +19 -0
  219. package/dest/test-helpers/reqresp-nodes.d.ts +2 -2
  220. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  221. package/dest/test-helpers/reqresp-nodes.js +4 -3
  222. package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
  223. package/dest/testbench/p2p_client_testbench_worker.js +16 -9
  224. package/dest/testbench/parse_log_file.d.ts +1 -1
  225. package/dest/testbench/testbench.d.ts +1 -1
  226. package/dest/testbench/testbench.js +2 -2
  227. package/dest/testbench/worker_client_manager.d.ts +1 -1
  228. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  229. package/dest/types/index.d.ts +1 -1
  230. package/dest/util.d.ts +2 -1
  231. package/dest/util.d.ts.map +1 -1
  232. package/dest/util.js +11 -2
  233. package/dest/versioning.d.ts +2 -2
  234. package/dest/versioning.d.ts.map +1 -1
  235. package/dest/versioning.js +2 -2
  236. package/package.json +21 -21
  237. package/src/bootstrap/bootstrap.ts +15 -4
  238. package/src/client/factory.ts +17 -3
  239. package/src/client/interface.ts +8 -5
  240. package/src/client/p2p_client.ts +88 -74
  241. package/src/config.ts +33 -6
  242. package/src/enr/generate-enr.ts +1 -1
  243. package/src/errors/attestation-pool.error.ts +13 -0
  244. package/src/mem_pools/attestation_pool/attestation_pool.ts +46 -5
  245. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +89 -48
  246. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +107 -24
  247. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +98 -19
  248. package/src/mem_pools/attestation_pool/mocks.ts +11 -8
  249. package/src/mem_pools/instrumentation.ts +13 -0
  250. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +116 -48
  251. package/src/mem_pools/tx_pool/memory_tx_pool.ts +98 -12
  252. package/src/mem_pools/tx_pool/tx_pool.ts +20 -5
  253. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +115 -7
  254. package/src/msg_validators/attestation_validator/attestation_validator.ts +41 -6
  255. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +91 -0
  256. package/src/msg_validators/attestation_validator/index.ts +1 -0
  257. package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +63 -18
  258. package/src/msg_validators/tx_validator/archive_cache.ts +1 -1
  259. package/src/msg_validators/tx_validator/block_header_validator.ts +2 -2
  260. package/src/msg_validators/tx_validator/double_spend_validator.ts +1 -1
  261. package/src/msg_validators/tx_validator/factory.ts +13 -6
  262. package/src/msg_validators/tx_validator/index.ts +1 -0
  263. package/src/msg_validators/tx_validator/metadata_validator.ts +8 -42
  264. package/src/msg_validators/tx_validator/phases_validator.ts +3 -1
  265. package/src/msg_validators/tx_validator/test_utils.ts +1 -1
  266. package/src/msg_validators/tx_validator/timestamp_validator.ts +47 -0
  267. package/src/services/discv5/discV5_service.ts +2 -2
  268. package/src/services/dummy_service.ts +1 -1
  269. package/src/services/encoding.ts +81 -6
  270. package/src/services/libp2p/instrumentation.ts +10 -1
  271. package/src/services/libp2p/libp2p_service.ts +427 -157
  272. package/src/services/peer-manager/metrics.ts +10 -0
  273. package/src/services/peer-manager/peer_manager.ts +22 -14
  274. package/src/services/peer-manager/peer_scoring.ts +46 -3
  275. package/src/services/reqresp/interface.ts +1 -22
  276. package/src/services/reqresp/protocols/auth.ts +2 -2
  277. package/src/services/reqresp/protocols/block.ts +3 -2
  278. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +1 -1
  279. package/src/services/reqresp/protocols/status.ts +9 -8
  280. package/src/services/reqresp/reqresp.ts +15 -11
  281. package/src/services/service.ts +1 -1
  282. package/src/services/tx_collection/fast_tx_collection.ts +8 -5
  283. package/src/services/tx_collection/slow_tx_collection.ts +7 -6
  284. package/src/services/tx_collection/tx_collection.ts +12 -10
  285. package/src/services/tx_collection/tx_collection_sink.ts +34 -3
  286. package/src/services/tx_collection/tx_source.ts +2 -2
  287. package/src/services/tx_provider.ts +9 -7
  288. package/src/test-helpers/make-enrs.ts +1 -1
  289. package/src/test-helpers/mock-tx-helpers.ts +24 -0
  290. package/src/test-helpers/reqresp-nodes.ts +3 -2
  291. package/src/testbench/p2p_client_testbench_worker.ts +12 -5
  292. package/src/testbench/testbench.ts +2 -2
  293. package/src/util.ts +12 -2
  294. package/src/versioning.ts +3 -3
@@ -1,3 +1,4 @@
1
+ import { BlockNumber } from '@aztec/foundation/branded-types';
1
2
  import { createLogger } from '@aztec/foundation/log';
2
3
  import type { TypedEventEmitter } from '@aztec/foundation/types';
3
4
  import type { TxAddedToPoolStats } from '@aztec/stdlib/stats';
@@ -18,8 +19,11 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
18
19
  * Our tx pool, stored as a Map in-memory, with K: tx hash and V: the transaction.
19
20
  */
20
21
  private txs: Map<bigint, Tx>;
21
- private minedTxs: Map<bigint, number>;
22
+ private minedTxs: Map<bigint, BlockNumber>;
22
23
  private pendingTxs: Set<bigint>;
24
+ private deletedMinedTxHashes: Map<bigint, BlockNumber>;
25
+ // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
26
+ private blockToDeletedMinedTxHash: Map<BlockNumber, Set<bigint>>;
23
27
 
24
28
  private metrics: PoolInstrumentation<Tx>;
25
29
 
@@ -35,6 +39,8 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
35
39
  this.txs = new Map<bigint, Tx>();
36
40
  this.minedTxs = new Map();
37
41
  this.pendingTxs = new Set();
42
+ this.deletedMinedTxHashes = new Map();
43
+ this.blockToDeletedMinedTxHash = new Map();
38
44
  this.metrics = new PoolInstrumentation(telemetry, PoolName.TX_POOL, this.countTx);
39
45
  }
40
46
 
@@ -54,6 +60,19 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
54
60
  public markAsMined(txHashes: TxHash[], blockHeader: BlockHeader): Promise<void> {
55
61
  const keys = txHashes.map(x => x.toBigInt());
56
62
  for (const key of keys) {
63
+ // If this tx was previously soft-deleted, remove it from the deleted sets
64
+ if (this.deletedMinedTxHashes.has(key)) {
65
+ const originalBlock = this.deletedMinedTxHashes.get(key)!;
66
+ this.deletedMinedTxHashes.delete(key);
67
+ // Remove from block-to-hash mapping
68
+ const txHashesForBlock = this.blockToDeletedMinedTxHash.get(originalBlock);
69
+ if (txHashesForBlock) {
70
+ txHashesForBlock.delete(key);
71
+ if (txHashesForBlock.size === 0) {
72
+ this.blockToDeletedMinedTxHash.delete(originalBlock);
73
+ }
74
+ }
75
+ }
57
76
  this.minedTxs.set(key, blockHeader.globalVariables.blockNumber);
58
77
  this.pendingTxs.delete(key);
59
78
  }
@@ -83,27 +102,37 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
83
102
  (tx1, tx2) => -getPendingTxPriority(tx1).localeCompare(getPendingTxPriority(tx2)),
84
103
  );
85
104
  const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
86
- return txHashes.filter(txHash => this.pendingTxs.has(txHash.toBigInt()));
105
+
106
+ // No need to check deleted since pending txs are never soft-deleted
107
+ return txHashes.filter(txHash => {
108
+ const key = txHash.toBigInt();
109
+ return this.pendingTxs.has(key);
110
+ });
87
111
  }
88
112
 
89
- public getMinedTxHashes(): Promise<[TxHash, number][]> {
113
+ public getMinedTxHashes(): Promise<[TxHash, BlockNumber][]> {
90
114
  return Promise.resolve(
91
115
  Array.from(this.minedTxs.entries()).map(([txHash, blockNumber]) => [TxHash.fromBigInt(txHash), blockNumber]),
92
116
  );
93
117
  }
94
118
 
95
119
  public getPendingTxCount(): Promise<number> {
120
+ // Soft-deleted transactions are always mined, never pending
96
121
  return Promise.resolve(this.pendingTxs.size);
97
122
  }
98
123
 
99
- public getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | undefined> {
124
+ public getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | 'deleted' | undefined> {
100
125
  const key = txHash.toBigInt();
101
- if (this.pendingTxs.has(key)) {
102
- return Promise.resolve('pending');
126
+
127
+ if (this.deletedMinedTxHashes.has(key)) {
128
+ return Promise.resolve('deleted');
103
129
  }
104
130
  if (this.minedTxs.has(key)) {
105
131
  return Promise.resolve('mined');
106
132
  }
133
+ if (this.pendingTxs.has(key)) {
134
+ return Promise.resolve('pending');
135
+ }
107
136
  return Promise.resolve(undefined);
108
137
  }
109
138
 
@@ -124,6 +153,11 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
124
153
  return Promise.resolve(txHashes.map(txHash => this.txs.has(txHash.toBigInt())));
125
154
  }
126
155
 
156
+ async hasTx(txHash: TxHash): Promise<boolean> {
157
+ const result = await this.hasTxs([txHash]);
158
+ return result[0];
159
+ }
160
+
127
161
  public getArchivedTxByHash(): Promise<Tx | undefined> {
128
162
  return Promise.resolve(undefined);
129
163
  }
@@ -161,15 +195,34 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
161
195
 
162
196
  /**
163
197
  * Deletes transactions from the pool. Tx hashes that are not present are ignored.
164
- * @param txHashes - An array of tx hashes to be removed from the tx pool.
165
- * @returns The number of transactions that was deleted from the pool.
198
+ * Mined transactions are soft-deleted with a timestamp, pending transactions are permanently deleted.
199
+ * @param txHashes - An array of tx hashes to be deleted from the tx pool.
200
+ * @returns Empty promise.
166
201
  */
167
- public deleteTxs(txHashes: TxHash[]): Promise<void> {
202
+ public deleteTxs(txHashes: TxHash[], opts?: { permanently?: boolean }): Promise<void> {
168
203
  for (const txHash of txHashes) {
169
204
  const key = txHash.toBigInt();
170
- this.txs.delete(key);
171
- this.pendingTxs.delete(key);
172
- this.minedTxs.delete(key);
205
+ if (this.txs.has(key)) {
206
+ if (this.minedTxs.has(key)) {
207
+ const blockNumber = this.minedTxs.get(key)!;
208
+ this.minedTxs.delete(key);
209
+ // Soft-delete mined transactions: remove from mined set but keep in storage
210
+ if (opts?.permanently) {
211
+ // Permanently delete mined transactions if specified
212
+ this.txs.delete(key);
213
+ } else {
214
+ this.deletedMinedTxHashes.set(key, blockNumber);
215
+ if (!this.blockToDeletedMinedTxHash.has(blockNumber)) {
216
+ this.blockToDeletedMinedTxHash.set(blockNumber, new Set());
217
+ }
218
+ this.blockToDeletedMinedTxHash.get(blockNumber)!.add(key);
219
+ }
220
+ } else {
221
+ // Permanently delete pending transactions
222
+ this.txs.delete(key);
223
+ this.pendingTxs.delete(key);
224
+ }
225
+ }
173
226
  }
174
227
 
175
228
  return Promise.resolve();
@@ -196,4 +249,37 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
196
249
  markTxsAsNonEvictable(_: TxHash[]): Promise<void> {
197
250
  return Promise.resolve();
198
251
  }
252
+
253
+ /**
254
+ * Permanently deletes deleted mined transactions from blocks up to and including the specified block number.
255
+ * @param blockNumber - Block number threshold. Deleted mined txs from this block or earlier will be permanently deleted.
256
+ * @returns The number of transactions permanently deleted.
257
+ */
258
+ public cleanupDeletedMinedTxs(blockNumber: BlockNumber): Promise<number> {
259
+ let deletedCount = 0;
260
+ const blocksToDelete: BlockNumber[] = [];
261
+
262
+ // Find all blocks up to the specified block number
263
+ for (const [block, txHashes] of this.blockToDeletedMinedTxHash.entries()) {
264
+ if (block <= blockNumber) {
265
+ // Permanently delete all transactions from this block
266
+ for (const txHash of txHashes) {
267
+ this.txs.delete(txHash);
268
+ this.deletedMinedTxHashes.delete(txHash);
269
+ deletedCount++;
270
+ }
271
+ blocksToDelete.push(block);
272
+ }
273
+ }
274
+
275
+ // Clean up block-to-hash mapping
276
+ for (const block of blocksToDelete) {
277
+ this.blockToDeletedMinedTxHash.delete(block);
278
+ }
279
+
280
+ if (deletedCount > 0) {
281
+ this.log.debug(`Permanently deleted ${deletedCount} deleted mined txs from blocks up to ${blockNumber}`);
282
+ }
283
+ return Promise.resolve(deletedCount);
284
+ }
199
285
  }
@@ -1,3 +1,4 @@
1
+ import type { BlockNumber } from '@aztec/foundation/branded-types';
1
2
  import type { TypedEventEmitter } from '@aztec/foundation/types';
2
3
  import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
3
4
 
@@ -43,6 +44,13 @@ export interface TxPool extends TypedEventEmitter<TxPoolEvents> {
43
44
  */
44
45
  hasTxs(txHashes: TxHash[]): Promise<boolean[]>;
45
46
 
47
+ /**
48
+ * Checks if a transaction exists in the pool
49
+ * @param txHash - The hash of the transaction to check for
50
+ * @returns True if the transaction exists, false otherwise
51
+ */
52
+ hasTx(txHash: TxHash): Promise<boolean>;
53
+
46
54
  /**
47
55
  * Checks if an archived transaction exists in the pool and returns it.
48
56
  * @param txHash - The hash of the transaction, used as an ID.
@@ -68,7 +76,7 @@ export interface TxPool extends TypedEventEmitter<TxPoolEvents> {
68
76
  * Deletes transactions from the pool. Tx hashes that are not present are ignored.
69
77
  * @param txHashes - An array of tx hashes to be removed from the tx pool.
70
78
  */
71
- deleteTxs(txHashes: TxHash[]): Promise<void>;
79
+ deleteTxs(txHashes: TxHash[], opts?: { permanently?: boolean }): Promise<void>;
72
80
 
73
81
  /**
74
82
  * Gets all transactions currently in the tx pool.
@@ -95,14 +103,14 @@ export interface TxPool extends TypedEventEmitter<TxPoolEvents> {
95
103
  * Gets the hashes of mined transactions currently in the tx pool.
96
104
  * @returns An array of mined transaction hashes found in the tx pool.
97
105
  */
98
- getMinedTxHashes(): Promise<[tx: TxHash, blockNumber: number][]>;
106
+ getMinedTxHashes(): Promise<[tx: TxHash, blockNumber: BlockNumber][]>;
99
107
 
100
108
  /**
101
- * Returns whether the given tx hash is flagged as pending or mined.
109
+ * Returns whether the given tx hash is flagged as pending, mined, or deleted.
102
110
  * @param txHash - Hash of the tx to query.
103
- * @returns Pending or mined depending on its status, or undefined if not found.
111
+ * @returns Pending, mined, or deleted depending on its status, or undefined if not found.
104
112
  */
105
- getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | undefined>;
113
+ getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | 'deleted' | undefined>;
106
114
 
107
115
  /**
108
116
  * Configure the maximum size of the tx pool
@@ -118,4 +126,11 @@ export interface TxPool extends TypedEventEmitter<TxPoolEvents> {
118
126
  * @param txHashes - Hashes of the transactions to mark as non-evictible.
119
127
  */
120
128
  markTxsAsNonEvictable(txHashes: TxHash[]): Promise<void>;
129
+
130
+ /**
131
+ * Permanently deletes deleted mined transactions from blocks up to and including the specified block number.
132
+ * @param blockNumber - Block number threshold. Deleted mined txs from this block or earlier will be permanently deleted.
133
+ * @returns The number of transactions permanently deleted.
134
+ */
135
+ cleanupDeletedMinedTxs(blockNumber: BlockNumber): Promise<number>;
121
136
  }
@@ -1,3 +1,4 @@
1
+ import { BlockNumber } from '@aztec/foundation/branded-types';
1
2
  import { unfreeze } from '@aztec/foundation/types';
2
3
  import { GasFees } from '@aztec/stdlib/gas';
3
4
  import { mockTx } from '@aztec/stdlib/testing';
@@ -13,7 +14,7 @@ export function describeTxPool(getTxPool: () => TxPool) {
13
14
  let pool: TxPool;
14
15
 
15
16
  const minedBlockHeader = BlockHeader.empty({
16
- globalVariables: GlobalVariables.empty({ blockNumber: 1, timestamp: 0n }),
17
+ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(1), timestamp: 0n }),
17
18
  });
18
19
 
19
20
  beforeEach(() => {
@@ -54,14 +55,24 @@ export function describeTxPool(getTxPool: () => TxPool) {
54
55
  expect(txsFromEvent).toEqual(expect.arrayContaining([tx2, tx3]));
55
56
  });
56
57
 
57
- it('removes txs from the pool', async () => {
58
- const tx1 = await mockTx();
58
+ it('permanently deletes pending txs and soft-deletes mined txs', async () => {
59
+ const pendingTx = await mockTx(1);
60
+ const minedTx = await mockTx(2);
59
61
 
60
- await pool.addTxs([tx1]);
61
- await pool.deleteTxs([tx1.getTxHash()]);
62
+ await pool.addTxs([pendingTx, minedTx]);
63
+ await pool.markAsMined([minedTx.getTxHash()], minedBlockHeader);
64
+
65
+ // Delete a pending tx - should be permanently deleted
66
+ await pool.deleteTxs([pendingTx.getTxHash()]);
67
+ await expect(pool.getTxByHash(pendingTx.getTxHash())).resolves.toBeUndefined();
68
+ await expect(pool.getTxStatus(pendingTx.getTxHash())).resolves.toBeUndefined();
69
+
70
+ // Delete a mined tx - should be soft-deleted (still in storage)
71
+ await pool.deleteTxs([minedTx.getTxHash()]);
72
+ await expect(pool.getTxByHash(minedTx.getTxHash())).resolves.toBeDefined();
73
+ await expect(pool.getTxStatus(minedTx.getTxHash())).resolves.toEqual('deleted');
74
+ await expect(pool.getMinedTxHashes()).resolves.toEqual([]);
62
75
 
63
- await expect(pool.getTxByHash(tx1.getTxHash())).resolves.toBeFalsy();
64
- await expect(pool.getTxStatus(tx1.getTxHash())).resolves.toBeUndefined();
65
76
  await expect(pool.getPendingTxCount()).resolves.toEqual(0);
66
77
  });
67
78
 
@@ -203,4 +214,101 @@ export function describeTxPool(getTxPool: () => TxPool) {
203
214
  expect(poolTxHashes).toHaveLength(4);
204
215
  expect(poolTxHashes).toEqual([tx4, tx1, tx3, tx2].map(tx => tx.getTxHash()));
205
216
  });
217
+
218
+ describe('soft-delete', () => {
219
+ it('soft-deletes mined txs and keeps them in storage', async () => {
220
+ const txs = await Promise.all([mockTx(1), mockTx(2), mockTx(3)]);
221
+ await pool.addTxs(txs);
222
+
223
+ // Mark first tx as mined
224
+ await pool.markAsMined([txs[0].getTxHash()], minedBlockHeader);
225
+
226
+ // Verify initial state
227
+ await expect(pool.getPendingTxCount()).resolves.toBe(2);
228
+ await expect(pool.getTxByHash(txs[0].getTxHash())).resolves.toBeDefined();
229
+ await expect(pool.getTxByHash(txs[1].getTxHash())).resolves.toBeDefined();
230
+
231
+ // Delete mined tx - should be soft-deleted
232
+ await pool.deleteTxs([txs[0].getTxHash()]);
233
+
234
+ // Delete pending tx - should be permanently deleted
235
+ await pool.deleteTxs([txs[1].getTxHash()]);
236
+
237
+ // Verify mined tx still exists in storage but has 'deleted' status
238
+ await expect(pool.getTxByHash(txs[0].getTxHash())).resolves.toBeDefined();
239
+ await expect(pool.getTxStatus(txs[0].getTxHash())).resolves.toEqual('deleted');
240
+
241
+ // Verify pending tx is permanently deleted
242
+ await expect(pool.getTxByHash(txs[1].getTxHash())).resolves.toBeUndefined();
243
+ await expect(pool.getTxStatus(txs[1].getTxHash())).resolves.toBeUndefined();
244
+
245
+ // Verify remaining pending count
246
+ await expect(pool.getPendingTxCount()).resolves.toBe(1);
247
+
248
+ // Verify pending hashes don't include deleted txs
249
+ const pendingHashes = await pool.getPendingTxHashes();
250
+ expect(pendingHashes).toHaveLength(1);
251
+ expect(pendingHashes.map(h => h.toString())).toContain(txs[2].getTxHash().toString());
252
+ });
253
+
254
+ it('cleans up old deleted mined transactions', async () => {
255
+ const txs = await Promise.all([mockTx(1), mockTx(2), mockTx(3)]);
256
+ await pool.addTxs(txs);
257
+
258
+ // Mark first two as mined in block 1
259
+ await pool.markAsMined([txs[0].getTxHash(), txs[1].getTxHash()], minedBlockHeader);
260
+
261
+ // Soft-delete mined transactions
262
+ await pool.deleteTxs([txs[0].getTxHash(), txs[1].getTxHash()]);
263
+
264
+ // Clean up deleted mined txs from block 1 and earlier
265
+ const deletedCount = await pool.cleanupDeletedMinedTxs(BlockNumber(1));
266
+
267
+ // Verify old transactions are permanently deleted
268
+ expect(deletedCount).toBe(2);
269
+ await expect(pool.getTxByHash(txs[0].getTxHash())).resolves.toBeUndefined();
270
+ await expect(pool.getTxByHash(txs[1].getTxHash())).resolves.toBeUndefined();
271
+ await expect(pool.getTxByHash(txs[2].getTxHash())).resolves.toBeDefined();
272
+ });
273
+
274
+ it('does not clean up recent deleted mined transactions', async () => {
275
+ const txs = await Promise.all([mockTx(1), mockTx(2)]);
276
+ await pool.addTxs(txs);
277
+
278
+ // Mark as mined in block 2
279
+ const laterBlockHeader = BlockHeader.empty({
280
+ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(2), timestamp: 0n }),
281
+ });
282
+ await pool.markAsMined([txs[0].getTxHash()], laterBlockHeader);
283
+
284
+ // Soft-delete a mined transaction
285
+ await pool.deleteTxs([txs[0].getTxHash()]);
286
+
287
+ // Try to clean up with block 1 (before the mined block)
288
+ const deletedCount = await pool.cleanupDeletedMinedTxs(BlockNumber(1));
289
+
290
+ // Verify no transactions were cleaned up
291
+ expect(deletedCount).toBe(0);
292
+ await expect(pool.getTxByHash(txs[0].getTxHash())).resolves.toBeDefined();
293
+ });
294
+
295
+ it('restores deleted mined tx when it is mined again', async () => {
296
+ const tx = await mockTx(1);
297
+ await pool.addTxs([tx]);
298
+
299
+ // Mark as mined
300
+ await pool.markAsMined([tx.getTxHash()], minedBlockHeader);
301
+
302
+ // Soft-delete it
303
+ await pool.deleteTxs([tx.getTxHash()]);
304
+ await expect(pool.getTxStatus(tx.getTxHash())).resolves.toEqual('deleted');
305
+
306
+ // Mark as mined again (e.g., after a reorg)
307
+ await pool.markAsMined([tx.getTxHash()], minedBlockHeader);
308
+
309
+ // Should be back to mined status
310
+ await expect(pool.getTxStatus(tx.getTxHash())).resolves.toEqual('mined');
311
+ await expect(pool.getTxByHash(tx.getTxHash())).resolves.toBeDefined();
312
+ });
313
+ });
206
314
  }
@@ -1,31 +1,66 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
- import { NoCommitteeError } from '@aztec/ethereum';
2
+ import { NoCommitteeError } from '@aztec/ethereum/contracts';
3
+ import { type Logger, createLogger } from '@aztec/foundation/log';
3
4
  import { type BlockAttestation, type P2PValidator, PeerErrorSeverity } from '@aztec/stdlib/p2p';
4
5
 
5
6
  export class AttestationValidator implements P2PValidator<BlockAttestation> {
6
- private epochCache: EpochCacheInterface;
7
+ protected epochCache: EpochCacheInterface;
8
+ protected logger: Logger;
7
9
 
8
10
  constructor(epochCache: EpochCacheInterface) {
9
11
  this.epochCache = epochCache;
12
+ this.logger = createLogger('p2p:attestation-validator');
10
13
  }
11
14
 
12
15
  async validate(message: BlockAttestation): Promise<PeerErrorSeverity | undefined> {
16
+ const slotNumber = message.payload.header.slotNumber;
17
+
13
18
  try {
14
- const { currentSlot, nextSlot } = await this.epochCache.getProposerAttesterAddressInCurrentOrNextSlot();
19
+ const { currentProposer, nextProposer, currentSlot, nextSlot } =
20
+ await this.epochCache.getProposerAttesterAddressInCurrentOrNextSlot();
15
21
 
16
- const slotNumberBigInt = message.payload.header.slotNumber.toBigInt();
17
- if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) {
22
+ if (slotNumber !== currentSlot && slotNumber !== nextSlot) {
23
+ this.logger.warn(`Attestation slot ${slotNumber} is not current (${currentSlot}) or next (${nextSlot}) slot`);
18
24
  return PeerErrorSeverity.HighToleranceError;
19
25
  }
20
26
 
27
+ // Verify the signature is valid
21
28
  const attester = message.getSender();
22
- if (!(await this.epochCache.isInCommittee(slotNumberBigInt, attester))) {
29
+ if (attester === undefined) {
30
+ this.logger.warn(`Invalid signature in attestation for slot ${slotNumber}`);
31
+ return PeerErrorSeverity.LowToleranceError;
32
+ }
33
+
34
+ // Verify the attester is in the committee for this slot
35
+ if (!(await this.epochCache.isInCommittee(slotNumber, attester))) {
36
+ this.logger.warn(`Attester ${attester.toString()} is not in committee for slot ${slotNumber}`);
37
+ return PeerErrorSeverity.HighToleranceError;
38
+ }
39
+
40
+ // Verify the proposer signature matches the expected proposer for this slot
41
+ const proposer = message.getProposer();
42
+ const expectedProposer = slotNumber === currentSlot ? currentProposer : nextProposer;
43
+ if (!expectedProposer) {
44
+ this.logger.warn(`No proposer defined for slot ${slotNumber}`);
45
+ return PeerErrorSeverity.HighToleranceError;
46
+ }
47
+ if (!proposer) {
48
+ this.logger.warn(`Invalid proposer signature in attestation for slot ${slotNumber}`);
49
+ return PeerErrorSeverity.LowToleranceError;
50
+ }
51
+ if (!proposer.equals(expectedProposer)) {
52
+ this.logger.warn(
53
+ `Proposer signature mismatch in attestation. ` +
54
+ `Expected ${expectedProposer?.toString() ?? 'none'} but got ${proposer.toString()} for slot ${slotNumber}`,
55
+ );
23
56
  return PeerErrorSeverity.HighToleranceError;
24
57
  }
58
+
25
59
  return undefined;
26
60
  } catch (e) {
27
61
  // People shouldn't be sending us attestations if the committee doesn't exist
28
62
  if (e instanceof NoCommitteeError) {
63
+ this.logger.warn(`No committee exists for attestation for slot ${slotNumber}`);
29
64
  return PeerErrorSeverity.LowToleranceError;
30
65
  }
31
66
  throw e;
@@ -0,0 +1,91 @@
1
+ import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
+ import { type BlockAttestation, PeerErrorSeverity } from '@aztec/stdlib/p2p';
3
+ import { Attributes, Metrics, type TelemetryClient, ValueType } from '@aztec/telemetry-client';
4
+
5
+ import type { AttestationPool } from '../../mem_pools/attestation_pool/attestation_pool.js';
6
+ import { AttestationValidator } from './attestation_validator.js';
7
+
8
+ /**
9
+ * FishermanAttestationValidator extends the base AttestationValidator to add
10
+ * additional validation for fisherman nodes: verifying that attestations sign
11
+ * the same payload as the original proposal.
12
+ * Invalid attestations are rejected (not propagated), but peer penalization is
13
+ * handled by LibP2PService based on the fishermanMode config to ensure a better
14
+ * view of the network.
15
+ */
16
+ export class FishermanAttestationValidator extends AttestationValidator {
17
+ private invalidAttestationCounter;
18
+
19
+ constructor(
20
+ epochCache: EpochCacheInterface,
21
+ private attestationPool: AttestationPool,
22
+ telemetryClient: TelemetryClient,
23
+ ) {
24
+ super(epochCache);
25
+ this.logger = this.logger.createChild('[FISHERMAN]');
26
+
27
+ const meter = telemetryClient.getMeter('FishermanAttestationValidator');
28
+ this.invalidAttestationCounter = meter.createUpDownCounter(Metrics.VALIDATOR_INVALID_ATTESTATION_RECEIVED_COUNT, {
29
+ description: 'The number of invalid attestations received',
30
+ valueType: ValueType.INT,
31
+ });
32
+ }
33
+
34
+ override async validate(message: BlockAttestation): Promise<PeerErrorSeverity | undefined> {
35
+ // First run the standard validation
36
+ const baseValidationResult = await super.validate(message);
37
+ if (baseValidationResult !== undefined) {
38
+ // Track base validation failures (invalid signature, wrong committee, etc.)
39
+ this.invalidAttestationCounter.add(1, {
40
+ [Attributes.ERROR_TYPE]: 'base_validation_failed',
41
+ });
42
+ return baseValidationResult;
43
+ }
44
+
45
+ // fisherman validation: verify attestation payload matches proposal payload
46
+ const slotNumberBigInt = message.payload.header.slotNumber;
47
+ const attester = message.getSender();
48
+ const proposer = message.getProposer();
49
+
50
+ if (!attester || !proposer) {
51
+ return undefined;
52
+ }
53
+
54
+ const proposalId = message.archive.toString();
55
+ const proposal = await this.attestationPool.getBlockProposal(proposalId);
56
+
57
+ if (proposal) {
58
+ // Compare the attestation payload with the proposal payload
59
+ if (!message.payload.equals(proposal.payload)) {
60
+ this.logger.error(
61
+ `Attestation payload mismatch for slot ${slotNumberBigInt}! ` +
62
+ `Attester ${attester.toString()} signed different data than the proposal.`,
63
+ {
64
+ slot: slotNumberBigInt.toString(),
65
+ attester: attester.toString(),
66
+ proposer: proposer.toString(),
67
+ proposalArchive: proposal.archive.toString(),
68
+ attestationArchive: message.archive.toString(),
69
+ proposalHeader: proposal.payload.header.hash().toString(),
70
+ attestationHeader: message.payload.header.hash().toString(),
71
+ },
72
+ );
73
+
74
+ // Track invalid attestation metric
75
+ this.invalidAttestationCounter.add(1, {
76
+ [Attributes.ERROR_TYPE]: 'payload_mismatch',
77
+ });
78
+
79
+ // Return error to reject the message, but LibP2PService won't penalize in fisherman mode
80
+ return PeerErrorSeverity.LowToleranceError;
81
+ }
82
+ } else {
83
+ // We might receive attestations before proposals in some cases
84
+ this.logger.debug(
85
+ `Received attestation for slot ${slotNumberBigInt} but proposal not found yet. ` + `Proposal ID: ${proposalId}`,
86
+ );
87
+ }
88
+
89
+ return undefined;
90
+ }
91
+ }
@@ -1 +1,2 @@
1
1
  export * from './attestation_validator.js';
2
+ export * from './fisherman_attestation_validator.js';
@@ -1,43 +1,88 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
- import { NoCommitteeError } from '@aztec/ethereum';
2
+ import { NoCommitteeError } from '@aztec/ethereum/contracts';
3
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
4
  import { type BlockProposal, type P2PValidator, PeerErrorSeverity } from '@aztec/stdlib/p2p';
5
5
 
6
6
  export class BlockProposalValidator implements P2PValidator<BlockProposal> {
7
7
  private epochCache: EpochCacheInterface;
8
8
  private logger: Logger;
9
+ private txsPermitted: boolean;
9
10
 
10
- constructor(epochCache: EpochCacheInterface) {
11
+ constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }) {
11
12
  this.epochCache = epochCache;
13
+ this.txsPermitted = opts.txsPermitted;
12
14
  this.logger = createLogger('p2p:block_proposal_validator');
13
15
  }
14
16
 
15
17
  async validate(block: BlockProposal): Promise<PeerErrorSeverity | undefined> {
16
18
  try {
19
+ // Check signature validity first - invalid signatures are a high-severity issue
20
+ const proposer = block.getSender();
21
+ if (!proposer) {
22
+ this.logger.debug(`Penalizing peer for block proposal with invalid signature`);
23
+ return PeerErrorSeverity.MidToleranceError;
24
+ }
25
+
26
+ // Check if transactions are permitted when the proposal contains transaction hashes
27
+ const embeddedTxCount = block.txs?.length ?? 0;
28
+ if (!this.txsPermitted && (block.txHashes.length > 0 || embeddedTxCount > 0)) {
29
+ this.logger.debug(
30
+ `Penalizing peer for block proposal with ${block.txHashes.length} transaction(s) when transactions are not permitted`,
31
+ );
32
+ return PeerErrorSeverity.MidToleranceError;
33
+ }
34
+
35
+ // If there are embedded txs, they must be listed in txHashes; if there are no txHashes, there must be no txs
36
+ const hashSet = new Set(block.txHashes.map(h => h.toString()));
37
+ const missingTxHashes =
38
+ embeddedTxCount > 0
39
+ ? block.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
40
+ : [];
41
+ if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
42
+ this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
43
+ embeddedTxCount,
44
+ txHashesLength: block.txHashes.length,
45
+ missingTxHashes,
46
+ });
47
+ return PeerErrorSeverity.MidToleranceError;
48
+ }
49
+
17
50
  const { currentProposer, nextProposer, currentSlot, nextSlot } =
18
51
  await this.epochCache.getProposerAttesterAddressInCurrentOrNextSlot();
19
52
 
20
53
  // Check that the attestation is for the current or next slot
21
- const slotNumberBigInt = block.payload.header.slotNumber.toBigInt();
22
- if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) {
23
- this.logger.debug(
24
- `Penalizing peer for invalid slot number ${slotNumberBigInt}, current slot: ${currentSlot}, next slot: ${nextSlot}`,
25
- );
54
+ const slotNumber = block.payload.header.slotNumber;
55
+ if (slotNumber !== currentSlot && slotNumber !== nextSlot) {
56
+ this.logger.debug(`Penalizing peer for invalid slot number ${slotNumber}`, { currentSlot, nextSlot });
26
57
  return PeerErrorSeverity.HighToleranceError;
27
58
  }
28
59
 
29
60
  // Check that the block proposal is from the current or next proposer
30
- const proposer = block.getSender();
31
- if (
32
- currentProposer !== undefined &&
33
- !proposer.equals(currentProposer) &&
34
- nextProposer !== undefined &&
35
- !proposer.equals(nextProposer)
36
- ) {
37
- this.logger.debug(
38
- `Penalizing peer for invalid proposer ${proposer.toString()}, current proposer: ${currentProposer.toString()}, next proposer: ${nextProposer.toString()}`,
39
- );
40
- return PeerErrorSeverity.HighToleranceError;
61
+ if (slotNumber === currentSlot && currentProposer !== undefined && !proposer.equals(currentProposer)) {
62
+ this.logger.debug(`Penalizing peer for invalid proposer for current slot ${slotNumber}`, {
63
+ currentProposer,
64
+ nextProposer,
65
+ proposer: proposer.toString(),
66
+ });
67
+ return PeerErrorSeverity.MidToleranceError;
68
+ }
69
+
70
+ if (slotNumber === nextSlot && nextProposer !== undefined && !proposer.equals(nextProposer)) {
71
+ this.logger.debug(`Penalizing peer for invalid proposer for next slot ${slotNumber}`, {
72
+ currentProposer,
73
+ nextProposer,
74
+ proposer: proposer.toString(),
75
+ });
76
+ return PeerErrorSeverity.MidToleranceError;
77
+ }
78
+
79
+ // Validate tx hashes for all txs embedded in the proposal
80
+ if (!(await Promise.all(block.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
81
+ this.logger.warn(`Penalizing peer for invalid tx hashes in block proposal`, {
82
+ proposer,
83
+ slotNumber,
84
+ });
85
+ return PeerErrorSeverity.LowToleranceError;
41
86
  }
42
87
 
43
88
  return undefined;
@@ -1,4 +1,4 @@
1
- import type { Fr } from '@aztec/foundation/fields';
1
+ import type { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import type { ArchiveSource } from '@aztec/p2p';
3
3
  import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
4
4
  import { MerkleTreeId } from '@aztec/stdlib/trees';