@aztec/p2p 0.0.1-commit.2f68f620 → 0.0.1-commit.3100065

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 (153) hide show
  1. package/README.md +7 -6
  2. package/dest/bootstrap/bootstrap.d.ts +1 -1
  3. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  4. package/dest/bootstrap/bootstrap.js +20 -12
  5. package/dest/client/factory.d.ts +2 -2
  6. package/dest/client/factory.d.ts.map +1 -1
  7. package/dest/client/factory.js +10 -5
  8. package/dest/client/interface.d.ts +36 -8
  9. package/dest/client/interface.d.ts.map +1 -1
  10. package/dest/client/p2p_client.d.ts +23 -12
  11. package/dest/client/p2p_client.d.ts.map +1 -1
  12. package/dest/client/p2p_client.js +38 -35
  13. package/dest/config.d.ts +19 -13
  14. package/dest/config.d.ts.map +1 -1
  15. package/dest/config.js +27 -26
  16. package/dest/errors/reqresp.error.d.ts +13 -1
  17. package/dest/errors/reqresp.error.d.ts.map +1 -1
  18. package/dest/errors/reqresp.error.js +14 -0
  19. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
  20. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/attestation_pool.js +9 -2
  22. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  23. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +43 -0
  25. package/dest/mem_pools/instrumentation.d.ts +9 -4
  26. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  27. package/dest/mem_pools/instrumentation.js +11 -8
  28. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -3
  29. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +6 -6
  31. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
  32. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +30 -5
  33. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +15 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +31 -0
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +9 -3
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +19 -8
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +17 -4
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +133 -34
  44. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +6 -5
  45. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  46. package/dest/msg_validators/attestation_validator/attestation_validator.js +24 -26
  47. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -4
  48. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  49. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
  50. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +4 -3
  51. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  52. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +2 -2
  53. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +4 -3
  54. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  55. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +8 -2
  56. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +19 -5
  57. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  58. package/dest/msg_validators/proposal_validator/proposal_validator.js +66 -30
  59. package/dest/msg_validators/tx_validator/factory.d.ts +9 -11
  60. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  61. package/dest/msg_validators/tx_validator/factory.js +15 -6
  62. package/dest/msg_validators/tx_validator/gas_validator.d.ts +12 -7
  63. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  64. package/dest/msg_validators/tx_validator/gas_validator.js +15 -15
  65. package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
  66. package/dest/services/discv5/discV5_service.d.ts +12 -2
  67. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  68. package/dest/services/discv5/discV5_service.js +84 -15
  69. package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
  70. package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
  71. package/dest/services/discv5/persisted_enr_store.js +145 -0
  72. package/dest/services/dummy_service.d.ts +6 -2
  73. package/dest/services/dummy_service.d.ts.map +1 -1
  74. package/dest/services/dummy_service.js +3 -0
  75. package/dest/services/encoding.d.ts +19 -6
  76. package/dest/services/encoding.d.ts.map +1 -1
  77. package/dest/services/encoding.js +25 -15
  78. package/dest/services/gossipsub/topic_score_params.d.ts +6 -23
  79. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  80. package/dest/services/gossipsub/topic_score_params.js +4 -31
  81. package/dest/services/libp2p/libp2p_service.d.ts +11 -3
  82. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  83. package/dest/services/libp2p/libp2p_service.js +135 -53
  84. package/dest/services/peer-manager/peer_manager.d.ts +15 -2
  85. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  86. package/dest/services/peer-manager/peer_manager.js +46 -18
  87. package/dest/services/peer-manager/peer_scoring.d.ts +28 -1
  88. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  89. package/dest/services/peer-manager/peer_scoring.js +70 -3
  90. package/dest/services/reqresp/config.d.ts +13 -1
  91. package/dest/services/reqresp/config.d.ts.map +1 -1
  92. package/dest/services/reqresp/config.js +11 -0
  93. package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
  94. package/dest/services/reqresp/reqresp.d.ts +1 -1
  95. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  96. package/dest/services/reqresp/reqresp.js +34 -3
  97. package/dest/services/service.d.ts +17 -1
  98. package/dest/services/service.d.ts.map +1 -1
  99. package/dest/services/tx_collection/tx_source.d.ts +1 -1
  100. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  101. package/dest/services/tx_collection/tx_source.js +4 -1
  102. package/dest/test-helpers/testbench-utils.d.ts +4 -1
  103. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  104. package/dest/test-helpers/testbench-utils.js +9 -0
  105. package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
  106. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  107. package/dest/testbench/p2p_client_testbench_worker.js +19 -2
  108. package/package.json +16 -17
  109. package/src/bootstrap/bootstrap.ts +15 -9
  110. package/src/client/factory.ts +9 -5
  111. package/src/client/interface.ts +29 -6
  112. package/src/client/p2p_client.ts +46 -48
  113. package/src/config.ts +50 -38
  114. package/src/errors/reqresp.error.ts +19 -0
  115. package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
  116. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
  117. package/src/mem_pools/instrumentation.ts +13 -11
  118. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +7 -7
  119. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
  120. package/src/mem_pools/tx_pool_v2/interfaces.ts +28 -4
  121. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +35 -0
  122. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +22 -8
  123. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +158 -33
  124. package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -26
  125. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -3
  126. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -2
  127. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +10 -2
  128. package/src/msg_validators/proposal_validator/proposal_validator.ts +69 -30
  129. package/src/msg_validators/tx_validator/README.md +2 -0
  130. package/src/msg_validators/tx_validator/factory.ts +19 -13
  131. package/src/msg_validators/tx_validator/gas_validator.ts +16 -20
  132. package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
  133. package/src/services/discv5/discV5_service.ts +88 -9
  134. package/src/services/discv5/persisted_enr_store.ts +158 -0
  135. package/src/services/dummy_service.ts +6 -0
  136. package/src/services/encoding.ts +28 -20
  137. package/src/services/gossipsub/README.md +33 -11
  138. package/src/services/gossipsub/topic_score_params.ts +9 -50
  139. package/src/services/libp2p/libp2p_service.ts +159 -56
  140. package/src/services/peer-manager/peer_manager.ts +48 -17
  141. package/src/services/peer-manager/peer_scoring.ts +75 -3
  142. package/src/services/reqresp/README.md +2 -23
  143. package/src/services/reqresp/config.ts +17 -0
  144. package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
  145. package/src/services/reqresp/reqresp.ts +42 -2
  146. package/src/services/service.ts +19 -0
  147. package/src/services/tx_collection/tx_source.ts +2 -1
  148. package/src/test-helpers/testbench-utils.ts +12 -0
  149. package/src/testbench/p2p_client_testbench_worker.ts +22 -2
  150. package/dest/msg_validators/clock_tolerance.d.ts +0 -32
  151. package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
  152. package/dest/msg_validators/clock_tolerance.js +0 -90
  153. package/src/msg_validators/clock_tolerance.ts +0 -121
