@aztec/p2p 0.0.1-commit.2d9cb6034 → 0.0.1-commit.2e20a94

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 (118) hide show
  1. package/dest/client/p2p_client.d.ts +1 -1
  2. package/dest/client/p2p_client.d.ts.map +1 -1
  3. package/dest/client/p2p_client.js +9 -2
  4. package/dest/config.d.ts +103 -99
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +10 -5
  7. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
  8. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  9. package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -3
  10. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
  11. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +1 -1
  12. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +5 -2
  13. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  14. package/dest/msg_validators/attestation_validator/attestation_validator.js +17 -9
  15. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -2
  16. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  17. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
  18. package/dest/msg_validators/clock_tolerance.d.ts +12 -1
  19. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  20. package/dest/msg_validators/clock_tolerance.js +50 -0
  21. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +2 -1
  22. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  23. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +2 -1
  24. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  25. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +3 -1
  26. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  27. package/dest/msg_validators/proposal_validator/proposal_validator.js +16 -8
  28. package/dest/msg_validators/tx_validator/archive_cache.js +1 -1
  29. package/dest/msg_validators/tx_validator/gas_validator.d.ts +1 -1
  30. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  31. package/dest/msg_validators/tx_validator/gas_validator.js +11 -9
  32. package/dest/services/data_store.d.ts +1 -1
  33. package/dest/services/data_store.d.ts.map +1 -1
  34. package/dest/services/data_store.js +5 -5
  35. package/dest/services/dummy_service.d.ts +2 -1
  36. package/dest/services/dummy_service.d.ts.map +1 -1
  37. package/dest/services/dummy_service.js +1 -0
  38. package/dest/services/gossipsub/topic_score_params.d.ts +13 -2
  39. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  40. package/dest/services/gossipsub/topic_score_params.js +21 -4
  41. package/dest/services/libp2p/libp2p_service.d.ts +2 -15
  42. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  43. package/dest/services/libp2p/libp2p_service.js +17 -55
  44. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  45. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  46. package/dest/services/peer-manager/peer_manager.js +15 -2
  47. package/dest/services/peer-manager/peer_scoring.d.ts +3 -1
  48. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  49. package/dest/services/peer-manager/peer_scoring.js +4 -0
  50. package/dest/services/reqresp/config.d.ts +3 -3
  51. package/dest/services/reqresp/config.d.ts.map +1 -1
  52. package/dest/services/reqresp/interface.d.ts +14 -9
  53. package/dest/services/reqresp/interface.d.ts.map +1 -1
  54. package/dest/services/reqresp/interface.js +10 -11
  55. package/dest/services/reqresp/metrics.d.ts +1 -1
  56. package/dest/services/reqresp/metrics.d.ts.map +1 -1
  57. package/dest/services/reqresp/metrics.js +0 -1
  58. package/dest/services/reqresp/protocols/index.d.ts +1 -2
  59. package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
  60. package/dest/services/reqresp/protocols/index.js +0 -1
  61. package/dest/services/reqresp/protocols/tx.d.ts +1 -1
  62. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  63. package/dest/services/reqresp/protocols/tx.js +1 -3
  64. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +1 -1
  65. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
  66. package/dest/services/reqresp/rate-limiter/rate_limits.js +0 -10
  67. package/dest/services/reqresp/reqresp.d.ts +4 -2
  68. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  69. package/dest/services/reqresp/reqresp.js +11 -2
  70. package/dest/test-helpers/mock-pubsub.d.ts +11 -3
  71. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  72. package/dest/test-helpers/mock-pubsub.js +35 -10
  73. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  74. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  75. package/dest/test-helpers/reqresp-nodes.js +1 -2
  76. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  77. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  78. package/dest/test-helpers/testbench-utils.js +1 -0
  79. package/dest/testbench/p2p_client_testbench_worker.js +22 -0
  80. package/dest/testbench/worker_client_manager.d.ts +8 -1
  81. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  82. package/dest/testbench/worker_client_manager.js +49 -0
  83. package/package.json +14 -14
  84. package/src/client/p2p_client.ts +11 -3
  85. package/src/config.ts +18 -6
  86. package/src/mem_pools/attestation_pool/attestation_pool.ts +5 -3
  87. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +3 -3
  88. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +1 -1
  89. package/src/msg_validators/attestation_validator/attestation_validator.ts +18 -7
  90. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -1
  91. package/src/msg_validators/clock_tolerance.ts +68 -0
  92. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -1
  93. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +4 -1
  94. package/src/msg_validators/proposal_validator/proposal_validator.ts +13 -7
  95. package/src/msg_validators/tx_validator/archive_cache.ts +1 -1
  96. package/src/msg_validators/tx_validator/gas_validator.ts +25 -9
  97. package/src/services/data_store.ts +5 -13
  98. package/src/services/dummy_service.ts +1 -0
  99. package/src/services/gossipsub/topic_score_params.ts +36 -4
  100. package/src/services/libp2p/libp2p_service.ts +13 -55
  101. package/src/services/peer-manager/peer_manager.ts +17 -2
  102. package/src/services/peer-manager/peer_scoring.ts +6 -0
  103. package/src/services/reqresp/config.ts +2 -2
  104. package/src/services/reqresp/interface.ts +21 -11
  105. package/src/services/reqresp/metrics.ts +0 -1
  106. package/src/services/reqresp/protocols/index.ts +0 -1
  107. package/src/services/reqresp/protocols/tx.ts +1 -3
  108. package/src/services/reqresp/rate-limiter/rate_limits.ts +0 -10
  109. package/src/services/reqresp/reqresp.ts +18 -1
  110. package/src/test-helpers/mock-pubsub.ts +31 -5
  111. package/src/test-helpers/reqresp-nodes.ts +2 -2
  112. package/src/test-helpers/testbench-utils.ts +1 -0
  113. package/src/testbench/p2p_client_testbench_worker.ts +27 -0
  114. package/src/testbench/worker_client_manager.ts +55 -0
  115. package/dest/services/reqresp/protocols/block.d.ts +0 -9
  116. package/dest/services/reqresp/protocols/block.d.ts.map +0 -1
  117. package/dest/services/reqresp/protocols/block.js +0 -32
  118. package/src/services/reqresp/protocols/block.ts +0 -37
