@aztec/p2p 0.0.1-commit.f1df4d2 → 0.0.1-commit.f2ce05ee

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 (189) hide show
  1. package/dest/client/factory.d.ts +3 -3
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +2 -2
  4. package/dest/client/interface.d.ts +9 -2
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +4 -3
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +11 -5
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
  10. package/dest/config.d.ts +5 -4
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +2 -1
  13. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +94 -87
  14. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  15. package/dest/mem_pools/attestation_pool/attestation_pool.js +411 -3
  16. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  17. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +351 -85
  19. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  20. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/index.js +1 -2
  22. package/dest/mem_pools/index.d.ts +2 -2
  23. package/dest/mem_pools/index.d.ts.map +1 -1
  24. package/dest/mem_pools/index.js +1 -1
  25. package/dest/mem_pools/interface.d.ts +3 -3
  26. package/dest/mem_pools/interface.d.ts.map +1 -1
  27. package/dest/mem_pools/tx_pool_v2/archive/index.d.ts +2 -0
  28. package/dest/mem_pools/tx_pool_v2/archive/index.d.ts.map +1 -0
  29. package/dest/mem_pools/tx_pool_v2/archive/index.js +1 -0
  30. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +43 -0
  31. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +103 -0
  33. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +47 -0
  34. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -0
  35. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +119 -0
  36. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +17 -0
  37. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -0
  38. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +90 -0
  39. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +19 -0
  40. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -0
  41. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +89 -0
  42. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +10 -0
  43. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -0
  44. package/dest/mem_pools/tx_pool_v2/eviction/index.js +11 -0
  45. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +131 -0
  46. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -0
  47. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +17 -0
  48. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts +15 -0
  49. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -0
  50. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +63 -0
  51. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts +17 -0
  52. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -0
  53. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +91 -0
  54. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +16 -0
  55. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -0
  56. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +70 -0
  57. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +20 -0
  58. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -0
  59. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +63 -0
  60. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +15 -0
  61. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -0
  62. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +19 -0
  63. package/dest/mem_pools/tx_pool_v2/index.d.ts +5 -0
  64. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -0
  65. package/dest/mem_pools/tx_pool_v2/index.js +4 -0
  66. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +197 -0
  67. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -0
  68. package/dest/mem_pools/tx_pool_v2/interfaces.js +6 -0
  69. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +71 -0
  70. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -0
  71. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +95 -0
  72. package/dest/mem_pools/tx_pool_v2/tx_pool_bench_metrics.d.ts +26 -0
  73. package/dest/mem_pools/tx_pool_v2/tx_pool_bench_metrics.d.ts.map +1 -0
  74. package/dest/mem_pools/tx_pool_v2/tx_pool_bench_metrics.js +70 -0
  75. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +99 -0
  76. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
  77. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +332 -0
  78. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +55 -0
  79. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -0
  80. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +156 -0
  81. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +69 -0
  82. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -0
  83. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +748 -0
  84. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
  85. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  86. package/dest/services/dummy_service.d.ts +6 -2
  87. package/dest/services/dummy_service.d.ts.map +1 -1
  88. package/dest/services/dummy_service.js +3 -0
  89. package/dest/services/gossipsub/index.d.ts +3 -0
  90. package/dest/services/gossipsub/index.d.ts.map +1 -0
  91. package/dest/services/gossipsub/index.js +2 -0
  92. package/dest/services/gossipsub/scoring.d.ts +21 -3
  93. package/dest/services/gossipsub/scoring.d.ts.map +1 -1
  94. package/dest/services/gossipsub/scoring.js +24 -7
  95. package/dest/services/gossipsub/topic_score_params.d.ts +161 -0
  96. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -0
  97. package/dest/services/gossipsub/topic_score_params.js +324 -0
  98. package/dest/services/libp2p/libp2p_service.d.ts +74 -33
  99. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  100. package/dest/services/libp2p/libp2p_service.js +317 -258
  101. package/dest/services/peer-manager/peer_scoring.d.ts +1 -1
  102. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  103. package/dest/services/peer-manager/peer_scoring.js +25 -2
  104. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -4
  105. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  106. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +8 -8
  107. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +6 -4
  108. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  109. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
  110. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +15 -10
  111. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  112. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +12 -11
  113. package/dest/services/service.d.ts +18 -1
  114. package/dest/services/service.d.ts.map +1 -1
  115. package/dest/services/tx_collection/config.d.ts +3 -3
  116. package/dest/services/tx_collection/config.js +3 -3
  117. package/dest/services/tx_collection/fast_tx_collection.d.ts +4 -5
  118. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  119. package/dest/services/tx_collection/fast_tx_collection.js +10 -14
  120. package/dest/services/tx_collection/index.d.ts +1 -1
  121. package/dest/services/tx_collection/proposal_tx_collector.d.ts +12 -12
  122. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  123. package/dest/services/tx_collection/proposal_tx_collector.js +4 -5
  124. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  125. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  126. package/dest/test-helpers/reqresp-nodes.js +2 -1
  127. package/dest/test-helpers/testbench-utils.d.ts +10 -16
  128. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  129. package/dest/test-helpers/testbench-utils.js +42 -31
  130. package/dest/testbench/p2p_client_testbench_worker.js +1 -1
  131. package/package.json +14 -14
  132. package/src/client/factory.ts +3 -4
  133. package/src/client/interface.ts +13 -1
  134. package/src/client/p2p_client.ts +20 -8
  135. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  136. package/src/config.ts +10 -2
  137. package/src/mem_pools/attestation_pool/attestation_pool.ts +444 -90
  138. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +436 -100
  139. package/src/mem_pools/attestation_pool/index.ts +9 -2
  140. package/src/mem_pools/index.ts +1 -1
  141. package/src/mem_pools/interface.ts +2 -2
  142. package/src/mem_pools/tx_pool_v2/README.md +209 -0
  143. package/src/mem_pools/tx_pool_v2/archive/index.ts +1 -0
  144. package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +120 -0
  145. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +147 -0
  146. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +118 -0
  147. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +111 -0
  148. package/src/mem_pools/tx_pool_v2/eviction/index.ts +23 -0
  149. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +164 -0
  150. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +74 -0
  151. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +101 -0
  152. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +86 -0
  153. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +72 -0
  154. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +31 -0
  155. package/src/mem_pools/tx_pool_v2/index.ts +11 -0
  156. package/src/mem_pools/tx_pool_v2/interfaces.ts +227 -0
  157. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +161 -0
  158. package/src/mem_pools/tx_pool_v2/tx_pool_bench_metrics.ts +77 -0
  159. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +417 -0
  160. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +212 -0
  161. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +882 -0
  162. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
  163. package/src/services/dummy_service.ts +6 -0
  164. package/src/services/gossipsub/README.md +626 -0
  165. package/src/services/gossipsub/index.ts +2 -0
  166. package/src/services/gossipsub/scoring.ts +29 -5
  167. package/src/services/gossipsub/topic_score_params.ts +451 -0
  168. package/src/services/libp2p/libp2p_service.ts +323 -261
  169. package/src/services/peer-manager/peer_scoring.ts +25 -0
  170. package/src/services/reqresp/batch-tx-requester/README.md +7 -7
  171. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +11 -11
  172. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +22 -13
  173. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +21 -15
  174. package/src/services/service.ts +20 -0
  175. package/src/services/tx_collection/config.ts +6 -6
  176. package/src/services/tx_collection/fast_tx_collection.ts +14 -24
  177. package/src/services/tx_collection/index.ts +1 -1
  178. package/src/services/tx_collection/proposal_tx_collector.ts +12 -14
  179. package/src/test-helpers/reqresp-nodes.ts +2 -1
  180. package/src/test-helpers/testbench-utils.ts +27 -39
  181. package/src/testbench/p2p_client_testbench_worker.ts +1 -1
  182. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  183. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  184. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  185. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  186. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  187. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  188. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  189. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
