@aztec/p2p 0.0.1-commit.f650c0a5c → 0.0.1-commit.f7ea82942

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 (124) hide show
  1. package/dest/client/factory.d.ts +3 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +16 -15
  4. package/dest/client/p2p_client.d.ts +1 -1
  5. package/dest/client/p2p_client.d.ts.map +1 -1
  6. package/dest/client/p2p_client.js +9 -2
  7. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +4 -1
  8. package/dest/config.d.ts +103 -99
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +11 -6
  11. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -3
  14. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +2 -1
  15. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  16. package/dest/mem_pools/tx_pool_v2/eviction/index.js +1 -0
  17. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +16 -0
  18. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -0
  19. package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +62 -0
  20. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
  21. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +4 -1
  22. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  23. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +5 -2
  24. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +8 -5
  26. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  27. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +2 -1
  29. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +5 -2
  30. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  31. package/dest/msg_validators/attestation_validator/attestation_validator.js +17 -9
  32. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -2
  33. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  34. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
  35. package/dest/msg_validators/clock_tolerance.d.ts +12 -1
  36. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  37. package/dest/msg_validators/clock_tolerance.js +50 -0
  38. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +2 -1
  39. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  40. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +2 -1
  41. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  42. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +3 -1
  43. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  44. package/dest/msg_validators/proposal_validator/proposal_validator.js +16 -8
  45. package/dest/msg_validators/tx_validator/archive_cache.js +1 -1
  46. package/dest/msg_validators/tx_validator/factory.d.ts +2 -2
  47. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  48. package/dest/msg_validators/tx_validator/factory.js +3 -3
  49. package/dest/msg_validators/tx_validator/gas_validator.d.ts +36 -4
  50. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  51. package/dest/msg_validators/tx_validator/gas_validator.js +46 -31
  52. package/dest/services/data_store.d.ts +1 -1
  53. package/dest/services/data_store.d.ts.map +1 -1
  54. package/dest/services/data_store.js +5 -5
  55. package/dest/services/dummy_service.d.ts +2 -1
  56. package/dest/services/dummy_service.d.ts.map +1 -1
  57. package/dest/services/dummy_service.js +1 -0
  58. package/dest/services/gossipsub/topic_score_params.d.ts +13 -2
  59. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  60. package/dest/services/gossipsub/topic_score_params.js +21 -4
  61. package/dest/services/libp2p/instrumentation.d.ts +3 -1
  62. package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
  63. package/dest/services/libp2p/instrumentation.js +14 -0
  64. package/dest/services/libp2p/libp2p_service.d.ts +5 -3
  65. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  66. package/dest/services/libp2p/libp2p_service.js +33 -20
  67. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  68. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  69. package/dest/services/peer-manager/peer_manager.js +15 -2
  70. package/dest/services/reqresp/config.d.ts +3 -3
  71. package/dest/services/reqresp/config.d.ts.map +1 -1
  72. package/dest/services/reqresp/interface.d.ts +14 -1
  73. package/dest/services/reqresp/interface.d.ts.map +1 -1
  74. package/dest/services/reqresp/interface.js +10 -0
  75. package/dest/services/reqresp/reqresp.d.ts +4 -2
  76. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  77. package/dest/services/reqresp/reqresp.js +10 -1
  78. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  79. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  80. package/dest/test-helpers/make-test-p2p-clients.js +4 -1
  81. package/dest/test-helpers/mock-pubsub.d.ts +11 -3
  82. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  83. package/dest/test-helpers/mock-pubsub.js +36 -11
  84. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  85. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  86. package/dest/test-helpers/reqresp-nodes.js +5 -1
  87. package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
  88. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  89. package/dest/testbench/p2p_client_testbench_worker.js +7 -2
  90. package/package.json +14 -14
  91. package/src/client/factory.ts +23 -18
  92. package/src/client/p2p_client.ts +11 -3
  93. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +2 -0
  94. package/src/config.ts +19 -7
  95. package/src/mem_pools/attestation_pool/attestation_pool.ts +5 -3
  96. package/src/mem_pools/tx_pool_v2/eviction/index.ts +1 -0
  97. package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +65 -0
  98. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +3 -3
  99. package/src/mem_pools/tx_pool_v2/interfaces.ts +3 -0
  100. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +13 -7
  101. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +2 -0
  102. package/src/msg_validators/attestation_validator/attestation_validator.ts +18 -7
  103. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -1
  104. package/src/msg_validators/clock_tolerance.ts +68 -0
  105. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -1
  106. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +4 -1
  107. package/src/msg_validators/proposal_validator/proposal_validator.ts +13 -7
  108. package/src/msg_validators/tx_validator/README.md +11 -3
  109. package/src/msg_validators/tx_validator/archive_cache.ts +1 -1
  110. package/src/msg_validators/tx_validator/factory.ts +3 -1
  111. package/src/msg_validators/tx_validator/gas_validator.ts +64 -31
  112. package/src/services/data_store.ts +5 -13
  113. package/src/services/dummy_service.ts +1 -0
  114. package/src/services/gossipsub/topic_score_params.ts +36 -4
  115. package/src/services/libp2p/instrumentation.ts +14 -0
  116. package/src/services/libp2p/libp2p_service.ts +31 -15
  117. package/src/services/peer-manager/peer_manager.ts +17 -2
  118. package/src/services/reqresp/config.ts +2 -2
  119. package/src/services/reqresp/interface.ts +21 -0
  120. package/src/services/reqresp/reqresp.ts +17 -0
  121. package/src/test-helpers/make-test-p2p-clients.ts +2 -0
  122. package/src/test-helpers/mock-pubsub.ts +34 -5
  123. package/src/test-helpers/reqresp-nodes.ts +4 -0
  124. package/src/testbench/p2p_client_testbench_worker.ts +3 -0
