@aztec/p2p 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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 +40 -9
- 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/archive/tx_archive.d.ts +1 -5
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +1 -7
- 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.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -3
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +3 -4
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +4 -5
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +6 -5
- 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/msg_validators/tx_validator/timestamp_validator.js +1 -1
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts +2 -2
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/tx_validation_cache.js +6 -7
- 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 +12 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +204 -114
- 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/batch-tx-requester/batch_tx_requester.d.ts +1 -8
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +21 -39
- 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/protocols/block_txs/bitvector.d.ts +4 -3
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/bitvector.js +3 -2
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +29 -28
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +12 -7
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +42 -24
- 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 +21 -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 +11 -3
- 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 +15 -17
- package/src/bootstrap/bootstrap.ts +15 -9
- package/src/client/factory.ts +9 -5
- package/src/client/interface.ts +33 -7
- 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/archive/tx_archive.ts +1 -9
- 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 +2 -4
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -2
- package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +3 -1
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +1 -3
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -4
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +5 -6
- 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/msg_validators/tx_validator/timestamp_validator.ts +1 -1
- package/src/msg_validators/tx_validator/tx_validation_cache.ts +6 -7
- 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 +234 -125
- 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/batch-tx-requester/batch_tx_requester.ts +21 -45
- package/src/services/reqresp/config.ts +17 -0
- package/src/services/reqresp/protocols/block_txs/bitvector.ts +3 -2
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +33 -31
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +47 -20
- package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
- package/src/services/reqresp/reqresp.ts +42 -2
- package/src/services/service.ts +23 -0
- package/src/services/tx_collection/tx_source.ts +2 -1
- package/src/test-helpers/testbench-utils.ts +13 -2
- 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 -95
- package/src/msg_validators/clock_tolerance.ts +0 -127
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
-
import { maxBy, merge } from '@aztec/foundation/collection';
|
|
3
|
+
import { compactArray, maxBy, merge } from '@aztec/foundation/collection';
|
|
4
4
|
import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
6
6
|
import { Timer } from '@aztec/foundation/timer';
|
|
7
7
|
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
8
8
|
import { protocolContractsHash } from '@aztec/protocol-contracts';
|
|
9
9
|
import type { EthAddress, L2BlockSource } from '@aztec/stdlib/block';
|
|
10
|
+
import { DEFAULT_MAX_BLOCKS_PER_CHECKPOINT } from '@aztec/stdlib/config';
|
|
10
11
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
11
|
-
import { type BlockMinFeesProvider, GasFees } from '@aztec/stdlib/gas';
|
|
12
|
+
import { type BlockMinFeesProvider, GasFees, getNetworkTxGasLimits } from '@aztec/stdlib/gas';
|
|
12
13
|
import type { ClientProtocolCircuitVerifier, PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
13
14
|
import {
|
|
14
15
|
BlockProposal,
|
|
@@ -24,9 +25,11 @@ import {
|
|
|
24
25
|
getTopicsForConfig,
|
|
25
26
|
metricsTopicStrToLabels,
|
|
26
27
|
} from '@aztec/stdlib/p2p';
|
|
28
|
+
import { ConsensusTimetable, getDefaultCheckpointProposalSyncGrace } from '@aztec/stdlib/timetable';
|
|
27
29
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
28
30
|
import { Tx, type TxValidationResult } from '@aztec/stdlib/tx';
|
|
29
31
|
import type { UInt64 } from '@aztec/stdlib/types';
|
|
32
|
+
import { InvalidBlockProposalTxsError } from '@aztec/stdlib/validators';
|
|
30
33
|
import { compressComponentVersions } from '@aztec/stdlib/versioning';
|
|
31
34
|
import {
|
|
32
35
|
Attributes,
|
|
@@ -81,14 +84,14 @@ import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
|
|
|
81
84
|
import { getVersions } from '../../versioning.js';
|
|
82
85
|
import { AztecDatastore } from '../data_store.js';
|
|
83
86
|
import { DiscV5Service } from '../discv5/discV5_service.js';
|
|
84
|
-
import { SnappyTransform,
|
|
87
|
+
import { SnappyTransform, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
|
|
85
88
|
import { APP_SPECIFIC_WEIGHT, gossipScoreThresholds } from '../gossipsub/scoring.js';
|
|
86
89
|
import { createAllTopicScoreParams } from '../gossipsub/topic_score_params.js';
|
|
87
90
|
import type { PeerManagerInterface } from '../peer-manager/interface.js';
|
|
88
91
|
import { PeerManager } from '../peer-manager/peer_manager.js';
|
|
89
92
|
import { PeerScoring } from '../peer-manager/peer_scoring.js';
|
|
90
93
|
import type { BatchTxRequesterLibP2PService } from '../reqresp/batch-tx-requester/interface.js';
|
|
91
|
-
import type
|
|
94
|
+
import { type P2PReqRespConfig, YAMUX_MAX_MESSAGE_SIZE_BYTES } from '../reqresp/config.js';
|
|
92
95
|
import {
|
|
93
96
|
AuthRequest,
|
|
94
97
|
BlockTxsRequest,
|
|
@@ -112,11 +115,30 @@ import type {
|
|
|
112
115
|
P2PCheckpointAttestationCallback,
|
|
113
116
|
P2PCheckpointReceivedCallback,
|
|
114
117
|
P2PDuplicateAttestationCallback,
|
|
118
|
+
P2POversizedProposalCallback,
|
|
115
119
|
P2PService,
|
|
116
120
|
PeerDiscoveryService,
|
|
117
121
|
} from '../service.js';
|
|
118
122
|
import { P2PInstrumentation } from './instrumentation.js';
|
|
119
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Builds the {@link ConsensusTimetable} shared by the gossip validators for proposal/attestation receive-window
|
|
126
|
+
* bounds. Derived purely from protocol slot-timing constants plus the block sub-slot duration and the consensus
|
|
127
|
+
* materialization grace, so every node agrees on these bounds without depending on proposer operational budgets.
|
|
128
|
+
*/
|
|
129
|
+
function buildConsensusTimetable(
|
|
130
|
+
config: P2PConfig,
|
|
131
|
+
l1Constants: ReturnType<EpochCacheInterface['getL1Constants']>,
|
|
132
|
+
): ConsensusTimetable {
|
|
133
|
+
const blockDuration = config.blockDurationMs / 1000;
|
|
134
|
+
return new ConsensusTimetable({
|
|
135
|
+
l1Constants,
|
|
136
|
+
blockDuration,
|
|
137
|
+
checkpointProposalSyncGrace:
|
|
138
|
+
config.checkpointProposalSyncGraceSeconds ?? getDefaultCheckpointProposalSyncGrace(blockDuration),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
120
142
|
interface ValidationResult {
|
|
121
143
|
name: string;
|
|
122
144
|
isValid: TxValidationResult;
|
|
@@ -152,6 +174,9 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
152
174
|
type: 'checkpoint' | 'block';
|
|
153
175
|
}) => void;
|
|
154
176
|
|
|
177
|
+
/** Callback invoked when an oversized block proposal is stored as slashing evidence (triggers slashing). */
|
|
178
|
+
private oversizedProposalCallback?: P2POversizedProposalCallback;
|
|
179
|
+
|
|
155
180
|
/** Callback invoked when a duplicate attestation is detected (triggers slashing). */
|
|
156
181
|
private duplicateAttestationCallback?: P2PDuplicateAttestationCallback;
|
|
157
182
|
|
|
@@ -233,28 +258,39 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
233
258
|
this.protocolVersion,
|
|
234
259
|
);
|
|
235
260
|
|
|
236
|
-
|
|
261
|
+
// Build the consensus timetable once from protocol slot-timing constants and inject it into every
|
|
262
|
+
// validator so they share one set of receive-window bounds, independent of proposer operational budgets.
|
|
263
|
+
const consensusTimetable = buildConsensusTimetable(config, epochCache.getL1Constants());
|
|
237
264
|
const proposalValidatorOpts = {
|
|
238
265
|
txsPermitted: !config.disableTransactions,
|
|
239
266
|
maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
|
|
240
267
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
241
|
-
p2pPropagationTime,
|
|
242
268
|
skipSlotValidation: config.skipProposalSlotValidation,
|
|
243
269
|
signatureContext: {
|
|
244
270
|
chainId: config.l1ChainId,
|
|
245
271
|
rollupAddress: config.rollupAddress,
|
|
246
272
|
},
|
|
273
|
+
clockDisparityMs: config.maxGossipClockDisparityMs,
|
|
247
274
|
};
|
|
248
|
-
this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
|
|
249
|
-
this.checkpointProposalValidator = new CheckpointProposalValidator(
|
|
275
|
+
this.blockProposalValidator = new BlockProposalValidator(epochCache, consensusTimetable, proposalValidatorOpts);
|
|
276
|
+
this.checkpointProposalValidator = new CheckpointProposalValidator(
|
|
277
|
+
epochCache,
|
|
278
|
+
consensusTimetable,
|
|
279
|
+
proposalValidatorOpts,
|
|
280
|
+
);
|
|
250
281
|
const attestationValidatorOpts = {
|
|
251
|
-
l1PublishingTime: config.l1PublishingTime,
|
|
252
|
-
p2pPropagationTime,
|
|
253
282
|
signatureContext: proposalValidatorOpts.signatureContext,
|
|
283
|
+
clockDisparityMs: config.maxGossipClockDisparityMs,
|
|
254
284
|
};
|
|
255
285
|
this.checkpointAttestationValidator = config.fishermanMode
|
|
256
|
-
? new FishermanAttestationValidator(
|
|
257
|
-
|
|
286
|
+
? new FishermanAttestationValidator(
|
|
287
|
+
epochCache,
|
|
288
|
+
consensusTimetable,
|
|
289
|
+
mempools.attestationPool,
|
|
290
|
+
telemetry,
|
|
291
|
+
attestationValidatorOpts,
|
|
292
|
+
)
|
|
293
|
+
: new CheckpointAttestationValidator(epochCache, consensusTimetable, attestationValidatorOpts);
|
|
258
294
|
|
|
259
295
|
this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
|
|
260
296
|
|
|
@@ -333,6 +369,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
333
369
|
packageVersion,
|
|
334
370
|
telemetry,
|
|
335
371
|
createLogger(`${logger.module}:discv5_service`, logger.getBindings()),
|
|
372
|
+
peerStore,
|
|
336
373
|
);
|
|
337
374
|
|
|
338
375
|
// Seed libp2p's bootstrap discovery with private and trusted peers
|
|
@@ -347,37 +384,49 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
347
384
|
const protocolVersion = compressComponentVersions(versions);
|
|
348
385
|
|
|
349
386
|
const preferredPeersEnrs: ENR[] = config.preferredPeers.map(enr => ENR.decodeTxt(enr));
|
|
350
|
-
const directPeers = (
|
|
387
|
+
const directPeers = compactArray(
|
|
351
388
|
await Promise.all(
|
|
352
389
|
preferredPeersEnrs.map(async enr => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
390
|
+
try {
|
|
391
|
+
const peerId = await enr.peerId();
|
|
392
|
+
const address = enr.getLocationMultiaddr('tcp');
|
|
393
|
+
if (address === undefined) {
|
|
394
|
+
throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
id: peerId,
|
|
398
|
+
addrs: [address],
|
|
399
|
+
};
|
|
400
|
+
} catch (err) {
|
|
401
|
+
// A malformed configured ENR shouldn't abort node setup — skip it and log.
|
|
402
|
+
logger.warn(`Skipping preferred peer with invalid ENR`, { err });
|
|
403
|
+
return undefined;
|
|
357
404
|
}
|
|
358
|
-
return {
|
|
359
|
-
id: peerId,
|
|
360
|
-
addrs: [address],
|
|
361
|
-
};
|
|
362
405
|
}),
|
|
363
|
-
)
|
|
364
|
-
)
|
|
406
|
+
),
|
|
407
|
+
);
|
|
365
408
|
|
|
366
409
|
const announceTcpMultiaddr = config.p2pIp ? [convertToMultiaddr(config.p2pIp, p2pPort, 'tcp')] : [];
|
|
367
410
|
|
|
368
|
-
// Create dynamic topic score params based on network configuration
|
|
411
|
+
// Create dynamic topic score params based on network configuration. Scoring uses the network-wide
|
|
412
|
+
// max-blocks-per-checkpoint config value directly to size expected per-slot message rates; these are
|
|
413
|
+
// peer-rate thresholds, not consensus deadlines, so they need no proposer operational budgets.
|
|
369
414
|
const l1Constants = epochCache.getL1Constants();
|
|
370
415
|
const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
|
|
371
416
|
slotDurationMs: l1Constants.slotDuration * 1000,
|
|
372
|
-
ethereumSlotDuration: l1Constants.ethereumSlotDuration,
|
|
373
417
|
heartbeatIntervalMs: config.gossipsubInterval,
|
|
374
418
|
targetCommitteeSize: l1Constants.targetCommitteeSize,
|
|
375
|
-
|
|
376
|
-
l1PublishingTime: config.l1PublishingTime,
|
|
377
|
-
p2pPropagationTime: config.attestationPropagationTime,
|
|
419
|
+
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint ?? DEFAULT_MAX_BLOCKS_PER_CHECKPOINT,
|
|
378
420
|
expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot,
|
|
379
421
|
});
|
|
380
422
|
|
|
423
|
+
// Restrict gossipsub to exactly the topics we subscribe to. Without this, an arbitrary-topic
|
|
424
|
+
// message is transformed, msg-id'd and inserted into the seenCache before the subscription check,
|
|
425
|
+
// so a crafted topic colliding on msg id can suppress a real message as a duplicate.
|
|
426
|
+
const allowedTopics = getTopicsForConfig(config.disableTransactions).map(topic =>
|
|
427
|
+
createTopicString(topic, protocolVersion),
|
|
428
|
+
);
|
|
429
|
+
|
|
381
430
|
const node = await createLibp2p({
|
|
382
431
|
start: false,
|
|
383
432
|
peerId,
|
|
@@ -409,7 +458,8 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
409
458
|
],
|
|
410
459
|
datastore,
|
|
411
460
|
peerDiscovery,
|
|
412
|
-
|
|
461
|
+
// Pin the yamux frame size: MAX_REQRESP_REQUEST_SIZE_BYTES relies on a reqresp request fitting in one frame.
|
|
462
|
+
streamMuxers: [yamux({ maxMessageSize: YAMUX_MAX_MESSAGE_SIZE_BYTES }), mplex()],
|
|
413
463
|
connectionEncryption: [noise()],
|
|
414
464
|
connectionManager: {
|
|
415
465
|
minConnections: 0, // Disable libp2p peer dialing, we do it manually
|
|
@@ -423,7 +473,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
423
473
|
},
|
|
424
474
|
connectionGater: {
|
|
425
475
|
denyInboundConnection: (maConn: MultiaddrConnection) => {
|
|
426
|
-
const allowed = peerManager.
|
|
476
|
+
const allowed = peerManager.isAddressAllowedToConnect(maConn.remoteAddr.nodeAddress().address);
|
|
427
477
|
if (allowed) {
|
|
428
478
|
return false;
|
|
429
479
|
}
|
|
@@ -434,7 +484,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
434
484
|
denyInboundEncryptedConnection: (peerId: PeerId, _maConn: MultiaddrConnection) => {
|
|
435
485
|
//NOTE: it is not necessary to check address here because this was already done by
|
|
436
486
|
// denyInboundConnection
|
|
437
|
-
const allowed = peerManager.
|
|
487
|
+
const allowed = peerManager.isPeerAllowedToConnect(peerId);
|
|
438
488
|
if (allowed) {
|
|
439
489
|
return false;
|
|
440
490
|
}
|
|
@@ -462,9 +512,13 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
462
512
|
mcacheLength: config.gossipsubMcacheLength,
|
|
463
513
|
mcacheGossip: config.gossipsubMcacheGossip,
|
|
464
514
|
seenTTL: config.gossipsubSeenTTL,
|
|
515
|
+
allowedTopics,
|
|
516
|
+
// No fastMsgIdFn: the fast-path dedup cache keys on a non-cryptographic 64-bit hash of the
|
|
517
|
+
// raw data only (no topic), so a collision — accidental or engineered via a weak seed — drops
|
|
518
|
+
// a different message with no fallback to the full id. Dedup instead rests solely on the
|
|
519
|
+
// cryptographic, topic-framed msgIdFn below.
|
|
465
520
|
msgIdFn: getMsgIdFn,
|
|
466
521
|
msgIdToStrFn: msgIdToStrFn,
|
|
467
|
-
fastMsgIdFn: fastMsgIdFn,
|
|
468
522
|
dataTransform: new SnappyTransform(),
|
|
469
523
|
metricsRegister: otelMetricsAdapter,
|
|
470
524
|
metricsTopicStrToLabel: metricsTopicStrToLabels(protocolVersion),
|
|
@@ -732,6 +786,13 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
732
786
|
this.duplicateProposalCallback = callback;
|
|
733
787
|
}
|
|
734
788
|
|
|
789
|
+
/**
|
|
790
|
+
* Registers a callback to be invoked when an oversized block proposal is stored as slashing evidence.
|
|
791
|
+
*/
|
|
792
|
+
public registerOversizedProposalCallback(callback: P2POversizedProposalCallback): void {
|
|
793
|
+
this.oversizedProposalCallback = callback;
|
|
794
|
+
}
|
|
795
|
+
|
|
735
796
|
/**
|
|
736
797
|
* Registers a callback to be invoked when a duplicate attestation is detected.
|
|
737
798
|
* A validator signing attestations for different proposals at the same slot.
|
|
@@ -1213,16 +1274,17 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1213
1274
|
const {
|
|
1214
1275
|
result,
|
|
1215
1276
|
obj: block,
|
|
1216
|
-
metadata: { isEquivocated } = {},
|
|
1217
|
-
} = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean }>(
|
|
1277
|
+
metadata: { isEquivocated, isOversized } = {},
|
|
1278
|
+
} = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean; isOversized: boolean }>(
|
|
1218
1279
|
() => this.validateAndStoreBlockProposal(source, BlockProposal.fromBuffer(payloadData)),
|
|
1219
1280
|
msgId,
|
|
1220
1281
|
source,
|
|
1221
1282
|
TopicType.block_proposal,
|
|
1222
1283
|
);
|
|
1223
1284
|
|
|
1224
|
-
// If not accepted or
|
|
1225
|
-
|
|
1285
|
+
// If not accepted, equivocated, or oversized, return. Oversized proposals are re-broadcast as
|
|
1286
|
+
// slashing evidence but never attested or processed.
|
|
1287
|
+
if (result !== TopicValidatorResult.Accept || !block || isEquivocated || isOversized) {
|
|
1226
1288
|
return;
|
|
1227
1289
|
}
|
|
1228
1290
|
|
|
@@ -1237,7 +1299,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1237
1299
|
protected async validateAndStoreBlockProposal(
|
|
1238
1300
|
peerId: PeerId,
|
|
1239
1301
|
block: BlockProposal,
|
|
1240
|
-
): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean }>> {
|
|
1302
|
+
): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean; isOversized: boolean }>> {
|
|
1241
1303
|
const validationResult = await this.blockProposalValidator.validate(block);
|
|
1242
1304
|
|
|
1243
1305
|
if (validationResult.result === 'reject') {
|
|
@@ -1252,6 +1314,12 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1252
1314
|
// Try to add the proposal: this handles existence check, cap check, and adding in one call
|
|
1253
1315
|
const { added, alreadyExists, count } = await this.mempools.attestationPool.tryAddBlockProposal(block);
|
|
1254
1316
|
const isEquivocated = count !== undefined && count > 1;
|
|
1317
|
+
// An oversized proposal (index at or beyond the consensus per-checkpoint limit) is structurally valid
|
|
1318
|
+
// proposer misbehavior: it is stored and re-broadcast as slashing evidence but never processed or
|
|
1319
|
+
// attested to. No-ops when maxBlocksPerCheckpoint is unset (local/test).
|
|
1320
|
+
const isOversized =
|
|
1321
|
+
this.config.maxBlocksPerCheckpoint !== undefined &&
|
|
1322
|
+
block.indexWithinCheckpoint >= this.config.maxBlocksPerCheckpoint;
|
|
1255
1323
|
|
|
1256
1324
|
// Duplicate proposal received, no need to re-broadcast
|
|
1257
1325
|
if (alreadyExists) {
|
|
@@ -1261,7 +1329,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1261
1329
|
proposer: block.getSender()?.toString(),
|
|
1262
1330
|
source: peerId.toString(),
|
|
1263
1331
|
});
|
|
1264
|
-
return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated } };
|
|
1332
|
+
return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated, isOversized } };
|
|
1265
1333
|
}
|
|
1266
1334
|
|
|
1267
1335
|
// Too many blocks received for this slot and index, penalize peer and do not re-broadcast
|
|
@@ -1275,11 +1343,27 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1275
1343
|
});
|
|
1276
1344
|
return {
|
|
1277
1345
|
result: TopicValidatorResult.Reject,
|
|
1278
|
-
metadata: { isEquivocated },
|
|
1346
|
+
metadata: { isEquivocated, isOversized },
|
|
1279
1347
|
severity: PeerErrorSeverity.HighToleranceError,
|
|
1280
1348
|
};
|
|
1281
1349
|
}
|
|
1282
1350
|
|
|
1351
|
+
// The proposal was stored: if oversized, invoke the oversized callback so the proposer can be
|
|
1352
|
+
// slashed. Fired alongside (not instead of) equivocation detection below.
|
|
1353
|
+
if (isOversized) {
|
|
1354
|
+
const proposer = block.getSender();
|
|
1355
|
+
if (proposer) {
|
|
1356
|
+
this.logger.warn(`Detected oversized block proposal at slot ${block.slotNumber}`, {
|
|
1357
|
+
...block.toBlockInfo(),
|
|
1358
|
+
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1359
|
+
maxBlocksPerCheckpoint: this.config.maxBlocksPerCheckpoint,
|
|
1360
|
+
source: peerId.toString(),
|
|
1361
|
+
proposer: proposer.toString(),
|
|
1362
|
+
});
|
|
1363
|
+
this.oversizedProposalCallback?.({ slot: block.slotNumber, proposer });
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1283
1367
|
// If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
|
|
1284
1368
|
// and do re-broadcast it so other nodes in the network know to slash the proposer
|
|
1285
1369
|
if (isEquivocated) {
|
|
@@ -1293,11 +1377,11 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1293
1377
|
if (proposer && count === 2) {
|
|
1294
1378
|
this.duplicateProposalCallback?.({ slot: block.slotNumber, proposer, type: 'block' });
|
|
1295
1379
|
}
|
|
1296
|
-
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated } };
|
|
1380
|
+
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated, isOversized } };
|
|
1297
1381
|
}
|
|
1298
1382
|
|
|
1299
1383
|
// Otherwise, we're good to go!
|
|
1300
|
-
return { result: TopicValidatorResult.Accept, obj: block };
|
|
1384
|
+
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated: false, isOversized } };
|
|
1301
1385
|
}
|
|
1302
1386
|
|
|
1303
1387
|
// REFACTOR(palla): This method should be moved to the p2p_client or to a separate component,
|
|
@@ -1318,11 +1402,29 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1318
1402
|
// Mark the txs in this proposal as protected
|
|
1319
1403
|
await this.mempools.txPool.protectTxs(block.txHashes, block.blockHeader);
|
|
1320
1404
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1405
|
+
if (!(await this.tryBlockReceivedCallback(block, sender))) {
|
|
1406
|
+
// Release the protections this proposal created so its txs return to pending. Only entries still
|
|
1407
|
+
// keyed to this slot are cleared, so a tx referenced by a live proposal at another slot stays protected.
|
|
1408
|
+
await this.mempools.txPool.unprotectTxs(block.txHashes, slot);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Runs the block received callback to validate a proposal, and returns whether it was accepted.
|
|
1414
|
+
* A callback that throws is reported as a rejection: the proposal is no more usable than one explicitly
|
|
1415
|
+
* rejected, and letting the error escape would leave the proposal's tx protections in place.
|
|
1416
|
+
* Note: Validators do NOT attest to individual blocks, only to checkpoint proposals.
|
|
1417
|
+
*/
|
|
1418
|
+
private async tryBlockReceivedCallback(block: BlockProposal, sender: PeerId): Promise<boolean> {
|
|
1419
|
+
try {
|
|
1420
|
+
const isValid = await this.blockReceivedCallback(block, sender);
|
|
1421
|
+
if (!isValid) {
|
|
1422
|
+
this.logger.info(`Block proposal validation failed for block ${block.blockNumber}`, block.toBlockInfo());
|
|
1423
|
+
}
|
|
1424
|
+
return isValid;
|
|
1425
|
+
} catch (err) {
|
|
1426
|
+
this.logger.error(`Error validating block proposal for block ${block.blockNumber}`, err, block.toBlockInfo());
|
|
1427
|
+
return false;
|
|
1326
1428
|
}
|
|
1327
1429
|
}
|
|
1328
1430
|
|
|
@@ -1334,17 +1436,21 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1334
1436
|
const {
|
|
1335
1437
|
result,
|
|
1336
1438
|
obj: checkpoint,
|
|
1337
|
-
metadata: { isEquivocated, processBlock } = {},
|
|
1338
|
-
} = await this.validateReceivedMessage<
|
|
1439
|
+
metadata: { isEquivocated, processBlock, isOversized } = {},
|
|
1440
|
+
} = await this.validateReceivedMessage<
|
|
1441
|
+
CheckpointProposal,
|
|
1442
|
+
{ isEquivocated: boolean; processBlock: boolean; isOversized: boolean }
|
|
1443
|
+
>(
|
|
1339
1444
|
() => this.validateAndStoreCheckpointProposal(source, CheckpointProposal.fromBuffer(payloadData)),
|
|
1340
1445
|
msgId,
|
|
1341
1446
|
source,
|
|
1342
1447
|
TopicType.checkpoint_proposal,
|
|
1343
1448
|
);
|
|
1344
1449
|
|
|
1345
|
-
//
|
|
1450
|
+
// An oversized checkpoint is re-broadcast as slashing evidence but never attested or processed.
|
|
1451
|
+
// Process checkpoint proposal if valid and neither equivocated nor oversized.
|
|
1346
1452
|
const processCheckpointFn = () =>
|
|
1347
|
-
result === TopicValidatorResult.Accept && checkpoint && !isEquivocated
|
|
1453
|
+
result === TopicValidatorResult.Accept && checkpoint && !isEquivocated && !isOversized
|
|
1348
1454
|
? this.processValidCheckpointProposal(checkpoint.toCore(), source)
|
|
1349
1455
|
: Promise.resolve();
|
|
1350
1456
|
|
|
@@ -1381,7 +1487,12 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1381
1487
|
protected async validateAndStoreCheckpointProposal(
|
|
1382
1488
|
peerId: PeerId,
|
|
1383
1489
|
checkpoint: CheckpointProposal,
|
|
1384
|
-
): Promise<
|
|
1490
|
+
): Promise<
|
|
1491
|
+
ReceivedMessageValidationResult<
|
|
1492
|
+
CheckpointProposal,
|
|
1493
|
+
{ isEquivocated: boolean; processBlock: boolean; isOversized: boolean }
|
|
1494
|
+
>
|
|
1495
|
+
> {
|
|
1385
1496
|
const validationResult = await this.checkpointProposalValidator.validate(checkpoint);
|
|
1386
1497
|
|
|
1387
1498
|
if (validationResult.result === 'reject') {
|
|
@@ -1396,13 +1507,16 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1396
1507
|
// Extract and try to add the block proposal first if present
|
|
1397
1508
|
const blockProposal = checkpoint.getBlockProposal();
|
|
1398
1509
|
let processBlock = false;
|
|
1510
|
+
let isOversized = false;
|
|
1399
1511
|
if (blockProposal) {
|
|
1400
1512
|
this.logger.debug(`Validating block proposal from propagated checkpoint`, {
|
|
1401
1513
|
[Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
|
|
1402
1514
|
[Attributes.P2P_ID]: peerId.toString(),
|
|
1403
1515
|
});
|
|
1404
1516
|
const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal);
|
|
1405
|
-
const { obj, metadata: { isEquivocated } = {} } = blockProposalResult;
|
|
1517
|
+
const { obj, metadata: { isEquivocated, isOversized: blockIsOversized } = {} } = blockProposalResult;
|
|
1518
|
+
isOversized = blockIsOversized ?? false;
|
|
1519
|
+
|
|
1406
1520
|
if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) {
|
|
1407
1521
|
this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
|
|
1408
1522
|
[Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
|
|
@@ -1415,7 +1529,8 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1415
1529
|
severity:
|
|
1416
1530
|
'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError,
|
|
1417
1531
|
};
|
|
1418
|
-
} else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated) {
|
|
1532
|
+
} else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated && !isOversized) {
|
|
1533
|
+
// An oversized terminal block is re-broadcast as slashing evidence but never processed.
|
|
1419
1534
|
processBlock = true;
|
|
1420
1535
|
}
|
|
1421
1536
|
}
|
|
@@ -1435,11 +1550,11 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1435
1550
|
return {
|
|
1436
1551
|
result: TopicValidatorResult.Ignore,
|
|
1437
1552
|
obj: checkpoint,
|
|
1438
|
-
metadata: { isEquivocated, processBlock },
|
|
1553
|
+
metadata: { isEquivocated, processBlock, isOversized },
|
|
1439
1554
|
};
|
|
1440
1555
|
}
|
|
1441
1556
|
|
|
1442
|
-
// Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
|
|
1557
|
+
// Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast.
|
|
1443
1558
|
// Note: We still return the checkpoint obj so the lastBlock can be processed if valid
|
|
1444
1559
|
if (!added) {
|
|
1445
1560
|
this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
|
|
@@ -1450,7 +1565,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1450
1565
|
return {
|
|
1451
1566
|
result: TopicValidatorResult.Reject,
|
|
1452
1567
|
obj: checkpoint,
|
|
1453
|
-
metadata: { isEquivocated, processBlock },
|
|
1568
|
+
metadata: { isEquivocated, processBlock, isOversized },
|
|
1454
1569
|
severity: PeerErrorSeverity.HighToleranceError,
|
|
1455
1570
|
};
|
|
1456
1571
|
}
|
|
@@ -1471,12 +1586,16 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1471
1586
|
return {
|
|
1472
1587
|
result: TopicValidatorResult.Accept,
|
|
1473
1588
|
obj: checkpoint,
|
|
1474
|
-
metadata: { isEquivocated, processBlock },
|
|
1589
|
+
metadata: { isEquivocated, processBlock, isOversized },
|
|
1475
1590
|
};
|
|
1476
1591
|
}
|
|
1477
1592
|
|
|
1478
1593
|
// Otherwise, we're good to go!
|
|
1479
|
-
return {
|
|
1594
|
+
return {
|
|
1595
|
+
result: TopicValidatorResult.Accept,
|
|
1596
|
+
obj: checkpoint,
|
|
1597
|
+
metadata: { processBlock, isEquivocated, isOversized },
|
|
1598
|
+
};
|
|
1480
1599
|
}
|
|
1481
1600
|
|
|
1482
1601
|
/**
|
|
@@ -1540,83 +1659,71 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1540
1659
|
peerId: PeerId,
|
|
1541
1660
|
): Promise<boolean> {
|
|
1542
1661
|
try {
|
|
1543
|
-
//
|
|
1544
|
-
|
|
1545
|
-
// attestation pool and archiver, but matched the requested hashes against its tx pool and
|
|
1546
|
-
// shipped what it found. This is legitimate behaviour, not misbehaviour — we just can't verify
|
|
1547
|
-
// membership/order without the block, so we drop the response without penalising the peer.
|
|
1548
|
-
if (response.archiveRoot.isZero()) {
|
|
1549
|
-
this.logger.debug(`Peer ${peerId.toString()} signalled missing block with Fr.zero archive root`);
|
|
1550
|
-
return false;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
if (!response.archiveRoot.equals(request.archiveRoot)) {
|
|
1554
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1555
|
-
throw new ValidationError(
|
|
1556
|
-
`Received block txs for unexpected archive root: expected ${request.archiveRoot.toString()}, got ${response.archiveRoot.toString()}`,
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
if (response.txIndices.getLength() !== request.txIndices.getLength()) {
|
|
1561
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1562
|
-
throw new ValidationError(
|
|
1563
|
-
`Received block txs with mismatched bitvector length: expected ${request.txIndices.getLength()}, got ${response.txIndices.getLength()}`,
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
// Check no duplicates and not exceeding returnable count
|
|
1568
|
-
const requestedIndices = new Set(request.txIndices.getTrueIndices());
|
|
1569
|
-
const availableIndices = new Set(response.txIndices.getTrueIndices());
|
|
1570
|
-
const maxReturnable = [...requestedIndices].filter(i => availableIndices.has(i)).length;
|
|
1571
|
-
|
|
1572
|
-
const returnedHashes = await Promise.all(response.txs.map(tx => tx.getTxHash().toString()));
|
|
1662
|
+
// Check for duplicates txs (or hashes) in the response.
|
|
1663
|
+
const returnedHashes = response.txs.map(tx => tx.getTxHash().toString());
|
|
1573
1664
|
const uniqueReturned = new Set(returnedHashes.map(h => h.toString()));
|
|
1574
1665
|
if (uniqueReturned.size !== returnedHashes.length) {
|
|
1575
1666
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1576
1667
|
throw new ValidationError(`Received duplicate txs in block txs response`);
|
|
1577
1668
|
}
|
|
1578
|
-
if (response.txs.length > maxReturnable) {
|
|
1579
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1580
|
-
throw new ValidationError(
|
|
1581
|
-
`Received more txs (${response.txs.length}) than requested-and-available (${maxReturnable})`,
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
1669
|
|
|
1585
|
-
//
|
|
1586
|
-
// block. Prefer the block proposal (held while a block is in flight), but fall back to the
|
|
1587
|
-
// archiver for blocks we only know as mined — e.g. a prover collecting txs to prove a block it
|
|
1588
|
-
// never received a proposal for. This mirrors the responder side (reqRespBlockTxsHandler),
|
|
1589
|
-
// which serves from proposal-or-archiver.
|
|
1670
|
+
// We get the block tx hashes from the proposal or the archiver.
|
|
1590
1671
|
const proposal = await this.mempools.attestationPool.getBlockProposalByArchive(request.archiveRoot.toString());
|
|
1591
1672
|
const blockTxHashes =
|
|
1592
1673
|
proposal?.txHashes ??
|
|
1593
1674
|
(await this.archiver.getBlock({ archive: request.archiveRoot }))?.body.txEffects.map(e => e.txHash);
|
|
1594
1675
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
// Enforce subset membership and preserve increasing order by index.
|
|
1600
|
-
const hashToIndexInBlock = new Map<string, number>(
|
|
1601
|
-
blockTxHashes.map((h, i) => [h.toString(), i] as [string, number]),
|
|
1602
|
-
);
|
|
1603
|
-
const allowedIndexSet = new Set(intersectIdx);
|
|
1604
|
-
const indices = returnedHashes.map(h => hashToIndexInBlock.get(h));
|
|
1605
|
-
const allAllowed = indices.every(idx => idx !== undefined && allowedIndexSet.has(idx));
|
|
1606
|
-
const strictlyIncreasing = indices.every((idx, i) => (i === 0 ? idx !== undefined : idx! > indices[i - 1]!));
|
|
1607
|
-
if (!allAllowed || !strictlyIncreasing) {
|
|
1608
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1609
|
-
throw new ValidationError('Returned txs do not match expected subset/order for requested indices');
|
|
1610
|
-
}
|
|
1611
|
-
} else {
|
|
1676
|
+
// If we don't have the block, we can't verify membership/order of the returned txs.
|
|
1677
|
+
if (!blockTxHashes) {
|
|
1678
|
+
// This shouldn't happen, since we asked for the block (proposal).
|
|
1612
1679
|
// Neither a local proposal nor an archived block: we cannot verify membership/order of the
|
|
1613
1680
|
// returned txs. This is a local-state gap, not a peer fault, so we do not penalize.
|
|
1614
1681
|
this.logger.warn(
|
|
1615
1682
|
`Block ${request.archiveRoot.toString()} not found in attestation pool or archiver; cannot validate membership/order of returned txs`,
|
|
1616
1683
|
);
|
|
1684
|
+
// NOTE: We mark the response as invalid, given the limitations of a true/false return value.
|
|
1617
1685
|
return false;
|
|
1618
1686
|
}
|
|
1619
1687
|
|
|
1688
|
+
// Verify that the returned tx hashes are a subset of (tx hashes by index) U (explicitly requested tx hashes).
|
|
1689
|
+
const uniqueRequestedHashes = new Set([
|
|
1690
|
+
// tx hashes requested by index.
|
|
1691
|
+
...request.txIndices.getTrueIndices().map(i => blockTxHashes[i].toString()),
|
|
1692
|
+
// explicitly requested tx hashes.
|
|
1693
|
+
...request.txHashes.map(h => h.toString()),
|
|
1694
|
+
]);
|
|
1695
|
+
if (!returnedHashes.every(h => uniqueRequestedHashes.has(h))) {
|
|
1696
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1697
|
+
throw new ValidationError(
|
|
1698
|
+
'Returned txs should be a subset of (tx hashes by index) U (explicitly requested tx hashes)',
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// Tx indices information is optional, but if present, we need to verify a few things.
|
|
1703
|
+
if (!response.peerHasBlock()) {
|
|
1704
|
+
this.logger.debug(`Peer ${peerId.toString()} signalled missing block`);
|
|
1705
|
+
return true;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// If the response has a non-empty bitvector, it needs to be consistent with the number of txs in the block.
|
|
1709
|
+
if (response.txIndices.getLength() !== blockTxHashes.length) {
|
|
1710
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1711
|
+
throw new ValidationError(
|
|
1712
|
+
`Received block txs with mismatched bitvector length: expected ${blockTxHashes.length}, got ${response.txIndices.getLength()}`,
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// For every index we requested that the peer claims to have (per its bitvector), the
|
|
1717
|
+
// corresponding tx must be present in the response. A peer advertising availability but
|
|
1718
|
+
// withholding the tx is inconsistent.
|
|
1719
|
+
const returnedHashSet = new Set(returnedHashes);
|
|
1720
|
+
const expectedIndices = request.txIndices.getTrueIndices().filter(i => response.txIndices.isSet(i));
|
|
1721
|
+
const missingExpected = expectedIndices.some(i => !returnedHashSet.has(blockTxHashes[i].toString()));
|
|
1722
|
+
if (missingExpected) {
|
|
1723
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1724
|
+
throw new ValidationError('Peer advertised requested txs via indices but did not return them');
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1620
1727
|
return true;
|
|
1621
1728
|
} catch (e: any) {
|
|
1622
1729
|
if (e instanceof ValidationError) {
|
|
@@ -1660,13 +1767,15 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1660
1767
|
);
|
|
1661
1768
|
|
|
1662
1769
|
const results = await Promise.all(
|
|
1663
|
-
txs.map(async tx => {
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1770
|
+
txs.map(async tx => ({ txHash: tx.getTxHash(), result: await validator.validateTx(tx) })),
|
|
1771
|
+
);
|
|
1772
|
+
|
|
1773
|
+
const invalidTxs = results.flatMap(({ txHash, result }) =>
|
|
1774
|
+
result.result === 'invalid' ? [{ txHash, reasons: result.reason }] : [],
|
|
1667
1775
|
);
|
|
1668
|
-
|
|
1669
|
-
|
|
1776
|
+
|
|
1777
|
+
if (invalidTxs.length > 0) {
|
|
1778
|
+
throw new InvalidBlockProposalTxsError(invalidTxs);
|
|
1670
1779
|
}
|
|
1671
1780
|
}
|
|
1672
1781
|
|
|
@@ -1682,6 +1791,7 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1682
1791
|
];
|
|
1683
1792
|
const blockNumber = BlockNumber(currentBlockNumber + 1);
|
|
1684
1793
|
const l1Constants = await this.archiver.getL1Constants();
|
|
1794
|
+
const networkTxGasLimits = getNetworkTxGasLimits(this.config, l1Constants);
|
|
1685
1795
|
|
|
1686
1796
|
return createFirstStageTxValidationsForGossipedTransactions(
|
|
1687
1797
|
nextSlotTimestamp,
|
|
@@ -1696,9 +1806,8 @@ export class LibP2PService extends WithTracer implements P2PService {
|
|
|
1696
1806
|
allowedInSetup,
|
|
1697
1807
|
this.logger.getBindings(),
|
|
1698
1808
|
{
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
maxBlockDAGas: this.config.validateMaxDABlockGas,
|
|
1809
|
+
maxTxL2Gas: networkTxGasLimits.l2Gas,
|
|
1810
|
+
maxTxDAGas: networkTxGasLimits.daGas,
|
|
1702
1811
|
},
|
|
1703
1812
|
);
|
|
1704
1813
|
}
|