@@ -1,5 +1,5 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
- import { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { randomInt } from '@aztec/foundation/crypto/random';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
@@ -45,7 +45,7 @@ import {
45
45
  type GossipsubMessage,
46
46
  gossipsub,
47
47
  } from '@chainsafe/libp2p-gossipsub';
48
- import { createPeerScoreParams, createTopicScoreParams } from '@chainsafe/libp2p-gossipsub/score';
48
+ import { createPeerScoreParams } from '@chainsafe/libp2p-gossipsub/score';
49
49
  import { SignaturePolicy } from '@chainsafe/libp2p-gossipsub/types';
50
50
  import { noise } from '@chainsafe/libp2p-noise';
51
51
  import { yamux } from '@chainsafe/libp2p-yamux';
@@ -59,29 +59,29 @@ import { ENR } from '@nethermindeth/enr';
59
59
  import { createLibp2p } from 'libp2p';
60
60
 
61
61
  import type { P2PConfig } from '../../config.js';
62
- import { ProposalSlotCapExceededError } from '../../errors/attestation-pool.error.js';
63
62
  import type { MemPools } from '../../mem_pools/interface.js';
64
63
  import {
65
64
  BlockProposalValidator,
66
65
  CheckpointAttestationValidator,
67
66
  CheckpointProposalValidator,
67
+ DoubleSpendTxValidator,
68
68
  FishermanAttestationValidator,
69
+ getDefaultAllowedSetupFunctions,
69
70
  } from '../../msg_validators/index.js';
70
71
  import { MessageSeenValidator } from '../../msg_validators/msg_seen_validator/msg_seen_validator.js';
71
- import { getDefaultAllowedSetupFunctions } from '../../msg_validators/tx_validator/allowed_public_setup.js';
72
72
  import {
73
73
  type MessageValidator,
74
74
  createTxMessageValidators,
75
75
  createTxReqRespValidator,
76
76
  } from '../../msg_validators/tx_validator/factory.js';
77
- import { DoubleSpendTxValidator } from '../../msg_validators/tx_validator/index.js';
78
77
  import { GossipSubEvent } from '../../types/index.js';
79
78
  import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
80
79
  import { getVersions } from '../../versioning.js';
81
80
  import { AztecDatastore } from '../data_store.js';
82
81
  import { DiscV5Service } from '../discv5/discV5_service.js';
83
82
  import { SnappyTransform, fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
84
- import { gossipScoreThresholds } from '../gossipsub/scoring.js';
83
+ import { APP_SPECIFIC_WEIGHT, gossipScoreThresholds } from '../gossipsub/scoring.js';
84
+ import { createAllTopicScoreParams } from '../gossipsub/topic_score_params.js';
85
85
  import type { PeerManagerInterface } from '../peer-manager/interface.js';
86
86
  import { PeerManager } from '../peer-manager/peer_manager.js';
87
87
  import { PeerScoring } from '../peer-manager/peer_scoring.js';
@@ -97,19 +97,19 @@ import {
97
97
  type ReqRespSubProtocolValidators,
98
98
  type SubProtocolMap,
99
99
  ValidationError,
100
- } from '../reqresp/interface.js';
101
- import { reqRespBlockTxsHandler } from '../reqresp/protocols/block_txs/block_txs_handler.js';
102
- import { reqGoodbyeHandler } from '../reqresp/protocols/goodbye.js';
100
+ } from '../reqresp/index.js';
103
101
  import {
104
102
  AuthRequest,
105
103
  BlockTxsRequest,
106
104
  BlockTxsResponse,
107
105
  StatusMessage,
108
106
  pingHandler,
107
+ reqGoodbyeHandler,
109
108
  reqRespBlockHandler,
109
+ reqRespBlockTxsHandler,
110
110
  reqRespStatusHandler,
111
111
  reqRespTxHandler,
112
- } from '../reqresp/protocols/index.js';
112
+ } from '../reqresp/index.js';
113
113
  import { ReqResp } from '../reqresp/reqresp.js';
