@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
@@ -317,6 +317,55 @@ class WorkerClientManager {
317
317
  this.logger.info('All worker processes cleaned up');
318
318
  }
319
319
  /**
320
+ * Checks that the aggregator (client 0) has sufficient peer connections before running a benchmark.
321
+ * This prevents benchmark cases from starting with degraded connectivity after a previous case
322
+ * caused connection failures.
323
+ */ async waitForConnectivity(minPeers, timeoutMs = 15_000) {
324
+ const waitInterval = 1000;
325
+ let waited = 0;
326
+ while(waited < timeoutMs){
327
+ const count = await this.getPeerCount(0, 5000);
328
+ if (count >= minPeers) {
329
+ this.logger.info(`Connectivity check passed: ${count}/${minPeers} peers connected`);
330
+ return count;
331
+ }
332
+ this.logger.debug(`Waiting for connectivity: ${count}/${minPeers} (waited ${waited}ms)`);
333
+ await sleep(waitInterval);
334
+ waited += waitInterval;
335
+ }
336
+ const finalCount = await this.getPeerCount(0, 5000);
337
+ this.logger.warn(`Connectivity check: only ${finalCount}/${minPeers} peers after ${timeoutMs}ms`);
338
+ return finalCount;
339
+ }
340
+ getPeerCount(clientIndex, timeoutMs) {
341
+ return new Promise((resolve)=>{
342
+ let resolved = false;
343
+ const handler = (msg)=>{
344
+ if (resolved) {
345
+ return;
346
+ }
347
+ if (msg.type === 'PEER_COUNT') {
348
+ resolved = true;
349
+ clearTimeout(timeout);
350
+ this.processes[clientIndex].off('message', handler);
351
+ resolve(msg.count);
352
+ }
353
+ };
354
+ const timeout = setTimeout(()=>{
355
+ if (resolved) {
356
+ return;
357
+ }
358
+ resolved = true;
359
+ this.processes[clientIndex].off('message', handler);
360
+ resolve(0);
361
+ }, timeoutMs);
362
+ this.processes[clientIndex].on('message', handler);
363
+ this.processes[clientIndex].send({
364
+ type: 'GET_PEER_COUNT'
365
+ });
366
+ });
367
+ }
368
+ /**
320
369
  * Run a req/resp benchmark across all worker clients.
321
370
  *
322
371
  * This sends a BENCH_REQRESP command to all workers:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/p2p",
3
- "version": "0.0.1-commit.2d9cb6034",
3
+ "version": "0.0.1-commit.2e20a94",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -67,17 +67,17 @@
67
67
  ]
68
68
  },
69
69
  "dependencies": {
70
- "@aztec/constants": "0.0.1-commit.2d9cb6034",
71
- "@aztec/epoch-cache": "0.0.1-commit.2d9cb6034",
72
- "@aztec/ethereum": "0.0.1-commit.2d9cb6034",
73
- "@aztec/foundation": "0.0.1-commit.2d9cb6034",
74
- "@aztec/kv-store": "0.0.1-commit.2d9cb6034",
75
- "@aztec/noir-contracts.js": "0.0.1-commit.2d9cb6034",
76
- "@aztec/noir-protocol-circuits-types": "0.0.1-commit.2d9cb6034",
77
- "@aztec/protocol-contracts": "0.0.1-commit.2d9cb6034",
78
- "@aztec/simulator": "0.0.1-commit.2d9cb6034",
79
- "@aztec/stdlib": "0.0.1-commit.2d9cb6034",
80
- "@aztec/telemetry-client": "0.0.1-commit.2d9cb6034",
70
+ "@aztec/constants": "0.0.1-commit.2e20a94",
71
+ "@aztec/epoch-cache": "0.0.1-commit.2e20a94",
72
+ "@aztec/ethereum": "0.0.1-commit.2e20a94",
73
+ "@aztec/foundation": "0.0.1-commit.2e20a94",
74
+ "@aztec/kv-store": "0.0.1-commit.2e20a94",
75
+ "@aztec/noir-contracts.js": "0.0.1-commit.2e20a94",
76
+ "@aztec/noir-protocol-circuits-types": "0.0.1-commit.2e20a94",
77
+ "@aztec/protocol-contracts": "0.0.1-commit.2e20a94",
78
+ "@aztec/simulator": "0.0.1-commit.2e20a94",
79
+ "@aztec/stdlib": "0.0.1-commit.2e20a94",
80
+ "@aztec/telemetry-client": "0.0.1-commit.2e20a94",
81
81
  "@chainsafe/libp2p-gossipsub": "13.0.0",
82
82
  "@chainsafe/libp2p-noise": "^15.0.0",
83
83
  "@chainsafe/libp2p-yamux": "^6.0.2",
@@ -104,8 +104,8 @@
104
104
  "xxhash-wasm": "^1.1.0"
105
105
  },
106
106
  "devDependencies": {
107
- "@aztec/archiver": "0.0.1-commit.2d9cb6034",
108
- "@aztec/world-state": "0.0.1-commit.2d9cb6034",
107
+ "@aztec/archiver": "0.0.1-commit.2e20a94",
108
+ "@aztec/world-state": "0.0.1-commit.2e20a94",
109
109
  "@jest/globals": "^30.0.0",
110
110
  "@types/jest": "^30.0.0",
111
111
  "@types/node": "^22.15.17",
@@ -1,4 +1,3 @@
1
- import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/constants';
2
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
3
2
  import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
4
3
  import { createLogger } from '@aztec/foundation/log';
@@ -9,6 +8,7 @@ import { L2TipsKVStore } from '@aztec/kv-store/stores';
9
8
  import {
10
9
  type CheckpointId,
11
10
  type EthAddress,
11
+ GENESIS_BLOCK_HEADER_HASH,
12
12
  type L2Block,
13
13
  type L2BlockId,
14
14
  type L2BlockSource,
@@ -257,7 +257,11 @@ export class P2PClient extends WithTracer implements P2P {
257
257
  });
258
258
  }
259
259
 
260
- this.blockStream!.start();
260
+ // Should never happen: all branches above call initBlockStream()
261
+ if (!this.blockStream) {
262
+ throw new Error('Block stream not initialized');
263
+ }
264
+ this.blockStream.start();
261
265
  await this.txCollection.start();
262
266
  this.txFileStore?.start();
263
267
 
@@ -319,7 +323,11 @@ export class P2PClient extends WithTracer implements P2P {
319
323
  /** Triggers a sync to the archiver. Used for testing. */
