@aztec/p2p 0.0.1-commit.684755437 → 0.0.1-commit.6b113946b

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 (141) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +1 -1
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +19 -7
  5. package/dest/client/p2p_client.d.ts +1 -1
  6. package/dest/client/p2p_client.d.ts.map +1 -1
  7. package/dest/client/p2p_client.js +22 -10
  8. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +6 -5
  9. package/dest/config.d.ts +13 -1
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +20 -0
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +3 -3
  13. package/dest/mem_pools/attestation_pool/attestation_pool.js +3 -3
  14. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
  15. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  16. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  17. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  18. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  19. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  20. package/dest/mem_pools/tx_pool/priority.js +4 -4
  21. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  22. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  23. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
  27. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -5
  28. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +9 -2
  30. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +7 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +19 -5
  38. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
  39. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  40. package/dest/msg_validators/attestation_validator/attestation_validator.js +5 -4
  41. package/dest/msg_validators/clock_tolerance.d.ts +1 -1
  42. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  43. package/dest/msg_validators/clock_tolerance.js +4 -3
  44. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +1 -1
  45. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  46. package/dest/msg_validators/proposal_validator/proposal_validator.js +5 -5
  47. package/dest/msg_validators/tx_validator/factory.d.ts +23 -4
  48. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  49. package/dest/msg_validators/tx_validator/factory.js +28 -8
  50. package/dest/msg_validators/tx_validator/gas_validator.d.ts +13 -4
  51. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  52. package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
  53. package/dest/msg_validators/tx_validator/phases_validator.d.ts +21 -1
  54. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  55. package/dest/msg_validators/tx_validator/phases_validator.js +27 -0
  56. package/dest/services/encoding.d.ts +5 -1
  57. package/dest/services/encoding.d.ts.map +1 -1
  58. package/dest/services/encoding.js +7 -1
  59. package/dest/services/libp2p/libp2p_service.d.ts +2 -9
  60. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  61. package/dest/services/libp2p/libp2p_service.js +37 -30
  62. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  63. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  64. package/dest/services/peer-manager/peer_manager.js +4 -2
  65. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +11 -8
  66. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  67. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +66 -65
  68. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +3 -2
  69. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  70. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +5 -4
  71. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  72. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +13 -7
  73. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +3 -1
  74. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  75. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +3 -0
  76. package/dest/services/reqresp/reqresp.d.ts +1 -1
  77. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  78. package/dest/services/reqresp/reqresp.js +17 -9
  79. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -4
  80. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  81. package/dest/services/tx_collection/fast_tx_collection.js +57 -73
  82. package/dest/services/tx_collection/proposal_tx_collector.d.ts +6 -7
  83. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  84. package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
  85. package/dest/services/tx_collection/request_tracker.d.ts +53 -0
  86. package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
  87. package/dest/services/tx_collection/request_tracker.js +84 -0
  88. package/dest/services/tx_collection/slow_tx_collection.js +1 -1
  89. package/dest/services/tx_collection/tx_collection.d.ts +3 -6
  90. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  91. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  92. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  93. package/dest/test-helpers/testbench-utils.js +22 -3
  94. package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
  95. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  96. package/dest/testbench/p2p_client_testbench_worker.js +6 -5
  97. package/package.json +14 -14
  98. package/src/client/factory.ts +34 -11
  99. package/src/client/p2p_client.ts +22 -12
  100. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +6 -7
  101. package/src/config.ts +33 -0
  102. package/src/mem_pools/attestation_pool/attestation_pool.ts +3 -3
  103. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
  104. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  105. package/src/mem_pools/tx_pool/priority.ts +4 -4
  106. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  107. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  108. package/src/mem_pools/tx_pool_v2/interfaces.ts +6 -4
  109. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +11 -1
  110. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +13 -1
  111. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +20 -5
  112. package/src/msg_validators/attestation_validator/README.md +49 -0
  113. package/src/msg_validators/attestation_validator/attestation_validator.ts +5 -4
  114. package/src/msg_validators/clock_tolerance.ts +4 -3
  115. package/src/msg_validators/proposal_validator/README.md +123 -0
  116. package/src/msg_validators/proposal_validator/proposal_validator.ts +6 -5
  117. package/src/msg_validators/tx_validator/README.md +5 -1
  118. package/src/msg_validators/tx_validator/factory.ts +36 -3
  119. package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
  120. package/src/msg_validators/tx_validator/phases_validator.ts +30 -0
  121. package/src/services/encoding.ts +9 -1
  122. package/src/services/libp2p/libp2p_service.ts +34 -33
  123. package/src/services/peer-manager/peer_manager.ts +5 -2
  124. package/src/services/reqresp/README.md +229 -0
  125. package/src/services/reqresp/batch-tx-requester/README.md +46 -7
  126. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +61 -69
  127. package/src/services/reqresp/batch-tx-requester/interface.ts +2 -1
  128. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +13 -6
  129. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +5 -0
  130. package/src/services/reqresp/reqresp.ts +19 -11
  131. package/src/services/tx_collection/fast_tx_collection.ts +57 -83
  132. package/src/services/tx_collection/proposal_tx_collector.ts +8 -13
  133. package/src/services/tx_collection/request_tracker.ts +127 -0
  134. package/src/services/tx_collection/slow_tx_collection.ts +1 -1
  135. package/src/services/tx_collection/tx_collection.ts +3 -5
  136. package/src/test-helpers/testbench-utils.ts +29 -3
  137. package/src/testbench/p2p_client_testbench_worker.ts +6 -8
  138. package/dest/services/tx_collection/missing_txs_tracker.d.ts +0 -32
  139. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +0 -1
  140. package/dest/services/tx_collection/missing_txs_tracker.js +0 -27
  141. package/src/services/tx_collection/missing_txs_tracker.ts +0 -52
