@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
@@ -0,0 +1,123 @@
1
+ # Proposal Validation
2
+
3
+ This module validates `BlockProposal` and `CheckpointProposal` gossipsub messages. Both share the same base `ProposalValidator` (neither subclass overrides `validate()`), with checkpoint-specific logic layered on top in the gossipsub handler.
4
+
5
+ ## BlockProposal
6
+
7
+ **Topic**: `block_proposal` | **Snappy size limit**: 10 MB
8
+
9
+ ### Stage 1: Gossipsub Validation (ProposalValidator)
10
+
11
+ File: `proposal_validator.ts`
12
+
13
+ | # | Rule | Consequence | Severity |
14
+ |---|------|-------------|----------|
15
+ | 1 | **Slot check**: must be `currentSlot` or `nextSlot`. Previous slot within 500ms tolerance: IGNORE. | REJECT | HighToleranceError |
16
+ | 2 | **Signature**: `getSender()` must recover a valid address. If `signedTxs` present, its recovered sender must match. | REJECT | MidToleranceError |
17
+ | 3 | **Txs permitted**: if `disableTransactions`, must have 0 txHashes and 0 embedded txs | REJECT | MidToleranceError |
18
+ | 4 | **Max txs**: `txHashes.length <= maxTxsPerBlock` | REJECT | MidToleranceError |
19
+ | 5 | **Embedded txs in txHashes**: every embedded tx's hash must appear in `txHashes` | REJECT | MidToleranceError |
20
+ | 6 | **Proposer check**: signer must match expected proposer for slot (skipped if committee size = 0) | REJECT | MidToleranceError |
21
+ | 7 | **Tx hash integrity**: each embedded tx's recomputed hash must match declared hash | REJECT | LowToleranceError |
22
+ | 8 | **NoCommitteeError**: epoch cache cannot determine committee | REJECT | LowToleranceError |
23
+
24
+ Deserialization guards: `BlockProposal.fromBuffer` and `SignedTxs.fromBuffer` both enforce `txCount <= MAX_TXS_PER_BLOCK` (65536). Violation -> REJECT + LowToleranceError.
25
+
26
+ ### Stage 2: Mempool (Attestation Pool)
27
+
28
+ | # | Rule | Consequence |
29
+ |---|------|-------------|
30
+ | 9 | **Duplicate**: same archive root already stored | IGNORE (no penalty) |
31
+ | 10 | **Per-position cap**: max 2 proposals per (slot, indexWithinCheckpoint) | REJECT + HighToleranceError |
32
+ | 11 | **Equivocation**: >1 distinct proposal for same (slot, index) | ACCEPT (rebroadcast for detection). At count=2: `duplicateProposalCallback` fires -> slash event (`OffenseType.DUPLICATE_PROPOSAL`, configured via `slashDuplicateProposalPenalty`) |
33
+
34
+ ### Stage 3: Validator-Client Processing (BlockProposalHandler)
35
+
36
+ Only runs on validator nodes. Non-validator nodes use a default handler that triggers tx collection without deep validation.
37
+
38
+ | # | Rule | Failure Reason |
39
+ |---|------|----------------|
40
+ | 12 | Signature re-check | `invalid_proposal` |
41
+ | 13 | ProposalValidator re-run | `invalid_proposal` |
42
+ | 14 | Self-proposal filter | Ignored silently |
43
+ | 15 | Parent block exists (`lastArchive.root` matches known block or genesis) | `parent_block_not_found` |
44
+ | 16 | Parent block slot <= proposal slot | `parent_block_wrong_slot` |
45
+ | 17 | Block number not already in archiver | `block_number_already_exists` |
46
+ | 18 | Checkpoint number consistency (multiple sub-rules for first/non-first blocks) | `invalid_proposal` |
47
+ | 19 | Global variables consistency (non-first block: chainId, version, slot, timestamp, coinbase, feeRecipient, gasFees match parent) | `global_variables_mismatch` |
48
+ | 20 | L1-to-L2 message hash matches `proposal.inHash` | `in_hash_mismatch` |
49
+ | 21 | All txs referenced by `txHashes` obtainable | `txs_not_available` |
50
+ | 22 | **Re-execution**: processed tx count matches `txHashes.length` | `timeout` (ReExTimeoutError) |
51
+ | 23 | **Re-execution**: no failed txs | `failed_txs` (ReExFailedTxsError) -- **SLASHABLE** |
52
+ | 24 | **Re-execution**: archive root and header match proposal | `state_mismatch` (ReExStateMismatchError) -- **SLASHABLE** |
53
+
54
+ **Escape hatch**: during escape hatch periods (`isEscapeHatchOpenAtSlot`), re-execution and slashing are both disabled, and the proposal is rejected locally.
55
+
56
+ **Conditional re-execution**: rules 22-24 only run when at least one condition is true: `fishermanMode` enabled, `slashBroadcastedInvalidBlockPenalty > 0` with `validatorReexecute`, committee membership with `validatorReexecute`, `alwaysReexecuteBlockProposals`, or `blobClient.canUpload()`.
57
+
58
+ **Slashing**: only `state_mismatch` and `failed_txs` trigger on-chain slashing (`OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL`, gated by `slashBroadcastedInvalidBlockPenalty > 0`). Unknown errors during re-execution do NOT slash.
59
+
60
+ **Embedded tx validation**: txs in `signedTxs` are validated via `createTxValidatorForBlockProposalReceivedTxs` (well-formedness only) when stored in the tx pool. Invalid embedded txs are rejected from the pool but do not cause the block proposal itself to be rejected at gossipsub level.
61
+
62
+ ### Gossipsub Topic Scoring
63
+
64
+ | Parameter | Effect |
65
+ |-----------|--------|
66
+ | P4 (invalidMessageDeliveries) | weight = -20, decay over 4 slots |
67
+ | P3 (meshMessageDeliveries) | Enabled only when `expectedBlockProposalsPerSlot > 0` (MBPS mode) |
68
+ | P1/P2 | Only active when P3 is enabled |
69
+
70
+ ---
71
+
72
+ ## CheckpointProposal
73
+
74
+ **Topic**: `checkpoint_proposal` | **Snappy size limit**: 10 MB
75
+
76
+ ### Stage 1: Gossipsub Validation (ProposalValidator)
77
+
78
+ Same `ProposalValidator.validate()` as BlockProposal (shared implementation, neither subclass overrides it). See BlockProposal Stage 1 rules 1-8.
79
+
80
+ ### Stage 2: Embedded Block Proposal Validation (if `lastBlock` present)
81
+
82
+ The checkpoint's embedded `lastBlock` is extracted via `getBlockProposal()` and validated through `BlockProposalValidator.validate()` plus block mempool checks.
83
+
84
+ | Rule | Consequence | File |
85
+ |------|-------------|------|
86
+ | Block proposal must pass `BlockProposalValidator.validate()` | If REJECT: entire checkpoint REJECTED | `libp2p_service.ts` |
87
+ | Block proposal must not exceed per-position cap (2) | Checkpoint REJECTED + HighToleranceError | same |
88
+ | Block equivocation detected (>1 proposals for same slot+index) | Checkpoint REJECTED (block itself is ACCEPT for re-broadcast) | same |
89
+
90
+ ### Stage 3: Mempool (Attestation Pool)
91
+
92
+ | Rule | Consequence | File |
93
+ |------|-------------|------|
94
+ | Duplicate (same archive ID) | IGNORE (no penalty). Embedded block still processed if valid. | `attestation_pool.ts` |
95
+ | Per-slot cap: `MAX_CHECKPOINT_PROPOSALS_PER_SLOT` = 2 | REJECT + HighToleranceError. Embedded block still processed. | same |
96
+
97
+ ### Stage 4: Equivocation Detection
98
+
99
+ When >1 checkpoint proposals exist for same slot (count > 1): ACCEPT (re-broadcast). At count == 2 (exactly): `duplicateProposalCallback` fires. Proposal NOT further processed. Callback fires only once per equivocation pair.
100
+
101
+ ### Stage 5: Validator-Client Consensus Validation
102
+
103
+ Determines whether the validator signs an attestation.
104
+
105
+ | Rule | Consequence | File |
106
+ |------|-------------|------|
107
+ | Escape hatch open | No attestation | `validator-client/src/validator.ts` |
108
+ | Signature invalid (re-check) | No attestation | same |
109
+ | Self-proposal | No attestation (ignored) | same |
110
+ | `feeAssetPriceModifier` outside [-100, +100] bps | No attestation | same |
111
+ | Not in committee (unless fisherman mode) | No attestation | same |
112
+ | Checkpoint header mismatch (computed vs proposal) | No attestation | same |
113
+ | Archive root mismatch | No attestation | same |
114
+ | Epoch out hash mismatch | No attestation | same |
115
+ | Last block not found / not matching | No attestation | same |
116
+ | Already attested to this or earlier slot | No attestation (unless `attestToEquivocatedProposals`) | same |
117
+
118
+ **`skipCheckpointProposalValidation` config**: when true, the re-execution checks (header/archive/epoch hash) are all skipped. Signature, fee modifier, committee, escape hatch, and equivocation checks still apply.
119
+
120
+ ### Gossipsub Topic Scoring
121
+
122
+ P3 enabled with expected rate of 1 message per slot. P4 weight = -20, max P3 penalty = -34 per topic.
123
+
@@ -31,13 +31,14 @@ export class ProposalValidator {
31
31
  /** Validates header-level fields: slot, signature, and proposer. */
32
32
  public async validate(proposal: BlockProposal | CheckpointProposalCore): Promise<ValidationResult> {
33
33
  try {
34
- // Slot check
35
- const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
34
+ // Slot check: use target slots since proposals target pipeline slots (slot + 1 when pipelining)
35
+ const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
36
+
36
37
  const slotNumber = proposal.slotNumber;
37
- if (slotNumber !== currentSlot && slotNumber !== nextSlot) {
38
+ if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
38
39
  // Check if message is for previous slot and within clock tolerance
39
- if (!isWithinClockTolerance(slotNumber, currentSlot, this.epochCache)) {
40
- this.logger.warn(`Penalizing peer for invalid slot number ${slotNumber}`, { currentSlot, nextSlot });
40
+ if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
41
+ this.logger.warn(`Penalizing peer for invalid slot number ${slotNumber}`, { targetSlot, nextSlot });
41
42
  return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
42
43
  }
43
44
  this.logger.verbose(`Ignoring proposal for previous slot ${slotNumber} within clock tolerance`);
@@ -75,10 +75,12 @@ This validator is invoked on **every** transaction potentially entering the pend
75
75
  - Startup hydration — revalidating persisted non-mined txs on node restart
76
76
 
77
77
  Runs:
78
- - DoubleSpend, BlockHeader, GasLimits, Timestamp
78
+ - DoubleSpend, BlockHeader, GasLimits, Timestamp, AllowedSetupCalls
79
79
 
80
80
  Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
81
81
 
82
+ The `AllowedSetupCallsMetaValidator` checks a precomputed boolean flag (`TxMetaData.allowedSetupCalls`) rather than re-running the full `PhasesTxValidator`. This flag is computed by `createCheckAllowedSetupCalls` when the tx first enters the pool (via `addProtectedTxs` or startup hydration), so the pool migration validator can reject txs with disallowed setup calls without needing the full `Tx` object or its dependencies.
83
+
82
84
  ## Individual Validators
83
85
 
84
86
  | Validator | What it checks | Benchmarked verification duration |
@@ -92,6 +94,7 @@ Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
92
94
  | `GasTxValidator` | Gas limits are within bounds (delegates to `GasLimitsValidator`), max fee per gas meets current block fees, and fee payer has sufficient FeeJuice balance | 1.02 ms |
93
95
  | `GasLimitsValidator` | Gas limits are >= fixed minimums and <= AVM max processable L2 gas. Used standalone in pool migration; also called internally by `GasTxValidator` | 3–10 us |
94
96
  | `PhasesTxValidator` | Public function calls in setup phase are on the allow list | 10.12–13.12 us |
97
+ | `AllowedSetupCallsMetaValidator` | Checks the precomputed `allowedSetupCalls` flag on `TxMetaData`. Used in pool migration instead of the full `PhasesTxValidator` | — |
95
98
  | `BlockHeaderTxValidator` | Transaction's anchor block hash exists in the archive tree | 98.88 us |
96
99
  | `TxProofValidator` | Client proof verifies correctly | ~250ms |
97
100
 
@@ -108,6 +111,7 @@ Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
108
111
  | Gas (balance + limits) | Stage 1 | Optional* | — | Yes | — |
109
112
  | GasLimits (standalone) | — | — | — | — | Yes |
110
113
  | Phases | Stage 1 | Yes | — | Yes | — |
114
+ | AllowedSetupCalls | — | — | — | — | Yes |
111
115
  | BlockHeader | Stage 1 | Yes | — | Yes | Yes |
112
116
  | Proof | Stage 2 | Optional** | Yes | — | — |
113
117
 
@@ -58,7 +58,7 @@ import { DoubleSpendTxValidator, type NullifierSource } from './double_spend_val
58
58
  import { GasLimitsValidator, GasTxValidator } from './gas_validator.js';
59
59
  import { MetadataTxValidator } from './metadata_validator.js';
60
60
  import { NullifierCache } from './nullifier_cache.js';
61
- import { PhasesTxValidator } from './phases_validator.js';
61
+ import { AllowedSetupCallsMetaValidator, PhasesTxValidator } from './phases_validator.js';
62
62
  import { SizeTxValidator } from './size_validator.js';
63
63
  import { TimestampTxValidator } from './timestamp_validator.js';
64
64
  import { TxPermittedValidator } from './tx_permitted_validator.js';
@@ -97,6 +97,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
97
97
  txsPermitted: boolean,
98
98
  allowedInSetup: AllowedElement[] = [],
99
99
  bindings?: LoggerBindings,
100
+ gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
100
101
  ): Record<string, TransactionValidator> {
101
102
  const merkleTree = worldStateSynchronizer.getCommitted();
102
103
 
@@ -158,6 +159,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
158
159
  ProtocolContractAddress.FeeJuice,
159
160
  gasFees,
160
161
  bindings,
162
+ gasLimitOpts,
161
163
  ),
162
164
  severity: PeerErrorSeverity.MidToleranceError,
163
165
  },
@@ -278,6 +280,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
278
280
  timestamp,
279
281
  blockNumber,
280
282
  txsPermitted,
283
+ rollupManaLimit,
284
+ maxBlockL2Gas,
285
+ maxBlockDAGas,
281
286
  }: {
282
287
  l1ChainId: number;
283
288
  rollupVersion: number;
@@ -287,6 +292,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
287
292
  timestamp: UInt64;
288
293
  blockNumber: BlockNumber;
289
294
  txsPermitted: boolean;
295
+ rollupManaLimit: number;
296
+ maxBlockL2Gas?: number;
297
+ maxBlockDAGas?: number;
290
298
  },
291
299
  bindings?: LoggerBindings,
292
300
  ): TxValidator<Tx> {
@@ -317,7 +325,11 @@ export function createTxValidatorForAcceptingTxsOverRPC(
317
325
 
318
326
  if (!skipFeeEnforcement) {
319
327
  validators.push(
320
- new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings),
328
+ new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings, {
329
+ rollupManaLimit,
330
+ maxBlockL2Gas,
331
+ maxBlockDAGas,
332
+ }),
321
333
  );
322
334
  }