@@ -1,14 +1,15 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
2
  import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
3
- import { maxBy, merge } from '@aztec/foundation/collection';
3
+ import { compactArray, maxBy, merge } from '@aztec/foundation/collection';
4
4
  import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
5
5
  import { RunningPromise } from '@aztec/foundation/running-promise';
6
6
  import { Timer } from '@aztec/foundation/timer';
7
7
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
8
8
  import { protocolContractsHash } from '@aztec/protocol-contracts';
9
9
  import type { EthAddress, L2BlockSource } from '@aztec/stdlib/block';
10
+ import { DEFAULT_MAX_BLOCKS_PER_CHECKPOINT } from '@aztec/stdlib/config';
10
11
  import type { ContractDataSource } from '@aztec/stdlib/contract';
11
- import { type BlockMinFeesProvider, GasFees } from '@aztec/stdlib/gas';
12
+ import { type BlockMinFeesProvider, GasFees, getNetworkTxGasLimits } from '@aztec/stdlib/gas';
12
13
  import type { ClientProtocolCircuitVerifier, PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
13
14
  import {
14
15
  BlockProposal,
@@ -24,6 +25,7 @@ import {
24
25
  getTopicsForConfig,
25
26
  metricsTopicStrToLabels,
26
27
  } from '@aztec/stdlib/p2p';
28
+ import { ConsensusTimetable, getDefaultCheckpointProposalSyncGrace } from '@aztec/stdlib/timetable';
27
29
  import { MerkleTreeId } from '@aztec/stdlib/trees';
28
30
  import { Tx, type TxValidationResult } from '@aztec/stdlib/tx';
29
31
  import type { UInt64 } from '@aztec/stdlib/types';
@@ -81,14 +83,14 @@ import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
81
83
  import { getVersions } from '../../versioning.js';
82
84
  import { AztecDatastore } from '../data_store.js';
83
85
  import { DiscV5Service } from '../discv5/discV5_service.js';
84
- import { SnappyTransform, fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
86
+ import { SnappyTransform, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
85
87
  import { APP_SPECIFIC_WEIGHT, gossipScoreThresholds } from '../gossipsub/scoring.js';
86
88
  import { createAllTopicScoreParams } from '../gossipsub/topic_score_params.js';
87
89
  import type { PeerManagerInterface } from '../peer-manager/interface.js';
88
90
  import { PeerManager } from '../peer-manager/peer_manager.js';
89
91
  import { PeerScoring } from '../peer-manager/peer_scoring.js';
90
92
  import type { BatchTxRequesterLibP2PService } from '../reqresp/batch-tx-requester/interface.js';
91
- import type { P2PReqRespConfig } from '../reqresp/config.js';
93
+ import { type P2PReqRespConfig, YAMUX_MAX_MESSAGE_SIZE_BYTES } from '../reqresp/config.js';
92
94
  import {
93
95
  AuthRequest,
94
96
  BlockTxsRequest,
@@ -112,11 +114,30 @@ import type {
112
114
  P2PCheckpointAttestationCallback,
113
115
  P2PCheckpointReceivedCallback,
114
116
  P2PDuplicateAttestationCallback,
117
+ P2POversizedProposalCallback,
115
118
  P2PService,
116
119
  PeerDiscoveryService,
117
120
  } from '../service.js';
118
121
  import { P2PInstrumentation } from './instrumentation.js';
119
122
 
123
+ /**
124
+ * Builds the {@link ConsensusTimetable} shared by the gossip validators for proposal/attestation receive-window
125
+ * bounds. Derived purely from protocol slot-timing constants plus the block sub-slot duration and the consensus
126
+ * materialization grace, so every node agrees on these bounds without depending on proposer operational budgets.
127
+ */
128
+ function buildConsensusTimetable(
129
+ config: P2PConfig,
130
+ l1Constants: ReturnType<EpochCacheInterface['getL1Constants']>,
131
+ ): ConsensusTimetable {
132
+ const blockDuration = config.blockDurationMs / 1000;
133
+ return new ConsensusTimetable({
134
+ l1Constants,
135
+ blockDuration,
136
+ checkpointProposalSyncGrace:
137
+ config.checkpointProposalSyncGraceSeconds ?? getDefaultCheckpointProposalSyncGrace(blockDuration),
138
+ });
139
+ }
140
+
120
141
  interface ValidationResult {
121
142
  name: string;
122
143
  isValid: TxValidationResult;
@@ -152,6 +173,9 @@ export class LibP2PService extends WithTracer implements P2PService {
152
173
  type: 'checkpoint' | 'block';
153
174
  }) => void;
154
175
 
176
+ /** Callback invoked when an oversized block proposal is stored as slashing evidence (triggers slashing). */
177
+ private oversizedProposalCallback?: P2POversizedProposalCallback;
178
+
155
179
  /** Callback invoked when a duplicate attestation is detected (triggers slashing). */
156
180
  private duplicateAttestationCallback?: P2PDuplicateAttestationCallback;
157
181
 
@@ -233,28 +257,39 @@ export class LibP2PService extends WithTracer implements P2PService {
233
257
  this.protocolVersion,
234
258
  );
235
259
 
236
- const p2pPropagationTime = config.attestationPropagationTime;
260
+ // Build the consensus timetable once from protocol slot-timing constants and inject it into every
261
+ // validator so they share one set of receive-window bounds, independent of proposer operational budgets.
262
+ const consensusTimetable = buildConsensusTimetable(config, epochCache.getL1Constants());
237
263
  const proposalValidatorOpts = {
238
264
  txsPermitted: !config.disableTransactions,
239
265
  maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
240
266
  maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
241
- p2pPropagationTime,
242
267
  skipSlotValidation: config.skipProposalSlotValidation,
243
268
  signatureContext: {
244
269
  chainId: config.l1ChainId,
245
270
  rollupAddress: config.rollupAddress,
246
271
  },
272
+ clockDisparityMs: config.maxGossipClockDisparityMs,
247
273
  };
248
- this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
249
- this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
274
+ this.blockProposalValidator = new BlockProposalValidator(epochCache, consensusTimetable, proposalValidatorOpts);
275
+ this.checkpointProposalValidator = new CheckpointProposalValidator(
276
+ epochCache,
277
+ consensusTimetable,
278
+ proposalValidatorOpts,
279
+ );
250
280
  const attestationValidatorOpts = {
251
- l1PublishingTime: config.l1PublishingTime,
252
- p2pPropagationTime,
253
281
  signatureContext: proposalValidatorOpts.signatureContext,
282
+ clockDisparityMs: config.maxGossipClockDisparityMs,
254
283
  };
255
284
  this.checkpointAttestationValidator = config.fishermanMode
256
- ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry, attestationValidatorOpts)
257
- : new CheckpointAttestationValidator(epochCache, attestationValidatorOpts);
285
+ ? new FishermanAttestationValidator(
286
+ epochCache,
287
+ consensusTimetable,
288
+ mempools.attestationPool,
289
+ telemetry,
290
+ attestationValidatorOpts,
291
+ )
292
+ : new CheckpointAttestationValidator(epochCache, consensusTimetable, attestationValidatorOpts);
258
293
 
259
294
  this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
260
295
 
@@ -333,6 +368,7 @@ export class LibP2PService extends WithTracer implements P2PService {
333
368
  packageVersion,
334
369
  telemetry,
335
370
  createLogger(`${logger.module}:discv5_service`, logger.getBindings()),
371
+ peerStore,
336
372
  );
337
373
 
338
374
  // Seed libp2p's bootstrap discovery with private and trusted peers
@@ -347,37 +383,49 @@ export class LibP2PService extends WithTracer implements P2PService {
347
383
  const protocolVersion = compressComponentVersions(versions);
348
384
 
349
385
  const preferredPeersEnrs: ENR[] = config.preferredPeers.map(enr => ENR.decodeTxt(enr));
350
- const directPeers = (
386
+ const directPeers = compactArray(
351
387
  await Promise.all(
352
388
  preferredPeersEnrs.map(async enr => {
353
- const peerId = await enr.peerId();
354
- const address = enr.getLocationMultiaddr('tcp');
355
- if (address === undefined) {
356
- throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
389
+ try {
390
+ const peerId = await enr.peerId();
391
+ const address = enr.getLocationMultiaddr('tcp');
392
+ if (address === undefined) {
393
+ throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
394
+ }
395
+ return {
396
+ id: peerId,
397
+ addrs: [address],
398
+ };
399
+ } catch (err) {
400
+ // A malformed configured ENR shouldn't abort node setup — skip it and log.
401
+ logger.warn(`Skipping preferred peer with invalid ENR`, { err });
402
+ return undefined;
357
403
  }
358
- return {
359
- id: peerId,
360
- addrs: [address],
361
- };
362
404
  }),
363
- )
364
- ).filter(peer => peer !== undefined);
405
+ ),
406
+ );
365
407
 
366
408
  const announceTcpMultiaddr = config.p2pIp ? [convertToMultiaddr(config.p2pIp, p2pPort, 'tcp')] : [];
367
409
 
368
- // Create dynamic topic score params based on network configuration
410
+ // Create dynamic topic score params based on network configuration. Scoring uses the network-wide
411
+ // max-blocks-per-checkpoint config value directly to size expected per-slot message rates; these are
412
+ // peer-rate thresholds, not consensus deadlines, so they need no proposer operational budgets.
369
413
  const l1Constants = epochCache.getL1Constants();
370
414
  const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
371
415
  slotDurationMs: l1Constants.slotDuration * 1000,
372
- ethereumSlotDuration: l1Constants.ethereumSlotDuration,
373
416
  heartbeatIntervalMs: config.gossipsubInterval,
374
417
  targetCommitteeSize: l1Constants.targetCommitteeSize,
375
- blockDurationMs: config.blockDurationMs,
376
- l1PublishingTime: config.l1PublishingTime,
377
- p2pPropagationTime: config.attestationPropagationTime,
418
+ maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint ?? DEFAULT_MAX_BLOCKS_PER_CHECKPOINT,
378
419
  expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot,
379
420
  });
380
421
 
422
+ // Restrict gossipsub to exactly the topics we subscribe to. Without this, an arbitrary-topic
423
+ // message is transformed, msg-id'd and inserted into the seenCache before the subscription check,
424
+ // so a crafted topic colliding on msg id can suppress a real message as a duplicate.
425
+ const allowedTopics = getTopicsForConfig(config.disableTransactions).map(topic =>
426
+ createTopicString(topic, protocolVersion),
427
+ );
428
+
381
429
  const node = await createLibp2p({
382
430
  start: false,
383
431
  peerId,
@@ -409,7 +457,8 @@ export class LibP2PService extends WithTracer implements P2PService {
409
457
  ],
410
458
  datastore,
411
459
  peerDiscovery,
412
- streamMuxers: [yamux(), mplex()],
460
+ // Pin the yamux frame size: MAX_REQRESP_REQUEST_SIZE_BYTES relies on a reqresp request fitting in one frame.
461
+ streamMuxers: [yamux({ maxMessageSize: YAMUX_MAX_MESSAGE_SIZE_BYTES }), mplex()],
413
462
  connectionEncryption: [noise()],
414
463
  connectionManager: {
415
464
  minConnections: 0, // Disable libp2p peer dialing, we do it manually
@@ -423,7 +472,7 @@ export class LibP2PService extends WithTracer implements P2PService {
423
472
  },
424
473
  connectionGater: {
425
474
  denyInboundConnection: (maConn: MultiaddrConnection) => {
426
- const allowed = peerManager.isNodeAllowedToConnect(maConn.remoteAddr.nodeAddress().address);
475
+ const allowed = peerManager.isAddressAllowedToConnect(maConn.remoteAddr.nodeAddress().address);
427
476
  if (allowed) {
428
477
  return false;
429
478
  }
@@ -434,7 +483,7 @@ export class LibP2PService extends WithTracer implements P2PService {
434
483
  denyInboundEncryptedConnection: (peerId: PeerId, _maConn: MultiaddrConnection) => {
435
484
  //NOTE: it is not necessary to check address here because this was already done by
436
485
  // denyInboundConnection
437
- const allowed = peerManager.isNodeAllowedToConnect(peerId);
486
+ const allowed = peerManager.isPeerAllowedToConnect(peerId);
438
487
  if (allowed) {
439
488
  return false;
440
489
  }
@@ -462,9 +511,13 @@ export class LibP2PService extends WithTracer implements P2PService {
462
511
  mcacheLength: config.gossipsubMcacheLength,
463
512
  mcacheGossip: config.gossipsubMcacheGossip,
464
513
  seenTTL: config.gossipsubSeenTTL,
514
+ allowedTopics,
515
+ // No fastMsgIdFn: the fast-path dedup cache keys on a non-cryptographic 64-bit hash of the
516
+ // raw data only (no topic), so a collision — accidental or engineered via a weak seed — drops
517
+ // a different message with no fallback to the full id. Dedup instead rests solely on the
518
+ // cryptographic, topic-framed msgIdFn below.
465
519
  msgIdFn: getMsgIdFn,
466
520
  msgIdToStrFn: msgIdToStrFn,
467
- fastMsgIdFn: fastMsgIdFn,
468
521
  dataTransform: new SnappyTransform(),
469
522
  metricsRegister: otelMetricsAdapter,
470
523
  metricsTopicStrToLabel: metricsTopicStrToLabels(protocolVersion),
@@ -732,6 +785,13 @@ export class LibP2PService extends WithTracer implements P2PService {
732
785
  this.duplicateProposalCallback = callback;
733
786
  }
734
787
 
788
+ /**
789
+ * Registers a callback to be invoked when an oversized block proposal is stored as slashing evidence.
790
+ */
791
+ public registerOversizedProposalCallback(callback: P2POversizedProposalCallback): void {
792
+ this.oversizedProposalCallback = callback;
793
+ }
794
+
735
795
  /**
736
796
  * Registers a callback to be invoked when a duplicate attestation is detected.
737
797
  * A validator signing attestations for different proposals at the same slot.
@@ -1213,16 +1273,17 @@ export class LibP2PService extends WithTracer implements P2PService {
1213
1273
  const {
1214
1274
  result,
1215
1275
  obj: block,
1216
- metadata: { isEquivocated } = {},
1217
- } = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean }>(
1276
+ metadata: { isEquivocated, isOversized } = {},
1277
+ } = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean; isOversized: boolean }>(
1218
1278
  () => this.validateAndStoreBlockProposal(source, BlockProposal.fromBuffer(payloadData)),
1219
1279
  msgId,
1220
1280
  source,
1221
1281
  TopicType.block_proposal,
1222
1282
  );
1223
1283
 
1224
- // If not accepted or equivocated, return
1225
- if (result !== TopicValidatorResult.Accept || !block || isEquivocated) {
1284
+ // If not accepted, equivocated, or oversized, return. Oversized proposals are re-broadcast as
1285
+ // slashing evidence but never attested or processed.
1286
+ if (result !== TopicValidatorResult.Accept || !block || isEquivocated || isOversized) {
1226
1287
  return;
1227
1288
  }
1228
1289
 
@@ -1237,7 +1298,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1237
1298
  protected async validateAndStoreBlockProposal(
1238
1299
  peerId: PeerId,
1239
1300
  block: BlockProposal,
1240
- ): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean }>> {
1301
+ ): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean; isOversized: boolean }>> {
1241
1302
  const validationResult = await this.blockProposalValidator.validate(block);
1242
1303
 
1243
1304
  if (validationResult.result === 'reject') {
@@ -1252,6 +1313,12 @@ export class LibP2PService extends WithTracer implements P2PService {
1252
1313
  // Try to add the proposal: this handles existence check, cap check, and adding in one call
1253
1314
  const { added, alreadyExists, count } = await this.mempools.attestationPool.tryAddBlockProposal(block);
1254
1315
  const isEquivocated = count !== undefined && count > 1;
1316
+ // An oversized proposal (index at or beyond the consensus per-checkpoint limit) is structurally valid
1317
+ // proposer misbehavior: it is stored and re-broadcast as slashing evidence but never processed or
1318
+ // attested to. No-ops when maxBlocksPerCheckpoint is unset (local/test).
1319
+ const isOversized =
1320
+ this.config.maxBlocksPerCheckpoint !== undefined &&
1321
+ block.indexWithinCheckpoint >= this.config.maxBlocksPerCheckpoint;
1255
1322
 
1256
1323
  // Duplicate proposal received, no need to re-broadcast
1257
1324
  if (alreadyExists) {
@@ -1261,7 +1328,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1261
1328
  proposer: block.getSender()?.toString(),
1262
1329
  source: peerId.toString(),
1263
1330
  });
1264
- return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated } };
1331
+ return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated, isOversized } };
1265
1332
  }
1266
1333
 
1267
1334
  // Too many blocks received for this slot and index, penalize peer and do not re-broadcast
@@ -1275,11 +1342,27 @@ export class LibP2PService extends WithTracer implements P2PService {
1275
1342
  });
1276
1343
  return {
1277
1344
  result: TopicValidatorResult.Reject,
1278
- metadata: { isEquivocated },
1345
+ metadata: { isEquivocated, isOversized },
1279
1346
  severity: PeerErrorSeverity.HighToleranceError,
1280
1347
  };
1281
1348
  }
1282
1349
 
1350
+ // The proposal was stored: if oversized, invoke the oversized callback so the proposer can be
1351
+ // slashed. Fired alongside (not instead of) equivocation detection below.
1352
+ if (isOversized) {
1353
+ const proposer = block.getSender();
1354
+ if (proposer) {
1355
+ this.logger.warn(`Detected oversized block proposal at slot ${block.slotNumber}`, {
1356
+ ...block.toBlockInfo(),
1357
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
1358
+ maxBlocksPerCheckpoint: this.config.maxBlocksPerCheckpoint,
1359
+ source: peerId.toString(),
1360
+ proposer: proposer.toString(),
1361
+ });
1362
+ this.oversizedProposalCallback?.({ slot: block.slotNumber, proposer });
1363
+ }
1364
+ }
1365
+
1283
1366
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
1284
1367
  // and do re-broadcast it so other nodes in the network know to slash the proposer
1285
1368
  if (isEquivocated) {
@@ -1293,11 +1376,11 @@ export class LibP2PService extends WithTracer implements P2PService {
1293
1376
  if (proposer && count === 2) {
1294
1377
  this.duplicateProposalCallback?.({ slot: block.slotNumber, proposer, type: 'block' });
1295
1378
  }
1296
- return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated } };
1379
+ return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated, isOversized } };
1297
1380
  }
1298
1381
 
1299
1382
  // Otherwise, we're good to go!
1300
- return { result: TopicValidatorResult.Accept, obj: block };
1383
+ return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated: false, isOversized } };
1301
1384
  }
1302
1385
 
1303
1386
  // REFACTOR(palla): This method should be moved to the p2p_client or to a separate component,
@@ -1323,6 +1406,9 @@ export class LibP2PService extends WithTracer implements P2PService {
1323
1406
  const isValid = await this.blockReceivedCallback(block, sender);
1324
1407
  if (!isValid) {
1325
1408
  this.logger.info(`Block proposal validation failed for block ${block.blockNumber}`, block.toBlockInfo());
1409
+ // Release the protections this proposal created so its txs return to pending. Only entries still
1410
+ // keyed to this slot are cleared, so a tx referenced by a live proposal at another slot stays protected.
1411
+ await this.mempools.txPool.unprotectTxs(block.txHashes, slot);
1326
1412
  }
1327
1413
  }
1328
1414
 
@@ -1334,17 +1420,21 @@ export class LibP2PService extends WithTracer implements P2PService {
1334
1420
  const {
1335
1421
  result,
1336
1422
  obj: checkpoint,
1337
- metadata: { isEquivocated, processBlock } = {},
1338
- } = await this.validateReceivedMessage<CheckpointProposal, { isEquivocated: boolean; processBlock: boolean }>(
1423
+ metadata: { isEquivocated, processBlock, isOversized } = {},
1424
+ } = await this.validateReceivedMessage<
1425
+ CheckpointProposal,
1426
+ { isEquivocated: boolean; processBlock: boolean; isOversized: boolean }
1427
+ >(
1339
1428
  () => this.validateAndStoreCheckpointProposal(source, CheckpointProposal.fromBuffer(payloadData)),
1340
1429
  msgId,
1341
1430
  source,
1342
1431
  TopicType.checkpoint_proposal,
1343
1432
  );
1344
1433
 
1345
- // Process checkpoint proposal if valid and not equivocated.
1434
+ // An oversized checkpoint is re-broadcast as slashing evidence but never attested or processed.
1435
+ // Process checkpoint proposal if valid and neither equivocated nor oversized.
1346
1436
  const processCheckpointFn = () =>
1347
- result === TopicValidatorResult.Accept && checkpoint && !isEquivocated
1437
+ result === TopicValidatorResult.Accept && checkpoint && !isEquivocated && !isOversized
1348
1438
  ? this.processValidCheckpointProposal(checkpoint.toCore(), source)
1349
1439
  : Promise.resolve();
1350
1440
 
@@ -1381,7 +1471,12 @@ export class LibP2PService extends WithTracer implements P2PService {
1381
1471
  protected async validateAndStoreCheckpointProposal(
1382
1472
  peerId: PeerId,
1383
1473
  checkpoint: CheckpointProposal,
1384
- ): Promise<ReceivedMessageValidationResult<CheckpointProposal, { isEquivocated: boolean; processBlock: boolean }>> {
1474
+ ): Promise<
1475
+ ReceivedMessageValidationResult<
1476
+ CheckpointProposal,
1477
+ { isEquivocated: boolean; processBlock: boolean; isOversized: boolean }
1478
+ >
1479
+ > {
1385
1480
  const validationResult = await this.checkpointProposalValidator.validate(checkpoint);
1386
1481
 
1387
1482
  if (validationResult.result === 'reject') {
@@ -1396,13 +1491,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1396
1491
  // Extract and try to add the block proposal first if present
1397
1492
  const blockProposal = checkpoint.getBlockProposal();
1398
1493
  let processBlock = false;
1494
+ let isOversized = false;
1399
1495
  if (blockProposal) {
1400
1496
  this.logger.debug(`Validating block proposal from propagated checkpoint`, {
1401
1497
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1402
1498
  [Attributes.P2P_ID]: peerId.toString(),
1403
1499
  });
1404
1500
  const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1405
- const { obj, metadata: { isEquivocated } = {} } = blockProposalResult;
1501
+ const { obj, metadata: { isEquivocated, isOversized: blockIsOversized } = {} } = blockProposalResult;
1502
+ isOversized = blockIsOversized ?? false;
1503
+
1406
1504
  if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1407
1505
  this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
1408
1506
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
@@ -1415,7 +1513,8 @@ export class LibP2PService extends WithTracer implements P2PService {
1415
1513
  severity:
1416
1514
  'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError,
1417
1515
  };
1418
- } else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1516
+ } else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated && !isOversized) {
1517
+ // An oversized terminal block is re-broadcast as slashing evidence but never processed.
1419
1518
  processBlock = true;
1420
1519
  }
1421
1520
  }
@@ -1435,11 +1534,11 @@ export class LibP2PService extends WithTracer implements P2PService {
1435
1534
  return {
1436
1535
  result: TopicValidatorResult.Ignore,
1437
1536
  obj: checkpoint,
1438
- metadata: { isEquivocated, processBlock },
1537
+ metadata: { isEquivocated, processBlock, isOversized },
1439
1538
  };
1440
1539
  }
1441
1540
 
1442
- // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
1541
+ // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast.
1443
1542
  // Note: We still return the checkpoint obj so the lastBlock can be processed if valid
1444
1543
  if (!added) {
1445
1544
  this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
@@ -1450,7 +1549,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1450
1549
  return {
1451
1550
  result: TopicValidatorResult.Reject,
1452
1551
  obj: checkpoint,
1453
- metadata: { isEquivocated, processBlock },
1552
+ metadata: { isEquivocated, processBlock, isOversized },
1454
1553
  severity: PeerErrorSeverity.HighToleranceError,
1455
1554
  };
1456
1555
  }
@@ -1471,12 +1570,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1471
1570
  return {
1472
1571
  result: TopicValidatorResult.Accept,
1473
1572
  obj: checkpoint,
1474
- metadata: { isEquivocated, processBlock },
1573
+ metadata: { isEquivocated, processBlock, isOversized },
1475
1574
  };
1476
1575
  }
1477
1576
 
1478
1577
  // Otherwise, we're good to go!
1479
- return { result: TopicValidatorResult.Accept, obj: checkpoint, metadata: { processBlock, isEquivocated } };
1578
+ return {
1579
+ result: TopicValidatorResult.Accept,
1580
+ obj: checkpoint,
1581
+ metadata: { processBlock, isEquivocated, isOversized },
1582
+ };
1480
1583
  }
1481
1584
 
1482
1585
  /**
@@ -1670,6 +1773,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1670
1773
  ];
1671
1774
  const blockNumber = BlockNumber(currentBlockNumber + 1);
1672
1775
  const l1Constants = await this.archiver.getL1Constants();
1776
+ const networkTxGasLimits = getNetworkTxGasLimits(this.config, l1Constants);
1673
1777
 
1674
1778
  return createFirstStageTxValidationsForGossipedTransactions(
1675
1779
  nextSlotTimestamp,
@@ -1684,9 +1788,8 @@ export class LibP2PService extends WithTracer implements P2PService {
1684
1788
  allowedInSetup,
1685
1789
  this.logger.getBindings(),
1686
1790
  {
1687
- rollupManaLimit: l1Constants.rollupManaLimit,
1688
- maxBlockL2Gas: this.config.validateMaxL2BlockGas,
1689
- maxBlockDAGas: this.config.validateMaxDABlockGas,
1791
+ maxTxL2Gas: networkTxGasLimits.l2Gas,
1792
+ maxTxDAGas: networkTxGasLimits.daGas,
1690
1793
  },
1691
1794
  );
1692
1795
  }
@@ -1,4 +1,5 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
+ import { compactArray } from '@aztec/foundation/collection';
2
3
  import { makeEthSignDigest, tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
3
4
  import { Fr } from '@aztec/foundation/curves/bn254';
4
5
  import type { EthAddress } from '@aztec/foundation/eth-address';
@@ -164,6 +165,7 @@ export class PeerManager implements PeerManagerInterface {
164
165
  public async heartbeat() {
165
166
  this.heartbeatCounter++;
166
167
  this.peerScoring.decayAllScores();
168
+ this.peerScoring.pruneExpiredBans();
167
169
  this.cleanupExpiredTimeouts();
168
170
 
169
171
  await this.setupDirectPeersIfValidator();
@@ -198,21 +200,27 @@ export class PeerManager implements PeerManagerInterface {
198
200
  .then(peerIds => peerIds.forEach(peerId => this.preferredPeers.add(peerId.toString())))
199
201
  .catch(e => this.logger.error('Error initializing preferred peers', e));
200
202
 
201
- const directPeers = (
203
+ const directPeers = compactArray(
202
204
  await Promise.all(
203
205
  preferredPeersEnrs.map(async enr => {
204
- const peerId = await enr.peerId();
205
- const address = enr.getLocationMultiaddr('tcp');
206
- if (address === undefined) {
207
- throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
206
+ try {
207
+ const peerId = await enr.peerId();
208
+ const address = enr.getLocationMultiaddr('tcp');
209
+ if (address === undefined) {
210
+ throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
211
+ }
212
+ return {
213
+ id: peerId,
214
+ addrs: [address],
215
+ };
216
+ } catch (err) {
217
+ // A malformed configured ENR shouldn't abort preferred-peer setup — skip it and log.
218
+ this.logger.warn(`Skipping preferred peer with invalid ENR`, { err });
219
+ return undefined;
208
220
  }
209
- return {
210
- id: peerId,
211
- addrs: [address],
212
- };
213
221
  }),
214
- )
215
- ).filter(peer => peer !== undefined);
222
+ ),
223
+ );
216
224
 
217
225
  await Promise.all(
218
226
  directPeers.map(peer => {
@@ -494,15 +502,38 @@ export class PeerManager implements PeerManagerInterface {
494
502
  *
495
503
  * @returns: True if node is allowed to connect, otherwise false
496
504
  * */
497
- public isNodeAllowedToConnect(id: string | PeerId): boolean {
498
- const entry = this.failedAuthHandshakes.get(id.toString());
505
+ /**
506
+ * Whether a peer is allowed to connect, given its peer id. Rejects peers serving an active ban and
507
+ * peers that have exceeded the failed auth-handshake limit. Use this once the peer id is known —
508
+ * i.e. for the encrypted-inbound gater and when dialing.
509
+ */
510
+ public isPeerAllowedToConnect(peerId: string | PeerId): boolean {
511
+ const id = peerId.toString();
512
+ if (this.peerScoring.getScoreState(id) === PeerScoreState.Banned) {
513
+ return false;
514
+ }
515
+ return this.isWithinFailedAuthLimit(id);
516
+ }
517
+
518
+ /**
519
+ * Whether a connection from an address is allowed. Bans are keyed by peer id, which isn't known at
520
+ * the raw-inbound layer, so only the failed auth-handshake limit (also tracked per address) is
521
+ * enforced here; the ban is applied once the peer id is known via {@link isPeerAllowedToConnect}.
522
+ */
523
+ public isAddressAllowedToConnect(address: string): boolean {
524
+ return this.isWithinFailedAuthLimit(address);
525
+ }
526
+
527
+ /** Whether the failed auth-handshake count for a peer id or address is below the configured limit. */
528
+ private isWithinFailedAuthLimit(key: string): boolean {
529
+ const entry = this.failedAuthHandshakes.get(key);
499
530
  if (!entry) {
500
531
  return true;
501
532
  }
502
533
 
503
534
  // In case entry is too old, remove it and allow connection
504
535
  if (this.dateProvider.now() - entry.lastFailureTimestamp > FAILED_AUTH_HANDSHAKE_EXPIRY_MS) {
505
- this.failedAuthHandshakes.delete(id.toString());
536
+ this.failedAuthHandshakes.delete(key);
506
537
  return true;
507
538
  }
508
539
 
@@ -729,9 +760,9 @@ export class PeerManager implements PeerManagerInterface {
729
760
  return;
730
761
  }
731
762
 
732
- // Don't dial peers that have exceeded the auth failure threshold
733
- if (!this.isNodeAllowedToConnect(peerId)) {
734
- this.logger.trace(`Skipping peer ${peerId} due to failed auth handshake attempts`);
763
+ // Don't dial banned peers or those that have exceeded the auth failure threshold
764
+ if (!this.isPeerAllowedToConnect(peerId)) {
765
+ this.logger.trace(`Skipping peer ${peerId} due to ban or failed auth handshake attempts`);
735
766
  return;
736
767
  }
737
768