@lodestar/beacon-node 1.45.0-dev.c6b2b7f1ba → 1.45.0-dev.d16fa2b13a
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/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +3 -2
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/index.d.ts +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +6 -43
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +48 -5
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +13 -3
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +48 -4
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +2 -2
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +2 -2
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/proof/index.d.ts +1 -1
- package/lib/api/impl/proof/index.d.ts.map +1 -1
- package/lib/api/impl/proof/index.js +2 -2
- package/lib/api/impl/proof/index.js.map +1 -1
- package/lib/api/impl/utils.d.ts +23 -0
- package/lib/api/impl/utils.d.ts.map +1 -1
- package/lib/api/impl/utils.js +44 -1
- package/lib/api/impl/utils.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +0 -14
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +67 -62
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +8 -0
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +2 -5
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +13 -5
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/builderCircuitBreaker.d.ts +31 -0
- package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
- package/lib/chain/builderCircuitBreaker.js +59 -0
- package/lib/chain/builderCircuitBreaker.js.map +1 -0
- package/lib/chain/chain.d.ts +3 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +4 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +48 -6
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +25 -4
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/interface.d.ts +3 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +11 -6
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/lightClient/proofs.d.ts.map +1 -1
- package/lib/chain/lightClient/proofs.js +28 -2
- package/lib/chain/lightClient/proofs.js.map +1 -1
- package/lib/chain/lightClient/types.d.ts +3 -1
- package/lib/chain/lightClient/types.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +5 -0
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -0
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +12 -7
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +4 -12
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +0 -1
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +0 -1
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +55 -4
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +30 -0
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +14 -5
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
- package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
- package/lib/execution/builder/http.d.ts +18 -0
- package/lib/execution/builder/http.d.ts.map +1 -1
- package/lib/execution/builder/http.js +20 -11
- package/lib/execution/builder/http.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +6 -0
- package/lib/metrics/metrics/beacon.d.ts.map +1 -1
- package/lib/metrics/metrics/beacon.js +18 -0
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/network/core/metrics.d.ts +65 -0
- package/lib/network/core/metrics.d.ts.map +1 -1
- package/lib/network/core/metrics.js +94 -0
- package/lib/network/core/metrics.js.map +1 -1
- package/lib/network/discv5/utils.d.ts.map +1 -1
- package/lib/network/discv5/utils.js +12 -3
- package/lib/network/discv5/utils.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +2 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +22 -9
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +4 -3
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +831 -226
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +47 -4
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +10 -0
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/score/score.d.ts +3 -1
- package/lib/network/peers/score/score.d.ts.map +1 -1
- package/lib/network/peers/score/score.js +6 -1
- package/lib/network/peers/score/score.js.map +1 -1
- package/lib/network/peers/score/store.d.ts.map +1 -1
- package/lib/network/peers/score/store.js +6 -1
- package/lib/network/peers/score/store.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.js +14 -0
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +18 -9
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +7 -2
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/graffiti.d.ts +15 -0
- package/lib/util/graffiti.d.ts.map +1 -1
- package/lib/util/graffiti.js +55 -0
- package/lib/util/graffiti.js.map +1 -1
- package/package.json +19 -19
- package/src/api/impl/beacon/blocks/index.ts +3 -2
- package/src/api/impl/beacon/index.ts +1 -1
- package/src/api/impl/beacon/pool/index.ts +5 -53
- package/src/api/impl/beacon/state/index.ts +55 -3
- package/src/api/impl/beacon/state/utils.ts +68 -4
- package/src/api/impl/debug/index.ts +3 -2
- package/src/api/impl/lodestar/index.ts +2 -2
- package/src/api/impl/proof/index.ts +2 -2
- package/src/api/impl/utils.ts +51 -1
- package/src/api/impl/validator/index.ts +75 -67
- package/src/api/rest/base.ts +9 -0
- package/src/chain/blocks/importBlock.ts +2 -7
- package/src/chain/blocks/utils/chainSegment.ts +16 -5
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
- package/src/chain/builderCircuitBreaker.ts +84 -0
- package/src/chain/chain.ts +7 -3
- package/src/chain/errors/blockError.ts +44 -7
- package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
- package/src/chain/interface.ts +2 -2
- package/src/chain/lightClient/index.ts +12 -7
- package/src/chain/lightClient/proofs.ts +36 -1
- package/src/chain/lightClient/types.ts +3 -1
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
- package/src/chain/options.ts +6 -0
- package/src/chain/prepareNextSlot.ts +11 -7
- package/src/chain/produceBlock/produceBlockBody.ts +9 -17
- package/src/chain/seenCache/index.ts +0 -1
- package/src/chain/validation/block.ts +72 -4
- package/src/chain/validation/executionPayloadBid.ts +14 -9
- package/src/chain/validation/executionPayloadEnvelope.ts +39 -0
- package/src/chain/validation/proposerPreferences.ts +15 -5
- package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
- package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
- package/src/execution/builder/http.ts +30 -17
- package/src/metrics/metrics/beacon.ts +19 -0
- package/src/network/core/metrics.ts +94 -0
- package/src/network/discv5/utils.ts +11 -3
- package/src/network/gossip/encoding.ts +20 -7
- package/src/network/gossip/gossipsub.ts +9 -3
- package/src/network/gossip/topic.ts +59 -4
- package/src/network/peers/peerManager.ts +11 -0
- package/src/network/peers/score/score.ts +8 -1
- package/src/network/peers/score/store.ts +8 -1
- package/src/network/peers/utils/prioritizePeers.ts +15 -0
- package/src/network/processor/gossipHandlers.ts +18 -8
- package/src/sync/unknownBlock.ts +8 -2
- package/src/util/graffiti.ts +78 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
- package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
- package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
2
|
import {ExecutionStatus} from "@lodestar/fork-choice";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ForkName,
|
|
5
|
+
MAX_ATTESTATIONS_ELECTRA,
|
|
6
|
+
MAX_ATTESTER_SLASHINGS_ELECTRA,
|
|
7
|
+
MAX_BLS_TO_EXECUTION_CHANGES,
|
|
8
|
+
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
9
|
+
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
|
|
10
|
+
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
|
|
11
|
+
MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
12
|
+
MAX_PAYLOAD_ATTESTATIONS,
|
|
13
|
+
MAX_PROPOSER_SLASHINGS,
|
|
14
|
+
MAX_VOLUNTARY_EXITS,
|
|
15
|
+
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
|
|
16
|
+
isForkPostBellatrix,
|
|
17
|
+
isForkPostDeneb,
|
|
18
|
+
isForkPostGloas,
|
|
19
|
+
} from "@lodestar/params";
|
|
4
20
|
import {
|
|
5
21
|
computeEpochAtSlot,
|
|
6
22
|
computeStartSlotAtEpoch,
|
|
@@ -88,8 +104,8 @@ export async function validateGossipBlock(
|
|
|
88
104
|
// 2. The parent is unknown to us, we probably want to download it since it might actually
|
|
89
105
|
// descend from the finalized root.
|
|
90
106
|
// (Non-Lighthouse): Since we prune all blocks non-descendant from finalized checking the `db.block` database won't be useful to guard
|
|
91
|
-
// against known bad fork blocks, so we throw
|
|
92
|
-
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.
|
|
107
|
+
// against known bad fork blocks, so we throw PARENT_BLOCK_UNKNOWN for cases (1) and (2)
|
|
108
|
+
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
// [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation
|
|
@@ -164,6 +180,58 @@ export async function validateGossipBlock(
|
|
|
164
180
|
});
|
|
165
181
|
}
|
|
166
182
|
|
|
183
|
+
// [REJECT] The counts of `block.body.parent_execution_requests` are within
|
|
184
|
+
// their respective limits -- i.e. validate that
|
|
185
|
+
// `len(block.body.parent_execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD`,
|
|
186
|
+
// `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
|
|
187
|
+
// `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
|
|
188
|
+
// `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
|
|
189
|
+
// and
|
|
190
|
+
// `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
|
|
191
|
+
// [REJECT] The counts of the block body operations are within their respective
|
|
192
|
+
// limits -- i.e. validate that
|
|
193
|
+
// `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
|
|
194
|
+
// `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
|
|
195
|
+
// `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
|
|
196
|
+
// `len(block.body.deposits) == 0`,
|
|
197
|
+
// `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
|
|
198
|
+
// `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
|
|
199
|
+
// and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
|
|
200
|
+
const body = (block as gloas.BeaconBlock).body;
|
|
201
|
+
const requests = body.parentExecutionRequests;
|
|
202
|
+
const countLimits: [string, number, number][] = [
|
|
203
|
+
["parentExecutionRequests.deposits", requests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
|
|
204
|
+
["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
|
|
205
|
+
[
|
|
206
|
+
"parentExecutionRequests.consolidations",
|
|
207
|
+
requests.consolidations.length,
|
|
208
|
+
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
|
|
209
|
+
],
|
|
210
|
+
[
|
|
211
|
+
"parentExecutionRequests.builderDeposits",
|
|
212
|
+
requests.builderDeposits.length,
|
|
213
|
+
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
214
|
+
],
|
|
215
|
+
["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
|
|
216
|
+
["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
|
|
217
|
+
["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
|
|
218
|
+
["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
|
|
219
|
+
["deposits", body.deposits.length, 0],
|
|
220
|
+
["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
|
|
221
|
+
["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
|
|
222
|
+
["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
|
|
223
|
+
];
|
|
224
|
+
for (const [name, count, limit] of countLimits) {
|
|
225
|
+
if (count > limit) {
|
|
226
|
+
throw new BlockGossipError(GossipAction.REJECT, {
|
|
227
|
+
code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
|
|
228
|
+
name,
|
|
229
|
+
count,
|
|
230
|
+
limit,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
167
235
|
// TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
|
|
168
236
|
// This requires execution engine integration to verify the parent block hash
|
|
169
237
|
}
|
|
@@ -176,7 +244,7 @@ export async function validateGossipBlock(
|
|
|
176
244
|
const blockState = await chain.regen
|
|
177
245
|
.getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
|
|
178
246
|
.catch(() => {
|
|
179
|
-
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.
|
|
247
|
+
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
180
248
|
});
|
|
181
249
|
|
|
182
250
|
// in forky condition, make sure to populate ShufflingCache with regened state
|
|
@@ -37,9 +37,11 @@ async function validateExecutionPayloadBid(
|
|
|
37
37
|
const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
|
|
38
38
|
const parentBlockHashHex = toRootHex(bid.parentBlockHash);
|
|
39
39
|
|
|
40
|
-
// [IGNORE] `bid.slot` is the current slot or the next slot.
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
41
|
+
if (
|
|
42
|
+
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
43
|
+
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot - 1)
|
|
44
|
+
) {
|
|
43
45
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
44
46
|
code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
|
|
45
47
|
builderIndex: bid.builderIndex,
|
|
@@ -122,17 +124,20 @@ async function validateExecutionPayloadBid(
|
|
|
122
124
|
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
// [REJECT] `bid.builder_index` is
|
|
126
|
-
// `
|
|
127
|
-
|
|
128
|
-
try
|
|
129
|
-
|
|
130
|
-
} catch {
|
|
127
|
+
// [REJECT] `bid.builder_index` is within bounds -- i.e. `bid.builder_index < len(state.builders)`.
|
|
128
|
+
// `state.getBuilder` returns a lazy SSZ `getReadonly` view that is not bounds-checked eagerly; an
|
|
129
|
+
// out-of-range index only throws (`LeafNode has no right node`) on deferred field access (e.g. inside
|
|
130
|
+
// `isActiveBuilder`), escaping a try/catch around `getBuilder`. Check the length explicitly instead.
|
|
131
|
+
if (bid.builderIndex >= state.getBuildersLength()) {
|
|
131
132
|
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
132
133
|
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
133
134
|
builderIndex: bid.builderIndex,
|
|
134
135
|
});
|
|
135
136
|
}
|
|
137
|
+
|
|
138
|
+
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
139
|
+
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
140
|
+
const builder = state.getBuilder(bid.builderIndex);
|
|
136
141
|
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
137
142
|
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
138
143
|
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
2
|
+
import {
|
|
3
|
+
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
4
|
+
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
|
|
5
|
+
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
|
|
6
|
+
MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
7
|
+
MAX_WITHDRAWALS_PER_PAYLOAD,
|
|
8
|
+
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
|
|
9
|
+
} from "@lodestar/params";
|
|
2
10
|
import {
|
|
3
11
|
computeStartSlotAtEpoch,
|
|
4
12
|
getExecutionPayloadEnvelopeSignatureSet,
|
|
@@ -115,6 +123,37 @@ async function validateExecutionPayloadEnvelope(
|
|
|
115
123
|
});
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
// [REJECT] The counts of `execution_requests` are within their respective limits.
|
|
127
|
+
// New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
|
|
128
|
+
// are enforced here in gossip validation.
|
|
129
|
+
const {executionRequests} = envelope;
|
|
130
|
+
const requestCountLimits: [string, number, number][] = [
|
|
131
|
+
["deposits", executionRequests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
|
|
132
|
+
["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
|
|
133
|
+
["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
|
|
134
|
+
["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
|
|
135
|
+
["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
|
|
136
|
+
];
|
|
137
|
+
for (const [name, count, limit] of requestCountLimits) {
|
|
138
|
+
if (count > limit) {
|
|
139
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
140
|
+
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
|
|
141
|
+
name,
|
|
142
|
+
count,
|
|
143
|
+
limit,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// [REJECT] The number of withdrawals is within the limit.
|
|
149
|
+
if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
150
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
151
|
+
code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
|
|
152
|
+
count: payload.withdrawals.length,
|
|
153
|
+
limit: MAX_WITHDRAWALS_PER_PAYLOAD,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
118
157
|
// Get the block state to verify the builder's signature.
|
|
119
158
|
const blockState = await chain.regen
|
|
120
159
|
.getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
|
|
@@ -32,8 +32,9 @@ export async function validateGossipProposerPreferences(
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// [IGNORE] `preferences.proposal_slot` has not already passed.
|
|
36
|
-
|
|
35
|
+
// [IGNORE] `preferences.proposal_slot` has not already passed, i.e. `proposal_slot > current_slot`,
|
|
36
|
+
// allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
37
|
+
const currentSlot = chain.clock.currentSlotWithGossipDisparity;
|
|
37
38
|
if (proposalSlot <= currentSlot) {
|
|
38
39
|
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
39
40
|
code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED,
|
|
@@ -81,7 +82,7 @@ export async function validateGossipProposerPreferences(
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// [IGNORE] First valid message for (dependent_root, proposal_slot, validator_index).
|
|
84
|
-
if (chain.
|
|
85
|
+
if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
|
|
85
86
|
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
86
87
|
code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
|
|
87
88
|
proposalSlot,
|
|
@@ -105,6 +106,15 @@ export async function validateGossipProposerPreferences(
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
//
|
|
109
|
-
chain.
|
|
109
|
+
// Repeated check - deals with race-condition between preferences submissions
|
|
110
|
+
if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
|
|
111
|
+
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
112
|
+
code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
|
|
113
|
+
proposalSlot,
|
|
114
|
+
validatorIndex,
|
|
115
|
+
dependentRoot: dependentRootHex,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
chain.proposerPreferencesPool.add(signedProposerPreferences);
|
|
110
120
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {BeaconConfig} from "@lodestar/config";
|
|
2
|
-
import {DOMAIN_AGGREGATE_AND_PROOF
|
|
2
|
+
import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params";
|
|
3
3
|
import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
|
-
import {Epoch, SignedAggregateAndProof, ValidatorIndex
|
|
4
|
+
import {Epoch, SignedAggregateAndProof, ValidatorIndex} from "@lodestar/types";
|
|
5
5
|
|
|
6
6
|
export function getAggregateAndProofSigningRoot(
|
|
7
7
|
config: BeaconConfig,
|
|
@@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
|
|
|
14
14
|
const slot = computeStartSlotAtEpoch(epoch);
|
|
15
15
|
const fork = config.getForkName(slot);
|
|
16
16
|
const aggregatorDomain = config.getDomainAtFork(fork, DOMAIN_AGGREGATE_AND_PROOF);
|
|
17
|
-
const sszType =
|
|
17
|
+
const sszType = config.getForkTypes(slot).AggregateAndProof;
|
|
18
18
|
return computeSigningRoot(sszType, aggregateAndProof.message, aggregatorDomain);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {ContainerType, VectorCompositeType} from "@chainsafe/ssz";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {DatabaseController, Repository} from "@lodestar/db";
|
|
4
|
+
import {CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS} from "@lodestar/params";
|
|
4
5
|
import {ssz} from "@lodestar/types";
|
|
5
6
|
import {SyncCommitteeWitness} from "../../chain/lightClient/types.js";
|
|
6
7
|
import {Bucket, getBucketNameByValue} from "../buckets.js";
|
|
7
8
|
|
|
8
|
-
// We add a 1-byte prefix where 0 means pre-
|
|
9
|
+
// We add a 1-byte prefix where 0 means pre-Electra, 1 means post-Electra, and 2 means post-Gloas.
|
|
9
10
|
enum PrefixByte {
|
|
10
11
|
PRE_ELECTRA = 0,
|
|
11
12
|
POST_ELECTRA = 1,
|
|
13
|
+
POST_GLOAS = 2,
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const NUM_WITNESS = 4;
|
|
@@ -35,6 +37,35 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
|
|
|
35
37
|
// Overrides for multi-fork
|
|
36
38
|
encodeValue(value: SyncCommitteeWitness): Uint8Array {
|
|
37
39
|
const numWitness = value.witness.length;
|
|
40
|
+
const hasGloasBranches =
|
|
41
|
+
value.currentSyncCommitteeBranch !== undefined || value.nextSyncCommitteeBranch !== undefined;
|
|
42
|
+
|
|
43
|
+
if (hasGloasBranches) {
|
|
44
|
+
if (
|
|
45
|
+
value.currentSyncCommitteeBranch?.length !== CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS ||
|
|
46
|
+
value.nextSyncCommitteeBranch?.length !== NEXT_SYNC_COMMITTEE_DEPTH_GLOAS
|
|
47
|
+
) {
|
|
48
|
+
throw Error(
|
|
49
|
+
`Invalid post-Gloas sync committee branch lengths current=${value.currentSyncCommitteeBranch?.length} next=${value.nextSyncCommitteeBranch?.length}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const type = new ContainerType({
|
|
54
|
+
currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
55
|
+
nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
56
|
+
currentSyncCommitteeRoot: ssz.Root,
|
|
57
|
+
nextSyncCommitteeRoot: ssz.Root,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const valueBytes = type.serialize({
|
|
61
|
+
currentSyncCommitteeBranch: value.currentSyncCommitteeBranch,
|
|
62
|
+
nextSyncCommitteeBranch: value.nextSyncCommitteeBranch,
|
|
63
|
+
currentSyncCommitteeRoot: value.currentSyncCommitteeRoot,
|
|
64
|
+
nextSyncCommitteeRoot: value.nextSyncCommitteeRoot,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return prefixData(PrefixByte.POST_GLOAS, valueBytes);
|
|
68
|
+
}
|
|
38
69
|
|
|
39
70
|
if (numWitness !== NUM_WITNESS && numWitness !== NUM_WITNESS_ELECTRA) {
|
|
40
71
|
throw Error(`Number of witness can only be 4 pre-electra or 5 post-electra numWitness=${numWitness}`);
|
|
@@ -51,19 +82,25 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
|
|
|
51
82
|
// We need to differentiate between post-electra and pre-electra witness
|
|
52
83
|
// such that we can deserialize correctly
|
|
53
84
|
const isPostElectra = numWitness === NUM_WITNESS_ELECTRA;
|
|
54
|
-
|
|
55
|
-
prefixByte[0] = isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA;
|
|
56
|
-
|
|
57
|
-
const prefixedData = new Uint8Array(1 + valueBytes.length);
|
|
58
|
-
prefixedData.set(prefixByte, 0);
|
|
59
|
-
prefixedData.set(valueBytes, 1);
|
|
60
|
-
|
|
61
|
-
return prefixedData;
|
|
85
|
+
return prefixData(isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA, valueBytes);
|
|
62
86
|
}
|
|
63
87
|
|
|
64
88
|
decodeValue(data: Uint8Array): SyncCommitteeWitness {
|
|
65
89
|
// First byte is written
|
|
66
90
|
const prefix = data.subarray(0, 1);
|
|
91
|
+
const isPostGloas = prefix[0] === PrefixByte.POST_GLOAS;
|
|
92
|
+
|
|
93
|
+
if (isPostGloas) {
|
|
94
|
+
const type = new ContainerType({
|
|
95
|
+
currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
96
|
+
nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
97
|
+
currentSyncCommitteeRoot: ssz.Root,
|
|
98
|
+
nextSyncCommitteeRoot: ssz.Root,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {witness: [], ...type.deserialize(data.subarray(1))};
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
const isPostElectra = prefix[0] === PrefixByte.POST_ELECTRA;
|
|
68
105
|
|
|
69
106
|
const type = new ContainerType({
|
|
@@ -75,3 +112,14 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
|
|
|
75
112
|
return type.deserialize(data.subarray(1));
|
|
76
113
|
}
|
|
77
114
|
}
|
|
115
|
+
|
|
116
|
+
function prefixData(prefix: PrefixByte, valueBytes: Uint8Array): Uint8Array {
|
|
117
|
+
const prefixByte = new Uint8Array(1);
|
|
118
|
+
prefixByte[0] = prefix;
|
|
119
|
+
|
|
120
|
+
const prefixedData = new Uint8Array(1 + valueBytes.length);
|
|
121
|
+
prefixedData.set(prefixByte, 0);
|
|
122
|
+
prefixedData.set(valueBytes, 1);
|
|
123
|
+
|
|
124
|
+
return prefixedData;
|
|
125
|
+
}
|
|
@@ -70,6 +70,33 @@ export class NoBidReceived extends Error {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Beacon clients select randomized values from the following ranges when initializing
|
|
75
|
+
* the circuit breaker (so at boot time and once for each unique boot).
|
|
76
|
+
*
|
|
77
|
+
* ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
|
|
78
|
+
* FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
|
|
79
|
+
*
|
|
80
|
+
* The values are randomized per node so builders cannot predict when a given proposer will
|
|
81
|
+
* fall back to local blocks. With fixed thresholds a builder could withhold payloads right up
|
|
82
|
+
* to the limit without ever tripping the breaker.
|
|
83
|
+
*/
|
|
84
|
+
export function getFaultInspectionParams(opts: {faultInspectionWindow?: number; allowedFaults?: number}): {
|
|
85
|
+
faultInspectionWindow: number;
|
|
86
|
+
allowedFaults: number;
|
|
87
|
+
} {
|
|
88
|
+
const faultInspectionWindow = Math.max(
|
|
89
|
+
opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
|
|
90
|
+
SLOTS_PER_EPOCH
|
|
91
|
+
);
|
|
92
|
+
// allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
|
|
93
|
+
const allowedFaults = Math.min(
|
|
94
|
+
opts.allowedFaults ?? Math.floor(faultInspectionWindow / 4),
|
|
95
|
+
Math.floor(faultInspectionWindow / 4)
|
|
96
|
+
);
|
|
97
|
+
return {faultInspectionWindow, allowedFaults};
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
/**
|
|
74
101
|
* Additional duration to account for potential event loop lag which causes
|
|
75
102
|
* builder blocks to be rejected even though the response was sent in time.
|
|
@@ -122,23 +149,9 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
|
|
|
122
149
|
this.registrations = new ValidatorRegistrationCache();
|
|
123
150
|
this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient;
|
|
124
151
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
*
|
|
129
|
-
* ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
|
|
130
|
-
* FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
|
|
131
|
-
*
|
|
132
|
-
*/
|
|
133
|
-
this.faultInspectionWindow = Math.max(
|
|
134
|
-
opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
|
|
135
|
-
SLOTS_PER_EPOCH
|
|
136
|
-
);
|
|
137
|
-
// allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
|
|
138
|
-
this.allowedFaults = Math.min(
|
|
139
|
-
opts.allowedFaults ?? Math.floor(this.faultInspectionWindow / 4),
|
|
140
|
-
Math.floor(this.faultInspectionWindow / 4)
|
|
141
|
-
);
|
|
152
|
+
const {faultInspectionWindow, allowedFaults} = getFaultInspectionParams(opts);
|
|
153
|
+
this.faultInspectionWindow = faultInspectionWindow;
|
|
154
|
+
this.allowedFaults = allowedFaults;
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
updateStatus(status: BuilderStatus): void {
|
|
@@ -138,6 +138,25 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
|
|
|
138
138
|
help: "Count of cached produced results",
|
|
139
139
|
}),
|
|
140
140
|
|
|
141
|
+
builderCircuitBreaker: {
|
|
142
|
+
active: register.gauge({
|
|
143
|
+
name: "beacon_builder_circuit_breaker_active",
|
|
144
|
+
help: "Whether the builder circuit breaker is active (1) causing builder bids to be ignored",
|
|
145
|
+
}),
|
|
146
|
+
faults: register.gauge({
|
|
147
|
+
name: "beacon_builder_circuit_breaker_faults",
|
|
148
|
+
help: "Count of blocks with unrevealed payloads in the fault inspection window",
|
|
149
|
+
}),
|
|
150
|
+
blocksPresent: register.gauge({
|
|
151
|
+
name: "beacon_builder_circuit_breaker_blocks_present",
|
|
152
|
+
help: "Count of blocks present in the fault inspection window",
|
|
153
|
+
}),
|
|
154
|
+
payloadsRevealed: register.gauge({
|
|
155
|
+
name: "beacon_builder_circuit_breaker_payloads_revealed",
|
|
156
|
+
help: "Count of blocks with revealed payloads in the fault inspection window",
|
|
157
|
+
}),
|
|
158
|
+
},
|
|
159
|
+
|
|
141
160
|
blockPayload: {
|
|
142
161
|
payloadAdvancePrepTime: register.histogram({
|
|
143
162
|
name: "beacon_block_payload_prepare_time",
|
|
@@ -127,6 +127,100 @@ export function createNetworkCoreMetrics(register: RegistryMetricCreator) {
|
|
|
127
127
|
name: "lodestar_peer_manager_starved_bool",
|
|
128
128
|
help: "Whether lodestar is starved of data while syncing",
|
|
129
129
|
}),
|
|
130
|
+
/**
|
|
131
|
+
* Core selection/pruning phase of the heartbeat, split out of heartbeatDuration so it
|
|
132
|
+
* can be attributed on its own. Divide by peersEvaluated for time-per-peer.
|
|
133
|
+
*/
|
|
134
|
+
prioritizePeersDuration: register.histogram({
|
|
135
|
+
name: "lodestar_peer_manager_prioritize_peers_seconds",
|
|
136
|
+
help: "prioritizePeers function duration in seconds, the core peer selection/pruning algorithm",
|
|
137
|
+
buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1],
|
|
138
|
+
}),
|
|
139
|
+
/**
|
|
140
|
+
* Score decay + map prune over every tracked peer, split out of heartbeatDuration.
|
|
141
|
+
* This loop iterates the whole score store, so divide by scoreMapSize (not
|
|
142
|
+
* peersEvaluated) for time-per-peer.
|
|
143
|
+
*/
|
|
144
|
+
scoreUpdateDuration: register.histogram({
|
|
145
|
+
name: "lodestar_peer_score_update_seconds",
|
|
146
|
+
help: "Peer score store update (decay + prune over all peers) duration in seconds",
|
|
147
|
+
buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1],
|
|
148
|
+
}),
|
|
149
|
+
/**
|
|
150
|
+
* Denominator for prioritizePeersDuration: it scales with the number of connected
|
|
151
|
+
* healthy peers, so time-per-peer (prioritizePeersDuration / this) is the comparable
|
|
152
|
+
* figure. For scoreUpdateDuration use scoreMapSize instead.
|
|
153
|
+
*/
|
|
154
|
+
peersEvaluated: register.histogram({
|
|
155
|
+
name: "lodestar_peer_manager_peers_evaluated_count",
|
|
156
|
+
help: "Number of connected healthy peers evaluated by prioritizePeers per heartbeat, denominator for prioritize_peers_seconds",
|
|
157
|
+
buckets: [0, 25, 50, 75, 100, 150, 200],
|
|
158
|
+
}),
|
|
159
|
+
/**
|
|
160
|
+
* Every peer the manager intends to disconnect. Covers both the top-of-heartbeat
|
|
161
|
+
* bad-score disconnects ("banned"/"score_too_low") and the prioritization reasons;
|
|
162
|
+
* the older peersRequestedToDisconnect gauge only recorded the latter. Counts intent,
|
|
163
|
+
* not completion (goodbye is fire-and-forget).
|
|
164
|
+
*/
|
|
165
|
+
peersPruned: register.counter<{reason: string}>({
|
|
166
|
+
name: "lodestar_peer_manager_peers_pruned_total",
|
|
167
|
+
help: "Total peers the peer manager intends to disconnect, labeled by reason (incl. bad-score and prioritization reasons)",
|
|
168
|
+
labelNames: ["reason"],
|
|
169
|
+
}),
|
|
170
|
+
/**
|
|
171
|
+
* Actual peer count per active subnet, to check the min-peers-per-subnet invariant.
|
|
172
|
+
* Buckets straddle the target (TARGET_SUBNET_PEERS = 6).
|
|
173
|
+
*/
|
|
174
|
+
peersPerActiveSubnet: register.histogram<{type: SubnetType}>({
|
|
175
|
+
name: "lodestar_peer_manager_peers_per_active_subnet",
|
|
176
|
+
help: "Histogram of connected peer count per active subnet, labeled by subnet type",
|
|
177
|
+
labelNames: ["type"],
|
|
178
|
+
buckets: [0, 2, 4, 6, 8, 12],
|
|
179
|
+
}),
|
|
180
|
+
/**
|
|
181
|
+
* Live outbound ratio, to check the OUTBOUND_PEERS_RATIO (10%) invariant (issue #2215).
|
|
182
|
+
* Denominator is connected healthy peers (banned/disconnected already removed).
|
|
183
|
+
*/
|
|
184
|
+
outboundPeersRatio: register.gauge({
|
|
185
|
+
name: "lodestar_peer_manager_outbound_peers_ratio",
|
|
186
|
+
help: "Ratio of outbound peers to total connected healthy peers, verifies the outbound peers invariant",
|
|
187
|
+
}),
|
|
188
|
+
/**
|
|
189
|
+
* Score state crossings (Healthy/Disconnected/Banned). Sensitive to the decay formula,
|
|
190
|
+
* thresholds, and gossip-score weighting.
|
|
191
|
+
*/
|
|
192
|
+
scoreStateTransitions: register.counter<{from: string; to: string}>({
|
|
193
|
+
name: "lodestar_peer_score_state_transitions_total",
|
|
194
|
+
help: "Total peer score state transitions, labeled by from and to state (Healthy/Disconnected/Banned)",
|
|
195
|
+
labelNames: ["from", "to"],
|
|
196
|
+
}),
|
|
197
|
+
/**
|
|
198
|
+
* Entry count of the score store (not bytes). Reflects the prune-to-MAX_ENTRIES /
|
|
199
|
+
* SCORE_THRESHOLD retention logic. Also the denominator for scoreUpdateDuration
|
|
200
|
+
* time-per-peer (that loop iterates the whole store, not just connected peers).
|
|
201
|
+
*/
|
|
202
|
+
scoreMapSize: register.gauge({
|
|
203
|
+
name: "lodestar_peer_manager_score_map_size",
|
|
204
|
+
help: "Current number of entries in the peer score store",
|
|
205
|
+
}),
|
|
206
|
+
/**
|
|
207
|
+
* Entry count of the connectedPeers map (not bytes). Should track libp2p_peers; a
|
|
208
|
+
* persistent gap indicates the connection leak guarded by leakedConnectionsCount.
|
|
209
|
+
*/
|
|
210
|
+
connectedPeersMapSize: register.gauge({
|
|
211
|
+
name: "lodestar_peer_manager_connected_peers_map_size",
|
|
212
|
+
help: "Current number of entries in the peer manager connectedPeers map",
|
|
213
|
+
}),
|
|
214
|
+
/**
|
|
215
|
+
* assertPeerRelevance outcome per Status exchange: "relevant", an irrelevant reason
|
|
216
|
+
* code, or "error". Fires on every status evaluation (inbound requests and our own
|
|
217
|
+
* requestStatus responses both route through onStatus).
|
|
218
|
+
*/
|
|
219
|
+
relevanceCheck: register.counter<{result: string}>({
|
|
220
|
+
name: "lodestar_peer_relevance_check_total",
|
|
221
|
+
help: "Total peer relevance checks on Status, labeled by result (relevant or irrelevant reason code)",
|
|
222
|
+
labelNames: ["result"],
|
|
223
|
+
}),
|
|
130
224
|
},
|
|
131
225
|
leakedConnectionsCount: register.gauge({
|
|
132
226
|
name: "lodestar_peer_manager_leaked_connections_count",
|
|
@@ -13,9 +13,17 @@ export enum ENRRelevance {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function enrRelevance(enr: ENR, config: BeaconConfig, clock: IClock): ENRRelevance {
|
|
16
|
-
// We are not interested in peers that don't advertise at least one transport (tcp or quic)
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
// We are not interested in peers that don't advertise at least one transport (tcp or quic).
|
|
17
|
+
let multiaddrTCP: ReturnType<ENR["getLocationMultiaddr"]>;
|
|
18
|
+
let multiaddrQUIC: ReturnType<ENR["getLocationMultiaddr"]>;
|
|
19
|
+
// Malformed ENR endpoint fields can make multiaddr conversion throw.
|
|
20
|
+
// Treat endpoints that cannot be converted as unusable.
|
|
21
|
+
try {
|
|
22
|
+
multiaddrTCP = enr.getLocationMultiaddr(ENRKey.tcp);
|
|
23
|
+
multiaddrQUIC = enr.getLocationMultiaddr(ENRKey.quic);
|
|
24
|
+
} catch {
|
|
25
|
+
return ENRRelevance.no_transport;
|
|
26
|
+
}
|
|
19
27
|
if (!multiaddrTCP && !multiaddrQUIC) {
|
|
20
28
|
return ENRRelevance.no_transport;
|
|
21
29
|
}
|
|
@@ -9,7 +9,7 @@ import {ForkName} from "@lodestar/params";
|
|
|
9
9
|
import {intToBytes} from "@lodestar/utils";
|
|
10
10
|
import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js";
|
|
11
11
|
import {Eth2GossipsubMetrics} from "./metrics.js";
|
|
12
|
-
import {GossipTopicCache, getGossipSSZType} from "./topic.js";
|
|
12
|
+
import {GossipTopicCache, getGossipSSZMaxSize, getGossipSSZType} from "./topic.js";
|
|
13
13
|
|
|
14
14
|
// Load WASM
|
|
15
15
|
const xxhash = await xxhashFactory();
|
|
@@ -52,6 +52,13 @@ export function fastMsgIdFn(rpcMsg: RPC.Message): string {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export function msgIdToStrFn(msgId: Uint8Array): string {
|
|
55
|
+
// Spec mandates a 20-byte gossipsub message-id, but control-message IDs from peers
|
|
56
|
+
// can be any length. Reject non-20-byte IDs to avoid a Buffer.set() RangeError on
|
|
57
|
+
// longer IDs and stale shared-buffer bytes on shorter ones.
|
|
58
|
+
// Ref: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.10/specs/phase0/p2p-interface.md?plain=1#L486
|
|
59
|
+
if (msgId.length !== 20) {
|
|
60
|
+
throw Error(`Expect msgId to be 20 bytes, got ${msgId.length}`);
|
|
61
|
+
}
|
|
55
62
|
// this is the same logic to `toHex(msgId)` with better performance
|
|
56
63
|
sharedMsgIdBuf.set(msgId);
|
|
57
64
|
return `0x${sharedMsgIdBuf.toString("hex")}`;
|
|
@@ -91,7 +98,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
|
|
|
91
98
|
export class DataTransformSnappy implements DataTransform {
|
|
92
99
|
constructor(
|
|
93
100
|
private readonly gossipTopicCache: GossipTopicCache,
|
|
94
|
-
private readonly
|
|
101
|
+
private readonly maxPayloadSize: number,
|
|
95
102
|
private readonly metrics: Eth2GossipsubMetrics | null
|
|
96
103
|
) {}
|
|
97
104
|
|
|
@@ -104,17 +111,18 @@ export class DataTransformSnappy implements DataTransform {
|
|
|
104
111
|
inboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
|
|
105
112
|
// check uncompressed data length before we actually decompress
|
|
106
113
|
const uncompressedDataLength = snappyWasm.decompress_len(data);
|
|
107
|
-
if (uncompressedDataLength > this.maxSizePerMessage) {
|
|
108
|
-
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${this.maxSizePerMessage}`);
|
|
109
|
-
}
|
|
110
114
|
|
|
111
115
|
const topic = this.gossipTopicCache.getTopic(topicStr);
|
|
112
116
|
const sszType = getGossipSSZType(topic);
|
|
117
|
+
const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
|
|
113
118
|
this.metrics?.dataTransform.inbound.inc({type: topic.type});
|
|
114
119
|
|
|
115
120
|
if (uncompressedDataLength < sszType.minSize) {
|
|
116
121
|
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
|
|
117
122
|
}
|
|
123
|
+
if (uncompressedDataLength > maxSize) {
|
|
124
|
+
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${maxSize}`);
|
|
125
|
+
}
|
|
118
126
|
if (uncompressedDataLength > sszType.maxSize) {
|
|
119
127
|
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
|
|
120
128
|
}
|
|
@@ -132,9 +140,14 @@ export class DataTransformSnappy implements DataTransform {
|
|
|
132
140
|
*/
|
|
133
141
|
outboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
|
|
134
142
|
const topic = this.gossipTopicCache.getTopic(topicStr);
|
|
143
|
+
const sszType = getGossipSSZType(topic);
|
|
144
|
+
const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
|
|
135
145
|
this.metrics?.dataTransform.outbound.inc({type: topic.type});
|
|
136
|
-
if (data.length >
|
|
137
|
-
throw Error(`ssz_snappy encoded data length ${data.length} > ${
|
|
146
|
+
if (data.length > maxSize) {
|
|
147
|
+
throw Error(`ssz_snappy encoded data length ${data.length} > ${maxSize}`);
|
|
148
|
+
}
|
|
149
|
+
if (data.length > sszType.maxSize) {
|
|
150
|
+
throw Error(`ssz_snappy encoded data length ${data.length} > ${sszType.maxSize}`);
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
// Using Buffer.alloc() instead of Buffer.allocUnsafe() to mitigate high GC pressure observed in some environments
|