323
335
 
@@ -403,6 +415,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
403
415
  worldStateSynchronizer: WorldStateSynchronizer,
404
416
  timestamp: bigint,
405
417
  blockNumber: BlockNumber,
418
+ gasLimitOpts: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
406
419
  bindings?: LoggerBindings,
407
420
  ): Promise<TxValidator<TxMetaData>> {
408
421
  await worldStateSynchronizer.syncImmediate();
@@ -419,9 +432,29 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
419
432
  },
420
433
  };
421
434
  return new AggregateTxValidator<TxMetaData>(
422
- new GasLimitsValidator<TxMetaData>(bindings),
435
+ new GasLimitsValidator<TxMetaData>({ ...gasLimitOpts, bindings }),
423
436
  new TimestampTxValidator<TxMetaData>({ timestamp, blockNumber }, bindings),
424
437
  new DoubleSpendTxValidator<TxMetaData>(nullifierSource, bindings),
425
438
  new BlockHeaderTxValidator<TxMetaData>(archiveSource, bindings),
439
+ new AllowedSetupCallsMetaValidator<TxMetaData>(bindings),
426
440
  );
427
441
  }
442
+
443
+ /**
444
+ * Creates a function that checks whether a tx's setup-phase calls are on the allow list.
445
+ *
446
+ * Uses the `PhasesTxValidator` on the full Tx. The result is stored as a boolean
447
+ * flag in `TxMetaData.allowedSetupCalls` at receipt time, so the pending pool
448
+ * migration validator can check it without needing the full Tx or its dependencies.
449
+ */
450
+ export function createCheckAllowedSetupCalls(
451
+ contractDataSource: ContractDataSource,
452
+ setupAllowList: AllowedElement[],
453
+ getTimestamp: () => UInt64,
454
+ ): (tx: Tx) => Promise<boolean> {
455
+ return async (tx: Tx) => {
456
+ const validator = new PhasesTxValidator(contractDataSource, setupAllowList, getTimestamp());
457
+ const result = await validator.validateTx(tx);
458
+ return result.result === 'valid';
459
+ };
460
+ }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
2
3
  MAX_PROCESSABLE_L2_GAS,