320
324
  public async sync() {
321
325
  this.initBlockStream();
322
- await this.blockStream!.sync();
326
+ // Should never happen: initBlockStream() creates blockStream if absent
327
+ if (!this.blockStream) {
328
+ throw new Error('Block stream not initialized');
329
+ }
330
+ await this.blockStream.sync();
323
331
  }
324
332
 
325
333
  @trackSpan('p2pClient.broadcastProposal', async proposal => ({
package/src/config.ts CHANGED
@@ -39,7 +39,14 @@ export interface P2PConfig
39
39
  ChainConfig,
40
40
  TxCollectionConfig,
41
41
  TxFileStoreConfig,
42
- Pick<SequencerConfig, 'blockDurationMs' | 'expectedBlockProposalsPerSlot' | 'maxTxsPerBlock'> {
42
+ Pick<
43
+ SequencerConfig,
44
+ | 'blockDurationMs'
45
+ | 'expectedBlockProposalsPerSlot'
46
+ | 'l1PublishingTime'
47
+ | 'maxTxsPerBlock'
48
+ | 'attestationPropagationTime'
49
+ > {
43
50
  /** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
44
51
  validateMaxTxsPerBlock?: number;
45
52
 
@@ -218,23 +225,23 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
218
225
  env: 'VALIDATOR_MAX_TX_PER_BLOCK',
219
226
  description:
220
227
  'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
221
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
228
+ parseEnv: (val: string) => parseInt(val, 10),
222
229
  },
223
230
  validateMaxTxsPerCheckpoint: {
224
231
  env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
225
232
  description:
226
233
  'Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set.',
227
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
234
+ parseEnv: (val: string) => parseInt(val, 10),
228
235
  },
229
236
  validateMaxL2BlockGas: {
230
237
  env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
231
238
  description: 'Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected.',
232
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
239
+ parseEnv: (val: string) => parseInt(val, 10),
233
240
  },
234
241
  validateMaxDABlockGas: {
235
242
  env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
236
243
  description: 'Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected.',
237
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
244
+ parseEnv: (val: string) => parseInt(val, 10),
238
245
  },
239
246
  p2pEnabled: {
240
247
  env: 'P2P_ENABLED',
@@ -436,7 +443,7 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
436
443
  },
437
444
  p2pStoreMapSizeKb: {
438
445
  env: 'P2P_STORE_MAP_SIZE_KB',
439
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
446
+ parseEnv: (val: string) => +val,
440
447
  description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb.',
441
448
  },
442
449
  txPublicSetupAllowListExtend: {
@@ -495,6 +502,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
495
502
  description: 'Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY',
496
503
  ...booleanConfigHelper(false),
497
504
  },
505
+ l1PublishingTime: {
506
+ env: 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT',
507
+ description: 'How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot).',
508
+ parseEnv: (val: string) => parseInt(val, 10),
509
+ },
498
510
  fishermanMode: {
499
511
  env: 'FISHERMAN_MODE',
500
512
  description:
@@ -154,14 +154,16 @@ export class AttestationPool {
154
154
  /** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */
155
155
  private static readonly MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1;
156
156
 
157
- /** Creates a position key for block proposals: (slot << 10) | indexWithinCheckpoint. */
157
+ /** Creates a position key for block proposals: slot * 1024 + indexWithinCheckpoint.
158
+ * Uses multiplication instead of bit-shift to avoid 32-bit signed integer overflow
159
+ * (bit-shift overflows after slot ~2^21, roughly 278 days of uptime). */
158
160
  private getBlockPositionKey(slot: number, indexWithinCheckpoint: number): number {
159
161
  if (indexWithinCheckpoint > AttestationPool.MAX_INDEX) {
160
162
  throw new Error(
161
163
  `Value for indexWithinCheckpoint ${indexWithinCheckpoint} exceeds maximum ${AttestationPool.MAX_INDEX}`,
162
164
  );
163
165
  }
164
- return (slot << AttestationPool.INDEX_BITS) | indexWithinCheckpoint;
166
+ return slot * (1 << AttestationPool.INDEX_BITS) + indexWithinCheckpoint;
165
167
  }
166
168
 
167
169
  /**
@@ -454,7 +456,7 @@ export class AttestationPool {
454
456
 
455
457
  // Delete block proposals for slots < oldestSlot, using blockProposalsForSlotAndIndex as index
456
458
  // Key format: (slot << INDEX_BITS) | indexWithinCheckpoint
457
- const blockPositionEndKey = oldestSlot << AttestationPool.INDEX_BITS;
459
+ const blockPositionEndKey = oldestSlot * (1 << AttestationPool.INDEX_BITS);
458
460
  for await (const positionKey of this.blockProposalsForSlotAndIndex.keysAsync({ end: blockPositionEndKey })) {
459
461
  const proposalIds = await toArray(this.blockProposalsForSlotAndIndex.getValuesAsync(positionKey));
460
462
  for (const proposalId of proposalIds) {
@@ -1,5 +1,5 @@
1
- import { Fr } from '@aztec/foundation/curves/bn254';
2
1
  import { createLogger } from '@aztec/foundation/log';
2
+ import { BlockHash } from '@aztec/stdlib/block';
3
3
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
4
4
  import { MerkleTreeId } from '@aztec/stdlib/trees';
5
5
 
@@ -33,14 +33,14 @@ export class InvalidTxsAfterReorgRule implements EvictionRule {
33
33
  const pendingTxs = pool.getPendingTxs();
34
34
 
35
35
  // Deduplicate block hashes to reduce redundant DB lookups
36
- const uniqueBlockHashes = new Map<string, Fr>();
36
+ const uniqueBlockHashes = new Map<string, BlockHash>();
37
37
  const txsByBlockHash = new Map<string, string[]>();
38
38
 
39
39
  for (const meta of pendingTxs) {
40
40
  const blockHashStr = meta.anchorBlockHeaderHash;
41
41
  if (!txsByBlockHash.has(blockHashStr)) {
42
42
  txsByBlockHash.set(blockHashStr, []);
43
- uniqueBlockHashes.set(blockHashStr, Fr.fromHexString(blockHashStr));
43
+ uniqueBlockHashes.set(blockHashStr, BlockHash.fromString(blockHashStr));
44
44
  }
45
45
  txsByBlockHash.get(blockHashStr)!.push(meta.txHash);
46
46
  }
@@ -291,7 +291,7 @@ export function stubTxMetaValidationData(overrides: { expirationTimestamp?: bigi
291
291
  expirationTimestamp: overrides.expirationTimestamp ?? 0n,
292
292
  constants: {
293
293
  anchorBlockHeader: {
294
- hash: () => Promise.resolve(new BlockHash(Fr.ZERO)),
294
+ hash: () => Promise.resolve(BlockHash.ZERO),
295
295
  globalVariables: { blockNumber: BlockNumber(0) },
296
296
  },
297
297
  txContext: {
@@ -8,14 +8,21 @@ import {
8
8
  type ValidationResult,
9
9
  } from '@aztec/stdlib/p2p';
10
10
 
11
- import { isWithinClockTolerance } from '../clock_tolerance.js';
11
+ import { PipeliningWindow, isWithinClockTolerance } from '../clock_tolerance.js';
12
12
 
13
13
  export class CheckpointAttestationValidator implements P2PValidator<CheckpointAttestation> {
14
14
  protected epochCache: EpochCacheInterface;
15
15
  protected logger: Logger;
16
+ private readonly pipeliningWindow: PipeliningWindow;
16
17
 
17
- constructor(epochCache: EpochCacheInterface) {
18
+ constructor(
19
+ epochCache: EpochCacheInterface,
20
+ opts: {
21
+ l1PublishingTime?: number;
22
+ },
23
+ ) {
18
24
  this.epochCache = epochCache;
25
+ this.pipeliningWindow = new PipeliningWindow(epochCache, { l1PublishingTime: opts.l1PublishingTime });
19
26
  this.logger = createLogger('p2p:checkpoint-attestation-validator');
20
27
  }
21
28
 
@@ -23,19 +30,23 @@ export class CheckpointAttestationValidator implements P2PValidator<CheckpointAt
23
30
  const slotNumber = message.payload.header.slotNumber;
24
31
 
25
32
  try {
26
- // Use target slots since proposals target pipeline slots (slot + 1 when pipelining)
33
+ // Use target slots since proposals target pipeline slots (slot + 1 when pipelining).
27
34
  const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
28
35
 
29
36
  if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
30
- // Check if message is for previous slot and within clock tolerance
31
- if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
37
+ // When pipelining, accept attestations for the current slot (built in the previous slot)
38
+ // until the target slot reaches its L1 publish cutoff.
39
+ if (this.pipeliningWindow.acceptsAttestation(slotNumber)) {
40
+ // Fall through to remaining validation (signature, committee, etc.)
41
+ } else if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
32
42
  this.logger.warn(
33
43
  `Checkpoint attestation slot ${slotNumber} is not current (${targetSlot}) or next (${nextSlot}) slot`,
34
44
  );
35
45
  return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
46
+ } else {
47
+ this.logger.debug(`Ignoring checkpoint attestation for previous slot ${slotNumber} within clock tolerance`);
48
+ return { result: 'ignore' };
36
49
  }
37
- this.logger.debug(`Ignoring checkpoint attestation for previous slot ${slotNumber} within clock tolerance`);
38
- return { result: 'ignore' };
39
50
  }
40
51
 
41
52
  // Verify the signature is valid
@@ -20,8 +20,11 @@ export class FishermanAttestationValidator extends CheckpointAttestationValidato
20
20
  epochCache: EpochCacheInterface,
21
21
  private attestationPool: AttestationPoolApi,
22
22
  telemetryClient: TelemetryClient,
23
+ opts: {
24
+ l1PublishingTime?: number;
25
+ } = {},
23
26
  ) {
24
- super(epochCache);
27
+ super(epochCache, opts);
25
28
  this.logger = this.logger.createChild('[FISHERMAN]');
26
29
 
27
30
  const meter = telemetryClient.getMeter('FishermanAttestationValidator');
@@ -1,5 +1,6 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
2
  import { SlotNumber } from '@aztec/foundation/branded-types';
3
+ import { DEFAULT_P2P_PROPAGATION_TIME, createPipelinedCheckpointTimingModel } from '@aztec/stdlib/timetable';
3
4
 
4
5
  /**
5
6
  * Maximum clock disparity tolerance for P2P message validation (in milliseconds).
@@ -50,3 +51,70 @@ export function isWithinClockTolerance(
50
51
 
51
52
  return elapsedMs < MAXIMUM_GOSSIP_CLOCK_DISPARITY_MS;
52
53
  }
54
+
55
+ /**
56
+ * Checks if a message should be accepted under the pipelining grace period.
57
+ *
58
+ * When pipelining is enabled, `targetSlot = slotNow + 1`. A proposal built in slot N-1
59
+ * for slot N arrives when validators are in slot N, so their `targetSlot = N+1`.
60
+ * This function accepts proposals for the current wallclock slot if we're within the
61
+ * first `windowSeconds` seconds of the slot (the pipelining grace period). - see stdlib/timetable/index.ts
62
+ *
63
+ * @param messageSlot - The slot number from the received message
64
+ * @param epochCache - EpochCache to get timing and pipelining state
65
+ * @param windowSeconds - The window grace period allowed for attestations into the next slot
66
+ * @returns true if pipelining is enabled, the message is for the current slot, and we're within the grace period
67
+ */
68
+ function isWithinPipeliningWindow(
69
+ messageSlot: SlotNumber,
70
+ epochCache: EpochCacheInterface,
71
+ windowSeconds: number,
72
+ ): boolean {
73
+ if (!epochCache.isProposerPipeliningEnabled()) {
74
+ return false;
75
+ }
76
+
77
+ const currentSlot = epochCache.getSlotNow();
78
+ if (messageSlot !== currentSlot) {
79
+ return false;
80
+ }
81
+
82
+ const { ts: slotStartTs, nowMs } = epochCache.getEpochAndSlotNow();
83
+ const slotStartMs = slotStartTs * 1000n;
84
+ const elapsedMs = Number(nowMs - slotStartMs);
85
+ const windowMs = windowSeconds * 1000 + MAXIMUM_GOSSIP_CLOCK_DISPARITY_MS;
86
+
87
+ return elapsedMs < windowMs;
88
+ }
89
+
90
+ export class PipeliningWindow {
91
+ private readonly proposalWindowIntoTargetSlot: number;
92
+ private readonly attestationWindowIntoTargetSlot: number;
93
+
94
+ constructor(
95
+ private readonly epochCache: EpochCacheInterface,
96
+ opts: {
97
+ p2pPropagationTime?: number;
98
+ l1PublishingTime?: number;
99
+ } = {},
100
+ ) {
101
+ const l1Constants = epochCache.getL1Constants();
102
+ const checkpointTiming = createPipelinedCheckpointTimingModel({
103
+ aztecSlotDuration: l1Constants.slotDuration,
104
+ ethereumSlotDuration: l1Constants.ethereumSlotDuration,
105
+ l1PublishingTime: opts.l1PublishingTime ?? l1Constants.ethereumSlotDuration,
106
+ p2pPropagationTime: opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME,
107
+ });
108
+
109
+ this.proposalWindowIntoTargetSlot = checkpointTiming.proposalWindowIntoTargetSlot;
110
+ this.attestationWindowIntoTargetSlot = checkpointTiming.attestationWindowIntoTargetSlot;
111
+ }
112
+
113
+ public acceptsProposal(messageSlot: SlotNumber): boolean {
114
+ return isWithinPipeliningWindow(messageSlot, this.epochCache, this.proposalWindowIntoTargetSlot);
115
+ }
116
+
117
+ public acceptsAttestation(messageSlot: SlotNumber): boolean {
118
+ return isWithinPipeliningWindow(messageSlot, this.epochCache, this.attestationWindowIntoTargetSlot);
119
+ }
120
+ }
@@ -6,7 +6,10 @@ import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
6
6
  export class BlockProposalValidator implements P2PValidator<BlockProposal> {
7
7
  private proposalValidator: ProposalValidator;
8
8
 
9
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
9
+ constructor(
10
+ epochCache: EpochCacheInterface,
11
+ opts: { txsPermitted: boolean; maxTxsPerBlock?: number; p2pPropagationTime?: number },
12
+ ) {
10
13
  this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:block_proposal_validator');
11
14
  }
12
15
 
@@ -6,7 +6,10 @@ import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
6
6
  export class CheckpointProposalValidator implements P2PValidator<CheckpointProposal> {
7
7
  private proposalValidator: ProposalValidator;
8
8
 
9
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
9
+ constructor(
10
+ epochCache: EpochCacheInterface,
11
+ opts: { txsPermitted: boolean; maxTxsPerBlock?: number; p2pPropagationTime?: number },
12
+ ) {
10
13
  this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:checkpoint_proposal_validator');
11
14
  }
12
15
 
@@ -8,7 +8,7 @@ import {
8
8
  type ValidationResult,
9
9
  } from '@aztec/stdlib/p2p';
10
10
 
11
- import { isWithinClockTolerance } from '../clock_tolerance.js';
11
+ import { PipeliningWindow, isWithinClockTolerance } from '../clock_tolerance.js';
12
12
 
13
13
  /** Validates header-level and tx-level fields of block and checkpoint proposals. */
14
14
  export class ProposalValidator {
@@ -16,33 +16,39 @@ export class ProposalValidator {
16
16
  private logger: Logger;
17
17
  private txsPermitted: boolean;
18
18
  private maxTxsPerBlock?: number;
19
+ private pipeliningWindow: PipeliningWindow;
19
20
 
20
21
  constructor(
21
22
  epochCache: EpochCacheInterface,
22
- opts: { txsPermitted: boolean; maxTxsPerBlock?: number },
23
+ opts: { txsPermitted: boolean; maxTxsPerBlock?: number; p2pPropagationTime?: number },
23
24
  loggerName: string,
24
25
  ) {
25
26
  this.epochCache = epochCache;
26
27
  this.txsPermitted = opts.txsPermitted;
27
28
  this.maxTxsPerBlock = opts.maxTxsPerBlock;
29
+ this.pipeliningWindow = new PipeliningWindow(epochCache, { p2pPropagationTime: opts.p2pPropagationTime });
28
30
  this.logger = createLogger(loggerName);
29
31
  }
30
32
 
31
33
  /** Validates header-level fields: slot, signature, and proposer. */
32
34
  public async validate(proposal: BlockProposal | CheckpointProposalCore): Promise<ValidationResult> {
33
35
  try {
34
- // Slot check: use target slots since proposals target pipeline slots (slot + 1 when pipelining)
36
+ // Slot check: use target slots since proposals target pipeline slots (slot + 1 when pipelining).
35
37
  const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
36
38
 
37
39
  const slotNumber = proposal.slotNumber;
38
40
  if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
39
- // Check if message is for previous slot and within clock tolerance
40
- if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
41
+ // When pipelining, accept proposals for the current slot (built in the previous slot)
42
+ // if they're still within the shared proposal acceptance window.
43
+ if (this.pipeliningWindow.acceptsProposal(slotNumber)) {
44
+ // Fall through to remaining validation (signature, proposer, etc.)
45
+ } else if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
41
46
  this.logger.warn(`Penalizing peer for invalid slot number ${slotNumber}`, { targetSlot, nextSlot });
42
47
  return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
48
+ } else {
49
+ this.logger.verbose(`Ignoring proposal for previous slot ${slotNumber} within clock tolerance`);
50
+ return { result: 'ignore' };
43
51
  }
44
- this.logger.verbose(`Ignoring proposal for previous slot ${slotNumber} within clock tolerance`);
45
- return { result: 'ignore' };
46
52
  }
47
53
 
48
54
  // Signature validity
@@ -15,7 +15,7 @@ export class ArchiveCache implements ArchiveSource {
15
15
  }
16
16
 
17
17
  public async getArchiveIndices(archives: BlockHash[]): Promise<(bigint | undefined)[]> {
18
- const toCheckDb = archives.filter(n => !this.archives.has(n.toString())).map(n => n.toFr());
18
+ const toCheckDb = archives.filter(n => !this.archives.has(n.toString()));
19
19
  const dbHits = await this.db.findLeafIndices(MerkleTreeId.ARCHIVE, toCheckDb);
20
20
  dbHits.forEach((x, index) => {
21
21
  if (x !== undefined) {
@@ -87,7 +87,12 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
87
87
  gasLimits,
88
88
  minGasLimits,
89
89
  });
90
- return { result: 'invalid', reason: [TX_ERROR_INSUFFICIENT_GAS_LIMIT] };
90
+ return {
91
+ result: 'invalid',
92
+ reason: [
93
+ `${TX_ERROR_INSUFFICIENT_GAS_LIMIT} (required=da:${minGasLimits.daGas},l2:${minGasLimits.l2Gas} got=da:${gasLimits.daGas},l2:${gasLimits.l2Gas})`,
94
+ ],
95
+ };
91
96
  }
92
97
 
93
98
  if (gasLimits.l2Gas > this.#effectiveMaxL2Gas) {
@@ -97,7 +102,10 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
97
102
  rollupManaLimit: this.#rollupManaLimit,
98
103
  maxBlockL2Gas: this.#maxBlockL2Gas,
99
104
  });
100
- return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
105
+ return {
106
+ result: 'invalid',
107
+ reason: [`${TX_ERROR_GAS_LIMIT_TOO_HIGH} (l2Gas=${gasLimits.l2Gas}, max=${this.#effectiveMaxL2Gas})`],
108
+ };
101
109
  }
102
110
 
103
111
  if (gasLimits.daGas > this.#effectiveMaxDAGas) {
@@ -106,7 +114,10 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
106
114
  effectiveMaxDAGas: this.#effectiveMaxDAGas,
107
115
  maxBlockDAGas: this.#maxBlockDAGas,
108
116
  });
109
- return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
117
+ return {
118
+ result: 'invalid',
119
+ reason: [`${TX_ERROR_GAS_LIMIT_TOO_HIGH} (daGas=${gasLimits.daGas}, max=${this.#effectiveMaxDAGas})`],
120
+ };
110
121
  }
111
122
 
112
123
  return { result: 'valid' };
@@ -157,19 +168,20 @@ export class GasTxValidator implements TxValidator<Tx> {
157
168
  if (gasLimitValidation.result === 'invalid') {
158
169
  return Promise.resolve(gasLimitValidation);
159
170
  }
160
- if (this.#shouldSkip(tx)) {
161
- return Promise.resolve({ result: 'skipped', reason: [TX_ERROR_INSUFFICIENT_FEE_PER_GAS] });
171
+ const skipReason = this.#getSkipReason(tx);
172
+ if (skipReason) {
173
+ return Promise.resolve({ result: 'skipped', reason: [skipReason] });
162
174
  }
163
175
  return await this.validateTxFee(tx);
164
176
  }
165
177
 
166
178
  /**
167
- * Check whether the tx's max fees are valid for the current block, and skip if not.
179
+ * Check whether the tx's max fees are valid for the current block, and return a skip reason if not.
168
180
  * We skip instead of invalidating since the tx may become eligible later.
169
181
  * Note that circuits check max fees even if fee payer is unset, so we
170
182
  * keep this validation even if the tx does not pay fees.
171
183
  */
172
- #shouldSkip(tx: Tx): boolean {
184
+ #getSkipReason(tx: Tx): string | undefined {
173
185
  const gasSettings = tx.data.constants.txContext.gasSettings;
174
186
 
175
187
  // Skip the tx if its max fees are not enough for the current block's gas fees.
@@ -182,8 +194,9 @@ export class GasTxValidator implements TxValidator<Tx> {
182
194
  txMaxFeesPerGas: maxFeesPerGas.toInspect(),
183
195
  currentGasFees: this.#gasFees.toInspect(),
184
196
  });
197
+ return `${TX_ERROR_INSUFFICIENT_FEE_PER_GAS} (maxFee=da:${maxFeesPerGas.feePerDaGas},l2:${maxFeesPerGas.feePerL2Gas} required=da:${this.#gasFees.feePerDaGas},l2:${this.#gasFees.feePerL2Gas})`;
185
198
  }
186
- return notEnoughMaxFees;
199
+ return undefined;
187
200
  }
188
201
 
189
202
  /**
@@ -212,7 +225,10 @@ export class GasTxValidator implements TxValidator<Tx> {
212
225
  balance,
213
226
  feeLimit,
214
227
  });
215
- return { result: 'invalid', reason: [TX_ERROR_INSUFFICIENT_FEE_PAYER_BALANCE] };
228
+ return {
229
+ result: 'invalid',
230
+ reason: [`${TX_ERROR_INSUFFICIENT_FEE_PAYER_BALANCE} (required=${feeLimit}, available=${balance})`],
231
+ };
216
232
  }
217
233
  return { result: 'valid' };
218
234
  }
@@ -28,8 +28,6 @@ export class AztecDatastore implements Datastore {
28
28
  #memoryDatastore: Map<string, MemoryItem>;
29
29
  #dbDatastore: AztecAsyncMap<string, Uint8Array>;
30
30
 
31
- #batchOps: BatchOp[] = [];
32
-
33
31
  private maxMemoryItems: number;
34
32
 
35
33
  constructor(db: AztecAsyncKVStore, { maxMemoryItems } = { maxMemoryItems: 50 }) {
@@ -92,23 +90,17 @@ export class AztecDatastore implements Datastore {
92
90
  }
93
91
 
94
92
  batch(): Batch {
93
+ const ops: BatchOp[] = [];
95
94
  return {
96
95
  put: (key, value) => {
97
- this.#batchOps.push({
98
- type: 'put',
99
- key,
100
- value,
101
- });
96
+ ops.push({ type: 'put', key, value });
102
97
  },
103
98
  delete: key => {
104
- this.#batchOps.push({
105
- type: 'del',
106
- key,
107
- });
99
+ ops.push({ type: 'del', key });
108
100
  },
109
101
  commit: async () => {
110
102
  await this.#db.transactionAsync(async () => {
111
- for (const op of this.#batchOps) {
103
+ for (const op of ops) {
112
104
  if (op.type === 'put' && op.value) {
113
105
  await this.put(op.key, op.value);
114
106
  } else if (op.type === 'del') {
@@ -116,7 +108,7 @@ export class AztecDatastore implements Datastore {
116
108
  }
117
109
  }
118
110
  });
119
- this.#batchOps = []; // Clear operations after commit
111
+ ops.length = 0;
120
112
  },
121
113
  };
122
114
  }
@@ -287,6 +287,7 @@ export class DummyPeerManager implements PeerManagerInterface {
287
287
 
288
288
  export class DummyReqResp implements ReqRespInterface {
289
289
  updateConfig(_config: Partial<P2PReqRespConfig>): void {}
290
+ setShouldRejectPeer(): void {}
290
291
  start(
291
292
  _subProtocolHandlers: ReqRespSubProtocolHandlers,
292
293
  _subProtocolValidators: ReqRespSubProtocolValidators,