@@ -669,31 +669,41 @@ export class P2PClient extends WithTracer implements P2P {
669
669
  }
670
670
 
671
671
  /**
672
- * Returns true if the prune crossed a checkpoint boundary.
673
- * If the old and new checkpoint numbers are the same, the prune is within a single checkpoint.
674
- * If they differ, the prune spans across checkpoints (epoch prune).
672
+ * Returns true if the prune is an epoch prune (new checkpoint number is less than old).
673
+ * If the checkpoint number stays the same or increases, the prune is within a checkpoint.
675
674
  */
676
675
  private async isEpochPrune(newCheckpoint: CheckpointId): Promise<boolean> {
677
676
  const tips = await this.l2Tips.getL2Tips();
678
677
  const oldCheckpointNumber = tips.checkpointed.checkpoint.number;
679
- if (oldCheckpointNumber <= CheckpointNumber.ZERO) {
678
+ if (oldCheckpointNumber <= CheckpointNumber.INITIAL) {
680
679
  return false;
681
680
  }
682
- const isEpochPrune = oldCheckpointNumber !== newCheckpoint.number;
683
- this.log.info(
684
- `Detected epoch prune: ${isEpochPrune}. Old checkpoint: ${oldCheckpointNumber}, new checkpoint: ${newCheckpoint.number}`,
685
- );
681
+ const newCheckpointNumber = newCheckpoint.number;
682
+ // We check that the new checkpoint number is less than the old checkpoint number in order to consider it an epoch prune.
683
+ // To be more certain that it is an epoch prune, we will check that at least 2 checkpoints were removed.
684
+ // This means we should avoid thinking checkpoints removed by L1 re-orgs are epoch prunes
685
+ const thresholdForEpochPrune = CheckpointNumber(oldCheckpointNumber - 2);
686
+ const isEpochPrune = newCheckpointNumber <= thresholdForEpochPrune;
687
+ if (isEpochPrune) {
688
+ this.log.info(`Detected epoch prune to ${newCheckpointNumber}`, {
689
+ oldCheckpointNumber,
690
+ newCheckpointNumber,
691
+ thresholdForEpochPrune,
692
+ });
693
+ }
686
694
  return isEpochPrune;
687
695
  }
688
696
 
689
697
  /** Checks if the slot has changed and calls prepareForSlot if so. */
690
698
  private async maybeCallPrepareForSlot(): Promise<void> {
691
- const { currentSlot } = this.epochCache.getCurrentAndNextSlot();
692
- if (currentSlot <= this.lastSlotProcessed) {
699
+ // If we have a pending checkpoint available, we want to prepare the target slot - otherwise we prepare the current slot
700
+ // Knowledege of pending checkpoints is in the PR above
701
+ const { targetSlot } = this.epochCache.getTargetAndNextSlot();
702
+ if (targetSlot <= this.lastSlotProcessed) {
693
703
  return;
694
704
  }
695
- this.lastSlotProcessed = currentSlot;
696
- await this.txPool.prepareForSlot(currentSlot);
705
+ this.lastSlotProcessed = targetSlot;
706
+ await this.txPool.prepareForSlot(targetSlot);
697
707
  }
698
708
 
699
709
  private async startServiceIfSynched() {
@@ -1,4 +1,5 @@
1
1
  import { MockL2BlockSource } from '@aztec/archiver/test';
2
+ import type { EpochCache } from '@aztec/epoch-cache';
2
3
  import { SecretValue } from '@aztec/foundation/config';
3
4
  import { createLogger } from '@aztec/foundation/log';
4
5
  import { sleep } from '@aztec/foundation/sleep';
@@ -14,12 +15,13 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
14
15
 
15
16
  import type { PeerId } from '@libp2p/interface';
16
17
  import { peerIdFromString } from '@libp2p/peer-id';
18
+ import { mock } from 'jest-mock-extended';
17
19
 
18
20
  import type { P2PConfig } from '../../../config.js';
19
21
  import { BatchTxRequesterCollector, SendBatchRequestCollector } from '../../../services/index.js';
20
22
  import type { IBatchRequestTxValidator } from '../../../services/reqresp/batch-tx-requester/tx_validator.js';
21
23
  import { RateLimitStatus } from '../../../services/reqresp/rate-limiter/rate_limiter.js';
22
- import { MissingTxsTracker } from '../../../services/tx_collection/missing_txs_tracker.js';
24
+ import { RequestTracker } from '../../../services/tx_collection/request_tracker.js';
23
25
  import {
24
26
  AlwaysTrueCircuitVerifier,
25
27
  BENCHMARK_CONSTANTS,
@@ -27,7 +29,6 @@ import {
27
29
  InMemoryTxPool,
28
30
  UNLIMITED_RATE_LIMIT_QUOTA,
29
31
  calculateInternalTimeout,
30
- createMockEpochCache,
31
32
  createMockWorldStateSynchronizer,
32
33
  } from '../../../test-helpers/index.js';
33
34
  import { createP2PClient } from '../../index.js';
@@ -98,7 +99,7 @@ function sendMessage(message: WorkerResponse): Promise<void> {
98
99
  async function startClient(config: P2PConfig, clientIndex: number) {
99
100
  txPool = new InMemoryTxPool();
100
101
  attestationPool = new InMemoryAttestationPool();
101
- const epochCache = createMockEpochCache();
102
+ const epochCache = mock<EpochCache>();
102
103
  const worldState = createMockWorldStateSynchronizer();
103
104
  const l2BlockSource = new MockL2BlockSource();
104
105
  const proofVerifier = new AlwaysTrueCircuitVerifier();
@@ -213,10 +214,9 @@ async function runCollector(cmd: Extract<WorkerCommand, { type: 'RUN_COLLECTOR'
213
214
  const fetched = await executeTimeout(
214
215
  (_signal: AbortSignal) =>
215
216
  collector.collectTxs(
216
- MissingTxsTracker.fromArray(parsedTxHashes),
217
+ RequestTracker.create(parsedTxHashes, new Date(Date.now() + internalTimeoutMs)),
217
218
  parsedProposal,
218
219
  pinnedPeer,
219
- internalTimeoutMs,
220
220
  ),
221
221
  timeoutMs,
222
222
  () => new Error(`Collector timed out after ${timeoutMs}ms`),
@@ -231,10 +231,9 @@ async function runCollector(cmd: Extract<WorkerCommand, { type: 'RUN_COLLECTOR'
231
231
  const fetched = await executeTimeout(
232
232
  (_signal: AbortSignal) =>
233
233
  collector.collectTxs(
234
- MissingTxsTracker.fromArray(parsedTxHashes),
234
+ RequestTracker.create(parsedTxHashes, new Date(Date.now() + internalTimeoutMs)),
235
235
  parsedProposal,
236
236
  pinnedPeer,
237
- internalTimeoutMs,
238
237
  ),
239
238
  timeoutMs,
240
239
  () => new Error(`Collector timed out after ${timeoutMs}ms`),
package/src/config.ts CHANGED
@@ -43,6 +43,15 @@ export interface P2PConfig
43
43
  /** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
44
44
  validateMaxTxsPerBlock?: number;
45
45
 
46
+ /** Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. */
47
+ validateMaxTxsPerCheckpoint?: number;
48
+
49
+ /** Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. */
50
+ validateMaxL2BlockGas?: number;
51
+
52
+ /** Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. */
53
+ validateMaxDABlockGas?: number;
54
+
46
55
  /** A flag dictating whether the P2P subsystem should be enabled. */
47
56
  p2pEnabled: boolean;
48
57
 
@@ -61,6 +70,9 @@ export interface P2PConfig
61
70
  /** The frequency in which to check for new peers. */
62
71
  peerCheckIntervalMS: number;
63
72
 
73
+ /** How long to ban a peer after it fails MAX_DIAL_ATTEMPTS dials. */
74
+ peerFailedBanTimeMs: number;
75
+
64
76
  /** Size of queue of L2 blocks to store. */
65
77
  l2QueueSize: number;
66
78
 
@@ -208,6 +220,22 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
208
220
  'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
209
221
  parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
210
222
  },
223
+ validateMaxTxsPerCheckpoint: {
224
+ env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
225
+ description:
226
+ '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),
228
+ },
229
+ validateMaxL2BlockGas: {
230
+ env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
231
+ 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),
233
+ },
234
+ validateMaxDABlockGas: {
235
+ env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
236
+ 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),
238
+ },
211
239
  p2pEnabled: {
212
240
  env: 'P2P_ENABLED',
213
241
  description: 'A flag dictating whether the P2P subsystem should be enabled.',
@@ -238,6 +266,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
238
266
  description: 'The frequency in which to check for new peers.',
239
267
  ...numberConfigHelper(30_000),
240
268
  },
269
+ peerFailedBanTimeMs: {
270
+ env: 'P2P_PEER_FAILED_BAN_TIME_MS',
271
+ description: 'How long to ban a peer after it fails maximum dial attempts.',
272
+ ...numberConfigHelper(5 * 60 * 1000),
273
+ },
241
274
  l2QueueSize: {
242
275
  env: 'P2P_L2_QUEUE_SIZE',
243
276
  description: 'Size of queue of L2 blocks to store.',
@@ -26,10 +26,10 @@ export type TryAddResult = {
26
26
  count: number;
27
27
  };
28
28
 
29
- export const MAX_CHECKPOINT_PROPOSALS_PER_SLOT = 5;
30
- export const MAX_BLOCK_PROPOSALS_PER_POSITION = 3;
29
+ export const MAX_CHECKPOINT_PROPOSALS_PER_SLOT = 2;
30
+ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
31
31
  /** Maximum attestations a single signer can make per slot before being rejected. */
32
- export const MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER = 3;
32
+ export const MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER = 2;
33
33
 
34
34
  /** Public API interface for attestation pools. Used for typing mocks and test implementations. */
35
35
  export type AttestationPoolApi = Pick<
@@ -446,12 +446,12 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
446
446
  const result2 = await ap.tryAddBlockProposal(proposal2);
447
447
  expect(result2.count).toBe(2);
448
448
 
449
- // Add a third proposal for same position
449
+ // Third proposal for same position should be rejected (cap is 2)
450
450
  const proposal3 = await mockBlockProposalWithIndex(signers[2], slotNumber, indexWithinCheckpoint);
451
451
  const result3 = await ap.tryAddBlockProposal(proposal3);
452
452
 
453
- expect(result3.added).toBe(true);
454
- expect(result3.count).toBe(3);
453
+ expect(result3.added).toBe(false);
454
+ expect(result3.count).toBe(2);
455
455
  });
456
456
 
457
457
  it('should return added=false when exceeding capacity', async () => {
@@ -666,12 +666,12 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
666
666
  const result2 = await ap.tryAddCheckpointProposal(proposal2);
667
667
  expect(result2.count).toBe(2);
668
668
 
669
- // Add a third proposal for same slot
669
+ // Third proposal for same slot should be rejected (cap is 2)
670
670
  const proposal3 = await mockCheckpointProposalCoreForPool(signers[2], slotNumber);
671
671
  const result3 = await ap.tryAddCheckpointProposal(proposal3);
672
672
 
673
- expect(result3.added).toBe(true);
674
- expect(result3.count).toBe(3);
673
+ expect(result3.added).toBe(false);
674
+ expect(result3.count).toBe(2);
675
675
  });
676
676
 
677
677
  it('should not count attestations as proposals for duplicate detection', async () => {
@@ -32,10 +32,11 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
32
32
 
33
33
  if (context.event === EvictionEvent.BLOCK_MINED) {
34
34
  const blockNumber = context.block.getBlockNumber();
35
+ const blockHash = await context.block.hash();
35
36
  // Ensure world state is synced to this block before accessing the snapshot.
36
37
  // This handles the race where a block is added to the archiver
37
38
  // but the world state hasn't synced it yet.
38
- await this.worldState.syncImmediate(blockNumber);
39
+ await this.worldState.syncImmediate(blockNumber, blockHash);
39
40
  return await this.evictForFeePayers(context.feePayers, this.worldState.getSnapshot(blockNumber), txPool);
40
41
  }
41
42
 
@@ -1,3 +1,4 @@
1
+ import { minBigint } from '@aztec/foundation/bigint';
1
2
  import { Buffer32 } from '@aztec/foundation/buffer';
2
3
  import type { Tx } from '@aztec/stdlib/tx';
3
4
 
@@ -11,10 +12,9 @@ export function getPendingTxPriority(tx: Tx): string {
11
12
  }
12
13
 
13
14
  /**
14
- * Returns the priority of a tx.
15
+ * Returns the priority of a tx based on the L2 priority fee only, capped by the max fees per gas.
15
16
  */
16
17
  export function getTxPriorityFee(tx: Tx): bigint {
17
- const priorityFees = tx.getGasSettings().maxPriorityFeesPerGas;
18
- const totalFees = priorityFees.feePerDaGas + priorityFees.feePerL2Gas;
19
- return totalFees;
18
+ const { maxPriorityFeesPerGas: priorityFees, maxFeesPerGas } = tx.getGasSettings();
19
+ return minBigint(maxFeesPerGas.feePerL2Gas, priorityFees.feePerL2Gas);
20
20
  }
@@ -204,7 +204,9 @@ export function describeTxPool(getTxPool: () => TxPool) {
204
204
 
205
205
  it('returns pending tx hashes sorted by priority', async () => {
206
206
  const withPriorityFee = (tx: Tx, fee: number) => {
207
- unfreeze(tx.data.constants.txContext.gasSettings).maxPriorityFeesPerGas = new GasFees(fee, fee);
207
+ const gs = unfreeze(tx.data.constants.txContext.gasSettings);
208
+ gs.maxPriorityFeesPerGas = new GasFees(fee, fee);
209
+ gs.maxFeesPerGas = new GasFees(fee, fee);
208
210
  return tx;
209
211
  };
210
212
 
@@ -29,7 +29,8 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
29
29
 
30
30
  if (context.event === EvictionEvent.BLOCK_MINED) {
31
31
  const blockNumber = context.block.getBlockNumber();
32
- await this.worldState.syncImmediate(blockNumber);
32
+ const blockHash = await context.block.hash();
33
+ await this.worldState.syncImmediate(blockNumber, blockHash);
33
34
  return await this.evictForFeePayers(context.feePayers, this.worldState.getSnapshot(blockNumber), pool);
34
35
  }
35
36
 
@@ -72,6 +72,8 @@ export type TxPoolV2Dependencies = {
72
72
  worldStateSynchronizer: WorldStateSynchronizer;
73
73
  /** Factory that creates a validator for re-validating pool transactions using metadata */
74
74
  createTxValidator: () => Promise<TxValidator<TxMetaData>>;
75
+ /** Checks whether a tx's setup-phase calls are on the allow list. Precomputed at receipt time. */
76
+ checkAllowedSetupCalls: (tx: Tx) => Promise<boolean>;
75
77
  };
76
78
 
77
79
  /**
@@ -158,10 +160,10 @@ export interface TxPoolV2 extends TypedEventEmitter<TxPoolV2Events> {
158
160
  handleMinedBlock(block: L2Block): Promise<void>;
159
161
 
160
162
  /**
161
- * Prepares the pool for a new slot.
162
- * Unprotects transactions from earlier slots and validates them before
163
- * returning to pending state.
164
- * @param slotNumber - The slot number to prepare for
163
+ * Prepares the pool for a new slot by unprotecting transactions from earlier
164
+ * slots and re-validating them before returning to pending state.
165
+ * @param slotNumber - The pipeline slot we are building for (i.e. the slot
166
+ * the resulting blocks will target on L1).
165
167
  */
166
168
  prepareForSlot(slotNumber: SlotNumber): Promise<void>;
167
169
 
@@ -67,6 +67,9 @@ export type TxMetaData = {
67
67
  /** Timestamp by which the transaction must be included (for expiration checks) */
68
68
  readonly expirationTimestamp: bigint;
69
69
 
70
+ /** Whether the tx's setup-phase calls pass the allow list check. Computed at receipt time. */
71
+ readonly allowedSetupCalls: boolean;
72
+
70
73
  /** Validator-compatible data, providing the same access patterns as Tx.data */
71
74
  readonly data: TxMetaValidationData;
72
75
 
@@ -84,8 +87,12 @@ export type TxState = 'pending' | 'protected' | 'mined' | 'deleted';
84
87
  * Builds TxMetaData from a full Tx object.
85
88
  * Extracts all relevant fields for efficient in-memory storage and querying.
86
89
  * Fr values are captured in closures for zero-cost re-validation.
90
+ *
91
+ * @param allowedSetupCalls - Whether the tx's setup-phase calls pass the allow list.
92
+ * For gossip/RPC txs this is always `true` (already validated by PhasesTxValidator).
93
+ * For req/resp txs this should be computed by the caller using the phases validator.
87
94
  */
88
- export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
95
+ export async function buildTxMetaData(tx: Tx, allowedSetupCalls: boolean = true): Promise<TxMetaData> {
89
96
  const txHashObj = tx.getTxHash();
90
97
  const txHash = txHashObj.toString();
91
98
  const txHashBigInt = txHashObj.toBigInt();
@@ -112,6 +119,7 @@ export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
112
119
  feeLimit,
113
120
  nullifiers,
114
121
  expirationTimestamp,
122
+ allowedSetupCalls,
115
123
  receivedAt: 0,
116
124
  estimatedSizeBytes,
117
125
  data: {
@@ -304,6 +312,7 @@ export function stubTxMetaData(
304
312
  nullifiers?: string[];
305
313
  expirationTimestamp?: bigint;
306
314
  anchorBlockHeaderHash?: string;
315
+ allowedSetupCalls?: boolean;
307
316
  } = {},
308
317
  ): TxMetaData {
309
318
  const txHashBigInt = Fr.fromHexString(txHash).toBigInt();
@@ -320,6 +329,7 @@ export function stubTxMetaData(
320
329
  feeLimit: overrides.feeLimit ?? 100n,
321
330
  nullifiers: overrides.nullifiers ?? [`0x${normalizedTxHash.slice(2)}null1`],
322
331
  expirationTimestamp,
332
+ allowedSetupCalls: overrides.allowedSetupCalls ?? true,
323
333
  receivedAt: 0,
324
334
  estimatedSizeBytes: 0,
325
335
  data: stubTxMetaValidationData({ expirationTimestamp }),
@@ -11,7 +11,14 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
11
11
  import EventEmitter from 'node:events';
12
12
 
13
13
  import { PoolInstrumentation, PoolName } from '../instrumentation.js';
14
- import type { AddTxsResult, TxPoolV2, TxPoolV2Config, TxPoolV2Dependencies, TxPoolV2Events } from './interfaces.js';
14
+ import type {
15
+ AddTxsResult,
16
+ PoolReadAccess,
17
+ TxPoolV2,
18
+ TxPoolV2Config,
19
+ TxPoolV2Dependencies,
20
+ TxPoolV2Events,
21
+ } from './interfaces.js';
15
22
  import type { TxState } from './tx_metadata.js';
16
23
  import { TxPoolV2Impl } from './tx_pool_v2_impl.js';
17
24
 
@@ -165,6 +172,11 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
165
172
  return this.#queue.put(() => Promise.resolve(this.#impl.getLowestPriorityPending(limit)));
166
173
  }
167
174
 
175
+ /** Returns read-only access to the pool. Used for testing. */
176
+ getPoolReadAccess(): PoolReadAccess {
177
+ return this.#impl.getPoolReadAccess();
178
+ }
179
+
168
180
  // === Configuration ===
169
181
 
170
182
  updateConfig(config: Partial<TxPoolV2Config>): Promise<void> {
@@ -62,6 +62,7 @@ export class TxPoolV2Impl {
62
62
  #l2BlockSource: L2BlockSource;
63
63
  #worldStateSynchronizer: WorldStateSynchronizer;
64
64
  #createTxValidator: TxPoolV2Dependencies['createTxValidator'];
65
+ #checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
65
66
 
66
67
  // === In-Memory Indices ===
67
68
  #indices: TxPoolIndices = new TxPoolIndices();
@@ -93,6 +94,7 @@ export class TxPoolV2Impl {
93
94
  this.#l2BlockSource = deps.l2BlockSource;
94
95
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
95
96
  this.#createTxValidator = deps.createTxValidator;
97
+ this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
96
98
 
97
99
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
98
100
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
@@ -354,6 +356,7 @@ export class TxPoolV2Impl {
354
356
 
355
357
  // Check if already in pool
356
358
  if (this.#indices.has(txHashStr)) {
359
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} already in pool`);
357
360
  return 'ignored';
358
361
  }
359
362
 
@@ -362,26 +365,37 @@ export class TxPoolV2Impl {
362
365
  const poolAccess = this.#createPreAddPoolAccess();
363
366
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
364
367
 
365
- return preAddResult.shouldIgnore ? 'ignored' : 'accepted';
368
+ if (preAddResult.shouldIgnore) {
369
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} ignored by pre-add rule`, {
370
+ reason: preAddResult.reason?.message ?? 'no reason provided',
371
+ });
372
+ return 'ignored';
373
+ }
374
+ return 'accepted';
366
375
  }
367
376
 
368
377
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
369
378
  const slotNumber = block.globalVariables.slotNumber;
370
379
 
380
+ // Precompute setup-call allow-list flags outside the store transaction
381
+ const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
382
+
371
383
  await this.#store.transactionAsync(async () => {
372
- for (const tx of txs) {
384
+ for (let i = 0; i < txs.length; i++) {
385
+ const tx = txs[i];
373
386
  const txHash = tx.getTxHash();
374
387
  const txHashStr = txHash.toString();
375
388
  const isNew = !this.#indices.has(txHashStr);
376
389
  const minedBlockId = await this.#getMinedBlockId(txHash);
377
390
 
378
391
  if (isNew) {
392
+ const meta = await buildTxMetaData(tx, allowedFlags[i]);
379
393
  // New tx - add as mined or protected (callback emitted by #addTx)
380
394
  if (minedBlockId) {
381
- await this.#addTx(tx, { mined: minedBlockId }, opts);
395
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
382
396
  this.#indices.setProtection(txHashStr, slotNumber);
383
397
  } else {
384
- await this.#addTx(tx, { protected: slotNumber }, opts);
398
+ await this.#addTx(tx, { protected: slotNumber }, opts, meta);
385
399
  }
386
400
  } else {
387
401
  // Existing tx - update protection and mined status
@@ -976,7 +990,8 @@ export class TxPoolV2Impl {
976
990
 
977
991
  try {
978
992
  const tx = Tx.fromBuffer(buffer);
979
- const meta = await buildTxMetaData(tx);
993
+ const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
994
+ const meta = await buildTxMetaData(tx, allowedSetupCalls);
980
995
  loaded.push({ tx, meta });
981
996
  } catch (err) {
982
997
  this.#log.warn(`Failed to deserialize tx ${txHashStr}, deleting`, { err });
@@ -0,0 +1,49 @@
1
+ # Attestation Validation
2
+
3
+ This module validates `CheckpointAttestation` gossipsub messages. Attestations are signatures from committee members endorsing a checkpoint proposal.
4
+
5
+ **Topic**: `checkpoint_attestation` | **Snappy size limit**: 5 KB
6
+
7
+ ## Stage 1: AttestationValidator (Gossipsub Validation)
8
+
9
+ | # | Rule | Consequence | Severity | File |
10
+ |---|------|-------------|----------|------|
11
+ | 1 | **Slot timeliness**: `currentSlot` or `nextSlot`. Previous slot within 500ms: IGNORE. Older: REJECT. | REJECT or IGNORE | HighToleranceError | `attestation_validator.ts` |
12
+ | 2 | **Attester signature**: `getSender()` must recover valid address | REJECT | LowToleranceError | same |
13
+ | 3 | **Attester in committee**: recovered address in committee for slot | REJECT | HighToleranceError | same |
14
+ | 4 | **Proposer exists**: `getProposerAttesterAddressInSlot` must return defined | REJECT | HighToleranceError | same |
15
+ | 5 | **Proposer signature**: `getProposer()` must recover valid address | REJECT | LowToleranceError | same |
16
+ | 6 | **Proposer matches expected**: recovered proposer = expected for slot | REJECT | HighToleranceError | same |
17
+ | 7 | **NoCommitteeError**: committee unavailable | REJECT | LowToleranceError | same |
18
+
19
+ **Fisherman mode extension** (`FishermanAttestationValidator`): if a checkpoint proposal for the same archive exists in pool, the attestation's `ConsensusPayload` must `.equals()` the stored proposal's payload. On mismatch: REJECT + LowToleranceError.
20
+
21
+ ## Stage 2: Pool Admission
22
+
23
+ | # | Rule | Consequence |
24
+ |---|------|-------------|
25
+ | 8 | Sender recoverable (pool-side) | Silent drop |
26
+ | 9 | Not a duplicate (same slot + proposalId + signer) | IGNORE |
27
+ | 10 | Per-signer cap: `MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER` = 2 | IGNORE |
28
+
29
+ Own attestations added via `addOwnCheckpointAttestations` bypass the per-signer cap.
30
+
31
+ ## Stage 3: Equivocation Detection
32
+
33
+ When a signer's attestation count for a slot reaches exactly 2 (different proposals): `duplicateAttestationCallback` fires -> `WANT_TO_SLASH_EVENT` with `OffenseType.DUPLICATE_ATTESTATION`. Attestation still ACCEPTED and rebroadcast. Callback fires once (not again at count 3+).
34
+
35
+ ## Validation at L1 Checkpoint Submission (Archiver)
36
+
37
+ | Rule | Consequence | File |
38
+ |------|-------------|------|
39
+ | Each attestation must have recoverable signature (or address-only is allowed but does not count toward quorum) | Checkpoint rejected as invalid | `archiver/src/modules/validation.ts` |
40
+ | Attestation at index `i` must correspond to committee member at index `i` | Checkpoint rejected as invalid | same |
41
+ | Valid attestation count >= floor(committee * 2/3) + 1 | Checkpoint rejected as invalid | same |
42
+ | No committee / escape hatch open | Accepted unconditionally | same |
43
+
44
+ Note: `skipValidateCheckpointAttestations` config flag bypasses all archiver attestation validation.
45
+
46
+ ## Gossipsub Topic Scoring
47
+
48
+ P3 enabled with expected messages per slot = `targetCommitteeSize`. Conservative threshold (30% of convergence value). Max P3 penalty = -34 per topic.
49
+
@@ -23,13 +23,14 @@ export class CheckpointAttestationValidator implements P2PValidator<CheckpointAt
23
23
  const slotNumber = message.payload.header.slotNumber;
24
24
 
25
25
  try {
26
- const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
26
+ // Use target slots since proposals target pipeline slots (slot + 1 when pipelining)
27
+ const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
27
28
 
28
- if (slotNumber !== currentSlot && slotNumber !== nextSlot) {
29
+ if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
29
30
  // Check if message is for previous slot and within clock tolerance
30
- if (!isWithinClockTolerance(slotNumber, currentSlot, this.epochCache)) {
31
+ if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
31
32
  this.logger.warn(
32
- `Checkpoint attestation slot ${slotNumber} is not current (${currentSlot}) or next (${nextSlot}) slot`,
33
+ `Checkpoint attestation slot ${slotNumber} is not current (${targetSlot}) or next (${nextSlot}) slot`,
33
34
  );
34
35
  return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
35
36
  }
@@ -36,10 +36,11 @@ export function isWithinClockTolerance(
36
36
  }
37
37
 
38
38
  // Check how far we are into the current slot (in milliseconds)
39
- const { ts: slotStartTs, nowMs, slot } = epochCache.getEpochAndSlotNow();
39
+ const { ts: slotStartTs, nowMs } = epochCache.getEpochAndSlotNow();
40
+ const targetSlot = epochCache.getTargetSlot();
40
41
 
41
- // Sanity check: ensure the epoch cache's current slot matches the expected current slot
42
- if (slot !== currentSlot) {
42
+ // Sanity check: ensure the epoch cache's target slot matches the expected current slot
43
+ if (targetSlot !== currentSlot) {
43
44
  return false;
44
45
  }
45
46