114
114
  import type {
115
115
  P2PBlockReceivedCallback,
@@ -128,9 +128,9 @@ interface ValidationResult {
128
128
  type ValidationOutcome = { allPassed: true } | { allPassed: false; failure: ValidationResult };
129
129
 
130
130
  // REFACTOR: Unify with the type above
131
- type ReceivedMessageValidationResult<T> =
132
- | { obj: T; result: Exclude<TopicValidatorResult, TopicValidatorResult.Reject> }
133
- | { obj?: undefined; result: TopicValidatorResult.Reject };
131
+ type ReceivedMessageValidationResult<T, M = undefined> =
132
+ | { obj: T; result: Exclude<TopicValidatorResult, TopicValidatorResult.Reject>; metadata?: M }
133
+ | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M };
134
134
 
135
135
  /**
136
136
  * Lib P2P implementation of the P2PService interface.
@@ -149,6 +149,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
149
149
 
150
150
  private feesCache: { blockNumber: BlockNumber; gasFees: GasFees } | undefined;
151
151
 
152
+ /** Callback invoked when a duplicate proposal is detected (triggers slashing). */
153
+ private duplicateProposalCallback?: (info: {
154
+ slot: SlotNumber;
155
+ proposer: EthAddress;
156
+ type: 'checkpoint' | 'block';
157
+ }) => void;
158
+
152
159
  /**
153
160
  * Callback for when a block is received from a peer.
154
161
  * @param block - The block received from the peer.
@@ -177,9 +184,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
177
184
  protected node: PubSubLibp2p,
178
185
  private peerDiscoveryService: PeerDiscoveryService,
179
186
  private reqresp: ReqRespInterface,
180
- private peerManager: PeerManagerInterface,
187
+ protected peerManager: PeerManagerInterface,
181
188
  protected mempools: MemPools,
182
- private archiver: L2BlockSource & ContractDataSource,
189
+ protected archiver: L2BlockSource & ContractDataSource,
183
190
  private epochCache: EpochCacheInterface,
184
191
  private proofVerifier: ClientProtocolCircuitVerifier,
185
192
  private worldStateSynchronizer: WorldStateSynchronizer,
@@ -305,11 +312,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
305
312
  const versions = getVersions(config);
306
313
  const protocolVersion = compressComponentVersions(versions);
307
314
 
308
- const txTopic = createTopicString(TopicType.tx, protocolVersion);
309
- const blockProposalTopic = createTopicString(TopicType.block_proposal, protocolVersion);
310
- const checkpointProposalTopic = createTopicString(TopicType.checkpoint_proposal, protocolVersion);
311
- const checkpointAttestationTopic = createTopicString(TopicType.checkpoint_attestation, protocolVersion);
312
-
313
315
  const preferredPeersEnrs: ENR[] = config.preferredPeers.map(enr => ENR.decodeTxt(enr));
314
316
  const directPeers = (
315
317
  await Promise.all(
@@ -329,6 +331,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
329
331
 
330
332
  const announceTcpMultiaddr = config.p2pIp ? [convertToMultiaddr(config.p2pIp, p2pPort, 'tcp')] : [];
331
333
 
334
+ // Create dynamic topic score params based on network configuration
335
+ const l1Constants = epochCache.getL1Constants();
336
+ const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
337
+ slotDurationMs: l1Constants.slotDuration * 1000,
338
+ heartbeatIntervalMs: config.gossipsubInterval,
339
+ targetCommitteeSize: l1Constants.targetCommitteeSize,
340
+ blockDurationMs: config.blockDurationMs,
341
+ });
342
+
332
343
  const node = await createLibp2p({
333
344
  start: false,
334
345
  peerId,
@@ -424,28 +435,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
424
435
  scoreParams: createPeerScoreParams({
425
436
  // IPColocation factor can be disabled for local testing - default to -5
426
437
  IPColocationFactorWeight: config.debugDisableColocationPenalty ? 0 : -5.0,
427
- topics: {
428
- [txTopic]: createTopicScoreParams({
429
- topicWeight: 1,
430
- invalidMessageDeliveriesWeight: -20,
431
- invalidMessageDeliveriesDecay: 0.5,
432
- }),
433
- [blockProposalTopic]: createTopicScoreParams({
434
- topicWeight: 1,
435
- invalidMessageDeliveriesWeight: -20,
436
- invalidMessageDeliveriesDecay: 0.5,
437
- }),
438
- [checkpointProposalTopic]: createTopicScoreParams({
439
- topicWeight: 1,
440
- invalidMessageDeliveriesWeight: -20,
441
- invalidMessageDeliveriesDecay: 0.5,
442
- }),
443
- [checkpointAttestationTopic]: createTopicScoreParams({
444
- topicWeight: 1,
445
- invalidMessageDeliveriesWeight: -20,
446
- invalidMessageDeliveriesDecay: 0.5,
447
- }),
448
- },
438
+ topics: topicScoreParams,
449
439
  }),
450
440
  }) as (components: GossipSubComponents) => GossipSub,
451
441
  components: (components: { connectionManager: ConnectionManager }) => ({
@@ -471,8 +461,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
471
461
  epochCache,
472
462
  );
473
463
 
474
- // Update gossipsub score params
475
- node.services.pubsub.score.params.appSpecificWeight = 10;
464
+ // Configure application-specific scoring for gossipsub.
465
+ // The weight scales app score to align with gossipsub thresholds:
466
+ // - Disconnect (-50) × 10 = -500 = gossipThreshold (stops receiving gossip)
467
+ // - Ban (-100) × 10 = -1000 = publishThreshold (cannot publish)
468
+ // Note: positive topic scores can offset penalties, so alignment is best-effort.
469
+ node.services.pubsub.score.params.appSpecificWeight = APP_SPECIFIC_WEIGHT;
476
470
  node.services.pubsub.score.params.appSpecificScore = (peerId: string) =>
477
471
  peerManager.shouldDisableP2PGossip(peerId) ? -Infinity : peerManager.getPeerScore(peerId);
478
472
 
@@ -524,7 +518,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
524
518
  };
525
519
 
526
520
  if (!this.config.disableTransactions) {
527
- const blockTxsHandler = reqRespBlockTxsHandler(this.mempools.attestationPool, this.mempools.txPool);
521
+ const blockTxsHandler = reqRespBlockTxsHandler(
522
+ this.mempools.attestationPool,
523
+ this.archiver,
524
+ this.mempools.txPool,
525
+ );
528
526
  requestResponseHandlers[ReqRespSubProtocol.BLOCK_TXS] = blockTxsHandler.bind(this);
529
527
  }
530
528
 
@@ -665,6 +663,16 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
665
663
  this.checkpointReceivedCallback = callback;
666
664
  }
667
665
 
666
+ /**
667
+ * Registers a callback to be invoked when a duplicate proposal is detected.
668
+ * This callback is triggered on the first duplicate (when count goes from 1 to 2).
669
+ */
670
+ public registerDuplicateProposalCallback(
671
+ callback: (info: { slot: SlotNumber; proposer: EthAddress; type: 'checkpoint' | 'block' }) => void,
672
+ ): void {
673
+ this.duplicateProposalCallback = callback;
674
+ }
675
+
668
676
  /**
669
677
  * Subscribes to a topic.
670
678
  * @param topic - The topic to subscribe to.
@@ -851,13 +859,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
851
859
  return;
852
860
  }
853
861
 
854
- protected async validateReceivedMessage<T>(
855
- validationFunc: () => Promise<ReceivedMessageValidationResult<T>>,
862
+ protected async validateReceivedMessage<T, M = undefined>(
863
+ validationFunc: () => Promise<ReceivedMessageValidationResult<T, M>>,
856
864
  msgId: string,
857
865
  source: PeerId,
858
866
  topicType: TopicType,
859
- ): Promise<ReceivedMessageValidationResult<T>> {
860
- let resultAndObj: ReceivedMessageValidationResult<T> = { result: TopicValidatorResult.Reject };
867
+ ): Promise<ReceivedMessageValidationResult<T, M>> {
868
+ let resultAndObj: ReceivedMessageValidationResult<T, M> = { result: TopicValidatorResult.Reject };
861
869
  const timer = new Timer();
862
870
  try {
863
871
  resultAndObj = await validationFunc();
@@ -929,47 +937,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
929
937
  msgId: string,
930
938
  source: PeerId,
931
939
  ): Promise<void> {
932
- const validationFunc: () => Promise<ReceivedMessageValidationResult<CheckpointAttestation>> = async () => {
933
- const attestation = CheckpointAttestation.fromBuffer(payloadData);
934
- const pool = this.mempools.attestationPool;
935
- const validationResult = await this.validateCheckpointAttestation(source, attestation);
936
- const isValid = validationResult.result === 'accept';
937
- const exists = isValid && (await pool.hasCheckpointAttestation(attestation));
938
-
939
- let canAdd = true;
940
- if (isValid && !exists) {
941
- const slot = attestation.payload.header.slotNumber;
942
- const { committee } = await this.epochCache.getCommittee(slot);
943
- const committeeSize = committee?.length ?? 0;
944
- canAdd = await pool.canAddCheckpointAttestation(attestation, committeeSize);
945
- }
946
-
947
- this.logger.trace(`Validate propagated checkpoint attestation`, {
948
- isValid,
949
- exists,
950
- canAdd,
951
- [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber.toString(),
952
- [Attributes.P2P_ID]: source.toString(),
953
- });
954
-
955
- if (validationResult.result === 'reject') {
956
- return { result: TopicValidatorResult.Reject };
957
- } else if (validationResult.result === 'ignore' || exists) {
958
- return { result: TopicValidatorResult.Ignore, obj: attestation };
959
- } else if (!canAdd) {
960
- this.logger.warn(`Dropping checkpoint attestation due to per-(slot, proposalId) attestation cap`, {
961
- slot: attestation.payload.header.slotNumber.toString(),
962
- archive: attestation.archive.toString(),
963
- source: source.toString(),
964
- });
965
- return { result: TopicValidatorResult.Ignore, obj: attestation };
966
- } else {
967
- return { result: TopicValidatorResult.Accept, obj: attestation };
968
- }
969
- };
970
-
971
940
  const { result, obj: attestation } = await this.validateReceivedMessage<CheckpointAttestation>(
972
- validationFunc,
941
+ () => this.validateAndStoreCheckpointAttestation(source, CheckpointAttestation.fromBuffer(payloadData)),
973
942
  msgId,
974
943
  source,
975
944
  TopicType.checkpoint_attestation,
@@ -979,8 +948,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
979
948
  return;
980
949
  }
981
950
 
982
- this.logger.debug(
983
- `Received checkpoint attestation for slot ${attestation.slotNumber} from external peer ${source.toString()}`,
951
+ this.logger.verbose(
952
+ `Received valid checkpoint attestation for slot ${attestation.slotNumber} from external peer ${source.toString()}`,
984
953
  {
985
954
  p2pMessageIdentifier: await attestation.p2pMessageLoggingIdentifier(),
986
955
  slot: attestation.slotNumber,
@@ -988,60 +957,154 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
988
957
  source: source.toString(),
989
958
  },
990
959
  );
991
-
992
- await this.mempools.attestationPool.addCheckpointAttestations([attestation]);
993
960
  }
994
961
 
995
- private async processBlockFromPeer(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
996
- const validationFunc: () => Promise<ReceivedMessageValidationResult<BlockProposal>> = async () => {
997
- const block = BlockProposal.fromBuffer(payloadData);
998
- const validationResult = await this.validateBlockProposal(source, block);
999
- const isValid = validationResult.result === 'accept';
1000
- const pool = this.mempools.attestationPool;
962
+ /** Validates a checkpoint attestation and adds it to the pool. Penalizes the peer if validation fails. */
963
+ @trackSpan('Libp2pService.validateAndStoreCheckpointAttestation', (_peerId, attestation) => ({
964
+ [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber.toString(),
965
+ }))
966
+ protected async validateAndStoreCheckpointAttestation(
967
+ peerId: PeerId,
968
+ attestation: CheckpointAttestation,
969
+ ): Promise<ReceivedMessageValidationResult<CheckpointAttestation>> {
970
+ const validationResult = await this.checkpointAttestationValidator.validate(attestation);
1001
971
 
1002
- const exists = isValid && (await pool.hasBlockProposal(block));
1003
- const canAdd = isValid && (await pool.canAddProposal(block));
972
+ if (validationResult.result === 'reject') {
973
+ this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
974
+ this.peerManager.penalizePeer(peerId, validationResult.severity);
975
+ return { result: TopicValidatorResult.Reject };
976
+ }
1004
977
 
1005
- this.logger.trace(`Validate propagated block proposal`, {
1006
- isValid,
1007
- exists,
1008
- canAdd,
1009
- [Attributes.SLOT_NUMBER]: block.slotNumber.toString(),
1010
- [Attributes.P2P_ID]: source.toString(),
978
+ if (validationResult.result === 'ignore') {
979
+ return { result: TopicValidatorResult.Ignore, obj: attestation };
980
+ }
981
+
982
+ // Get committee size for the attestation's slot
983
+ const slot = attestation.payload.header.slotNumber;
984
+ const { committee } = await this.epochCache.getCommittee(slot);
985
+ const committeeSize = committee?.length ?? 0;
986
+
987
+ // Try to add the attestation: this handles existence check, cap check, and adding in one call
988
+ const { added, alreadyExists } = await this.mempools.attestationPool.tryAddCheckpointAttestation(
989
+ attestation,
990
+ committeeSize,
991
+ );
992
+
993
+ this.logger.trace(`Validate propagated checkpoint attestation`, {
994
+ added,
995
+ alreadyExists,
996
+ [Attributes.SLOT_NUMBER]: slot.toString(),
997
+ [Attributes.P2P_ID]: peerId.toString(),
998
+ });
999
+
1000
+ // Duplicate attestation received, no need to re-broadcast
1001
+ if (alreadyExists) {
1002
+ return { result: TopicValidatorResult.Ignore, obj: attestation };
1003
+ }
1004
+
1005
+ // Could not add (cap reached), no need to re-broadcast
1006
+ if (!added) {
1007
+ this.logger.warn(`Dropping checkpoint attestation due to per-(slot, proposalId) attestation cap`, {
1008
+ slot: slot.toString(),
1009
+ archive: attestation.archive.toString(),
1010
+ source: peerId.toString(),
1011
1011
  });
1012
+ return { result: TopicValidatorResult.Ignore, obj: attestation };
1013
+ }
1012
1014
 
1013
- if (validationResult.result === 'reject') {
1014
- return { result: TopicValidatorResult.Reject };
1015
- } else if (validationResult.result === 'ignore' || exists) {
1016
- return { result: TopicValidatorResult.Ignore, obj: block };
1017
- } else if (!canAdd) {
1018
- this.peerManager.penalizePeer(source, PeerErrorSeverity.MidToleranceError);
1019
- this.logger.warn(`Penalizing peer for block proposal exceeding per-slot cap`, {
1020
- slot: block.slotNumber.toString(),
1021
- archive: block.archive.toString(),
1022
- source: source.toString(),
1023
- });
1024
- return { result: TopicValidatorResult.Reject };
1025
- } else {
1026
- return { result: TopicValidatorResult.Accept, obj: block };
1027
- }
1028
- };
1015
+ // Attestation was added successfully
1016
+ return { result: TopicValidatorResult.Accept, obj: attestation };
1017
+ }
1029
1018
 
1030
- const { result, obj: block } = await this.validateReceivedMessage<BlockProposal>(
1031
- validationFunc,
1019
+ protected async processBlockFromPeer(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
1020
+ const {
1021
+ result,
1022
+ obj: block,
1023
+ metadata: { isEquivocated } = {},
1024
+ } = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean }>(
1025
+ () => this.validateAndStoreBlockProposal(source, BlockProposal.fromBuffer(payloadData)),
1032
1026
  msgId,
1033
1027
  source,
1034
1028
  TopicType.block_proposal,
1035
1029
  );
1036
1030
 
1037
- if (!result || !block) {
1031
+ // If not accepted or equivocated, return
1032
+ if (result !== TopicValidatorResult.Accept || !block || isEquivocated) {
1038
1033
  return;
1039
1034
  }
1040
1035
 
1041
1036
  await this.processValidBlockProposal(block, source);
1042
1037
  }
1043
1038
 
1044
- // REVIEW: callback pattern https://github.com/AztecProtocol/aztec-packages/issues/7963
1039
+ /** Validates a block proposal. Triggers a penalization to the peer that sent it if invalid. Adds to the mempool if valid. */
1040
+ @trackSpan('Libp2pService.validateAndStoreBlockProposal', (_peerId, block) => ({
1041
+ [Attributes.BLOCK_NUMBER]: block.blockNumber.toString(),
1042
+ [Attributes.SLOT_NUMBER]: block.slotNumber.toString(),
1043
+ }))
1044
+ protected async validateAndStoreBlockProposal(
1045
+ peerId: PeerId,
1046
+ block: BlockProposal,
1047
+ ): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean }>> {
1048
+ const validationResult = await this.blockProposalValidator.validate(block);
1049
+
1050
+ if (validationResult.result === 'reject') {
1051
+ this.logger.warn(`Penalizing peer ${peerId} for block proposal validation failure`);
1052
+ this.peerManager.penalizePeer(peerId, validationResult.severity);
1053
+ return { result: TopicValidatorResult.Reject };
1054
+ }
1055
+
1056
+ if (validationResult.result === 'ignore') {
1057
+ return { result: TopicValidatorResult.Ignore, obj: block };
1058
+ }
1059
+
1060
+ // Try to add the proposal: this handles existence check, cap check, and adding in one call
1061
+ const { added, alreadyExists, totalForPosition } = await this.mempools.attestationPool.tryAddBlockProposal(block);
1062
+ const isEquivocated = totalForPosition !== undefined && totalForPosition > 1;
1063
+
1064
+ // Duplicate proposal received, no need to re-broadcast
1065
+ if (alreadyExists) {
1066
+ this.logger.debug(`Ignoring duplicate block proposal received`, {
1067
+ ...block.toBlockInfo(),
1068
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
1069
+ proposer: block.getSender()?.toString(),
1070
+ source: peerId.toString(),
1071
+ });
1072
+ return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated } };
1073
+ }
1074
+
1075
+ // Too many blocks received for this slot and index, penalize peer and do not re-broadcast
1076
+ if (!added) {
1077
+ this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1078
+ this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
1079
+ ...block.toBlockInfo(),
1080
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
1081
+ totalForPosition,
1082
+ proposer: block.getSender()?.toString(),
1083
+ source: peerId.toString(),
1084
+ });
1085
+ return { result: TopicValidatorResult.Reject, metadata: { isEquivocated } };
1086
+ }
1087
+
1088
+ // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
1089
+ // and do re-broadcast it so other nodes in the network know to slash the proposer
1090
+ if (isEquivocated) {
1091
+ const proposer = block.getSender();
1092
+ this.logger.warn(`Detected duplicate block proposal (equivocation) at slot ${block.slotNumber}`, {
1093
+ ...block.toBlockInfo(),
1094
+ source: peerId.toString(),
1095
+ proposer: proposer?.toString(),
1096
+ });
1097
+ // Invoke the duplicate callback on the first duplicate spotted only
1098
+ if (proposer && totalForPosition === 2) {
1099
+ this.duplicateProposalCallback?.({ slot: block.slotNumber, proposer, type: 'block' });
1100
+ }
1101
+ return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated } };
1102
+ }
1103
+
1104
+ // Otherwise, we're good to go!
1105
+ return { result: TopicValidatorResult.Accept, obj: block };
1106
+ }
1107
+
1045
1108
  // REFACTOR(palla): This method should be moved to the p2p_client or to a separate component,
