@aztec/p2p 4.0.0-nightly.20260111 → 4.0.0-nightly.20260113

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 (86) hide show
  1. package/dest/client/interface.d.ts +18 -5
  2. package/dest/client/interface.d.ts.map +1 -1
  3. package/dest/client/p2p_client.d.ts +9 -12
  4. package/dest/client/p2p_client.d.ts.map +1 -1
  5. package/dest/client/p2p_client.js +59 -103
  6. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +61 -42
  7. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  8. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  9. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  10. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +225 -262
  11. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +21 -18
  12. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +113 -108
  14. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +17 -16
  15. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  16. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +89 -128
  17. package/dest/mem_pools/attestation_pool/mocks.d.ts +7 -6
  18. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  19. package/dest/mem_pools/attestation_pool/mocks.js +9 -8
  20. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +4 -4
  21. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  22. package/dest/msg_validators/attestation_validator/attestation_validator.js +12 -10
  23. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +5 -5
  24. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  25. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +5 -5
  26. package/dest/msg_validators/index.d.ts +2 -2
  27. package/dest/msg_validators/index.d.ts.map +1 -1
  28. package/dest/msg_validators/index.js +1 -1
  29. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +9 -0
  30. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -0
  31. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +6 -0
  32. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +9 -0
  33. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -0
  34. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +6 -0
  35. package/dest/msg_validators/proposal_validator/index.d.ts +4 -0
  36. package/dest/msg_validators/proposal_validator/index.d.ts.map +1 -0
  37. package/dest/msg_validators/proposal_validator/index.js +3 -0
  38. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -0
  39. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -0
  40. package/dest/msg_validators/{block_proposal_validator/block_proposal_validator.js → proposal_validator/proposal_validator.js} +19 -21
  41. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +23 -0
  42. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +1 -0
  43. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +183 -0
  44. package/dest/services/dummy_service.d.ts +6 -2
  45. package/dest/services/dummy_service.d.ts.map +1 -1
  46. package/dest/services/dummy_service.js +3 -0
  47. package/dest/services/encoding.d.ts +1 -1
  48. package/dest/services/encoding.d.ts.map +1 -1
  49. package/dest/services/encoding.js +4 -2
  50. package/dest/services/libp2p/libp2p_service.d.ts +26 -10
  51. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  52. package/dest/services/libp2p/libp2p_service.js +218 -88
  53. package/dest/services/service.d.ts +16 -3
  54. package/dest/services/service.d.ts.map +1 -1
  55. package/dest/testbench/p2p_client_testbench_worker.js +25 -11
  56. package/dest/testbench/worker_client_manager.d.ts +1 -1
  57. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  58. package/dest/testbench/worker_client_manager.js +6 -1
  59. package/package.json +14 -14
  60. package/src/client/interface.ts +19 -4
  61. package/src/client/p2p_client.ts +69 -110
  62. package/src/mem_pools/attestation_pool/attestation_pool.ts +68 -41
  63. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +231 -287
  64. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +162 -140
  65. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +141 -164
  66. package/src/mem_pools/attestation_pool/mocks.ts +13 -9
  67. package/src/msg_validators/attestation_validator/attestation_validator.ts +16 -13
  68. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +7 -7
  69. package/src/msg_validators/index.ts +1 -1
  70. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +10 -0
  71. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +13 -0
  72. package/src/msg_validators/proposal_validator/index.ts +3 -0
  73. package/src/msg_validators/{block_proposal_validator/block_proposal_validator.ts → proposal_validator/proposal_validator.ts} +23 -28
  74. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +206 -0
  75. package/src/services/dummy_service.ts +6 -0
  76. package/src/services/encoding.ts +3 -1
  77. package/src/services/libp2p/libp2p_service.ts +258 -94
  78. package/src/services/service.ts +19 -4
  79. package/src/testbench/p2p_client_testbench_worker.ts +34 -11
  80. package/src/testbench/worker_client_manager.ts +6 -1
  81. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +0 -12
  82. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +0 -1
  83. package/dest/msg_validators/block_proposal_validator/index.d.ts +0 -2
  84. package/dest/msg_validators/block_proposal_validator/index.d.ts.map +0 -1
  85. package/dest/msg_validators/block_proposal_validator/index.js +0 -1
  86. package/src/msg_validators/block_proposal_validator/index.ts +0 -1
@@ -1,5 +1,5 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
- import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { BlockNumber } 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';
@@ -13,8 +13,10 @@ import type { ContractDataSource } from '@aztec/stdlib/contract';
13
13
  import { GasFees } from '@aztec/stdlib/gas';
