@aztec/p2p 0.0.1-commit.2f68f620 → 0.0.1-commit.321f6a9
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.
- package/README.md +7 -6
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +20 -12
- package/dest/client/factory.d.ts +2 -2
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +10 -5
- package/dest/client/interface.d.ts +36 -8
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +23 -12
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +38 -35
- package/dest/config.d.ts +19 -13
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +27 -26
- package/dest/errors/reqresp.error.d.ts +13 -1
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +14 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +9 -2
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +43 -0
- package/dest/mem_pools/instrumentation.d.ts +9 -4
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +11 -8
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/deleted_pool.js +6 -6
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +30 -5
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +15 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +31 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +9 -3
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +19 -8
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +17 -4
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +133 -34
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +6 -5
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +24 -26
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -4
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +4 -3
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.js +2 -2
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +4 -3
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +8 -2
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +19 -5
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +66 -30
- package/dest/msg_validators/tx_validator/factory.d.ts +9 -11
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +15 -6
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +12 -7
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +15 -15
- package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
- package/dest/services/discv5/discV5_service.d.ts +12 -2
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +84 -15
- package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
- package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
- package/dest/services/discv5/persisted_enr_store.js +145 -0
- package/dest/services/dummy_service.d.ts +6 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +3 -0
- package/dest/services/encoding.d.ts +19 -6
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +25 -15
- package/dest/services/gossipsub/topic_score_params.d.ts +6 -23
- package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
- package/dest/services/gossipsub/topic_score_params.js +4 -31
- package/dest/services/libp2p/libp2p_service.d.ts +11 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +135 -53
- package/dest/services/peer-manager/peer_manager.d.ts +15 -2
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +46 -18
- package/dest/services/peer-manager/peer_scoring.d.ts +28 -1
- package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_scoring.js +70 -3
- package/dest/services/reqresp/config.d.ts +13 -1
- package/dest/services/reqresp/config.d.ts.map +1 -1
- package/dest/services/reqresp/config.js +11 -0
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +34 -3
- package/dest/services/service.d.ts +17 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.d.ts +1 -1
- package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.js +4 -1
- package/dest/test-helpers/testbench-utils.d.ts +4 -1
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +9 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +19 -2
- package/package.json +16 -17
- package/src/bootstrap/bootstrap.ts +15 -9
- package/src/client/factory.ts +9 -5
- package/src/client/interface.ts +29 -6
- package/src/client/p2p_client.ts +46 -48
- package/src/config.ts +50 -38
- package/src/errors/reqresp.error.ts +19 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
- package/src/mem_pools/instrumentation.ts +13 -11
- package/src/mem_pools/tx_pool_v2/deleted_pool.ts +7 -7
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +28 -4
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +35 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +22 -8
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +158 -33
- package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -26
- package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -3
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -2
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +10 -2
- package/src/msg_validators/proposal_validator/proposal_validator.ts +69 -30
- package/src/msg_validators/tx_validator/README.md +2 -0
- package/src/msg_validators/tx_validator/factory.ts +19 -13
- package/src/msg_validators/tx_validator/gas_validator.ts +16 -20
- package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
- package/src/services/discv5/discV5_service.ts +88 -9
- package/src/services/discv5/persisted_enr_store.ts +158 -0
- package/src/services/dummy_service.ts +6 -0
- package/src/services/encoding.ts +28 -20
- package/src/services/gossipsub/README.md +33 -11
- package/src/services/gossipsub/topic_score_params.ts +9 -50
- package/src/services/libp2p/libp2p_service.ts +159 -56
- package/src/services/peer-manager/peer_manager.ts +48 -17
- package/src/services/peer-manager/peer_scoring.ts +75 -3
- package/src/services/reqresp/README.md +2 -23
- package/src/services/reqresp/config.ts +17 -0
- package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
- package/src/services/reqresp/reqresp.ts +42 -2
- package/src/services/service.ts +19 -0
- package/src/services/tx_collection/tx_source.ts +2 -1
- package/src/test-helpers/testbench-utils.ts +12 -0
- package/src/testbench/p2p_client_testbench_worker.ts +22 -2
- package/dest/msg_validators/clock_tolerance.d.ts +0 -32
- package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
- package/dest/msg_validators/clock_tolerance.js +0 -90
- package/src/msg_validators/clock_tolerance.ts +0 -121
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import { NoCommitteeError } from '@aztec/ethereum/contracts';
|
|
3
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT } from '@aztec/stdlib/deserialization';
|
|
4
5
|
import {
|
|
5
6
|
type BlockProposal,
|
|
6
7
|
type CheckpointProposalCore,
|
|
@@ -9,39 +10,39 @@ import {
|
|
|
9
10
|
type ValidationResult,
|
|
10
11
|
hasValidSignatureContext,
|
|
11
12
|
} from '@aztec/stdlib/p2p';
|
|
12
|
-
|
|
13
|
-
import { PipeliningWindow, isWithinClockTolerance } from '../clock_tolerance.js';
|
|
13
|
+
import type { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
14
14
|
|
|
15
15
|
/** Validates header-level and tx-level fields of block and checkpoint proposals. */
|
|
16
16
|
export class ProposalValidator {
|
|
17
17
|
private epochCache: EpochCacheInterface;
|
|
18
|
+
private timetable: ConsensusTimetable;
|
|
18
19
|
private logger: Logger;
|
|
19
20
|
private txsPermitted: boolean;
|
|
20
21
|
private maxTxsPerBlock?: number;
|
|
21
|
-
private maxBlocksPerCheckpoint?: number;
|
|
22
|
-
private pipeliningWindow: PipeliningWindow;
|
|
23
22
|
private skipSlotValidation: boolean;
|
|
24
23
|
private signatureContext: CoordinationSignatureContext;
|
|
24
|
+
private clockDisparityMs: number;
|
|
25
25
|
|
|
26
26
|
constructor(
|
|
27
27
|
epochCache: EpochCacheInterface,
|
|
28
|
+
timetable: ConsensusTimetable,
|
|
28
29
|
opts: {
|
|
29
30
|
txsPermitted: boolean;
|
|
30
31
|
maxTxsPerBlock?: number;
|
|
31
32
|
maxBlocksPerCheckpoint?: number;
|
|
32
|
-
p2pPropagationTime?: number;
|
|
33
33
|
skipSlotValidation?: boolean;
|
|
34
34
|
signatureContext: CoordinationSignatureContext;
|
|
35
|
+
clockDisparityMs: number;
|
|
35
36
|
},
|
|
36
37
|
loggerName: string,
|
|
37
38
|
) {
|
|
38
39
|
this.epochCache = epochCache;
|
|
40
|
+
this.timetable = timetable;
|
|
39
41
|
this.txsPermitted = opts.txsPermitted;
|
|
40
42
|
this.maxTxsPerBlock = opts.maxTxsPerBlock;
|
|
41
|
-
this.maxBlocksPerCheckpoint = opts.maxBlocksPerCheckpoint;
|
|
42
|
-
this.pipeliningWindow = new PipeliningWindow(epochCache, { p2pPropagationTime: opts.p2pPropagationTime });
|
|
43
43
|
this.skipSlotValidation = opts.skipSlotValidation ?? false;
|
|
44
44
|
this.signatureContext = opts.signatureContext;
|
|
45
|
+
this.clockDisparityMs = opts.clockDisparityMs;
|
|
45
46
|
this.logger = createLogger(loggerName);
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -59,21 +60,31 @@ export class ProposalValidator {
|
|
|
59
60
|
return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
// Slot check:
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
// Slot check: the tight checkpoint proposal receive window (`[receiveStart - δ, target_slot_start -
|
|
64
|
+
// E - D + δ]`) is the sole acceptance gate, applied to both block and checkpoint proposals. The
|
|
65
|
+
// window itself bounds which slots are valid, so far/wrong slots fall outside it. Every block
|
|
66
|
+
// proposal for slot N is sent before the checkpoint proposal for slot N, so nothing legitimate can
|
|
67
|
+
// arrive after the checkpoint receive deadline; gating block proposals on the same window rejects
|
|
68
|
+
// late block proposals at p2p ingress. The attestation deadline remains their re-execution/
|
|
69
|
+
// validation deadline downstream, not their arrival gate.
|
|
65
70
|
const slotNumber = proposal.slotNumber;
|
|
66
|
-
if (!this.skipSlotValidation
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
if (!this.skipSlotValidation) {
|
|
72
|
+
// Proposal receive window: [checkpoint_proposal_receive_start, checkpoint_proposal_receive_deadline],
|
|
73
|
+
// widened by the configured clock-disparity tolerance on both ends.
|
|
74
|
+
const startSeconds = this.timetable.getCheckpointProposalReceiveStart(slotNumber);
|
|
75
|
+
const deadlineSeconds = this.timetable.getCheckpointProposalReceiveDeadline(slotNumber);
|
|
76
|
+
const nowMs = Number(this.epochCache.getEpochAndSlotNow().nowMs);
|
|
77
|
+
if (
|
|
78
|
+
nowMs < startSeconds * 1000 - this.clockDisparityMs ||
|
|
79
|
+
nowMs > deadlineSeconds * 1000 + this.clockDisparityMs
|
|
80
|
+
) {
|
|
81
|
+
this.logger.warn(`Penalizing peer for invalid slot number ${slotNumber}`, {
|
|
82
|
+
slotNumber,
|
|
83
|
+
nowMs,
|
|
84
|
+
windowStartSeconds: startSeconds,
|
|
85
|
+
windowDeadlineSeconds: deadlineSeconds,
|
|
86
|
+
});
|
|
73
87
|
return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
|
|
74
|
-
} else {
|
|
75
|
-
this.logger.verbose(`Ignoring proposal for previous slot ${slotNumber} within clock tolerance`);
|
|
76
|
-
return { result: 'ignore' };
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
|
|
@@ -86,7 +97,11 @@ export class ProposalValidator {
|
|
|
86
97
|
|
|
87
98
|
// Proposer check
|
|
88
99
|
const expectedProposer = await this.epochCache.getProposerAttesterAddressInSlot(slotNumber);
|
|
89
|
-
if (expectedProposer
|
|
100
|
+
if (expectedProposer === undefined) {
|
|
101
|
+
this.logger.warn(`Penalizing peer for proposal with no expected proposer for current slot ${slotNumber}`);
|
|
102
|
+
return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
|
|
103
|
+
}
|
|
104
|
+
if (!proposer.equals(expectedProposer)) {
|
|
90
105
|
this.logger.warn(`Penalizing peer for invalid proposer for current slot ${slotNumber}`, {
|
|
91
106
|
expectedProposer,
|
|
92
107
|
proposer: proposer.toString(),
|
|
@@ -94,15 +109,17 @@ export class ProposalValidator {
|
|
|
94
109
|
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
|
|
112
|
+
// A block proposal whose index lands at or beyond the hard attestable ceiling is structurally
|
|
113
|
+
// impossible garbage, so reject it immediately at ingress. Indices in
|
|
114
|
+
// `[maxBlocksPerCheckpoint, MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT)` are over the consensus limit
|
|
115
|
+
// but structurally valid proposer misbehavior; they pass gossip validation here so the offending
|
|
116
|
+
// proposal can be retained and re-broadcast as slashing evidence (handled downstream in the p2p
|
|
117
|
+
// service), rather than penalizing the relaying peer.
|
|
118
|
+
if ('indexWithinCheckpoint' in proposal) {
|
|
119
|
+
const indexResult = this.validateBlockIndexWithinCheckpoint(proposal);
|
|
120
|
+
if (indexResult.result !== 'accept') {
|
|
121
|
+
return indexResult;
|
|
122
|
+
}
|
|
106
123
|
}
|
|
107
124
|
|
|
108
125
|
return { result: 'accept' };
|
|
@@ -114,6 +131,28 @@ export class ProposalValidator {
|
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Rejects a block proposal whose index within its checkpoint lands at or beyond the hard attestable
|
|
136
|
+
* ceiling `MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT` (a structurally impossible index that can never be
|
|
137
|
+
* part of any checkpoint we could attest to). `indexWithinCheckpoint` is 0-based, so a ceiling of 72
|
|
138
|
+
* rejects the 73rd block.
|
|
139
|
+
*
|
|
140
|
+
* Indices in `[maxBlocksPerCheckpoint, MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT)` are over the configured
|
|
141
|
+
* consensus limit but still structurally valid: they are proposer misbehavior, not relaying-peer
|
|
142
|
+
* fault, so they are *not* rejected here. The p2p service retains and re-broadcasts the first such
|
|
143
|
+
* proposal per (slot, proposer) as slashing evidence and skips processing it. Applies to standalone
|
|
144
|
+
* block proposals and to the terminal block embedded in a checkpoint proposal.
|
|
145
|
+
*/
|
|
146
|
+
public validateBlockIndexWithinCheckpoint(proposal: BlockProposal): ValidationResult {
|
|
147
|
+
if (proposal.indexWithinCheckpoint >= MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT) {
|
|
148
|
+
this.logger.warn(
|
|
149
|
+
`Penalizing peer for proposal with indexWithinCheckpoint ${proposal.indexWithinCheckpoint} >= attestable ceiling ${MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT}`,
|
|
150
|
+
);
|
|
151
|
+
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
152
|
+
}
|
|
153
|
+
return { result: 'accept' };
|
|
154
|
+
}
|
|
155
|
+
|
|
117
156
|
/** Validates transaction-related fields of a block proposal. */
|
|
118
157
|
public async validateTxs(proposal: BlockProposal): Promise<ValidationResult> {
|
|
119
158
|
// Transactions permitted check
|
|
@@ -120,6 +120,8 @@ The `AllowedSetupCallsMetaValidator` checks a precomputed boolean flag (`TxMetaD
|
|
|
120
120
|
\* Gas balance check is skipped when `skipFeeEnforcement` is set (testing/dev). `GasTxValidator` internally delegates to `GasLimitsValidator` and `MaxFeePerGasValidator` as its first steps, so gas limits and fee-per-gas are checked wherever `GasTxValidator` runs. Pool migration uses `GasLimitsValidator` and `MaxFeePerGasValidator` standalone because it doesn't need the balance check.
|
|
121
121
|
\** Proof verification is skipped for simulations (no verifier provided).
|
|
122
122
|
|
|
123
|
+
The gas-limit bounds `GasLimitsValidator` enforces here — the per-tx protocol maxima and the network admission limits — are documented in [`stdlib/src/gas/README.md`](../../../../stdlib/src/gas/README.md) under "Gas and Data Limits".
|
|
124
|
+
|
|
123
125
|
## Fee-Per-Gas Rejection Strategy
|
|
124
126
|
|
|
125
127
|
The `MaxFeePerGasValidator` and `InsufficientFeePerGasEvictionRule` reject and evict transactions whose `maxFeesPerGas` falls below the current block's gas fees. This is a simple strategy: if a tx can't pay the current fees, it gets rejected on entry and evicted after each new block.
|
|
@@ -100,7 +100,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
|
|
|
100
100
|
txsPermitted: boolean,
|
|
101
101
|
allowedInSetup: AllowedElement[] = [],
|
|
102
102
|
bindings?: LoggerBindings,
|
|
103
|
-
gasLimitOpts?: {
|
|
103
|
+
gasLimitOpts?: { maxTxL2Gas?: number; maxTxDAGas?: number },
|
|
104
104
|
): Record<string, TransactionValidator> {
|
|
105
105
|
const merkleTree = worldStateSynchronizer.getCommitted();
|
|
106
106
|
|
|
@@ -292,24 +292,24 @@ export function createTxValidatorForAcceptingTxsOverRPC(
|
|
|
292
292
|
setupAllowList,
|
|
293
293
|
gasFees,
|
|
294
294
|
skipFeeEnforcement,
|
|
295
|
+
isSimulation,
|
|
295
296
|
timestamp,
|
|
296
297
|
blockNumber,
|
|
297
298
|
txsPermitted,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
maxBlockDAGas,
|
|
299
|
+
maxTxL2Gas,
|
|
300
|
+
maxTxDAGas,
|
|
301
301
|
}: {
|
|
302
302
|
l1ChainId: number;
|
|
303
303
|
rollupVersion: number;
|
|
304
304
|
setupAllowList: AllowedElement[];
|
|
305
305
|
gasFees: GasFees;
|
|
306
306
|
skipFeeEnforcement?: boolean;
|
|
307
|
+
isSimulation?: boolean;
|
|
307
308
|
timestamp: UInt64;
|
|
308
309
|
blockNumber: BlockNumber;
|
|
309
310
|
txsPermitted: boolean;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
maxBlockDAGas?: number;
|
|
311
|
+
maxTxL2Gas?: number;
|
|
312
|
+
maxTxDAGas?: number;
|
|
313
313
|
},
|
|
314
314
|
bindings?: LoggerBindings,
|
|
315
315
|
): TxValidator<Tx> {
|
|
@@ -339,13 +339,19 @@ export function createTxValidatorForAcceptingTxsOverRPC(
|
|
|
339
339
|
new ContractInstanceTxValidator(bindings),
|
|
340
340
|
];
|
|
341
341
|
|
|
342
|
+
// Declared gas-limit admission is not fee enforcement, so it runs even when fees are skipped, but it is
|
|
343
|
+
// skipped during simulation: gas estimation submits intentionally-inflated `forEstimation` limits (above
|
|
344
|
+
// the per-tx max) and the wallet clamps the real tx to the admission limit afterward, so enforcing the
|
|
345
|
+
// limit on the estimation tx would reject a valid estimation. The fee-balance check below stays behind
|
|
346
|
+
// `skipFeeEnforcement`, and GasTxValidator is constructed without the limit opts so it does not re-run
|
|
347
|
+
// this same check.
|
|
348
|
+
if (!isSimulation) {
|
|
349
|
+
validators.push(new GasLimitsValidator<Tx>({ maxTxL2Gas, maxTxDAGas, bindings }));
|
|
350
|
+
}
|
|
351
|
+
|
|
342
352
|
if (!skipFeeEnforcement) {
|
|
343
353
|
validators.push(
|
|
344
|
-
new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings,
|
|
345
|
-
rollupManaLimit,
|
|
346
|
-
maxBlockL2Gas,
|
|
347
|
-
maxBlockDAGas,
|
|
348
|
-
}),
|
|
354
|
+
new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings),
|
|
349
355
|
);
|
|
350
356
|
}
|
|
351
357
|
|
|
@@ -431,7 +437,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
|
|
|
431
437
|
worldStateSynchronizer: WorldStateSynchronizer,
|
|
432
438
|
timestamp: bigint,
|
|
433
439
|
blockNumber: BlockNumber,
|
|
434
|
-
gasLimitOpts: {
|
|
440
|
+
gasLimitOpts: { maxTxL2Gas?: number; maxTxDAGas?: number },
|
|
435
441
|
gasFees: GasFees,
|
|
436
442
|
bindings?: LoggerBindings,
|
|
437
443
|
): Promise<TxValidator<TxMetaData>> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
|
|
3
2
|
MAX_PROCESSABLE_L2_GAS,
|
|
3
|
+
MAX_TX_DA_GAS,
|
|
4
4
|
PRIVATE_TX_L2_GAS_OVERHEAD,
|
|
5
5
|
PUBLIC_TX_L2_GAS_OVERHEAD,
|
|
6
6
|
TX_DA_GAS_OVERHEAD,
|
|
@@ -64,22 +64,21 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
|
|
|
64
64
|
#log: Logger;
|
|
65
65
|
#effectiveMaxL2Gas: number;
|
|
66
66
|
#effectiveMaxDAGas: number;
|
|
67
|
-
#rollupManaLimit: number;
|
|
68
|
-
#maxBlockL2Gas: number;
|
|
69
|
-
#maxBlockDAGas: number;
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
/**
|
|
69
|
+
* @param maxTxL2Gas - The network admission limit on L2 gas a single tx may declare (the per-block mana
|
|
70
|
+
* allocation, see {@link computeNetworkTxGasLimits}). Defaults to the per-tx protocol maximum, so callers
|
|
71
|
+
* that pass nothing (e.g. block building) enforce only the protocol ceiling.
|
|
72
|
+
* @param maxTxDAGas - The network admission limit on DA gas a single tx may declare. Defaults to the
|
|
73
|
+
* per-tx protocol maximum {@link MAX_TX_DA_GAS}.
|
|
74
|
+
*/
|
|
75
|
+
constructor(opts?: { maxTxL2Gas?: number; maxTxDAGas?: number; bindings?: LoggerBindings }) {
|
|
77
76
|
this.#log = createLogger('sequencer:tx_validator:tx_gas', opts?.bindings);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.#effectiveMaxL2Gas = Math.min(MAX_PROCESSABLE_L2_GAS,
|
|
82
|
-
this.#effectiveMaxDAGas = Math.min(
|
|
77
|
+
// The passed limits are network admission limits; clamp to the per-tx protocol maxima as a hard ceiling.
|
|
78
|
+
// MAX_TX_DA_GAS bounds DA by what a single tx can actually post to a blob; declaring more is meaningless
|
|
79
|
+
// and would let a tx reserve checkpoint/block DA budget during proposal building it can't use.
|
|
80
|
+
this.#effectiveMaxL2Gas = Math.min(MAX_PROCESSABLE_L2_GAS, opts?.maxTxL2Gas ?? Infinity);
|
|
81
|
+
this.#effectiveMaxDAGas = Math.min(MAX_TX_DA_GAS, opts?.maxTxDAGas ?? Infinity);
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
validateTx(tx: T): Promise<TxValidationResult> {
|
|
@@ -111,8 +110,6 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
|
|
|
111
110
|
this.#log.verbose(`Rejecting transaction due to the L2 gas limit being higher than the effective maximum`, {
|
|
112
111
|
gasLimits,
|
|
113
112
|
effectiveMaxL2Gas: this.#effectiveMaxL2Gas,
|
|
114
|
-
rollupManaLimit: this.#rollupManaLimit,
|
|
115
|
-
maxBlockL2Gas: this.#maxBlockL2Gas,
|
|
116
113
|
});
|
|
117
114
|
return {
|
|
118
115
|
result: 'invalid',
|
|
@@ -124,7 +121,6 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
|
|
|
124
121
|
this.#log.verbose(`Rejecting transaction due to the DA gas limit being higher than the effective maximum`, {
|
|
125
122
|
gasLimits,
|
|
126
123
|
effectiveMaxDAGas: this.#effectiveMaxDAGas,
|
|
127
|
-
maxBlockDAGas: this.#maxBlockDAGas,
|
|
128
124
|
});
|
|
129
125
|
return {
|
|
130
126
|
result: 'invalid',
|
|
@@ -201,14 +197,14 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
201
197
|
#publicDataSource: PublicStateSource;
|
|
202
198
|
#feeJuiceAddress: AztecAddress;
|
|
203
199
|
#gasFees: GasFees;
|
|
204
|
-
#gasLimitOpts?: {
|
|
200
|
+
#gasLimitOpts?: { maxTxL2Gas?: number; maxTxDAGas?: number };
|
|
205
201
|
|
|
206
202
|
constructor(
|
|
207
203
|
publicDataSource: PublicStateSource,
|
|
208
204
|
feeJuiceAddress: AztecAddress,
|
|
209
205
|
gasFees: GasFees,
|
|
210
206
|
private bindings?: LoggerBindings,
|
|
211
|
-
opts?: {
|
|
207
|
+
opts?: { maxTxL2Gas?: number; maxTxDAGas?: number },
|
|
212
208
|
) {
|
|
213
209
|
this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
|
|
214
210
|
this.#publicDataSource = publicDataSource;
|
|
@@ -97,7 +97,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
|
|
|
97
97
|
}
|
|
98
98
|
if (
|
|
99
99
|
entry.rejectNullMsgSender &&
|
|
100
|
-
publicCall.request.msgSender.equals(AztecAddress.
|
|
100
|
+
publicCall.request.msgSender.equals(AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS))
|
|
101
101
|
) {
|
|
102
102
|
return TX_ERROR_SETUP_NULL_MSG_SENDER;
|
|
103
103
|
}
|
|
@@ -130,7 +130,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
|
|
|
130
130
|
}
|
|
131
131
|
if (
|
|
132
132
|
entry.rejectNullMsgSender &&
|
|
133
|
-
publicCall.request.msgSender.equals(AztecAddress.
|
|
133
|
+
publicCall.request.msgSender.equals(AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS))
|
|
134
134
|
) {
|
|
135
135
|
return TX_ERROR_SETUP_NULL_MSG_SENDER;
|
|
136
136
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import { sleep } from '@aztec/foundation/sleep';
|
|
3
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
4
|
+
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
3
5
|
import { type ComponentsVersions, checkCompressedComponentVersion } from '@aztec/stdlib/versioning';
|
|
4
6
|
import { OtelMetricsAdapter, type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
5
7
|
|
|
@@ -14,9 +16,13 @@ import { createNodeENR } from '../../enr/generate-enr.js';
|
|
|
14
16
|
import { AZTEC_ENR_KEY, Discv5Event, PeerEvent } from '../../types/index.js';
|
|
15
17
|
import { convertToMultiaddr } from '../../util.js';
|
|
16
18
|
import { type PeerDiscoveryService, PeerDiscoveryState } from '../service.js';
|
|
19
|
+
import { PersistedEnrStore } from './persisted_enr_store.js';
|
|
17
20
|
|
|
18
21
|
const delayBeforeStart = 2000; // 2sec
|
|
19
22
|
|
|
23
|
+
/** Upper bound on persisted peer ENRs, to keep the store bounded as peers churn. */
|
|
24
|
+
const MAX_PERSISTED_PEER_ENRS = 100;
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* Peer discovery service using Discv5.
|
|
22
28
|
*/
|
|
@@ -35,14 +41,19 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
35
41
|
private bootstrapNodePeerIds: PeerId[] = [];
|
|
36
42
|
public bootstrapNodeEnrs: ENR[] = [];
|
|
37
43
|
private trustedPeerEnrs: ENR[] = [];
|
|
44
|
+
/** Node IDs of all config-provided peers (bootstrap, trusted, private); these are never persisted. */
|
|
45
|
+
private readonly configProvidedNodeIds: Set<string>;
|
|
38
46
|
|
|
39
47
|
private startTime = 0;
|
|
40
48
|
|
|
41
49
|
private currentIp: string | undefined;
|
|
42
50
|
|
|
51
|
+
/** Persistent store of discovered peer ENRs, used to re-seed discovery after a restart. */
|
|
52
|
+
private readonly persistedEnrs?: PersistedEnrStore;
|
|
53
|
+
|
|
43
54
|
private handlers = {
|
|
44
55
|
onMultiaddrUpdated: this.onMultiaddrUpdated.bind(this),
|
|
45
|
-
|
|
56
|
+
onPeerDiscovered: this.onPeerDiscovered.bind(this),
|
|
46
57
|
onEnrAdded: this.onEnrAdded.bind(this),
|
|
47
58
|
};
|
|
48
59
|
|
|
@@ -52,16 +63,27 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
52
63
|
private readonly packageVersion: string,
|
|
53
64
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
54
65
|
private logger = createLogger('p2p:discv5_service'),
|
|
66
|
+
store?: AztecAsyncKVStore,
|
|
55
67
|
configOverrides: Partial<IDiscv5CreateOptions> = {},
|
|
68
|
+
private readonly dateProvider: DateProvider = new DateProvider(),
|
|
56
69
|
) {
|
|
57
70
|
super();
|
|
58
71
|
|
|
72
|
+
this.persistedEnrs = store
|
|
73
|
+
? new PersistedEnrStore(store, MAX_PERSISTED_PEER_ENRS, createLogger(`${this.logger.module}:enr-store`))
|
|
74
|
+
: undefined;
|
|
75
|
+
|
|
59
76
|
const { p2pIp, p2pPort, p2pBroadcastPort, bootstrapNodes, trustedPeers, privatePeers } = config;
|
|
60
77
|
|
|
61
78
|
this.currentIp = p2pIp;
|
|
62
79
|
this.bootstrapNodeEnrs = bootstrapNodes.map(x => ENR.decodeTxt(x));
|
|
63
80
|
const privatePeerEnrs = new Set(privatePeers);
|
|
64
81
|
this.trustedPeerEnrs = trustedPeers.filter(x => !privatePeerEnrs.has(x)).map(x => ENR.decodeTxt(x));
|
|
82
|
+
// Peers supplied via config are re-injected from config on every start, so they must never end up
|
|
83
|
+
// in the persisted (discovered-peer) store — private peers especially must not be written there.
|
|
84
|
+
this.configProvidedNodeIds = new Set(
|
|
85
|
+
[...bootstrapNodes, ...trustedPeers, ...privatePeers].map(x => ENR.decodeTxt(x).nodeId),
|
|
86
|
+
);
|
|
65
87
|
|
|
66
88
|
// If no overridden broadcast port is provided, use the p2p port as the broadcast port
|
|
67
89
|
if (!p2pBroadcastPort) {
|
|
@@ -126,7 +148,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
126
148
|
}
|
|
127
149
|
};
|
|
128
150
|
|
|
129
|
-
this.discv5.on(Discv5Event.DISCOVERED, this.handlers.
|
|
151
|
+
this.discv5.on(Discv5Event.DISCOVERED, this.handlers.onPeerDiscovered);
|
|
130
152
|
this.discv5.on(Discv5Event.ENR_ADDED, this.handlers.onEnrAdded);
|
|
131
153
|
this.discv5.on(Discv5Event.MULTIADDR_UPDATED, this.handlers.onMultiaddrUpdated);
|
|
132
154
|
}
|
|
@@ -168,7 +190,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
168
190
|
}
|
|
169
191
|
this.logger.debug('Starting DiscV5');
|
|
170
192
|
await this.discv5.start();
|
|
171
|
-
this.startTime =
|
|
193
|
+
this.startTime = this.dateProvider.now();
|
|
172
194
|
|
|
173
195
|
const enrUpdateEnabled = this.config.queryForIp || !this.config.p2pIp;
|
|
174
196
|
this.logger.info(`DiscV5 service started`, {
|
|
@@ -220,6 +242,26 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
220
242
|
this.discv5.addEnr(enr);
|
|
221
243
|
}
|
|
222
244
|
}
|
|
245
|
+
|
|
246
|
+
// Re-seed discovery from peers persisted on previous runs. This lets a node rejoin the network
|
|
247
|
+
// after a restart even when no bootstrap node is reachable: discv5 has no on-disk routing table,
|
|
248
|
+
// so without this its DHT would start empty and rediscovery would depend on inbound dials alone.
|
|
249
|
+
if (this.persistedEnrs) {
|
|
250
|
+
// Drop peers that are now config-provided (re-injected above) or fail version checks.
|
|
251
|
+
const enrs = await this.persistedEnrs.load(
|
|
252
|
+
enr => this.validateEnr(enr) && !this.configProvidedNodeIds.has(enr.nodeId),
|
|
253
|
+
);
|
|
254
|
+
for (const enr of enrs) {
|
|
255
|
+
try {
|
|
256
|
+
this.discv5.addEnr(enr);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
this.logger.debug(`Error re-seeding persisted ENR ${enr.nodeId}`, err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (enrs.length > 0) {
|
|
262
|
+
this.logger.info(`Re-seeded discovery with ${enrs.length} persisted peer ENRs`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
223
265
|
}
|
|
224
266
|
|
|
225
267
|
public async runRandomNodesQuery(): Promise<void> {
|
|
@@ -229,8 +271,8 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
229
271
|
|
|
230
272
|
// First, wait some time before starting the peer discovery
|
|
231
273
|
// reference: https://github.com/ChainSafe/lodestar/issues/3423
|
|
232
|
-
const msSinceStart =
|
|
233
|
-
if (
|
|
274
|
+
const msSinceStart = this.dateProvider.now() - this.startTime;
|
|
275
|
+
if (this.dateProvider.now() - this.startTime <= delayBeforeStart) {
|
|
234
276
|
await sleep(delayBeforeStart - msSinceStart);
|
|
235
277
|
}
|
|
236
278
|
|
|
@@ -265,7 +307,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
265
307
|
if (this.currentState !== PeerDiscoveryState.RUNNING) {
|
|
266
308
|
return;
|
|
267
309
|
}
|
|
268
|
-
await this.discv5.off(Discv5Event.DISCOVERED, this.handlers.
|
|
310
|
+
await this.discv5.off(Discv5Event.DISCOVERED, this.handlers.onPeerDiscovered);
|
|
269
311
|
await this.discv5.off(Discv5Event.ENR_ADDED, this.handlers.onEnrAdded);
|
|
270
312
|
await this.discv5.off(Discv5Event.MULTIADDR_UPDATED, this.handlers.onMultiaddrUpdated);
|
|
271
313
|
|
|
@@ -275,12 +317,38 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
275
317
|
}
|
|
276
318
|
|
|
277
319
|
private async onEnrAdded(enr: ENR) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
320
|
+
try {
|
|
321
|
+
const multiAddrTcp = await enr.getFullMultiaddr('tcp');
|
|
322
|
+
const multiAddrUdp = await enr.getFullMultiaddr('udp');
|
|
323
|
+
this.logger.debug(`Added ENR ${enr.encodeTxt()}`, { multiAddrTcp, multiAddrUdp, nodeId: enr.nodeId });
|
|
324
|
+
// A peer just entered the routing table — persist it so we can re-seed discovery after a restart.
|
|
325
|
+
if (this.persistedEnrs && this.shouldPersist(enr)) {
|
|
326
|
+
await this.persistedEnrs.persist(enr);
|
|
327
|
+
}
|
|
328
|
+
this.onDiscovered(enr);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
// A malformed ENR address field (e.g. a 3-byte TCP port) makes @nethermindeth/enr throw while
|
|
331
|
+
// parsing the multiaddr. This is an async event listener, so an unhandled rejection would crash
|
|
332
|
+
// the process — catch, log, and drop the ENR instead.
|
|
333
|
+
this.logger.warn(`Dropping ENR with unparseable address`, { nodeId: enr.nodeId, err });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private async onPeerDiscovered(enr: ENR) {
|
|
338
|
+
// discv5 updates an existing routing-table entry on an ENR sequence bump without emitting enrAdded,
|
|
339
|
+
// so DISCOVERED is our only signal that a peer we already track may have changed address. Refresh
|
|
340
|
+
// the persisted copy (a no-op for peers we don't already store) so we don't keep dialing a stale ENR.
|
|
341
|
+
if (this.persistedEnrs && this.shouldPersist(enr)) {
|
|
342
|
+
await this.persistedEnrs.refresh(enr);
|
|
343
|
+
}
|
|
281
344
|
this.onDiscovered(enr);
|
|
282
345
|
}
|
|
283
346
|
|
|
347
|
+
/** Whether an ENR belongs in the persisted store: a validated, non-config peer that isn't ourselves. */
|
|
348
|
+
private shouldPersist(enr: ENR): boolean {
|
|
349
|
+
return !this.configProvidedNodeIds.has(enr.nodeId) && enr.nodeId !== this.enr.nodeId && this.validateEnr(enr);
|
|
350
|
+
}
|
|
351
|
+
|
|
284
352
|
private isOurBootnode(enr: ENR) {
|
|
285
353
|
return this.bootstrapNodeEnrs.some(x => x.nodeId === enr.nodeId);
|
|
286
354
|
}
|
|
@@ -314,6 +382,17 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
314
382
|
return false;
|
|
315
383
|
}
|
|
316
384
|
|
|
385
|
+
// A malformed TCP field (e.g. a non-2-byte port) makes @nethermindeth/enr throw while building the
|
|
386
|
+
// multiaddr. Reject such an ENR here so it's never emitted as a full peer or re-parsed by an
|
|
387
|
+
// unguarded listener (which would crash the process). A missing TCP addr is allowed — those peers
|
|
388
|
+
// are simply skipped at dial time.
|
|
389
|
+
try {
|
|
390
|
+
enr.getLocationMultiaddr('tcp');
|
|
391
|
+
} catch (err) {
|
|
392
|
+
this.logger.debug(`Peer node ${enr.nodeId} has an unparseable TCP address`, { err });
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
317
396
|
// And check it has the correct version
|
|
318
397
|
let compressedVersion;
|
|
319
398
|
try {
|