1046
1109
  // should not be here as it does not deal with p2p networking.
1047
1110
  @trackSpan('Libp2pService.processValidBlockProposal', async block => ({
@@ -1049,7 +1112,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1049
1112
  [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
1050
1113
  [Attributes.P2P_ID]: await block.p2pMessageLoggingIdentifier().then(i => i.toString()),
1051
1114
  }))
1052
- private async processValidBlockProposal(block: BlockProposal, sender: PeerId) {
1115
+ protected async processValidBlockProposal(block: BlockProposal, sender: PeerId) {
1053
1116
  const slot = block.slotNumber;
1054
1117
  this.logger.verbose(`Received block proposal for slot ${slot} from external peer ${sender.toString()}.`, {
1055
1118
  p2pMessageIdentifier: await block.p2pMessageLoggingIdentifier(),
@@ -1057,22 +1120,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1057
1120
  ...block.toBlockInfo(),
1058
1121
  });
1059
1122
 
1060
- // Attempt to add proposal
1061
- try {
1062
- await this.mempools.attestationPool.addBlockProposal(block);
1063
- } catch (err: unknown) {
1064
- // Drop proposals if we hit per-slot cap in the attestation pool; rethrow unknown errors
1065
- if (err instanceof ProposalSlotCapExceededError) {
1066
- this.logger.warn(`Dropping block proposal due to per-slot proposal cap`, {
1067
- slot: String(slot),
1068
- archive: block.archive.toString(),
1069
- error: (err as Error).message,
1070
- });
1071
- return;
1072
- }
1073
- throw err;
1074
- }
1075
-
1076
1123
  // Mark the txs in this proposal as non-evictable
1077
1124
  await this.mempools.txPool.markTxsAsNonEvictable(block.txHashes);
1078
1125
 
@@ -1088,67 +1135,145 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1088
1135
  * Handle a gossiped checkpoint proposal.
1089
1136
  * Validates and processes the checkpoint proposal, then triggers the callback for attestation.
1090
1137
  */
1091
- private async handleGossipedCheckpointProposal(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
1092
- // TODO(palla/mbps): This pattern is repeated across multiple message handlers, consider abstracting it.
1093
- const validationFunc: () => Promise<ReceivedMessageValidationResult<CheckpointProposal>> = async () => {
1094
- const checkpoint = CheckpointProposal.fromBuffer(payloadData);
1095
- const validationResult = await this.validateCheckpointProposal(source, checkpoint);
1096
- const isValid = validationResult.result === 'accept';
1097
- const pool = this.mempools.attestationPool;
1098
-
1099
- const exists = isValid && (await pool.hasCheckpointProposal(checkpoint));
1100
- const canAdd = isValid && (await pool.canAddCheckpointProposal(checkpoint));
1101
-
1102
- this.logger.trace(`Validate propagated checkpoint proposal`, {
1103
- isValid,
1104
- exists,
1105
- canAdd,
1138
+ protected async handleGossipedCheckpointProposal(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
1139
+ const {
1140
+ result,
1141
+ obj: checkpoint,
1142
+ metadata: { isEquivocated, processBlock } = {},
1143
+ } = await this.validateReceivedMessage<CheckpointProposal, { isEquivocated: boolean; processBlock: boolean }>(
1144
+ () => this.validateAndStoreCheckpointProposal(source, CheckpointProposal.fromBuffer(payloadData)),
1145
+ msgId,
1146
+ source,
1147
+ TopicType.checkpoint_proposal,
1148
+ );
1149
+
1150
+ // If the checkpoint contained a valid last block, we process it even if the checkpoint itself is to be rejected
1151
+ // TODO(palla/mbps): Is this ok? Should we be considering a block from a checkpoint that was equivocated?
1152
+ if (processBlock && checkpoint?.getBlockProposal()) {
1153
+ await this.processValidBlockProposal(checkpoint.getBlockProposal()!, source);
1154
+ }
1155
+
1156
+ if (result !== TopicValidatorResult.Accept || !checkpoint || isEquivocated) {
1157
+ return;
1158
+ }
1159
+
1160
+ await this.processValidCheckpointProposal(checkpoint.toCore(), source);
1161
+ }
1162
+
1163
+ /**
1164
+ * Validates a checkpoint proposal. Penalizes peer if validation fails. Adds the checkpoint and
1165
+ * its last block (if present) to the mempool if valid. Triggers equivocation detection on both.
1166
+ */
1167
+ @trackSpan('Libp2pService.validateAndStoreCheckpointProposal', (_peerId, checkpoint) => ({
1168
+ [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1169
+ }))
1170
+ protected async validateAndStoreCheckpointProposal(
1171
+ peerId: PeerId,
1172
+ checkpoint: CheckpointProposal,
1173
+ ): Promise<ReceivedMessageValidationResult<CheckpointProposal, { isEquivocated: boolean; processBlock: boolean }>> {
1174
+ const validationResult = await this.checkpointProposalValidator.validate(checkpoint);
1175
+
1176
+ if (validationResult.result === 'reject') {
1177
+ this.logger.warn(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
1178
+ this.peerManager.penalizePeer(peerId, validationResult.severity);
1179
+ return { result: TopicValidatorResult.Reject };
1180
+ }
1181
+
1182
+ if (validationResult.result === 'ignore') {
1183
+ return { result: TopicValidatorResult.Ignore, obj: checkpoint };
1184
+ }
1185
+
1186
+ // Extract and try to add the block proposal first if present
1187
+ const blockProposal = checkpoint.getBlockProposal();
1188
+ let processBlock = false;
1189
+ if (blockProposal) {
1190
+ this.logger.debug(`Validating block proposal from propagated checkpoint`, {
1106
1191
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1107
- [Attributes.P2P_ID]: source.toString(),
1192
+ [Attributes.P2P_ID]: peerId.toString(),
1108
1193
  });
1109
-
1110
- if (validationResult.result === 'reject') {
1111
- return { result: TopicValidatorResult.Reject };
1112
- } else if (validationResult.result === 'ignore' || exists) {
1113
- return { result: TopicValidatorResult.Ignore, obj: checkpoint };
1114
- } else if (!canAdd) {
1115
- this.peerManager.penalizePeer(source, PeerErrorSeverity.MidToleranceError);
1116
- this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
1117
- slot: checkpoint.slotNumber.toString(),
1118
- archive: checkpoint.archive.toString(),
1119
- source: source.toString(),
1194
+ const {
1195
+ result,
1196
+ obj,
1197
+ metadata: { isEquivocated } = {},
1198
+ } = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1199
+ if (result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1200
+ this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
1201
+ [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1202
+ [Attributes.P2P_ID]: peerId.toString(),
1203
+ isEquivocated,
1204
+ result,
1120
1205
  });
1121
1206
  return { result: TopicValidatorResult.Reject };
1122
- } else {
1123
- return { result: TopicValidatorResult.Accept, obj: checkpoint };
1207
+ } else if (result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1208
+ processBlock = true;
1124
1209
  }
1125
- };
1210
+ }
1126
1211
 
1127
- const { result, obj: checkpoint } = await this.validateReceivedMessage<CheckpointProposal>(
1128
- validationFunc,
1129
- msgId,
1130
- source,
1131
- TopicType.checkpoint_proposal,
1132
- );
1212
+ // Try to add the checkpoint proposal core: this handles existence check, cap check, and adding in one call
1213
+ const checkpointCore = checkpoint.toCore();
1214
+ const tryAddResult = await this.mempools.attestationPool.tryAddCheckpointProposal(checkpointCore);
1215
+ const { added, alreadyExists, totalForPosition } = tryAddResult;
1216
+ const isEquivocated = totalForPosition !== undefined && totalForPosition > 1;
1217
+
1218
+ // Duplicate proposal received, do not re-broadcast
1219
+ if (alreadyExists) {
1220
+ this.logger.debug(`Ignoring duplicate checkpoint proposal received`, {
1221
+ ...checkpoint.toCheckpointInfo(),
1222
+ source: peerId.toString(),
1223
+ });
1224
+ return {
1225
+ result: TopicValidatorResult.Ignore,
1226
+ obj: checkpoint,
1227
+ metadata: { isEquivocated, processBlock },
1228
+ };
1229
+ }
1133
1230
 
1134
- if (result !== TopicValidatorResult.Accept || !checkpoint) {
1135
- return;
1231
+ // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
1232
+ // Note: We still return the checkpoint obj so the lastBlock can be processed if valid
1233
+ if (!added) {
1234
+ this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1235
+ this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
1236
+ ...checkpoint.toCheckpointInfo(),
1237
+ totalForPosition,
1238
+ source: peerId.toString(),
1239
+ });
1240
+ return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } };
1241
+ }
1242
+
1243
+ // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
1244
+ // and do re-broadcast it so other nodes in the network know to slash the proposer
1245
+ if (isEquivocated) {
1246
+ const proposer = checkpoint.getSender();
1247
+ this.logger.warn(`Detected duplicate checkpoint proposal (equivocation) at slot ${checkpoint.slotNumber}`, {
1248
+ ...checkpoint.toCheckpointInfo(),
1249
+ source: peerId.toString(),
1250
+ proposer: proposer?.toString(),
1251
+ });
1252
+ // Invoke the duplicate callback on the first duplicate spotted only
1253
+ if (proposer && totalForPosition === 2) {
1254
+ this.duplicateProposalCallback?.({ slot: checkpoint.slotNumber, proposer, type: 'checkpoint' });
1255
+ }
1256
+ return {
1257
+ result: TopicValidatorResult.Accept,
1258
+ obj: checkpoint,
1259
+ metadata: { isEquivocated, processBlock },
1260
+ };
1136
1261
  }
1137
1262
 
1138
- await this.processValidCheckpointProposal(checkpoint, source);
1263
+ // Otherwise, we're good to go!
1264
+ return { result: TopicValidatorResult.Accept, obj: checkpoint, metadata: { processBlock, isEquivocated } };
1139
1265
  }
1140
1266
 
1141
1267
  /**
1142
1268
  * Process a validated checkpoint proposal.
1143
- * Extracts and processes the last block proposal (if present) first, then processes the checkpoint.
1144
- * The block callback is invoked before the checkpoint callback.
1269
+ * Note: The proposal was already added to the pool by tryAddCheckpointProposal in handleGossipedCheckpointProposal.
1145
1270
  */
1146
1271
  @trackSpan('Libp2pService.processValidCheckpointProposal', async checkpoint => ({
1147
1272
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber,
1148
1273
  [Attributes.BLOCK_ARCHIVE]: checkpoint.archive.toString(),
1149
1274
  [Attributes.P2P_ID]: await checkpoint.p2pMessageLoggingIdentifier().then(i => i.toString()),
1150
1275
  }))
1151
- private async processValidCheckpointProposal(checkpoint: CheckpointProposal, sender: PeerId) {
1276
+ protected async processValidCheckpointProposal(checkpoint: CheckpointProposalCore, sender: PeerId) {
1152
1277
  const slot = checkpoint.slotNumber;
1153
1278
  this.logger.verbose(`Received checkpoint proposal for slot ${slot} from external peer ${sender.toString()}.`, {
1154
1279
  p2pMessageIdentifier: await checkpoint.p2pMessageLoggingIdentifier(),
@@ -1157,37 +1282,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1157
1282
  source: sender.toString(),
1158
1283
  });
1159
1284
 
1160
- // Extract block proposal before adding to pool (pool stores them separately)
1161
- const blockProposal = checkpoint.getBlockProposal();
1162
-
1163
- // Add proposal to the pool (this extracts and stores block proposal separately)
1164
- await this.mempools.attestationPool.addCheckpointProposal(checkpoint);
1165
-
1166
- // Mark txs as non-evictable if present (from the last block)
1167
- if (checkpoint.txHashes.length > 0) {
1168
- await this.mempools.txPool.markTxsAsNonEvictable(checkpoint.txHashes);
1169
- }
1170
-
1171
- // If there was a last block proposal, invoke the block callback first for validation.
1172
- // Note: The block proposal is already stored in the pool by addCheckpointProposal.
1173
- if (blockProposal) {
1174
- const isValid = await this.blockReceivedCallback(blockProposal, sender);
1175
- if (!isValid) {
1176
- this.logger.warn(`Block proposal from checkpoint failed validation`, {
1177
- slot: slot.toString(),
1178
- archive: checkpoint.archive.toString(),
1179
- blockNumber: blockProposal.blockNumber.toString(),
1180
- });
1181
- return;
1182
- }
1183
- }
1184
-
1185
1285
  // Call the checkpoint received callback with the core version (without lastBlock)
1186
1286
  // to validate and potentially generate attestations
1187
- const attestations = await this.checkpointReceivedCallback(checkpoint.toCore(), sender);
1287
+ const attestations = await this.checkpointReceivedCallback(checkpoint, sender);
1188
1288
  if (attestations && attestations.length > 0) {
1189
1289
  // If the callback returned attestations, add them to the pool and propagate them
1190
- await this.mempools.attestationPool.addCheckpointAttestations(attestations);
1290
+ await this.mempools.attestationPool.addOwnCheckpointAttestations(attestations);
1191
1291
  for (const attestation of attestations) {
1192
1292
  await this.propagate(attestation);
1193
1293
  }
@@ -1216,7 +1316,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1216
1316
  @trackSpan('Libp2pService.validateRequestedBlockTxs', request => ({
1217
1317
  [Attributes.BLOCK_ARCHIVE]: request.archiveRoot.toString(),
1218
1318
  }))
1219
- private async validateRequestedBlockTxs(
1319
+ protected async validateRequestedBlockTxs(
1220
1320
  request: BlockTxsRequest,
1221
1321
  response: BlockTxsResponse,
1222
1322
  peerId: PeerId,
@@ -1346,7 +1446,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1346
1446
  @trackSpan('Libp2pService.validateRequestedBlock', (requestedBlockNumber, _responseBlock) => ({
1347
1447
  [Attributes.BLOCK_NUMBER]: requestedBlockNumber.toString(),
1348
1448
  }))
1349
- private async validateRequestedBlock(
1449
+ protected async validateRequestedBlock(
1350
1450
  requestedBlockNumber: Fr,
1351
1451
  responseBlock: L2Block,
1352
1452
  peerId: PeerId,
@@ -1379,7 +1479,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1379
1479
  }
1380
1480
  }
1381
1481
 
1382
- private async validateRequestedTx(tx: Tx, peerId: PeerId, txValidator: TxValidator, requested?: Set<`0x${string}`>) {
1482
+ protected async validateRequestedTx(
1483
+ tx: Tx,
1484
+ peerId: PeerId,
1485
+ txValidator: TxValidator,
1486
+ requested?: Set<`0x${string}`>,
1487
+ ) {
1383
1488
  const penalize = (severity: PeerErrorSeverity) => this.peerManager.penalizePeer(peerId, severity);
1384
1489
  if (requested && !requested.has(tx.getTxHash().toString())) {
1385
1490
  penalize(PeerErrorSeverity.MidToleranceError);
@@ -1393,7 +1498,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1393
1498
  }
1394
1499
  }
1395
1500
 
1396
- private createRequestedTxValidator(): TxValidator {
1501
+ protected createRequestedTxValidator(): TxValidator {
1397
1502
  return createTxReqRespValidator(this.proofVerifier, {
1398
1503
  l1ChainId: this.config.l1ChainId,
1399
1504
  rollupVersion: this.config.rollupVersion,
@@ -1601,50 +1706,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1601
1706
  const result = await this.checkpointAttestationValidator.validate(attestation);
1602
1707
 
1603
1708
  if (result.result === 'reject') {
1604
- this.logger.debug(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1605
- this.peerManager.penalizePeer(peerId, result.severity);
1606
- }
1607
-
1608
- return result;
1609
- }
1610
-
1611
- /**
1612
- * Validate a block proposal.
1613
- *
1614
- * @param block - The block proposal to validate.
1615
- * @returns True if the block proposal is valid, false otherwise.
1616
- */
1617
- @trackSpan('Libp2pService.validateBlockProposal', (_peerId, block) => ({
1618
- [Attributes.SLOT_NUMBER]: block.slotNumber.toString(),
1619
- }))
1620
- public async validateBlockProposal(peerId: PeerId, block: BlockProposal): Promise<P2PValidationResult> {
1621
- const result = await this.blockProposalValidator.validate(block);
1622
-
1623
- if (result.result === 'reject') {
1624
- this.logger.debug(`Penalizing peer ${peerId} for block proposal validation failure`);
1625
- this.peerManager.penalizePeer(peerId, result.severity);
1626
- }
1627
-
1628
- return result;
1629
- }
1630
-
1631
- /**
1632
- * Validate a checkpoint proposal.
1633
- *
1634
- * @param checkpoint - The checkpoint proposal to validate.
1635
- * @returns True if the checkpoint proposal is valid, false otherwise.
1636
- */
1637
- @trackSpan('Libp2pService.validateCheckpointProposal', (_peerId, checkpoint) => ({
1638
- [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1639
- }))
1640
- public async validateCheckpointProposal(
1641
- peerId: PeerId,
1642
- checkpoint: CheckpointProposal,
1643
- ): Promise<P2PValidationResult> {
1644
- const result = await this.checkpointProposalValidator.validate(checkpoint);
1645
-
1646
- if (result.result === 'reject') {
1647
- this.logger.debug(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
1709
+ this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1648
1710
  this.peerManager.penalizePeer(peerId, result.severity);
1649
1711
  }
1650
1712