@@ -36,6 +36,18 @@ export interface HasGasLimitData {
36
36
  };
37
37
  }
38
38
 
39
+ /** Structural interface for types that carry max fee per gas data, used by {@link MaxFeePerGasValidator}. */
40
+ export interface HasMaxFeePerGasData {
41
+ txHash: { toString(): string };
42
+ data: {
43
+ constants: {
44
+ txContext: {
45
+ gasSettings: { maxFeesPerGas: GasFees };
46
+ };
47
+ };
48
+ };
49
+ }
50
+
39
51
  /**
40
52
  * Validates that a transaction's gas limits are within acceptable bounds.
41
53
  *
@@ -124,15 +136,60 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
124
136
  }
125
137
  }
126
138
 
139
+ /**
140
+ * Validates that a transaction's max fee per gas meets the current block's gas fees.
141
+ *
142
+ * Rejects transactions whose maxFeesPerGas is below the current block's gas fees
143
+ * on either dimension (DA or L2). This is a cheap, stateless check.
144
+ *
145
+ * Generic over T so it can validate both full {@link Tx} objects and {@link TxMetaData}
146
+ * (used during pending pool migration).
147
+ *
148
+ * Used by: pending pool migration (via factory), and indirectly by {@link GasTxValidator}.
149
+ */
150
+ export class MaxFeePerGasValidator<T extends HasMaxFeePerGasData> implements TxValidator<T> {
151
+ #log: Logger;
152
+ #gasFees: GasFees;
153
+
154
+ constructor(gasFees: GasFees, bindings?: LoggerBindings) {
155
+ this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
156
+ this.#gasFees = gasFees;
157
+ }
158
+
159
+ validateTx(tx: T): Promise<TxValidationResult> {
160
+ return Promise.resolve(this.validateMaxFeePerGas(tx));
161
+ }
162
+
163
+ /** Checks maxFeesPerGas >= current block gas fees on both dimensions. */
164
+ validateMaxFeePerGas(tx: T): TxValidationResult {
165
+ const maxFeesPerGas = tx.data.constants.txContext.gasSettings.maxFeesPerGas;
166
+ const notEnoughMaxFees =
167
+ maxFeesPerGas.feePerDaGas < this.#gasFees.feePerDaGas || maxFeesPerGas.feePerL2Gas < this.#gasFees.feePerL2Gas;
168
+
169
+ if (notEnoughMaxFees) {
170
+ this.#log.verbose(`Rejecting transaction ${tx.txHash.toString()} due to insufficient fee per gas`, {
171
+ txMaxFeesPerGas: maxFeesPerGas.toInspect(),
172
+ currentGasFees: this.#gasFees.toInspect(),
173
+ });
174
+ return {
175
+ result: 'invalid',
176
+ reason: [
177
+ `${TX_ERROR_INSUFFICIENT_FEE_PER_GAS} (maxFee=da:${maxFeesPerGas.feePerDaGas},l2:${maxFeesPerGas.feePerL2Gas} required=da:${this.#gasFees.feePerDaGas},l2:${this.#gasFees.feePerL2Gas})`,
178
+ ],
179
+ };
180
+ }
181
+ return { result: 'valid' };
182
+ }
183
+ }
184
+
127
185
  /**
128
186
  * Validates that a transaction can pay its gas fees.
129
187
  *
130
188
  * Runs three checks in order:
131
189
  * 1. **Gas limits** (delegates to {@link GasLimitsValidator}) — rejects if limits are
132
190
  * out of bounds.
133
- * 2. **Max fee per gas** — skips (not rejects) the tx if its maxFeesPerGas is below
134
- * the current block's gas fees. We skip rather than reject because the tx may
135
- * become eligible in a later block with lower fees.
191
+ * 2. **Max fee per gas** — rejects the tx if its maxFeesPerGas is below
192
+ * the current block's gas fees.
136
193
  * 3. **Fee payer balance** — reads the fee payer's FeeJuice balance from public state,
137
194
  * adds any pending claim from a setup-phase `_increase_public_balance` call, and
138
195
  * rejects if the total is less than the tx's fee limit (gasLimits * maxFeePerGas).
@@ -166,39 +223,15 @@ export class GasTxValidator implements TxValidator<Tx> {
166
223
  bindings: this.bindings,
167
224
  }).validateGasLimit(tx);
168
225
  if (gasLimitValidation.result === 'invalid') {
169
- return Promise.resolve(gasLimitValidation);
226
+ return gasLimitValidation;
170
227
  }
171
- const skipReason = this.#getSkipReason(tx);
172
- if (skipReason) {
173
- return Promise.resolve({ result: 'skipped', reason: [skipReason] });
228
+ const maxFeeValidation = new MaxFeePerGasValidator(this.#gasFees, this.bindings).validateMaxFeePerGas(tx);
229
+ if (maxFeeValidation.result === 'invalid') {
230
+ return maxFeeValidation;
174
231
  }
175
232
  return await this.validateTxFee(tx);
176
233
  }
177
234
 
178
- /**
179
- * Check whether the tx's max fees are valid for the current block, and return a skip reason if not.
180
- * We skip instead of invalidating since the tx may become eligible later.
181
- * Note that circuits check max fees even if fee payer is unset, so we
182
- * keep this validation even if the tx does not pay fees.
183
- */
184
- #getSkipReason(tx: Tx): string | undefined {
185
- const gasSettings = tx.data.constants.txContext.gasSettings;
186
-
187
- // Skip the tx if its max fees are not enough for the current block's gas fees.
188
- const maxFeesPerGas = gasSettings.maxFeesPerGas;
189
- const notEnoughMaxFees =
190
- maxFeesPerGas.feePerDaGas < this.#gasFees.feePerDaGas || maxFeesPerGas.feePerL2Gas < this.#gasFees.feePerL2Gas;
191
-
192
- if (notEnoughMaxFees) {
193
- this.#log.verbose(`Skipping transaction ${tx.getTxHash().toString()} due to insufficient fee per gas`, {
194
- txMaxFeesPerGas: maxFeesPerGas.toInspect(),
195
- currentGasFees: this.#gasFees.toInspect(),
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})`;
198
- }
199
- return undefined;
200
- }
201
-
202
235
  /**
203
236
  * Checks the fee payer has enough FeeJuice balance to cover the tx's fee limit.
204
237
  * Accounts for any pending claim from a setup-phase `_increase_public_balance` call.
@@ -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,
@@ -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
 
@@ -18,6 +18,7 @@ export class P2PInstrumentation {
18
18
  private messagePrevalidationCount: UpDownCounter;
19
19
  private messageLatency: Histogram;
20
20
  private txReceivedCount: UpDownCounter;
21
+ private slowValidationCount: UpDownCounter;
21
22
 
22
23
  private aggLatencyHisto = new Map<TopicType, RecordableHistogram>();
23
24
  private aggValidationHisto = new Map<TopicType, RecordableHistogram>();
@@ -48,6 +49,15 @@ export class P2PInstrumentation {
48
49
 
49
50
  this.txReceivedCount = createUpDownCounterWithDefault(meter, Metrics.P2P_GOSSIP_TX_RECEIVED_COUNT);
50
51
 
52
+ this.slowValidationCount = createUpDownCounterWithDefault(meter, Metrics.P2P_GOSSIP_SLOW_VALIDATION_COUNT, {
53
+ [Attributes.TOPIC_NAME]: [
54
+ TopicType.tx,
55
+ TopicType.block_proposal,
56
+ TopicType.checkpoint_proposal,
57
+ TopicType.checkpoint_attestation,
58
+ ],
59
+ });
60
+
51
61
  this.aggLatencyMetrics = {
52
62
  avg: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_AVG),
53
63
  max: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_MAX),
@@ -87,6 +97,10 @@ export class P2PInstrumentation {
87
97
  this.txReceivedCount.add(count);
88
98
  }
89
99
 
100
+ public incSlowValidation(topicName: TopicType) {
101
+ this.slowValidationCount.add(1, { [Attributes.TOPIC_NAME]: topicName });
102
+ }
103
+
90
104
  public incMessagePrevalidationStatus(passed: boolean, topicName: TopicType | undefined) {
91
105
  this.messagePrevalidationCount.add(1, { [Attributes.TOPIC_NAME]: topicName, [Attributes.OK]: passed });
92
106
  }
@@ -8,7 +8,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
10
  import type { ContractDataSource } from '@aztec/stdlib/contract';
11
- import { GasFees } from '@aztec/stdlib/gas';
11
+ import { type BlockMinFeesProvider, GasFees } from '@aztec/stdlib/gas';
12
12
  import type { ClientProtocolCircuitVerifier, PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
13
13
  import {
14
14
  BlockProposal,
@@ -146,8 +146,6 @@ export class LibP2PService extends WithTracer implements P2PService {
146
146
  private protocolVersion = '';
147
147
  private topicStrings: Record<TopicType, string> = {} as Record<TopicType, string>;
148
148
 
149
- private feesCache: { blockNumber: BlockNumber; gasFees: GasFees } | undefined;
150
-
151
149
  /** Callback invoked when a duplicate proposal is detected (triggers slashing). */
152
150
  private duplicateProposalCallback?: (info: {
153
151
  slot: SlotNumber;
@@ -197,6 +195,7 @@ export class LibP2PService extends WithTracer implements P2PService {
197
195
  private epochCache: EpochCacheInterface,
198
196
  private proofVerifier: ClientProtocolCircuitVerifier,
199
197
  private worldStateSynchronizer: WorldStateSynchronizer,
198
+ private blockMinFeesProvider: BlockMinFeesProvider,
200
199
  telemetry: TelemetryClient,
201
200
  logger: Logger = createLogger('p2p:libp2p_service'),
202
201
  ) {
@@ -228,15 +227,19 @@ export class LibP2PService extends WithTracer implements P2PService {
228
227
  this.protocolVersion,
229
228
  );
230
229
 
230
+ const p2pPropagationTime = config.attestationPropagationTime;
231
231
  const proposalValidatorOpts = {
232
232
  txsPermitted: !config.disableTransactions,
233
233
  maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
234
+ p2pPropagationTime,
234
235
  };
235
236
  this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
236
237
  this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
237
238
  this.checkpointAttestationValidator = config.fishermanMode
238
- ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry)
239
- : new CheckpointAttestationValidator(epochCache);
239
+ ? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry, {
240
+ l1PublishingTime: config.l1PublishingTime,
241
+ })
242
+ : new CheckpointAttestationValidator(epochCache, { l1PublishingTime: config.l1PublishingTime });
240
243
 
241
244
  this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
242
245
 
@@ -281,6 +284,7 @@ export class LibP2PService extends WithTracer implements P2PService {
281
284
  proofVerifier: ClientProtocolCircuitVerifier;
282
285
  worldStateSynchronizer: WorldStateSynchronizer;
283
286
  peerStore: AztecAsyncKVStore;
287
+ blockMinFeesProvider: BlockMinFeesProvider;
284
288
  telemetry: TelemetryClient;
285
289
  logger: Logger;
286
290
  packageVersion: string;
@@ -293,6 +297,7 @@ export class LibP2PService extends WithTracer implements P2PService {
293
297
  mempools,
294
298
  proofVerifier,
295
299
  peerStore,
300
+ blockMinFeesProvider,
296
301
  telemetry,
297
302
  logger,
298
303
  packageVersion,
@@ -346,9 +351,12 @@ export class LibP2PService extends WithTracer implements P2PService {
346
351
  const l1Constants = epochCache.getL1Constants();
347
352
  const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
348
353
  slotDurationMs: l1Constants.slotDuration * 1000,
354
+ ethereumSlotDuration: l1Constants.ethereumSlotDuration,
349
355
  heartbeatIntervalMs: config.gossipsubInterval,
350
356
  targetCommitteeSize: l1Constants.targetCommitteeSize,
351
357
  blockDurationMs: config.blockDurationMs,
358
+ l1PublishingTime: config.l1PublishingTime,
359
+ p2pPropagationTime: config.attestationPropagationTime,
352
360
  expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot,
353
361
  });
354
362
 
@@ -473,6 +481,9 @@ export class LibP2PService extends WithTracer implements P2PService {
473
481
  epochCache,
474
482
  );
475
483
 
484
+ // Gate req/resp data protocols for unauthenticated peers when p2pAllowOnlyValidators is enabled
485
+ reqresp.setShouldRejectPeer(peerId => peerManager.shouldDisableP2PGossip(peerId));
486
+
476
487
  // Configure application-specific scoring for gossipsub.
477
488
  // The weight scales app score to align with gossipsub thresholds:
478
489
  // - Disconnect (-50) × 10 = -500 = gossipThreshold (stops receiving gossip)
@@ -493,6 +504,7 @@ export class LibP2PService extends WithTracer implements P2PService {
493
504
  epochCache,
494
505
  proofVerifier,
495
506
  worldStateSynchronizer,
507
+ blockMinFeesProvider,
496
508
  telemetry,
497
509
  logger,
498
510
  );
@@ -907,6 +919,17 @@ export class LibP2PService extends WithTracer implements P2PService {
907
919
  this.logger.error(`Error validating gossipsub message`, err, { msgId, source: source.toString(), topicType });
908
920
  }
909
921
 
922
+ const validationTimeMs = timer.ms();
923
+ const mcacheWindowMs = this.config.gossipsubMcacheLength * this.config.gossipsubInterval;
924
+ if (validationTimeMs > mcacheWindowMs * 0.75) {
925
+ this.instrumentation.incSlowValidation(topicType);
926
+ this.logger.warn(
927
+ `Gossip validation for ${topicType} took ${validationTimeMs}ms, approaching mcache eviction window of ${mcacheWindowMs}ms. ` +
928
+ `Message forwarding may be skipped if validation exceeds the window.`,
929
+ { msgId, source: source.toString(), topicType, validationTimeMs, mcacheWindowMs },
930
+ );
931
+ }
932
+
910
933
  if (resultAndObj.result === TopicValidatorResult.Accept) {
911
934
  this.logger.debug(`Message ${topicType} accepted by validator`, { msgId, source: source.toString(), topicType });
912
935
  this.instrumentation.recordMessageValidation(topicType, timer);
@@ -1595,15 +1618,8 @@ export class LibP2PService extends WithTracer implements P2PService {
1595
1618
  });
1596
1619
  }
1597
1620
 
1598
- private async getGasFees(blockNumber: BlockNumber): Promise<GasFees> {
1599
- if (blockNumber === this.feesCache?.blockNumber) {
1600
- return this.feesCache.gasFees;
1601
- }
1602
-
1603
- const header = await this.archiver.getBlockHeader(blockNumber);
1604
- const gasFees = header?.globalVariables.gasFees ?? GasFees.empty();
1605
- this.feesCache = { blockNumber, gasFees };
1606
- return gasFees;
1621
+ private getGasFees(): Promise<GasFees> {
1622
+ return this.blockMinFeesProvider.getCurrentMinFees();
1607
1623
  }
1608
1624
 
1609
1625
  /**
@@ -1645,7 +1661,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1645
1661
  currentBlockNumber: BlockNumber,
1646
1662
  nextSlotTimestamp: UInt64,
1647
1663
  ): Promise<Record<string, TransactionValidator>> {
1648
- const gasFees = await this.getGasFees(currentBlockNumber);
1664
+ const gasFees = await this.getGasFees();
1649
1665
  const allowedInSetup = [
1650
1666
  ...(await getDefaultAllowedSetupFunctions()),
1651
1667
  ...(this.config.txPublicSetupAllowListExtend ?? []),
@@ -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
  /*
@@ -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.',
@@ -95,6 +95,24 @@ export type ReqRespSubProtocolValidators = {
95
95
  [S in ReqRespSubProtocol]: ResponseValidator<any, any>;
96
96
  };
97
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
+
98
116
  export const DEFAULT_SUB_PROTOCOL_VALIDATORS: ReqRespSubProtocolValidators = {
99
117
  [ReqRespSubProtocol.PING]: noopValidator,
100
118
  [ReqRespSubProtocol.STATUS]: noopValidator,
@@ -253,5 +271,8 @@ export interface ReqRespInterface {
253
271
 
254
272
  updateConfig(config: Partial<P2PReqRespConfig>): void;
255
273
 
274
+ /** Sets the callback used to reject unauthenticated peers on gated req/resp protocols. */
275
+ setShouldRejectPeer(checker: ShouldRejectPeer): void;
276
+
256
277
  getConnectionSampler(): Pick<ConnectionSampler, 'getPeerListSortedByConnectionCountAsc'>;
257
278
  }
@@ -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
  }
@@ -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);
@@ -5,6 +5,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
5
5
  import { retryUntil } from '@aztec/foundation/retry';
6
6
  import { sleep } from '@aztec/foundation/sleep';
7
7
  import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
8
+ import { GasFees } from '@aztec/stdlib/gas';
8
9
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
9
10
  import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
10
11
 
@@ -102,6 +103,7 @@ export async function makeTestP2PClient(
102
103
  proofVerifier,
103
104
  mockWorldState,
104
105
  mockEpochCache,
106
+ { getCurrentMinFees: () => Promise.resolve(GasFees.empty()) },
105
107
  'test-p2p-client',
106
108
  undefined,
107
109
  undefined,