@@ -1,5 +1,5 @@
1
1
  import { TopicType, createTopicString } from '@aztec/stdlib/p2p';
2
- import { calculateMaxBlocksPerSlot } from '@aztec/stdlib/timetable';
2
+ import { createCheckpointTimingModel } from '@aztec/stdlib/timetable';
3
3
 
4
4
  import { createTopicScoreParams } from '@chainsafe/libp2p-gossipsub/score';
5
5
 
@@ -9,12 +9,18 @@ import { createTopicScoreParams } from '@chainsafe/libp2p-gossipsub/score';
9
9
  export type TopicScoringNetworkParams = {
10
10
  /** L2 slot duration in milliseconds */
11
11
  slotDurationMs: number;
12
+ /** L1 slot duration in seconds */
13
+ ethereumSlotDuration: number;
12
14
  /** Gossipsub heartbeat interval in milliseconds */
13
15
  heartbeatIntervalMs: number;
14
16
  /** Target committee size (number of validators expected to attest per slot) */
15
17
  targetCommitteeSize: number;
16
18
  /** Duration per block in milliseconds when building multiple blocks per slot. If undefined, single block mode. */
17
19
  blockDurationMs?: number;
20
+ /** Time budget in seconds reserved for L1 publishing. Defaults to ethereumSlotDuration. */
21
+ l1PublishingTime?: number;
22
+ /** One-way proposal/attestation propagation budget in seconds. */
23
+ p2pPropagationTime?: number;
18
24
  /** Expected number of block proposals per slot for scoring override. 0 disables scoring, undefined falls back to blocksPerSlot - 1. */
19
25
  expectedBlockProposalsPerSlot?: number;
20
26
  };