14
14
  import type { ClientProtocolCircuitVerifier, PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
15
15
  import {
16
- BlockAttestation,
17
16
  BlockProposal,
17
+ CheckpointAttestation,
18
+ CheckpointProposal,
19
+ type CheckpointProposalCore,
18
20
  type Gossipable,
19
21
  P2PClientType,
20
22
  P2PMessage,
@@ -60,8 +62,9 @@ import type { P2PConfig } from '../../config.js';
60
62
  import { ProposalSlotCapExceededError } from '../../errors/attestation-pool.error.js';
61
63
  import type { MemPools } from '../../mem_pools/interface.js';
62
64
  import {
63
- AttestationValidator,
64
65
  BlockProposalValidator,
66
+ CheckpointAttestationValidator,
67
+ CheckpointProposalValidator,
65
68
  FishermanAttestationValidator,
66
69
  } from '../../msg_validators/index.js';
67
70
  import { MessageSeenValidator } from '../../msg_validators/msg_seen_validator/msg_seen_validator.js';
@@ -108,7 +111,12 @@ import {
108
111
  reqRespTxHandler,
109
112
  } from '../reqresp/protocols/index.js';
110
113
  import { ReqResp } from '../reqresp/reqresp.js';
111
- import type { P2PBlockReceivedCallback, P2PService, PeerDiscoveryService } from '../service.js';
114
+ import type {
115
+ P2PBlockReceivedCallback,
116
+ P2PCheckpointReceivedCallback,
117
+ P2PService,
118
+ PeerDiscoveryService,
119
+ } from '../service.js';
112
120
  import { P2PInstrumentation } from './instrumentation.js';
113
121
 
114
122
  interface ValidationResult {
@@ -132,8 +140,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
132
140
  private msgIdSeenValidators: Record<TopicType, MessageSeenValidator> = {} as Record<TopicType, MessageSeenValidator>;
133
141
 
134
142
  // Message validators
135
- private attestationValidator: AttestationValidator;
136
143
  private blockProposalValidator: BlockProposalValidator;
144
+ private checkpointProposalValidator: CheckpointProposalValidator;
145
+ private checkpointAttestationValidator: CheckpointAttestationValidator;
137
146
 
138
147
  private protocolVersion = '';
139
148
  private topicStrings: Record<TopicType, string> = {} as Record<TopicType, string>;
@@ -147,6 +156,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
147
156
  */
148
157
  private blockReceivedCallback: P2PBlockReceivedCallback;
149
158
 
159
+ /**
160
+ * Callback for when a checkpoint proposal is received from a peer.
161
+ * @param checkpoint - The checkpoint proposal received from the peer.
162
+ * @returns The attestations for the checkpoint, if any.
163
+ */
164
+ private checkpointReceivedCallback: P2PCheckpointReceivedCallback;
165
+
150
166
  private gossipSubEventHandler: (e: CustomEvent<GossipsubMessage>) => void;
151
167
 
152
168
  private instrumentation: P2PInstrumentation;
@@ -180,7 +196,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
180
196
 
181
197
  this.msgIdSeenValidators[TopicType.tx] = new MessageSeenValidator(config.seenMessageCacheSize);
182
198
  this.msgIdSeenValidators[TopicType.block_proposal] = new MessageSeenValidator(config.seenMessageCacheSize);
183
- this.msgIdSeenValidators[TopicType.block_attestation] = new MessageSeenValidator(config.seenMessageCacheSize);
199
+ this.msgIdSeenValidators[TopicType.checkpoint_proposal] = new MessageSeenValidator(config.seenMessageCacheSize);
200
+ this.msgIdSeenValidators[TopicType.checkpoint_attestation] = new MessageSeenValidator(config.seenMessageCacheSize);
184
201
 
185
202
  const versions = getVersions(config);
186
203
  this.protocolVersion = compressComponentVersions(versions);
@@ -188,25 +205,40 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
188
205
 
189
206
  this.topicStrings[TopicType.tx] = createTopicString(TopicType.tx, this.protocolVersion);
190
207
  this.topicStrings[TopicType.block_proposal] = createTopicString(TopicType.block_proposal, this.protocolVersion);
191
- this.topicStrings[TopicType.block_attestation] = createTopicString(
192
- TopicType.block_attestation,
208
+ this.topicStrings[TopicType.checkpoint_proposal] = createTopicString(
209
+ TopicType.checkpoint_proposal,
210
+ this.protocolVersion,
211
+ );
212
+ this.topicStrings[TopicType.checkpoint_attestation] = createTopicString(
213
+ TopicType.checkpoint_attestation,
193
214
  this.protocolVersion,
194
215
  );
195
216
 
196
- // Use FishermanAttestationValidator in fisherman mode to validate attestation payloads against proposals
197
- this.attestationValidator = config.fishermanMode
198
- ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry)
199
- : new AttestationValidator(epochCache);
200
217
  this.blockProposalValidator = new BlockProposalValidator(epochCache, { txsPermitted: !config.disableTransactions });
218
+ this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, {
219
+ txsPermitted: !config.disableTransactions,
220
+ });
221
+ this.checkpointAttestationValidator = config.fishermanMode
222
+ ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry)
223
+ : new CheckpointAttestationValidator(epochCache);
201
224
 
202
225
  this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
203
226
 
204
- this.blockReceivedCallback = async (block: BlockProposal): Promise<BlockAttestation[] | undefined> => {
227
+ this.blockReceivedCallback = async (block: BlockProposal): Promise<boolean> => {
205
228
  this.logger.debug(
206
229
  `Handler not yet registered: Block received callback not set. Received block for slot ${block.slotNumber} from peer.`,
207
230
  { p2pMessageIdentifier: await block.p2pMessageLoggingIdentifier() },
208
231
  );
209
- return undefined;
232
+ return false;
233
+ };
234
+
235
+ this.checkpointReceivedCallback = (
236
+ checkpoint: CheckpointProposalCore,
237
+ ): Promise<CheckpointAttestation[] | undefined> => {
238
+ this.logger.debug(
239
+ `Handler not yet registered: Checkpoint received callback not set. Received checkpoint for slot ${checkpoint.slotNumber} from peer.`,
240
+ );
241
+ return Promise.resolve(undefined);
210
242
  };
211
243
  }
212
244
 
@@ -275,7 +307,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
275
307
 
276
308
  const txTopic = createTopicString(TopicType.tx, protocolVersion);
277
309
  const blockProposalTopic = createTopicString(TopicType.block_proposal, protocolVersion);
278
- const blockAttestationTopic = createTopicString(TopicType.block_attestation, protocolVersion);
310
+ const checkpointProposalTopic = createTopicString(TopicType.checkpoint_proposal, protocolVersion);
311
+ const checkpointAttestationTopic = createTopicString(TopicType.checkpoint_attestation, protocolVersion);
279
312
 
280
313
  const preferredPeersEnrs: ENR[] = config.preferredPeers.map(enr => ENR.decodeTxt(enr));
281
314
  const directPeers = (
@@ -397,12 +430,17 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
397
430
  invalidMessageDeliveriesWeight: -20,
398
431
  invalidMessageDeliveriesDecay: 0.5,
399
432
  }),