3
4
  PRIVATE_TX_L2_GAS_OVERHEAD,
4
5
  PUBLIC_TX_L2_GAS_OVERHEAD,
@@ -49,16 +50,31 @@ export interface HasGasLimitData {
49
50
  */
50
51
  export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidator<T> {
51
52
  #log: Logger;
52
-
53
- constructor(bindings?: LoggerBindings) {
54
- this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
53
+ #effectiveMaxL2Gas: number;
54
+ #effectiveMaxDAGas: number;
55
+ #rollupManaLimit: number;
56
+ #maxBlockL2Gas: number;
57
+ #maxBlockDAGas: number;
58
+
59
+ constructor(opts?: {
60
+ rollupManaLimit?: number;
61
+ maxBlockL2Gas?: number;
62
+ maxBlockDAGas?: number;
63
+ bindings?: LoggerBindings;
64
+ }) {
65
+ this.#log = createLogger('sequencer:tx_validator:tx_gas', opts?.bindings);
66
+ this.#rollupManaLimit = opts?.rollupManaLimit ?? Infinity;
67
+ this.#maxBlockL2Gas = opts?.maxBlockL2Gas ?? Infinity;
68
+ this.#maxBlockDAGas = opts?.maxBlockDAGas ?? Infinity;
69
+ this.#effectiveMaxL2Gas = Math.min(MAX_PROCESSABLE_L2_GAS, this.#rollupManaLimit, this.#maxBlockL2Gas);
70
+ this.#effectiveMaxDAGas = Math.min(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, this.#maxBlockDAGas);
55
71
  }
56
72
 
57
73
  validateTx(tx: T): Promise<TxValidationResult> {
58
74
  return Promise.resolve(this.validateGasLimit(tx));
59
75
  }
60
76
 
61
- /** Checks gas limits are >= fixed minimums and <= AVM max processable L2 gas. */
77
+ /** Checks gas limits are >= fixed minimums and <= effective max gas (L2 and DA). */
62
78
  validateGasLimit(tx: T): TxValidationResult {
63
79
  const gasLimits = tx.data.constants.txContext.gasSettings.gasLimits;
64
80
  const minGasLimits = new Gas(
@@ -74,10 +90,21 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
74
90
  return { result: 'invalid', reason: [TX_ERROR_INSUFFICIENT_GAS_LIMIT] };
75
91
  }
76
92
 
77
- if (gasLimits.l2Gas > MAX_PROCESSABLE_L2_GAS) {
78
- this.#log.verbose(`Rejecting transaction due to the gas limit(s) being higher than the maximum processable gas`, {
93
+ if (gasLimits.l2Gas > this.#effectiveMaxL2Gas) {
94
+ this.#log.verbose(`Rejecting transaction due to the L2 gas limit being higher than the effective maximum`, {
79
95
  gasLimits,
80
- minGasLimits,
96
+ effectiveMaxL2Gas: this.#effectiveMaxL2Gas,
97
+ rollupManaLimit: this.#rollupManaLimit,
98
+ maxBlockL2Gas: this.#maxBlockL2Gas,
99
+ });
100
+ return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
101
+ }
102
+
103
+ if (gasLimits.daGas > this.#effectiveMaxDAGas) {
104
+ this.#log.verbose(`Rejecting transaction due to the DA gas limit being higher than the effective maximum`, {
105
+ gasLimits,
106
+ effectiveMaxDAGas: this.#effectiveMaxDAGas,
107
+ maxBlockDAGas: this.#maxBlockDAGas,
81
108
  });
82
109
  return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
83
110
  }
@@ -106,21 +133,27 @@ export class GasTxValidator implements TxValidator<Tx> {
106
133
  #publicDataSource: PublicStateSource;
107
134
  #feeJuiceAddress: AztecAddress;
108
135
  #gasFees: GasFees;
136
+ #gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number };
109
137
 
110
138
  constructor(
111
139
  publicDataSource: PublicStateSource,
112
140
  feeJuiceAddress: AztecAddress,
113
141
  gasFees: GasFees,
114
142
  private bindings?: LoggerBindings,
143
+ opts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
115
144
  ) {
116
145
  this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
117
146
  this.#publicDataSource = publicDataSource;
118
147
  this.#feeJuiceAddress = feeJuiceAddress;
119
148
  this.#gasFees = gasFees;
149
+ this.#gasLimitOpts = opts;
120
150
  }
121
151
 
122
152
  async validateTx(tx: Tx): Promise<TxValidationResult> {
123
- const gasLimitValidation = new GasLimitsValidator(this.bindings).validateGasLimit(tx);
153
+ const gasLimitValidation = new GasLimitsValidator({
154
+ ...this.#gasLimitOpts,
155
+ bindings: this.bindings,
156
+ }).validateGasLimit(tx);
124
157
  if (gasLimitValidation.result === 'invalid') {
125
158
  return Promise.resolve(gasLimitValidation);
126
159
  }
@@ -141,3 +141,33 @@ export class PhasesTxValidator implements TxValidator<Tx> {
141
141
  return TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED;
142
142
  }
143
143
  }
144
+
145
+ /** Structural interface for the allowed-setup-calls flag check. */
146
+ export interface HasAllowedSetupCallsData {
147
+ txHash: { toString(): string };
148
+ allowedSetupCalls: boolean;
149
+ }
150
+
151
+ /**
152
+ * Validates that a transaction's setup-phase calls were allowed at receipt time.
153
+ *
154
+ * Checks the precomputed `allowedSetupCalls` flag on TxMetaData. The flag is
155
+ * computed by running the PhasesTxValidator on the full Tx when it first enters
156
+ * the pool. This lightweight validator is used during pending pool migration to
157
+ * reject txs whose setup calls are not on the allow list.
158
+ */
159
+ export class AllowedSetupCallsMetaValidator<T extends HasAllowedSetupCallsData> implements TxValidator<T> {
160
+ #log: Logger;
161
+
162
+ constructor(bindings?: LoggerBindings) {
163
+ this.#log = createLogger('sequencer:tx_validator:tx_phases_meta', bindings);
164
+ }
165
+
166
+ validateTx(tx: T): Promise<TxValidationResult> {
167
+ if (!tx.allowedSetupCalls) {
168
+ this.#log.verbose(`Rejecting tx ${tx.txHash} because its setup calls are not on the allow list`);
169
+ return Promise.resolve({ result: 'invalid', reason: [TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED] });
170
+ }
171
+ return Promise.resolve({ result: 'valid' });
172
+ }
173
+ }
@@ -9,6 +9,14 @@ import { webcrypto } from 'node:crypto';
9
9
  import { compressSync, uncompressSync } from 'snappy';
10
10
  import xxhashFactory from 'xxhash-wasm';
11
11
 
12
+ /** Thrown when a Snappy-compressed response exceeds the allowed decompressed size. */
13
+ export class OversizedSnappyResponseError extends Error {
14
+ constructor(decompressedSize: number, maxSizeKb: number) {
15
+ super(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
16
+ this.name = 'OversizedSnappyResponseError';
17
+ }
18
+ }
19
+
12
20
  // Load WASM
13
21
  const xxhash = await xxhashFactory();
14
22
 
@@ -86,7 +94,7 @@ export class SnappyTransform implements DataTransform {
86
94
  const { decompressedSize } = readSnappyPreamble(data);
87
95
  if (decompressedSize > maxSizeKb * 1024) {
88
96
  this.logger.warn(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
89
- throw new Error(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
97
+ throw new OversizedSnappyResponseError(decompressedSize, maxSizeKb);
90
98
  }
91
99
 
92
100
  return Buffer.from(uncompressSync(data, { asBuffer: true }));
@@ -18,7 +18,6 @@ import {
18
18
  type CheckpointProposalCore,
19
19
  type Gossipable,
20
20
  P2PMessage,
21
- type ValidationResult as P2PValidationResult,
22
21
  PeerErrorSeverity,
23
22
  PeerErrorSeverityByHarshness,
24
23
  TopicType,
@@ -226,7 +225,7 @@ export class LibP2PService extends WithTracer implements P2PService {
226
225
 
227
226
  const proposalValidatorOpts = {
228
227
  txsPermitted: !config.disableTransactions,
229
- maxTxsPerBlock: config.validateMaxTxsPerBlock,
228
+ maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
230
229
  };
231
230
  this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
232
231
  this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
@@ -237,11 +236,11 @@ export class LibP2PService extends WithTracer implements P2PService {
237
236
  this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
238
237
 
239
238
  this.blockReceivedCallback = async (block: BlockProposal): Promise<boolean> => {
240
- this.logger.debug(
241
- `Handler not yet registered: Block received callback not set. Received block for slot ${block.slotNumber} from peer.`,
239
+ this.logger.warn(
240
+ `Handler for block received not yet registered on P2P service. Received block ${block.blockNumber} for slot ${block.slotNumber} from peer.`,
242
241
  { p2pMessageIdentifier: await block.p2pMessageLoggingIdentifier() },
243
242
  );
244
- return false;
243
+ return true;
245
244
  };
246
245
 
247
246
  this.checkpointReceivedCallback = (
@@ -754,6 +753,9 @@ export class LibP2PService extends WithTracer implements P2PService {
754
753
  if (!validator || !validator.addMessage(msgId)) {
755
754
  this.instrumentation.incMessagePrevalidationStatus(false, topicType);
756
755
  this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), TopicValidatorResult.Ignore);
756
+ if (topicType === TopicType.tx) {
757
+ this.logger.verbose(`Ignoring already-seen tx gossip message`, { msgId, source: source.toString() });
758
+ }
757
759
  return { result: false, topicType };
758
760
  }
759
761
 
@@ -923,6 +925,11 @@ export class LibP2PService extends WithTracer implements P2PService {
923
925
  severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
924
926
  }
925
927
 
928
+ this.logger.verbose(`Rejecting gossiped tx ${tx.getTxHash().toString()}: stage 1 validation failed`, {
929
+ validator: name,
930
+ severity,
931
+ source: source.toString(),
932
+ });
926
933
  this.peerManager.penalizePeer(source, severity);
927
934
  return { result: TopicValidatorResult.Reject };
928
935
  }
@@ -930,6 +937,9 @@ export class LibP2PService extends WithTracer implements P2PService {
930
937
  // Pool pre-check: see if the pool would accept this tx before doing expensive proof verification
931
938
  const canAdd = await this.mempools.txPool.canAddPendingTx(tx);
932
939
  if (canAdd === 'ignored') {
940
+ this.logger.verbose(`Ignoring gossiped tx ${tx.getTxHash().toString()}: pool pre-check returned ignored`, {
941
+ source: source.toString(),
942
+ });
933
943
  return { result: TopicValidatorResult.Ignore, obj: tx };
934
944
  }
935
945
 
@@ -937,7 +947,12 @@ export class LibP2PService extends WithTracer implements P2PService {
937
947
  const secondStageValidators = this.createSecondStageMessageValidators();
938
948
  const secondStageOutcome = await this.runValidations(tx, secondStageValidators);
939
949
  if (!secondStageOutcome.allPassed) {
940
- const { severity } = secondStageOutcome.failure;
950
+ const { severity, name } = secondStageOutcome.failure;
951
+ this.logger.verbose(`Rejecting gossiped tx ${tx.getTxHash().toString()}: stage 2 validation failed`, {
952
+ validator: name,
953
+ severity,
954
+ source: source.toString(),
955
+ });
941
956
  this.peerManager.penalizePeer(source, severity);
942
957
  return { result: TopicValidatorResult.Reject };
943
958
  }
@@ -949,7 +964,7 @@ export class LibP2PService extends WithTracer implements P2PService {
949
964
  const wasAccepted = addResult.accepted.some(h => h.equals(txHash));
950
965
  const wasIgnored = addResult.ignored.some(h => h.equals(txHash));
951
966
 
952
- this.logger.trace(`Validate propagated tx`, {
967
+ this.logger.verbose(`Validate propagated tx ${txHash.toString()}`, {
953
968
  wasAccepted,
954
969
  wasIgnored,
955
970
  [Attributes.P2P_ID]: source.toString(),
@@ -960,6 +975,11 @@ export class LibP2PService extends WithTracer implements P2PService {
960
975
  } else if (wasIgnored) {
961
976
  return { result: TopicValidatorResult.Ignore, obj: tx };
962
977
  } else {
978
+ this.logger.warn(`Gossiped tx ${txHash.toString()} unexpectedly rejected by pool`, {
979
+ source: source.toString(),
980
+ txHash: txHash.toString(),
981
+ });
982
+ this.peerManager.penalizePeer(source, PeerErrorSeverity.HighToleranceError);
963
983
  return { result: TopicValidatorResult.Reject };
964
984
  }
965
985
  };
@@ -1192,7 +1212,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1192
1212
  // Note: Validators do NOT attest to individual blocks, only to checkpoint proposals.
1193
1213
  const isValid = await this.blockReceivedCallback(block, sender);
1194
1214
  if (!isValid) {
1195
- this.logger.warn(`Block proposal validation failed for block ${block.blockNumber}`, block.toBlockInfo());
1215
+ this.logger.info(`Block proposal validation failed for block ${block.blockNumber}`, block.toBlockInfo());
1196
1216
  }
1197
1217
  }
1198
1218
 
@@ -1626,6 +1646,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1626
1646
  ...(this.config.txPublicSetupAllowListExtend ?? []),
1627
1647
  ];
1628
1648
  const blockNumber = BlockNumber(currentBlockNumber + 1);
1649
+ const l1Constants = await this.archiver.getL1Constants();
1629
1650
 
1630
1651
  return createFirstStageTxValidationsForGossipedTransactions(
1631
1652
  nextSlotTimestamp,
@@ -1639,6 +1660,11 @@ export class LibP2PService extends WithTracer implements P2PService {
1639
1660
  !this.config.disableTransactions,
1640
1661
  allowedInSetup,
1641
1662
  this.logger.getBindings(),
1663
+ {
1664
+ rollupManaLimit: l1Constants.rollupManaLimit,
1665
+ maxBlockL2Gas: this.config.validateMaxL2BlockGas,
1666
+ maxBlockDAGas: this.config.validateMaxDABlockGas,
1667
+ },
1642
1668
  );
1643
1669
  }
1644
1670
 
@@ -1720,31 +1746,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1720
1746
  return PeerErrorSeverity.HighToleranceError;
1721
1747
  }
1722
1748
 
1723
- /**
1724
- * Validate a checkpoint attestation.
1725
- *
1726
- * @param attestation - The checkpoint attestation to validate.
1727
- * @returns True if the checkpoint attestation is valid, false otherwise.
1728
- */
1729
- @trackSpan('Libp2pService.validateCheckpointAttestation', async (_, attestation) => ({
1730
- [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber,
1731
- [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
1732
- [Attributes.P2P_ID]: await attestation.p2pMessageLoggingIdentifier().then(i => i.toString()),
1733
- }))
1734
- public async validateCheckpointAttestation(
1735
- peerId: PeerId,
1736
- attestation: CheckpointAttestation,
1737
- ): Promise<P2PValidationResult> {
1738
- const result = await this.checkpointAttestationValidator.validate(attestation);
1739
-
1740
- if (result.result === 'reject') {
1741
- this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1742
- this.peerManager.penalizePeer(peerId, result.severity);
1743
- }
1744
-
1745
- return result;
1746
- }
1747
-
1748
1749
  public getPeerScore(peerId: PeerId): number {
1749
1750
  return this.node.services.pubsub.score.score(peerId.toString());
1750
1751
  }
@@ -32,7 +32,7 @@ import { PeerScoreState, type PeerScoring } from './peer_scoring.js';
32
32
  const MAX_DIAL_ATTEMPTS = 3;
33
33
  const MAX_CACHED_PEERS = 100;
34
34
  const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
35
- const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
35
+ const DEFAULT_FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
36
36
  const GOODBYE_DIAL_TIMEOUT_MS = 1000;
37
37
  const FAILED_AUTH_HANDSHAKE_EXPIRY_MS = 60 * 60 * 1000; // 1 hour
38
38
 
@@ -776,7 +776,8 @@ export class PeerManager implements PeerManagerInterface {
776
776
  // Add to timed out peers
777
777
  this.timedOutPeers.set(id, {
778
778
  peerId: id,
779
- timeoutUntilMs: this.dateProvider.now() + FAILED_PEER_BAN_TIME_MS,
779
+ timeoutUntilMs:
780
+ this.dateProvider.now() + (this.config.peerFailedBanTimeMs ?? DEFAULT_FAILED_PEER_BAN_TIME_MS),
780
781
  });
781
782
  }
782
783
  }
@@ -938,6 +939,8 @@ export class PeerManager implements PeerManagerInterface {
938
939
  `Received auth for validator ${sender.toString()} from peer ${peerIdString}, but this validator is already authenticated to peer ${peerForAddress.toString()}`,
939
940
  { ...logData, address: sender.toString() },
940
941
  );
942
+ this.markAuthHandshakeFailed(peerId);
943
+ this.markPeerForDisconnect(peerId);
941
944
  return;
942
945
  }
943
946