@@ -25,10 +31,32 @@ export type TopicScoringNetworkParams = {
25
31
  *
26
32
  * @param slotDurationMs - L2 slot duration in milliseconds
27
33
  * @param blockDurationMs - Duration per block in milliseconds (undefined = single block mode)
34
+ * @param opts - Shared checkpoint timing inputs used by the sequencer and validators
28
35
  * @returns Number of blocks per slot
29
36
  */
30
- export function calculateBlocksPerSlot(slotDurationMs: number, blockDurationMs: number | undefined): number {
31
- return calculateMaxBlocksPerSlot(slotDurationMs / 1000, blockDurationMs ? blockDurationMs / 1000 : undefined);
37
+ export function calculateBlocksPerSlot(
38
+ slotDurationMs: number,
39
+ blockDurationMs: number | undefined,
40
+ opts?: {
41
+ ethereumSlotDuration: number;
42
+ l1PublishingTime?: number;
43
+ p2pPropagationTime?: number;
44
+ },
45
+ ): number {
46
+ if (!opts) {
47
+ return createCheckpointTimingModel({
48
+ aztecSlotDuration: slotDurationMs / 1000,
49
+ blockDuration: blockDurationMs ? blockDurationMs / 1000 : undefined,
50
+ }).calculateMaxBlocksPerSlot();
51
+ }
52
+
53
+ return createCheckpointTimingModel({
54
+ aztecSlotDuration: slotDurationMs / 1000,
55
+ ethereumSlotDuration: opts.ethereumSlotDuration,
56
+ blockDuration: blockDurationMs ? blockDurationMs / 1000 : undefined,
57
+ l1PublishingTime: opts.l1PublishingTime ?? opts.ethereumSlotDuration,
58
+ p2pPropagationTime: opts.p2pPropagationTime,
59
+ }).calculateMaxBlocksPerSlot();
32
60
  }
33
61
 
34
62
  /**
@@ -279,7 +307,11 @@ export class TopicScoreParamsFactory {
279
307
  const { slotDurationMs, heartbeatIntervalMs, blockDurationMs } = params;
280
308
 
281
309
  // Compute values that are the same for all topics
282
- this.blocksPerSlot = calculateBlocksPerSlot(slotDurationMs, blockDurationMs);
310
+ this.blocksPerSlot = calculateBlocksPerSlot(slotDurationMs, blockDurationMs, {
311
+ ethereumSlotDuration: params.ethereumSlotDuration,
312
+ l1PublishingTime: params.l1PublishingTime,
313
+ p2pPropagationTime: params.p2pPropagationTime,
314
+ });
283
315
  this.heartbeatsPerSlot = slotDurationMs / heartbeatIntervalMs;
284
316
  this.invalidDecay = computeDecay(heartbeatIntervalMs, slotDurationMs, INVALID_DECAY_WINDOW_SLOTS);
285
317
 
@@ -1,13 +1,12 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
2
  import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { maxBy } from '@aztec/foundation/collection';
4
- import { Fr } from '@aztec/foundation/curves/bn254';
5
4
  import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
6
5
  import { RunningPromise } from '@aztec/foundation/running-promise';
7
6
  import { Timer } from '@aztec/foundation/timer';
8
7
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
9
8
  import { protocolContractsHash } from '@aztec/protocol-contracts';
10
- import type { EthAddress, L2Block, L2BlockSource } from '@aztec/stdlib/block';
9
+ import type { EthAddress, L2BlockSource } from '@aztec/stdlib/block';
11
10
  import type { ContractDataSource } from '@aztec/stdlib/contract';
12
11
  import { GasFees } from '@aztec/stdlib/gas';
13
12
  import type { ClientProtocolCircuitVerifier, PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
@@ -105,7 +104,6 @@ import {
105
104
  ValidationError,
106
105
  pingHandler,
107
106
  reqGoodbyeHandler,
108
- reqRespBlockHandler,
109
107
  reqRespBlockTxsHandler,
110
108
  reqRespStatusHandler,
111
109
  reqRespTxHandler,
@@ -230,15 +228,19 @@ export class LibP2PService extends WithTracer implements P2PService {
230
228
  this.protocolVersion,
231
229
  );
232
230
 
231
+ const p2pPropagationTime = config.attestationPropagationTime;
233
232
  const proposalValidatorOpts = {
234
233
  txsPermitted: !config.disableTransactions,
235
234
  maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
235
+ p2pPropagationTime,
236
236
  };
237
237
  this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
238
238
  this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
239
239
  this.checkpointAttestationValidator = config.fishermanMode
240
- ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry)
241
- : new CheckpointAttestationValidator(epochCache);
240
+ ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry, {
241
+ l1PublishingTime: config.l1PublishingTime,
242
+ })
243
+ : new CheckpointAttestationValidator(epochCache, { l1PublishingTime: config.l1PublishingTime });
242
244
 
243
245
  this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
244
246
 
@@ -348,9 +350,12 @@ export class LibP2PService extends WithTracer implements P2PService {
348
350
  const l1Constants = epochCache.getL1Constants();
349
351
  const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
350
352
  slotDurationMs: l1Constants.slotDuration * 1000,
353
+ ethereumSlotDuration: l1Constants.ethereumSlotDuration,
351
354
  heartbeatIntervalMs: config.gossipsubInterval,
352
355
  targetCommitteeSize: l1Constants.targetCommitteeSize,
353
356
  blockDurationMs: config.blockDurationMs,
357
+ l1PublishingTime: config.l1PublishingTime,
358
+ p2pPropagationTime: config.attestationPropagationTime,
354
359
  expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot,
355
360
  });
356
361
 
@@ -475,6 +480,9 @@ export class LibP2PService extends WithTracer implements P2PService {
475
480
  epochCache,
476
481
  );
477
482
 
483
+ // Gate req/resp data protocols for unauthenticated peers when p2pAllowOnlyValidators is enabled
484
+ reqresp.setShouldRejectPeer(peerId => peerManager.shouldDisableP2PGossip(peerId));
485
+
478
486
  // Configure application-specific scoring for gossipsub.
479
487
  // The weight scales app score to align with gossipsub thresholds:
480
488
  // - Disconnect (-50) × 10 = -500 = gossipThreshold (stops receiving gossip)
@@ -520,14 +528,12 @@ export class LibP2PService extends WithTracer implements P2PService {
520
528
  // Create request response protocol handlers
521
529
  const txHandler = reqRespTxHandler(this.mempools);
522
530
  const goodbyeHandler = reqGoodbyeHandler(this.peerManager);
523
- const blockHandler = reqRespBlockHandler(this.archiver);
524
531
  const statusHandler = reqRespStatusHandler(this.protocolVersion, this.worldStateSynchronizer, this.logger);
525
532
 
526
533
  const requestResponseHandlers: Partial<ReqRespSubProtocolHandlers> = {
527
534
  [ReqRespSubProtocol.PING]: pingHandler,
528
535
  [ReqRespSubProtocol.STATUS]: statusHandler.bind(this),
529
536
  [ReqRespSubProtocol.GOODBYE]: goodbyeHandler.bind(this),
530
- [ReqRespSubProtocol.BLOCK]: blockHandler.bind(this),
531
537
  };
532
538
 
533
539
  if (!this.config.disableTransactions) {
@@ -548,7 +554,6 @@ export class LibP2PService extends WithTracer implements P2PService {
548
554
  ...DEFAULT_SUB_PROTOCOL_VALIDATORS,
549
555
  [ReqRespSubProtocol.TX]: this.validateRequestedTxs.bind(this),
550
556
  [ReqRespSubProtocol.BLOCK_TXS]: this.validateRequestedBlockTxs.bind(this),
551
- [ReqRespSubProtocol.BLOCK]: this.validateRequestedBlock.bind(this),
552
557
  };
553
558
 
554
559
  await this.peerManager.initializePeers();
@@ -1574,53 +1579,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1574
1579
  }
1575
1580
  }
1576
1581
 
1577
- /**
1578
- * Validates a BLOCK response.
1579
- *
1580
- * If a local copy exists, enforces hash equality. If missing, rejects (no penalty) since the hash cannot be verified.
1581
- * Penalizes on block number mismatch or hash mismatch.
1582
- *
1583
- * @param requestedBlockNumber - The requested block number.
1584
- * @param responseBlock - The block returned by the peer.
1585
- * @param peerId - The peer that returned the block.
1586
- * @returns True if the response is valid, false otherwise.
1587
- */
1588
- @trackSpan('Libp2pService.validateRequestedBlock', (requestedBlockNumber, _responseBlock) => ({
1589
- [Attributes.BLOCK_NUMBER]: requestedBlockNumber.toString(),
1590
- }))
1591
- protected async validateRequestedBlock(
1592
- requestedBlockNumber: Fr,
1593
- responseBlock: L2Block,
1594
- peerId: PeerId,
1595
- ): Promise<boolean> {
1596
- try {
1597
- const reqNum = Number(requestedBlockNumber.toString());
1598
- if (responseBlock.number !== reqNum) {
1599
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
1600
- return false;
1601
- }
1602
-
1603
- const local = await this.archiver.getBlock(BlockNumber(reqNum));
1604
- if (!local) {
1605
- // We are missing the local block; we cannot verify the hash yet. Reject without penalizing.
1606
- // TODO: Consider extending this validator to accept an expected hash or
1607
- // performing quorum-based checks when using P2P syncing prior to L1 sync.
1608
- this.logger.warn(`Local block ${reqNum} not found; rejecting BLOCK response without hash verification`);
1609
- return false;
1610
- }
1611
- const [localHash, respHash] = await Promise.all([local.hash(), responseBlock.hash()]);
1612
- if (!localHash.equals(respHash)) {
1613
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
1614
- return false;
1615
- }
1616
-
1617
- return true;
1618
- } catch (e) {
1619
- this.logger.warn(`Error validating requested block`, e);
1620
- return false;
1621
- }
1622
- }
1623
-
1624
1582
  protected async validateRequestedTx(
1625
1583
  tx: Tx,
1626
1584
  peerId: PeerId,
@@ -728,6 +728,12 @@ export class PeerManager implements PeerManagerInterface {
728
728
  return;
729
729
  }
730
730
 
731
+ // Don't dial peers that have exceeded the auth failure threshold
732
+ if (!this.isNodeAllowedToConnect(peerId)) {
733
+ this.logger.trace(`Skipping peer ${peerId} due to failed auth handshake attempts`);
734
+ return;
735
+ }
736
+
731
737
  const [multiaddrTcp] = await Promise.all([enr.getFullMultiaddr('tcp')]);
732
738
 
733
739
  this.logger.trace(`Handling discovered peer ${peerId} at ${multiaddrTcp?.toString() ?? 'undefined address'}`);
@@ -985,14 +991,14 @@ export class PeerManager implements PeerManagerInterface {
985
991
  const peerIdStr = peerId.toString();
986
992
 
987
993
  const existingEntry = this.failedAuthHandshakes.get(peerIdStr);
994
+ const failureCount = (existingEntry?.count || 0) + 1;
988
995
  this.failedAuthHandshakes.set(peerIdStr, {
989
- count: (existingEntry?.count || 0) + 1,
996
+ count: failureCount,
990
997
  lastFailureTimestamp: now,
991
998
  });
992
999
 
993
1000
  const connections = this.libP2PNode.getConnections(peerId);
994
1001
  connections.forEach(conn => {
995
- // We mark the IP address
996
1002
  const address = conn.remoteAddr.nodeAddress().address;
997
1003
  const existingAddressEntry = this.failedAuthHandshakes.get(address);
998
1004
  this.failedAuthHandshakes.set(address, {
@@ -1000,6 +1006,15 @@ export class PeerManager implements PeerManagerInterface {
1000
1006
  lastFailureTimestamp: now,
1001
1007
  });
1002
1008
  });
1009
+
1010
+ // Ban the peer from being re-dialed for a cooldown period (exponential backoff)
1011
+ const banTimeMs = this.config.peerFailedBanTimeMs ?? DEFAULT_FAILED_PEER_BAN_TIME_MS;
1012
+ const backoffMs = banTimeMs * Math.pow(2, Math.min(failureCount - 1, 5));
1013
+ this.timedOutPeers.set(peerIdStr, {
1014
+ peerId: peerIdStr,
1015
+ timeoutUntilMs: now + backoffMs,
1016
+ });
1017
+ this.cachedPeers.delete(peerIdStr);
1003
1018
  }
1004
1019
 
1005
1020
  /*
@@ -135,6 +135,12 @@ export class PeerScoring {
135
135
  }
136
136
  }
137
137
 
138
+ /** Resets all peer scores. Useful for benchmarks to prevent cross-case contamination. */
139
+ resetAllScores(): void {
140
+ this.scores.clear();
141
+ this.lastUpdateTime.clear();
142
+ }
143
+
138
144
  removePeer(peerId: string): void {
139
145
  this.scores.delete(peerId);
140
146
  this.lastUpdateTime.delete(peerId);
@@ -1,4 +1,4 @@
1
- import { type ConfigMapping, booleanConfigHelper, numberConfigHelper } from '@aztec/foundation/config';
1
+ import { type ConfigMappingsType, booleanConfigHelper, numberConfigHelper } from '@aztec/foundation/config';
2
2
 
3
3
  export const DEFAULT_INDIVIDUAL_REQUEST_TIMEOUT_MS = 10_000;
4
4
  export const DEFAULT_OVERALL_REQUEST_TIMEOUT_MS = 10_000; // Not currently used
@@ -27,7 +27,7 @@ export interface P2PReqRespConfig {
27
27
  dialTimeoutMs: number;
28
28
  }
29
29
 
30
- export const p2pReqRespConfigMappings: Record<keyof P2PReqRespConfig, ConfigMapping> = {
30
+ export const p2pReqRespConfigMappings: ConfigMappingsType<P2PReqRespConfig> = {
31
31
  overallRequestTimeoutMs: {
32
32
  env: 'P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS',
33
33
  description: 'The overall timeout for a request response operation.',
@@ -1,6 +1,3 @@
1
- import { Fr } from '@aztec/foundation/curves/bn254';
2
- import { L2Block } from '@aztec/stdlib/block';
3
- import { MAX_L2_BLOCK_SIZE_KB } from '@aztec/stdlib/p2p';
4
1
  import { TxArray, TxHashArray } from '@aztec/stdlib/tx';
5
2
 
6
3
  import type { PeerId } from '@libp2p/interface';
@@ -24,7 +21,6 @@ export const PING_PROTOCOL = '/aztec/req/ping/1.0.0';
24
21
  export const STATUS_PROTOCOL = '/aztec/req/status/1.0.0';
25
22
  export const GOODBYE_PROTOCOL = '/aztec/req/goodbye/1.0.0';
26
23
  export const TX_REQ_PROTOCOL = '/aztec/req/tx/1.0.0';
27
- export const BLOCK_REQ_PROTOCOL = '/aztec/req/block/1.0.0';
28
24
  export const AUTH_PROTOCOL = '/aztec/req/auth/1.0.0';
29
25
  export const BLOCK_TXS_REQ_PROTOCOL = '/aztec/req/block_txs/1.0.0';
30
26
 
@@ -33,7 +29,6 @@ export enum ReqRespSubProtocol {
33
29
  STATUS = STATUS_PROTOCOL,
34
30
  GOODBYE = GOODBYE_PROTOCOL,
35
31
  TX = TX_REQ_PROTOCOL,
36
- BLOCK = BLOCK_REQ_PROTOCOL,
37
32
  AUTH = AUTH_PROTOCOL,
38
33
  BLOCK_TXS = BLOCK_TXS_REQ_PROTOCOL,
39
34
  }
@@ -100,12 +95,29 @@ export type ReqRespSubProtocolValidators = {
100
95
  [S in ReqRespSubProtocol]: ResponseValidator<any, any>;
101
96
  };
102
97
 
98
+ /**
99
+ * Protocols that are always allowed without authentication, even when p2pAllowOnlyValidators is enabled.
100
+ * These are needed for the handshake and connection management flow.
101
+ * All other protocols require the remote peer to be authenticated.
102
+ */
103
+ export const UNAUTHENTICATED_ALLOWED_PROTOCOLS: ReadonlySet<ReqRespSubProtocol> = new Set([
104
+ ReqRespSubProtocol.PING,
105
+ ReqRespSubProtocol.STATUS,
106
+ ReqRespSubProtocol.AUTH,
107
+ ReqRespSubProtocol.GOODBYE,
108
+ ]);
109
+
110
+ /**
111
+ * Callback that checks whether a peer should be rejected from req/resp data protocols.
112
+ * Returns true if the peer should be rejected (i.e. p2pAllowOnlyValidators is on and peer is unauthenticated).
113
+ */
114
+ export type ShouldRejectPeer = (peerId: string) => boolean;
115
+
103
116
  export const DEFAULT_SUB_PROTOCOL_VALIDATORS: ReqRespSubProtocolValidators = {
104
117
  [ReqRespSubProtocol.PING]: noopValidator,
105
118
  [ReqRespSubProtocol.STATUS]: noopValidator,
106
119
  [ReqRespSubProtocol.TX]: noopValidator,
107
120
  [ReqRespSubProtocol.GOODBYE]: noopValidator,
108
- [ReqRespSubProtocol.BLOCK]: noopValidator,
109
121
  [ReqRespSubProtocol.AUTH]: noopValidator,
110
122
  [ReqRespSubProtocol.BLOCK_TXS]: noopValidator,
111
123
  };
@@ -203,10 +215,6 @@ export const subProtocolMap = {
203
215
  request: RequestableBuffer,
204
216
  response: RequestableBuffer,
205
217
  },
206
- [ReqRespSubProtocol.BLOCK]: {
207
- request: Fr, // block number
208
- response: L2Block,
209
- },
210
218
  [ReqRespSubProtocol.AUTH]: {
211
219
  request: AuthRequest,
212
220
  response: AuthResponse,
@@ -229,7 +237,6 @@ export type ExpectedResponseSizeCalculator = (requestBuffer: Buffer) => number;
229
237
  export const subProtocolSizeCalculators: Record<ReqRespSubProtocol, ExpectedResponseSizeCalculator> = {
230
238
  [ReqRespSubProtocol.TX]: calculateTxResponseSize,
231
239
  [ReqRespSubProtocol.BLOCK_TXS]: calculateBlockTxsResponseSize,
232
- [ReqRespSubProtocol.BLOCK]: () => MAX_L2_BLOCK_SIZE_KB,
233
240
  [ReqRespSubProtocol.STATUS]: () => 1,
234
241
  [ReqRespSubProtocol.PING]: () => 1,
235
242
  [ReqRespSubProtocol.AUTH]: () => 1,
@@ -264,5 +271,8 @@ export interface ReqRespInterface {
264
271
 
265
272
  updateConfig(config: Partial<P2PReqRespConfig>): void;
266
273
 
274
+ /** Sets the callback used to reject unauthenticated peers on gated req/resp protocols. */
275
+ setShouldRejectPeer(checker: ShouldRejectPeer): void;
276
+
267
277
  getConnectionSampler(): Pick<ConnectionSampler, 'getPeerListSortedByConnectionCountAsc'>;
268
278
  }
@@ -26,7 +26,6 @@ export class ReqRespMetrics {
26
26
  ReqRespSubProtocol.STATUS,
27
27
  ReqRespSubProtocol.GOODBYE,
28
28
  ReqRespSubProtocol.TX,
29
- ReqRespSubProtocol.BLOCK,
30
29
  ReqRespSubProtocol.AUTH,
31
30
  ReqRespSubProtocol.BLOCK_TXS,
32
31
  ],
@@ -5,6 +5,5 @@ export * from './ping.js';
5
5
  export * from './status.js';
6
6
  export * from './tx.js';
7
7
  export * from './goodbye.js';
8
- export * from './block.js';
9
8
  export * from './auth.js';
10
9
  export * from './block_txs/index.js';
@@ -51,9 +51,7 @@ export function reqRespTxHandler(mempools: MemPools): ReqRespSubProtocolHandler
51
51
  * Per: https://github.com/AztecProtocol/aztec-packages/issues/15149#issuecomment-2999054485
52
52
  * we define Q as max number of transactions per batch, the comment explains why we use 8.
53
53
  */
54
- //TODO: (mralj) chunk size should by default be 8, this is just temporary until the protocol is implemented correctly
55
- //more info: https://github.com/AztecProtocol/aztec-packages/pull/15516#pullrequestreview-2995474321
56
- export function chunkTxHashesRequest(hashes: TxHash[], chunkSize = 1): Array<TxHashArray> {
54
+ export function chunkTxHashesRequest(hashes: TxHash[], chunkSize = 8): Array<TxHashArray> {
57
55
  return chunk(hashes, chunkSize).map(chunk => new TxHashArray(...chunk));
58
56
  }
59
57
 
@@ -42,16 +42,6 @@ export const DEFAULT_RATE_LIMITS: ReqRespSubProtocolRateLimits = {
42
42
  quotaCount: 200,
43
43
  },
44
44
  },
45
- [ReqRespSubProtocol.BLOCK]: {
46
- peerLimit: {
47
- quotaTimeMs: 1000,
48
- quotaCount: 2,
49
- },
50
- globalLimit: {
51
- quotaTimeMs: 1000,
52
- quotaCount: 5,
53
- },
54
- },
55
45
  [ReqRespSubProtocol.GOODBYE]: {
56
46
  peerLimit: {
57
47
  quotaTimeMs: 1000,
@@ -34,7 +34,9 @@ import {
34
34
  type ReqRespSubProtocolHandlers,
35
35
  type ReqRespSubProtocolRateLimits,
36
36
  type ReqRespSubProtocolValidators,
37
+ type ShouldRejectPeer,
37
38
  type SubProtocolMap,
39
+ UNAUTHENTICATED_ALLOWED_PROTOCOLS,
38
40
  responseFromBuffer,
39
41
  subProtocolSizeCalculators,
40
42
  } from './interface.js';
@@ -72,6 +74,8 @@ export class ReqResp implements ReqRespInterface {
72
74
 
73
75
  private snappyTransform: SnappyTransform;
74
76
 
77
+ private shouldRejectPeer: ShouldRejectPeer | undefined;
78
+
75
79
  private metrics: ReqRespMetrics;
76
80
 
77
81
  constructor(
@@ -108,6 +112,10 @@ export class ReqResp implements ReqRespInterface {
108
112
  }
109
113
  }
110
114
 
115
+ public setShouldRejectPeer(checker: ShouldRejectPeer): void {
116
+ this.shouldRejectPeer = checker;
117
+ }
118
+
111
119
  get tracer() {
112
120
  return this.metrics.tracer;
113
121
  }
@@ -462,7 +470,7 @@ export class ReqResp implements ReqRespInterface {
462
470
  );
463
471
  return resp;
464
472
  } catch (e: any) {
465
- this.logger.warn(`SUBPROTOCOL: ${subProtocol}\n`, e);
473
+ this.logger.debug(`SUBPROTOCOL: ${subProtocol}\n`, e);
466
474
  // On error we immediately abort the stream, this is preferred way,
467
475
  // because it signals to the sender that error happened, whereas
468
476
  // closing the stream only closes our side and is much slower
@@ -596,6 +604,15 @@ export class ReqResp implements ReqRespInterface {
596
604
  throw new ReqRespStatusError(ReqRespStatus.RATE_LIMIT_EXCEEDED);
597
605
  }
598
606
 
607
+ // When p2pAllowOnlyValidators is enabled, reject unauthenticated peers on data protocols
608
+ if (
609
+ !UNAUTHENTICATED_ALLOWED_PROTOCOLS.has(protocol) &&
610
+ (this.shouldRejectPeer?.(connection.remotePeer.toString()) ?? false)
611
+ ) {
612
+ this.logger.debug(`Rejecting unauthenticated peer ${connection.remotePeer} on gated protocol ${protocol}`);
613
+ throw new ReqRespStatusError(ReqRespStatus.FAILURE);
614
+ }
615
+
599
616
  await this.processStream(protocol, incomingStream);
600
617
  } catch (err: any) {
601
618
  this.metrics.recordResponseError(protocol);
@@ -1,5 +1,6 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
2
  import { type Logger, createLogger } from '@aztec/foundation/log';
3
+ import { sleep } from '@aztec/foundation/sleep';
3
4
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
4
5
  import type { L2BlockSource } from '@aztec/stdlib/block';
5
6
  import type { ContractDataSource } from '@aztec/stdlib/contract';
@@ -100,6 +101,7 @@ class MockReqResp implements ReqRespInterface {
100
101
  }
101
102
 
102
103
  updateConfig(_config: Partial<P2PReqRespConfig>): void {}
104
+ setShouldRejectPeer(): void {}
103
105
 
104
106
  start(
105
107
  subProtocolHandlers: Partial<ReqRespSubProtocolHandlers>,
@@ -138,6 +140,11 @@ class MockReqResp implements ReqRespInterface {
138
140
  const responses: InstanceType<SubProtocolMap[SubProtocol]['response']>[] = [];
139
141
  const peers = this.network.getReqRespPeers().filter(p => !p.peerId.equals(this.peerId));
140
142
  const targetPeers = pinnedPeer ? peers.filter(p => p.peerId.equals(pinnedPeer)) : peers;
143
+ const delayMs = this.network.getPropagationDelayMs();
144
+
145
+ if (delayMs > 0) {
146
+ await sleep(delayMs);
147
+ }
141
148
 
142
149
  for (const request of requests) {
143
150
  const requestBuffer = request.toBuffer();
@@ -174,7 +181,12 @@ class MockReqResp implements ReqRespInterface {
174
181
  return { status: ReqRespStatus.SUCCESS, data: Buffer.from([]) };
175
182
  }
176
183
  try {
184
+ const delayMs = this.network.getPropagationDelayMs();
185
+ if (delayMs > 0) {
186
+ await sleep(delayMs);
187
+ }
177
188
  const data = await handler(this.peerId, payload);
189
+
178
190
  return { status: ReqRespStatus.SUCCESS, data };
179
191
  } catch {
180
192
  return { status: ReqRespStatus.FAILURE };
@@ -242,10 +254,10 @@ class MockGossipSubService extends TypedEventEmitter<GossipsubEvents> implements
242
254
  score: (_peerId: PeerIdStr) => 0,
243
255
  };
244
256
 
245
- publish(topic: TopicStr, data: Uint8Array, _opts?: PublishOpts): Promise<PublishResult> {
257
+ async publish(topic: TopicStr, data: Uint8Array, _opts?: PublishOpts): Promise<PublishResult> {
246
258
  this.logger.debug(`Publishing message on topic ${topic}`, { topic, sender: this.peerId.toString() });
247
- this.network.publishToPeers(topic, data, this.peerId);
248
- return Promise.resolve({ recipients: this.network.getPeers().filter(peer => !this.peerId.equals(peer)) });
259
+ await this.network.publishToPeers(topic, data, this.peerId);
260
+ return { recipients: this.network.getPeers().filter(peer => !this.peerId.equals(peer)) };
249
261
  }
250
262
 
251
263
  receive(msg: GossipsubMessage) {
@@ -281,7 +293,8 @@ class MockGossipSubService extends TypedEventEmitter<GossipsubEvents> implements
281
293
 
282
294
  /**
283
295
  * Mock gossip sub network used for testing.
284
- * All instances of MockGossipSubService connected to the same network will instantly receive the same messages.
296
+ * All instances of MockGossipSubService connected to the same network receive the same messages,
297
+ * optionally delayed by a configurable propagation time.
285
298
  */
286
299
  export class MockGossipSubNetwork {
287
300
  private peers: MockGossipSubService[] = [];
@@ -290,6 +303,15 @@ export class MockGossipSubNetwork {
290
303
 
291
304
  private logger = createLogger('p2p:test:mock-gossipsub-network');
292
305
 
306
+ constructor(
307
+ /** Artificial propagation delay in milliseconds applied to each message delivery. */
308
+ private propagationDelayMs: number = 0,
309
+ ) {}
310
+
311
+ public getPropagationDelayMs(): number {
312
+ return this.propagationDelayMs;
313
+ }
314
+
293
315
  public getPeers(): PeerId[] {
294
316
  return this.peers.map(peer => peer.peerId);
295
317
  }
@@ -306,7 +328,7 @@ export class MockGossipSubNetwork {
306
328
  return this.reqRespPeers;
307
329
  }
308
330
 
309
- public publishToPeers(topic: TopicStr, data: Uint8Array, sender: PeerId): void {
331
+ public async publishToPeers(topic: TopicStr, data: Uint8Array, sender: PeerId): Promise<void> {
310
332
  const msgId = (this.nextMsgId++).toString();
311
333
  this.logger.debug(`Network is distributing message on topic ${topic}`, {
312
334
  topic,
@@ -315,6 +337,10 @@ export class MockGossipSubNetwork {
315
337
  msgId,
316
338
  });
317
339
 
340
+ if (this.propagationDelayMs > 0) {
341
+ await sleep(this.propagationDelayMs);
342
+ }
343
+
318
344
  const gossipSubMsg: GossipsubMessage = { msgId, msg: { type: 'unsigned', topic, data }, propagationSource: sender };
319
345
  for (const peer of this.peers) {
320
346
  if (peer.subscribedTopics.has(topic)) {
@@ -153,6 +153,8 @@ export async function createTestLibP2PService(
153
153
  epochCache,
154
154
  );
155
155
 
156
+ reqresp.setShouldRejectPeer(peerId => peerManager.shouldDisableP2PGossip(peerId));
157
+
156
158
  p2pNode.services.pubsub.score.params.appSpecificWeight = APP_SPECIFIC_WEIGHT;
157
159
  p2pNode.services.pubsub.score.params.appSpecificScore = (peerId: string) =>
158
160
  peerManager.shouldDisableP2PGossip(peerId) ? -Infinity : peerManager.getPeerScore(peerId);
@@ -187,7 +189,6 @@ export const MOCK_SUB_PROTOCOL_HANDLERS: ReqRespSubProtocolHandlers = {
187
189
  [ReqRespSubProtocol.STATUS]: (_msg: any) => Promise.resolve(Buffer.from('status')),
188
190
  [ReqRespSubProtocol.TX]: (_msg: any) => Promise.resolve(Buffer.from('tx')),
189
191
  [ReqRespSubProtocol.GOODBYE]: (_msg: any) => Promise.resolve(Buffer.from('goodbye')),
190
- [ReqRespSubProtocol.BLOCK]: (_msg: any) => Promise.resolve(Buffer.from('block')),
191
192
  [ReqRespSubProtocol.AUTH]: (_msg: any) => Promise.resolve(Buffer.from('auth')),
192
193
  [ReqRespSubProtocol.BLOCK_TXS]: (_msg: any) => Promise.resolve(Buffer.from('block_txs')),
193
194
  };
@@ -199,7 +200,6 @@ export const MOCK_SUB_PROTOCOL_VALIDATORS: ReqRespSubProtocolValidators = {
199
200
  [ReqRespSubProtocol.STATUS]: noopValidator,
200
201
  [ReqRespSubProtocol.TX]: noopValidator,
201
202
  [ReqRespSubProtocol.GOODBYE]: noopValidator,
202
- [ReqRespSubProtocol.BLOCK]: noopValidator,
203
203
  [ReqRespSubProtocol.AUTH]: noopValidator,
204
204
  [ReqRespSubProtocol.BLOCK_TXS]: noopValidator,
205
205
  };
@@ -287,6 +287,7 @@ export function createMockEpochCache(): EpochCacheInterface {
287
287
  nowMs: 0n,
288
288
  }),
289
289
  isProposerPipeliningEnabled: () => false,
290
+ pipeliningOffset: () => 0,
290
291
  computeProposerIndex: () => 0n,
291
292
  getCurrentAndNextSlot: () => ({ currentSlot: SlotNumber.ZERO, nextSlot: SlotNumber.ZERO }),
292
293
  getTargetAndNextSlot: () => ({ targetSlot: SlotNumber.ZERO, nextSlot: SlotNumber.ZERO }),
@@ -204,6 +204,25 @@ function installUnlimitedRateLimits(client: P2PClient): void {
204
204
  rateLimiter.allow = () => RateLimitStatus.Allowed;
205
205
  }
206
206
 
207
+ /** Resets peer scores to prevent cross-case contamination in benchmarks. */
208
+ function resetPeerScores(client: P2PClient): void {
209
+ const peerManager = (client as any).p2pService.peerManager;
210
+ const peerScoring = peerManager?.peerScoring;
211
+ if (peerScoring?.resetAllScores) {
212
+ peerScoring.resetAllScores();
213
+ }
214
+ }
215
+
216
+ /** Returns the number of connected peers for connectivity checks. */
217
+ function getConnectedPeerCount(client: P2PClient): number {
218
+ const p2pService = (client as any).p2pService;
219
+ const connectionSampler = p2pService?.reqresp?.getConnectionSampler?.();
220
+ if (connectionSampler?.getPeerListSortedByConnectionCountAsc) {
221
+ return connectionSampler.getPeerListSortedByConnectionCountAsc().length;
222
+ }
223
+ return 0;
224
+ }
225
+
207
226
  async function runAggregatorBenchmark(
208
227
  client: P2PClient,
209
228
  blockProposal: BlockProposal,
@@ -452,6 +471,13 @@ process.on('message', async msg => {
452
471
  }
453
472
  break;
454
473
 
474
+ case 'GET_PEER_COUNT':
475
+ process.send!({
476
+ type: 'PEER_COUNT',
477
+ count: workerClient ? getConnectedPeerCount(workerClient) : 0,
478
+ });
479
+ break;
480
+
455
481
  case 'BENCH_REQRESP': {
456
482
  const benchCmd = cmd as BenchReqRespCommand;
457
483
  if (!workerClient || !workerTxPool || !workerAttestationPool || !workerConfig || !workerLogger) {
@@ -468,6 +494,7 @@ process.on('message', async msg => {
468
494
  // Reset state before each benchmark run to avoid cross-run contamination
469
495
  workerTxPool.resetState();
470
496
  workerAttestationPool.resetState();
497
+ resetPeerScores(workerClient);
471
498
 
472
499
  installUnlimitedRateLimits(workerClient);
473
500