400
- [blockAttestationTopic]: createTopicScoreParams({
433
+ [blockProposalTopic]: createTopicScoreParams({
401
434
  topicWeight: 1,
402
435
  invalidMessageDeliveriesWeight: -20,
403
436
  invalidMessageDeliveriesDecay: 0.5,
404
437
  }),
405
- [blockProposalTopic]: createTopicScoreParams({
438
+ [checkpointProposalTopic]: createTopicScoreParams({
439
+ topicWeight: 1,
440
+ invalidMessageDeliveriesWeight: -20,
441
+ invalidMessageDeliveriesDecay: 0.5,
442
+ }),
443
+ [checkpointAttestationTopic]: createTopicScoreParams({
406
444
  topicWeight: 1,
407
445
  invalidMessageDeliveriesWeight: -20,
408
446
  invalidMessageDeliveriesDecay: 0.5,
@@ -472,17 +510,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
472
510
  }
473
511
  const announceTcpMultiaddr = convertToMultiaddr(p2pIp, p2pPort, 'tcp');
474
512
 
475
- await this.peerManager.initializePeers();
476
- if (!this.config.p2pDiscoveryDisabled) {
477
- await this.peerDiscoveryService.start();
478
- }
479
- await this.node.start();
480
-
481
- // Subscribe to standard GossipSub topics by default
482
- for (const topic of getTopicsForClientAndConfig(this.clientType, this.config.disableTransactions)) {
483
- this.subscribeToTopic(this.topicStrings[topic]);
484
- }
485
-
486
513
  // Create request response protocol handlers
487
514
  const txHandler = reqRespTxHandler(this.mempools);
488
515
  const goodbyeHandler = reqGoodbyeHandler(this.peerManager);
@@ -505,10 +532,32 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
505
532
  requestResponseHandlers[ReqRespSubProtocol.TX] = txHandler.bind(this);
506
533
  }
507
534
 
535
+ // Define the sub protocol validators - This is done within this start() method to gain a callback to the existing validateTx function
536
+ const reqrespSubProtocolValidators = {
537
+ ...DEFAULT_SUB_PROTOCOL_VALIDATORS,
538
+ [ReqRespSubProtocol.TX]: this.validateRequestedTxs.bind(this),
539
+ [ReqRespSubProtocol.BLOCK_TXS]: this.validateRequestedBlockTxs.bind(this),
540
+ [ReqRespSubProtocol.BLOCK]: this.validateRequestedBlock.bind(this),
541
+ };
542
+
543
+ await this.peerManager.initializePeers();
544
+
545
+ await this.reqresp.start(requestResponseHandlers, reqrespSubProtocolValidators);
546
+
547
+ await this.node.start();
548
+
549
+ // Subscribe to standard GossipSub topics by default
550
+ for (const topic of getTopicsForClientAndConfig(this.clientType, this.config.disableTransactions)) {
551
+ this.subscribeToTopic(this.topicStrings[topic]);
552
+ }
553
+
508
554
  // add GossipSub listener
509
555
  this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
510
556
 
511
557
  // Start running promise for peer discovery and metrics collection
558
+ if (!this.config.p2pDiscoveryDisabled) {
559
+ await this.peerDiscoveryService.start();
560
+ }
512
561
  this.discoveryRunningPromise = new RunningPromise(
513
562
  async () => {
514
563
  await this.peerManager.heartbeat();
@@ -518,14 +567,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
518
567
  );
519
568
  this.discoveryRunningPromise.start();
520
569
 
521
- // Define the sub protocol validators - This is done within this start() method to gain a callback to the existing validateTx function
522
- const reqrespSubProtocolValidators = {
523
- ...DEFAULT_SUB_PROTOCOL_VALIDATORS,
524
- [ReqRespSubProtocol.TX]: this.validateRequestedTxs.bind(this),
525
- [ReqRespSubProtocol.BLOCK_TXS]: this.validateRequestedBlockTxs.bind(this),
526
- [ReqRespSubProtocol.BLOCK]: this.validateRequestedBlock.bind(this),
527
- };
528
- await this.reqresp.start(requestResponseHandlers, reqrespSubProtocolValidators);
529
570
  this.logger.info(`Started P2P service`, {
530
571
  listen: this.config.listenAddress,
531
572
  port: this.config.p2pPort,
@@ -611,6 +652,10 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
611
652
  this.blockReceivedCallback = callback;
612
653
  }
613
654
 
655
+ public registerCheckpointReceivedCallback(callback: P2PCheckpointReceivedCallback) {
656
+ this.checkpointReceivedCallback = callback;
657
+ }
658
+
614
659
  /**
615
660
  * Subscribes to a topic.
616
661
  * @param topic - The topic to subscribe to.
@@ -656,12 +701,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
656
701
  case this.topicStrings[TopicType.tx]:
657
702
  topicType = TopicType.tx;
658
703
  break;
659
- case this.topicStrings[TopicType.block_attestation]:
660
- topicType = TopicType.block_attestation;
661
- break;
662
704
  case this.topicStrings[TopicType.block_proposal]:
663
705
  topicType = TopicType.block_proposal;
664
706
  break;
707
+ case this.topicStrings[TopicType.checkpoint_proposal]:
708
+ topicType = TopicType.checkpoint_proposal;
709
+ break;
710
+ case this.topicStrings[TopicType.checkpoint_attestation]:
711
+ topicType = TopicType.checkpoint_attestation;
712
+ break;
665
713
  default:
666
714
  this.logger.error(`Received message on unknown topic: ${msg.topic}`);
667
715
  break;
@@ -723,24 +771,28 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
723
771
  // Determine topic type for attributes
724
772
  if (msg.topic === this.topicStrings[TopicType.tx]) {
725
773
  topicType = TopicType.tx;
726
- } else if (msg.topic === this.topicStrings[TopicType.block_attestation]) {
727
- topicType = TopicType.block_attestation;
774
+ } else if (msg.topic === this.topicStrings[TopicType.checkpoint_attestation]) {
775
+ topicType = TopicType.checkpoint_attestation;
728
776
  } else if (msg.topic === this.topicStrings[TopicType.block_proposal]) {
729
777
  topicType = TopicType.block_proposal;
778
+ } else if (msg.topic === this.topicStrings[TopicType.checkpoint_proposal]) {
779
+ topicType = TopicType.checkpoint_proposal;
730
780
  }
731
781
 
732
782
  // Process the message, optionally within a linked span for trace propagation
733
783
  const processMessage = async () => {
734
784
  if (msg.topic === this.topicStrings[TopicType.tx]) {
735
785
  await this.handleGossipedTx(p2pMessage.payload, msgId, source);
736
- }
737
- if (msg.topic === this.topicStrings[TopicType.block_attestation]) {
786
+ } else if (msg.topic === this.topicStrings[TopicType.checkpoint_attestation]) {
738
787
  if (this.clientType === P2PClientType.Full) {
739
- await this.processAttestationFromPeer(p2pMessage.payload, msgId, source);
788
+ await this.processCheckpointAttestationFromPeer(p2pMessage.payload, msgId, source);
740
789
  }
741
- }
742
- if (msg.topic === this.topicStrings[TopicType.block_proposal]) {
790
+ } else if (msg.topic === this.topicStrings[TopicType.block_proposal]) {
743
791
  await this.processBlockFromPeer(p2pMessage.payload, msgId, source);
792
+ } else if (msg.topic === this.topicStrings[TopicType.checkpoint_proposal]) {
793
+ await this.handleGossipedCheckpointProposal(p2pMessage.payload, msgId, source);
794
+ } else {
795
+ this.logger.error(`Received message on unknown topic: ${msg.topic}`);
744
796
  }
745
797
  };
746
798
 
@@ -860,27 +912,29 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
860
912
  }
861
913
 
862
914
  /**
863
- * Process Attestation From Peer
864
- * When a proposal is received from a peer, we add it to the attestation pool, so it can be accessed by other services.
865
- *
866
- * @param attestation - The attestation to process.
915
+ * Process a checkpoint attestation from a peer.
916
+ * Validates the attestation and adds it to the pool.
867
917
  */
868
- private async processAttestationFromPeer(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
869
- const validationFunc: () => Promise<ReceivedMessageValidationResult<BlockAttestation>> = async () => {
870
- const attestation = BlockAttestation.fromBuffer(payloadData);
918
+ private async processCheckpointAttestationFromPeer(
919
+ payloadData: Buffer,
920
+ msgId: string,
921
+ source: PeerId,
922
+ ): Promise<void> {
923
+ const validationFunc: () => Promise<ReceivedMessageValidationResult<CheckpointAttestation>> = async () => {
924
+ const attestation = CheckpointAttestation.fromBuffer(payloadData);
871
925
  const pool = this.mempools.attestationPool;
872
- const isValid = await this.validateAttestation(source, attestation);
873
- const exists = isValid && (await pool.hasAttestation(attestation));
926
+ const isValid = await this.validateCheckpointAttestation(source, attestation);
927
+ const exists = isValid && (await pool.hasCheckpointAttestation(attestation));
874
928
 
875
929
  let canAdd = true;
876
930
  if (isValid && !exists) {
877
931
  const slot = attestation.payload.header.slotNumber;
878
932
  const { committee } = await this.epochCache.getCommittee(slot);
879
933
  const committeeSize = committee?.length ?? 0;
880
- canAdd = await pool.canAddAttestation(attestation, committeeSize);
934
+ canAdd = await pool.canAddCheckpointAttestation(attestation, committeeSize);
881
935
  }
882
936
 
883
- this.logger.trace(`Validate propagated block attestation`, {
937
+ this.logger.trace(`Validate propagated checkpoint attestation`, {
884
938
  isValid,
885
939
  exists,
886
940
  canAdd,
@@ -893,7 +947,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
893
947
  } else if (exists) {
894
948
  return { result: TopicValidatorResult.Ignore, obj: attestation };
895
949
  } else if (!canAdd) {
896
- this.logger.warn(`Dropping block attestation due to per-(slot, proposalId) attestation cap`, {
950
+ this.logger.warn(`Dropping checkpoint attestation due to per-(slot, proposalId) attestation cap`, {
897
951
  slot: attestation.payload.header.slotNumber.toString(),
898
952
  archive: attestation.archive.toString(),
899
953
  source: source.toString(),
@@ -904,11 +958,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
904
958
  }
905
959
  };
906
960
 
907
- const { result, obj: attestation } = await this.validateReceivedMessage<BlockAttestation>(
961
+ const { result, obj: attestation } = await this.validateReceivedMessage<CheckpointAttestation>(
908
962
  validationFunc,
909
963
  msgId,
910
964
  source,
911
- TopicType.block_attestation,
965
+ TopicType.checkpoint_attestation,
912
966
  );
913
967
 
914
968
  if (result !== TopicValidatorResult.Accept || !attestation) {
@@ -916,7 +970,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
916
970
  }
917
971
 
918
972
  this.logger.debug(
919
- `Received attestation for slot ${attestation.slotNumber} from external peer ${source.toString()}`,
973
+ `Received checkpoint attestation for slot ${attestation.slotNumber} from external peer ${source.toString()}`,
920
974
  {
921
975
  p2pMessageIdentifier: await attestation.p2pMessageLoggingIdentifier(),
922
976
  slot: attestation.slotNumber,
@@ -925,7 +979,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
925
979
  },
926
980
  );
927
981
 
928
- await this.mempools.attestationPool.addAttestations([attestation]);
982
+ await this.mempools.attestationPool.addCheckpointAttestations([attestation]);
929
983
  }
930
984
 
931
985
  private async processBlockFromPeer(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
@@ -941,7 +995,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
941
995
  isValid,
942
996
  exists,
943
997
  canAdd,
944
- [Attributes.SLOT_NUMBER]: block.payload.header.slotNumber.toString(),
998
+ [Attributes.SLOT_NUMBER]: block.slotNumber.toString(),
945
999
  [Attributes.P2P_ID]: source.toString(),
946
1000
  });
947
1001
 
@@ -977,6 +1031,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
977
1031
  }
978
1032
 
979
1033
  // REVIEW: callback pattern https://github.com/AztecProtocol/aztec-packages/issues/7963
1034
+ // REFACTOR(palla): This method should be moved to the p2p_client or to a separate component,
1035
+ // should not be here as it does not deal with p2p networking.
980
1036
  @trackSpan('Libp2pService.processValidBlockProposal', async block => ({
981
1037
  [Attributes.SLOT_NUMBER]: block.slotNumber,
982
1038
  [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
@@ -984,17 +1040,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
984
1040
  }))
985
1041
  private async processValidBlockProposal(block: BlockProposal, sender: PeerId) {
986
1042
  const slot = block.slotNumber;
987
- const previousSlot = SlotNumber(slot - 1);
988
1043
  this.logger.verbose(`Received block proposal for slot ${slot} from external peer ${sender.toString()}.`, {
989
1044
  p2pMessageIdentifier: await block.p2pMessageLoggingIdentifier(),
990
- slot: block.slotNumber,
991
- archive: block.archive.toString(),
992
1045
  source: sender.toString(),
1046
+ ...block.toBlockInfo(),
993
1047
  });
994
- const attestationsForPreviousSlot = await this.mempools.attestationPool.getAttestationsForSlot(previousSlot);
995
- this.logger.verbose(`Received ${attestationsForPreviousSlot.length} attestations for slot ${previousSlot}`);
996
1048
 
997
- // Attempt to add proposal, then mark the txs in this proposal as non-evictable
1049
+ // Attempt to add proposal
998
1050
  try {
999
1051
  await this.mempools.attestationPool.addBlockProposal(block);
1000
1052
  } catch (err: unknown) {
@@ -1009,34 +1061,125 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1009
1061
  }
1010
1062
  throw err;
1011
1063
  }
1064
+
1065
+ // Mark the txs in this proposal as non-evictable
1012
1066
  await this.mempools.txPool.markTxsAsNonEvictable(block.txHashes);
1013
- const attestations = await this.blockReceivedCallback(block, sender);
1014
1067
 
1015
- // TODO: fix up this pattern - the abstraction is not nice
1016
- // The attestation can be undefined if no handler is registered / the validator deems the block invalid / in fisherman mode
1017
- if (attestations?.length) {
1018
- for (const attestation of attestations) {
1019
- this.logger.verbose(`Broadcasting attestation for slot ${attestation.slotNumber}`, {
1020
- p2pMessageIdentifier: await attestation.p2pMessageLoggingIdentifier(),
1021
- slot: attestation.slotNumber,
1022
- archive: attestation.archive.toString(),
1068
+ // Call the block received callback to validate the proposal.
1069
+ // Note: Validators do NOT attest to individual blocks, only to checkpoint proposals.
1070
+ const isValid = await this.blockReceivedCallback(block, sender);
1071
+ if (!isValid) {
1072
+ this.logger.warn(`Block proposal validation failed for block ${block.blockNumber}`, block.toBlockInfo());
1073
+ }
1074
+ }
1075
+
1076
+ /**
1077
+ * Handle a gossiped checkpoint proposal.
1078
+ * Validates and processes the checkpoint proposal, then triggers the callback for attestation.
1079
+ */
1080
+ private async handleGossipedCheckpointProposal(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
1081
+ // TODO(palla/mbps): This pattern is repeated across multiple message handlers, consider abstracting it.
1082
+ const validationFunc: () => Promise<ReceivedMessageValidationResult<CheckpointProposal>> = async () => {
1083
+ const checkpoint = CheckpointProposal.fromBuffer(payloadData);
1084
+ const isValid = await this.validateCheckpointProposal(source, checkpoint);
1085
+ const pool = this.mempools.attestationPool;
1086
+
1087
+ const exists = isValid && (await pool.hasCheckpointProposal(checkpoint));
1088
+ const canAdd = isValid && (await pool.canAddCheckpointProposal(checkpoint));
1089
+
1090
+ this.logger.trace(`Validate propagated checkpoint proposal`, {
1091
+ isValid,
1092
+ exists,
1093
+ canAdd,
1094
+ [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1095
+ [Attributes.P2P_ID]: source.toString(),
1096
+ });
1097
+
1098
+ if (!isValid) {
1099
+ return { result: TopicValidatorResult.Reject };
1100
+ } else if (exists) {
1101
+ return { result: TopicValidatorResult.Ignore, obj: checkpoint };
1102
+ } else if (!canAdd) {
1103
+ this.peerManager.penalizePeer(source, PeerErrorSeverity.MidToleranceError);
1104
+ this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
1105
+ slot: checkpoint.slotNumber.toString(),
1106
+ archive: checkpoint.archive.toString(),
1107
+ source: source.toString(),
1023
1108
  });
1024
- await this.broadcastAttestation(attestation);
1109
+ return { result: TopicValidatorResult.Reject };
1110
+ } else {
1111
+ return { result: TopicValidatorResult.Accept, obj: checkpoint };
1025
1112
  }
1113
+ };
1114
+
1115
+ const { result, obj: checkpoint } = await this.validateReceivedMessage<CheckpointProposal>(
1116
+ validationFunc,
1117
+ msgId,
1118
+ source,
1119
+ TopicType.checkpoint_proposal,
1120
+ );
1121
+
1122
+ if (result !== TopicValidatorResult.Accept || !checkpoint) {
1123
+ return;
1026
1124
  }
1125
+
1126
+ await this.processValidCheckpointProposal(checkpoint, source);
1027
1127
  }
1028
1128
 
1029
1129
  /**
1030
- * Broadcast an attestation to all peers.
1031
- * @param attestation - The attestation to broadcast.
1130
+ * Process a validated checkpoint proposal.
1131
+ * Extracts and processes the last block proposal (if present) first, then processes the checkpoint.
1132
+ * The block callback is invoked before the checkpoint callback.
1032
1133
  */
1033
- @trackSpan('Libp2pService.broadcastAttestation', async attestation => ({
1034
- [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber,
1035
- [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
1036
- [Attributes.P2P_ID]: await attestation.p2pMessageLoggingIdentifier().then(i => i.toString()),
1134
+ @trackSpan('Libp2pService.processValidCheckpointProposal', async checkpoint => ({
1135
+ [Attributes.SLOT_NUMBER]: checkpoint.slotNumber,
1136
+ [Attributes.BLOCK_ARCHIVE]: checkpoint.archive.toString(),
1137
+ [Attributes.P2P_ID]: await checkpoint.p2pMessageLoggingIdentifier().then(i => i.toString()),
1037
1138
  }))
1038
- private async broadcastAttestation(attestation: BlockAttestation) {
1039
- await this.propagate(attestation);
1139
+ private async processValidCheckpointProposal(checkpoint: CheckpointProposal, sender: PeerId) {
1140
+ const slot = checkpoint.slotNumber;
1141
+ this.logger.verbose(`Received checkpoint proposal for slot ${slot} from external peer ${sender.toString()}.`, {
1142
+ p2pMessageIdentifier: await checkpoint.p2pMessageLoggingIdentifier(),
1143
+ slot: checkpoint.slotNumber,
1144
+ archive: checkpoint.archive.toString(),
1145
+ source: sender.toString(),
1146
+ });
1147
+
1148
+ // Extract block proposal before adding to pool (pool stores them separately)
1149
+ const blockProposal = checkpoint.getBlockProposal();
1150
+
1151
+ // Add proposal to the pool (this extracts and stores block proposal separately)
1152
+ await this.mempools.attestationPool.addCheckpointProposal(checkpoint);
1153
+
1154
+ // Mark txs as non-evictable if present (from the last block)
1155
+ if (checkpoint.txHashes.length > 0) {
1156
+ await this.mempools.txPool.markTxsAsNonEvictable(checkpoint.txHashes);
1157
+ }
1158
+
1159
+ // If there was a last block proposal, invoke the block callback first for validation.
1160
+ // Note: The block proposal is already stored in the pool by addCheckpointProposal.
1161
+ if (blockProposal) {
1162
+ const isValid = await this.blockReceivedCallback(blockProposal, sender);
1163
+ if (!isValid) {
1164
+ this.logger.warn(`Block proposal from checkpoint failed validation`, {
1165
+ slot: slot.toString(),
1166
+ archive: checkpoint.archive.toString(),
1167
+ blockNumber: blockProposal.blockNumber.toString(),
1168
+ });
1169
+ return;
1170
+ }
1171
+ }
1172
+
1173
+ // Call the checkpoint received callback with the core version (without lastBlock)
1174
+ // to validate and potentially generate attestations
1175
+ const attestations = await this.checkpointReceivedCallback(checkpoint.toCore(), sender);
1176
+ if (attestations && attestations.length > 0) {
1177
+ // If the callback returned attestations, add them to the pool and propagate them
1178
+ await this.mempools.attestationPool.addCheckpointAttestations(attestations);
1179
+ for (const attestation of attestations) {
1180
+ await this.propagate(attestation);
1181
+ }
1182
+ }
1040
1183
  }
1041
1184
 
1042
1185
  /**
@@ -1421,19 +1564,20 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1421
1564
  }
1422
1565
 
1423
1566
  /**
1424
- * Validate an attestation.
1567
+ * Validate a checkpoint attestation.
1425
1568
  *
1426
- * @param attestation - The attestation to validate.
1427
- * @returns True if the attestation is valid, false otherwise.
1569
+ * @param attestation - The checkpoint attestation to validate.
1570
+ * @returns True if the checkpoint attestation is valid, false otherwise.
1428
1571
  */
1429
- @trackSpan('Libp2pService.validateAttestation', async (_, attestation) => ({
1572
+ @trackSpan('Libp2pService.validateCheckpointAttestation', async (_, attestation) => ({
1430
1573
  [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber,
1431
1574
  [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
1432
1575
  [Attributes.P2P_ID]: await attestation.p2pMessageLoggingIdentifier().then(i => i.toString()),
1433
1576
  }))
1434
- public async validateAttestation(peerId: PeerId, attestation: BlockAttestation): Promise<boolean> {
1435
- const severity = await this.attestationValidator.validate(attestation);
1577
+ public async validateCheckpointAttestation(peerId: PeerId, attestation: CheckpointAttestation): Promise<boolean> {
1578
+ const severity = await this.checkpointAttestationValidator.validate(attestation);
1436
1579
  if (severity) {
1580
+ this.logger.debug(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1437
1581
  this.peerManager.penalizePeer(peerId, severity);
1438
1582
  return false;
1439
1583
  }
@@ -1448,7 +1592,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1448
1592
  * @returns True if the block proposal is valid, false otherwise.
1449
1593
  */
1450
1594
  @trackSpan('Libp2pService.validateBlockProposal', (_peerId, block) => ({
1451
- [Attributes.SLOT_NUMBER]: block.payload.header.slotNumber.toString(),
1595
+ [Attributes.SLOT_NUMBER]: block.slotNumber.toString(),
1452
1596
  }))
1453
1597
  public async validateBlockProposal(peerId: PeerId, block: BlockProposal): Promise<boolean> {
1454
1598
  const severity = await this.blockProposalValidator.validate(block);
@@ -1461,6 +1605,26 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1461
1605
  return true;
1462
1606
  }
1463
1607
 
1608
+ /**
1609
+ * Validate a checkpoint proposal.
1610
+ *
1611
+ * @param checkpoint - The checkpoint proposal to validate.
1612
+ * @returns True if the checkpoint proposal is valid, false otherwise.
1613
+ */
1614
+ @trackSpan('Libp2pService.validateCheckpointProposal', (_peerId, checkpoint) => ({
1615
+ [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1616
+ }))
1617
+ public async validateCheckpointProposal(peerId: PeerId, checkpoint: CheckpointProposal): Promise<boolean> {
1618
+ const severity = await this.checkpointProposalValidator.validate(checkpoint);
1619
+ if (severity) {
1620
+ this.logger.debug(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
1621
+ this.peerManager.penalizePeer(peerId, severity);
1622
+ return false;
1623
+ }
1624
+
1625
+ return true;
1626
+ }
1627
+
1464
1628
  public getPeerScore(peerId: PeerId): number {
1465
1629
  return this.node.services.pubsub.score.score(peerId.toString());
1466
1630
  }
@@ -1,6 +1,6 @@
1
1
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
2
  import type { PeerInfo } from '@aztec/stdlib/interfaces/server';
3
- import type { BlockAttestation, BlockProposal, Gossipable } from '@aztec/stdlib/p2p';
3
+ import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore, Gossipable } from '@aztec/stdlib/p2p';
4
4
  import type { Tx } from '@aztec/stdlib/tx';
5
5
 
6
6
  import type { PeerId } from '@libp2p/interface';
@@ -22,10 +22,23 @@ export enum PeerDiscoveryState {
22
22
  STOPPED = 'stopped',
23
23
  }
24
24
 
25
- export type P2PBlockReceivedCallback = (
26
- block: BlockProposal,
25
+ /**
26
+ * Callback for when a block proposal is received.
27
+ * Validators validate but DO NOT attest to individual blocks - attestations are only for checkpoints.
28
+ * @returns true if the proposal is valid, false otherwise
29
+ */
30
+ export type P2PBlockReceivedCallback = (block: BlockProposal, sender: PeerId) => Promise<boolean>;
31
+
32
+ /**
33
+ * Callback for when a checkpoint proposal is received.
34
+ * The checkpoint proposal is passed as CheckpointProposalCore (without lastBlock) since
35
+ * the lastBlock is extracted and stored separately as a BlockProposal, and the block
36
+ * callback is invoked and awaited before this checkpoint callback.
37
+ */
38
+ export type P2PCheckpointReceivedCallback = (
39
+ checkpoint: CheckpointProposalCore,
27
40
  sender: PeerId,
28
- ) => Promise<BlockAttestation[] | undefined>;
41
+ ) => Promise<CheckpointAttestation[] | undefined>;
29
42
 
30
43
  export type AuthReceivedCallback = (peerId: PeerId, authRequest: AuthRequest) => Promise<AuthResponse | undefined>;
31
44
 
@@ -70,6 +83,8 @@ export interface P2PService {
70
83
  // Leaky abstraction: fix https://github.com/AztecProtocol/aztec-packages/issues/7963
71
84
  registerBlockReceivedCallback(callback: P2PBlockReceivedCallback): void;
72
85
 
86
+ registerCheckpointReceivedCallback(callback: P2PCheckpointReceivedCallback): void;
87
+
73
88
  getEnr(): ENR | undefined;
74
89
 
75
90
  getPeers(includePending?: boolean): PeerInfo[];