@lodestar/beacon-node 1.43.0-dev.b05ea98d04 → 1.43.0-dev.b0e1143d06
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/chain/blocks/importExecutionPayload.d.ts +4 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +12 -1
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +6 -7
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +8 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +19 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +29 -26
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +5 -4
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +15 -1
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +7 -6
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +21 -5
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +749 -2
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +3 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +31 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +5 -0
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/interface.d.ts +1 -1
- package/lib/network/reqresp/interface.js +1 -1
- package/lib/network/reqresp/protocols.d.ts +1 -0
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +5 -0
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
- package/lib/network/reqresp/rateLimit.js +4 -0
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/score.d.ts.map +1 -1
- package/lib/network/reqresp/score.js +1 -0
- package/lib/network/reqresp/score.js.map +1 -1
- package/lib/network/reqresp/types.d.ts +3 -0
- package/lib/network/reqresp/types.d.ts.map +1 -1
- package/lib/network/reqresp/types.js +3 -0
- package/lib/network/reqresp/types.js.map +1 -1
- package/lib/sync/constants.d.ts +5 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +5 -1
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +27 -6
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +149 -26
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +10 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +55 -7
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +54 -5
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/range/utils/peerBalancer.d.ts +2 -1
- package/lib/sync/range/utils/peerBalancer.d.ts.map +1 -1
- package/lib/sync/range/utils/peerBalancer.js +8 -4
- package/lib/sync/range/utils/peerBalancer.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +7 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +127 -12
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +36 -9
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +87 -38
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/rateLimit.d.ts +2 -0
- package/lib/sync/utils/rateLimit.d.ts.map +1 -0
- package/lib/sync/utils/rateLimit.js +15 -0
- package/lib/sync/utils/rateLimit.js.map +1 -0
- package/package.json +16 -16
- package/src/chain/blocks/importExecutionPayload.ts +18 -6
- package/src/chain/blocks/index.ts +5 -7
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -1
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
- package/src/chain/blocks/utils/chainSegment.ts +30 -27
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +7 -4
- package/src/chain/chain.ts +17 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +23 -6
- package/src/metrics/metrics/lodestar.ts +6 -0
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +12 -0
- package/src/network/processor/gossipHandlers.ts +35 -1
- package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
- package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
- package/src/network/reqresp/handlers/index.ts +5 -0
- package/src/network/reqresp/interface.ts +1 -1
- package/src/network/reqresp/protocols.ts +6 -0
- package/src/network/reqresp/rateLimit.ts +4 -0
- package/src/network/reqresp/score.ts +1 -0
- package/src/network/reqresp/types.ts +5 -0
- package/src/sync/constants.ts +5 -1
- package/src/sync/range/batch.ts +188 -30
- package/src/sync/range/chain.ts +72 -13
- package/src/sync/range/range.ts +68 -5
- package/src/sync/range/utils/peerBalancer.ts +9 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +157 -12
- package/src/sync/utils/downloadByRange.ts +153 -39
- package/src/sync/utils/rateLimit.ts +16 -0
|
@@ -17,8 +17,6 @@ export type ChainSegmentResult = {warnings: OrphanedPayloadEnvelope[] | null};
|
|
|
17
17
|
* Assert this chain segment of blocks is linear with slot numbers and hashes,
|
|
18
18
|
* and that the provided envelopes are consistent with their respective blocks.
|
|
19
19
|
*
|
|
20
|
-
* Must be called after verifyBlocksSanityChecks so that parentBlock (from forkchoice)
|
|
21
|
-
* is available to seed the execution hash chain.
|
|
22
20
|
*
|
|
23
21
|
* For each block:
|
|
24
22
|
* - Verifies parent root + slot linearity
|
|
@@ -31,7 +29,7 @@ export function assertLinearChainSegment(
|
|
|
31
29
|
config: ChainForkConfig,
|
|
32
30
|
blocks: IBlockInput[],
|
|
33
31
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
34
|
-
parentBlock: ProtoBlock
|
|
32
|
+
parentBlock: ProtoBlock | null
|
|
35
33
|
): ChainSegmentResult {
|
|
36
34
|
const warnings: OrphanedPayloadEnvelope[] = [];
|
|
37
35
|
|
|
@@ -39,15 +37,16 @@ export function assertLinearChainSegment(
|
|
|
39
37
|
// Starts from the known forkchoice parent's execution hash.
|
|
40
38
|
// - FULL variant (envelope present for slot): advances to envelope.payload.blockHash
|
|
41
39
|
// - EMPTY variant (no envelope for slot): execution hash is unchanged
|
|
42
|
-
|
|
43
|
-
let currentExecHash: string | null = parentBlock.executionPayloadBlockHash;
|
|
40
|
+
let currentExecHash: string | null = parentBlock?.executionPayloadBlockHash ?? null;
|
|
44
41
|
// Checkpoint sync first batch: parent is the anchor PENDING whose executionPayloadBlockHash
|
|
45
42
|
// is the inherited parentBlockHash semantic (= grandparent's payload), not its own payload.
|
|
46
43
|
// If parent's own payload envelope arrives in this batch, advance currentExecHash to that
|
|
47
44
|
// payload's blockHash so the segment validation sees the true EL chain head.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
if (parentBlock !== null) {
|
|
46
|
+
const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
|
|
47
|
+
if (parentPayloadInput?.hasPayloadEnvelope()) {
|
|
48
|
+
currentExecHash = parentPayloadInput.getBlockHashHex();
|
|
49
|
+
}
|
|
51
50
|
}
|
|
52
51
|
// Track the execution hash before the last FULL advancement so we can recover
|
|
53
52
|
// if the next block reveals that envelope was orphaned.
|
|
@@ -76,27 +75,31 @@ export function assertLinearChainSegment(
|
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
if (isGloasBeaconBlock(block.message)
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
if (bidParentHash !==
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
if (isGloasBeaconBlock(block.message)) {
|
|
79
|
+
// Bid check fires only when we have a seeded execution hash. With parentBlock=null the
|
|
80
|
+
// chain is seeded mid-segment by the first FULL envelope.
|
|
81
|
+
if (currentExecHash !== null) {
|
|
82
|
+
// Verify the bid's parentBlockHash matches the tracked execution hash.
|
|
83
|
+
// This ensures the block was built on the correct FULL or EMPTY variant of its parent.
|
|
84
|
+
const bidParentHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
|
|
85
|
+
if (bidParentHash !== currentExecHash) {
|
|
86
|
+
// Maybe the previous slot's FULL envelope was orphaned — try falling back.
|
|
87
|
+
// If even prevExecHash doesn't match, the segment is non-linear.
|
|
88
|
+
if (bidParentHash !== prevExecHash) {
|
|
89
|
+
throw new BlockError(block, {
|
|
90
|
+
code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
|
|
91
|
+
parentRoot: toRootHex(block.message.parentRoot),
|
|
92
|
+
parentBlockHash: bidParentHash,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (lastFullSlot !== null && payloadEnvelopes !== null) {
|
|
96
|
+
const orphanedInput = payloadEnvelopes.get(lastFullSlot);
|
|
97
|
+
if (orphanedInput != null) {
|
|
98
|
+
warnings.push({slot: lastFullSlot, payloadEnvelopeInput: orphanedInput});
|
|
99
|
+
}
|
|
97
100
|
}
|
|
101
|
+
currentExecHash = prevExecHash;
|
|
98
102
|
}
|
|
99
|
-
currentExecHash = prevExecHash;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
const payloadInput = payloadEnvelopes?.get(slot) ?? null;
|
|
@@ -82,16 +82,19 @@ export function verifyExecutionPayloadEnvelope(
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
// should not use state.slot, it does not work for skipped slot checkpoint sync
|
|
86
|
+
const blockSlot = state.latestBlockHeader.slot;
|
|
87
|
+
if (payload.slotNumber !== blockSlot) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Slot mismatch between payload and latest block header payload=${payload.slotNumber} latestBlockHeader=${blockSlot}`
|
|
90
|
+
);
|
|
88
91
|
}
|
|
89
92
|
if (!byteArrayEquals(payload.parentHash, state.latestBlockHash)) {
|
|
90
93
|
throw new Error(
|
|
91
94
|
`Parent hash mismatch between payload and state payload=${toRootHex(payload.parentHash)} state=${toRootHex(state.latestBlockHash)}`
|
|
92
95
|
);
|
|
93
96
|
}
|
|
94
|
-
const expectedTimestamp = computeTimeAtSlot(config,
|
|
97
|
+
const expectedTimestamp = computeTimeAtSlot(config, blockSlot, state.genesisTime);
|
|
95
98
|
if (payload.timestamp !== expectedTimestamp) {
|
|
96
99
|
throw new Error(
|
|
97
100
|
`Timestamp mismatch between payload and state payload=${payload.timestamp} state=${expectedTimestamp}`
|
package/src/chain/chain.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getEffectiveBalancesFromStateBytes,
|
|
24
24
|
isStatePostAltair,
|
|
25
25
|
isStatePostElectra,
|
|
26
|
+
isStatePostGloas,
|
|
26
27
|
} from "@lodestar/state-transition";
|
|
27
28
|
import {
|
|
28
29
|
BeaconBlock,
|
|
@@ -390,6 +391,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
390
391
|
metrics,
|
|
391
392
|
logger
|
|
392
393
|
);
|
|
394
|
+
|
|
393
395
|
const regen = new QueuedStateRegenerator({
|
|
394
396
|
config,
|
|
395
397
|
forkChoice,
|
|
@@ -426,6 +428,21 @@ export class BeaconChain implements IBeaconChain {
|
|
|
426
428
|
logger,
|
|
427
429
|
});
|
|
428
430
|
|
|
431
|
+
const anchorBlockSlot = anchorState.latestBlockHeader.slot;
|
|
432
|
+
if (isStatePostGloas(anchorState) && anchorBlockSlot > 0) {
|
|
433
|
+
const anchorBid = anchorState.latestExecutionPayloadBid;
|
|
434
|
+
this.seenPayloadEnvelopeInputCache.addFromBid({
|
|
435
|
+
blockRootHex: toRootHex(checkpoint.root),
|
|
436
|
+
slot: anchorBlockSlot,
|
|
437
|
+
forkName: anchorState.forkName,
|
|
438
|
+
proposerIndex: anchorState.latestBlockHeader.proposerIndex,
|
|
439
|
+
bid: anchorBid,
|
|
440
|
+
sampledColumns: this.custodyConfig.sampledColumns,
|
|
441
|
+
custodyColumns: this.custodyConfig.custodyColumns,
|
|
442
|
+
timeCreatedSec: Math.floor(Date.now() / 1000),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
429
446
|
this.clock = clock;
|
|
430
447
|
this.regen = regen;
|
|
431
448
|
this.bls = bls;
|
|
@@ -7,7 +7,7 @@ import {Metrics} from "../../metrics/metrics.js";
|
|
|
7
7
|
import {IClock} from "../../util/clock.js";
|
|
8
8
|
import {SerializedCache} from "../../util/serializedCache.js";
|
|
9
9
|
import {isDaOutOfRange} from "../blocks/blockInput/index.js";
|
|
10
|
-
import {CreateFromBlockProps, PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
|
|
10
|
+
import {CreateFromBidProps, CreateFromBlockProps, PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
|
|
11
11
|
import {ChainEvent, ChainEventEmitter} from "../emitter.js";
|
|
12
12
|
|
|
13
13
|
export type {PayloadEnvelopeInputState} from "../blocks/payloadEnvelopeInput/index.js";
|
|
@@ -27,11 +27,7 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
27
27
|
/**
|
|
28
28
|
* Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
|
|
29
29
|
*
|
|
30
|
-
* Created
|
|
31
|
-
* - `prepareNextSlot` calls `pruneBelow(headParentSlot)` every slot once the head we'll build
|
|
32
|
-
* on is known.
|
|
33
|
-
* - `onFinalized` calls `pruneBelow(finalizedSlot)` on every finalization for bulk cleanup.
|
|
34
|
-
*
|
|
30
|
+
* Created whenever we have a block because it needs block bid.
|
|
35
31
|
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
|
|
36
32
|
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
|
|
37
33
|
* transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
|
|
@@ -123,6 +119,27 @@ export class SeenPayloadEnvelopeInput {
|
|
|
123
119
|
return input;
|
|
124
120
|
}
|
|
125
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Used at chain initialization to seed the anchor block's PayloadEnvelopeInput from
|
|
124
|
+
* `state.latestExecutionPayloadBid`.
|
|
125
|
+
*/
|
|
126
|
+
addFromBid(props: Omit<CreateFromBidProps, "daOutOfRange">): PayloadEnvelopeInput {
|
|
127
|
+
const existing = this.payloadInputs.get(props.blockRootHex);
|
|
128
|
+
if (existing !== undefined) {
|
|
129
|
+
return existing;
|
|
130
|
+
}
|
|
131
|
+
const daOutOfRange = isDaOutOfRange(this.config, props.forkName, props.slot, this.clock.currentEpoch);
|
|
132
|
+
const input = PayloadEnvelopeInput.createFromBid({...props, daOutOfRange});
|
|
133
|
+
this.payloadInputs.set(props.blockRootHex, input);
|
|
134
|
+
this.metrics?.seenCache.payloadEnvelopeInput.created.inc();
|
|
135
|
+
this.logger?.verbose("SeenPayloadEnvelopeInput.addFromBid created new entry", {
|
|
136
|
+
slot: input.slot,
|
|
137
|
+
root: props.blockRootHex,
|
|
138
|
+
daOutOfRange,
|
|
139
|
+
});
|
|
140
|
+
return input;
|
|
141
|
+
}
|
|
142
|
+
|
|
126
143
|
get(blockRootHex: RootHex): PayloadEnvelopeInput | undefined {
|
|
127
144
|
return this.payloadInputs.get(blockRootHex);
|
|
128
145
|
}
|
|
@@ -3,6 +3,7 @@ import {NotReorgedReason} from "@lodestar/fork-choice";
|
|
|
3
3
|
import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
|
|
4
4
|
import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
|
|
5
5
|
import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
|
|
6
|
+
import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
6
7
|
import {PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
7
8
|
import {JobQueueItemType} from "../../chain/bls/index.js";
|
|
8
9
|
import {AttestationErrorCode, BlockErrorCode} from "../../chain/errors/index.js";
|
|
@@ -890,6 +891,11 @@ export function createLodestarMetrics(
|
|
|
890
891
|
labelNames: ["source"],
|
|
891
892
|
buckets: [0.5, 1, 2, 4, 6, 12],
|
|
892
893
|
}),
|
|
894
|
+
processPayloadErrors: register.gauge<{error: PayloadErrorCode | "NOT_PAYLOAD_ERROR"}>({
|
|
895
|
+
name: "lodestar_gossip_execution_payload_envelope_process_payload_errors",
|
|
896
|
+
help: "Count of errors, by error type, while processing execution payload envelopes",
|
|
897
|
+
labelNames: ["error"],
|
|
898
|
+
}),
|
|
893
899
|
},
|
|
894
900
|
// recovery in the case of specific blob rows required
|
|
895
901
|
recoverBlobSidecars: {
|
package/src/network/interface.ts
CHANGED
|
@@ -78,6 +78,7 @@ export interface INetwork extends INetworkCorePublic {
|
|
|
78
78
|
// ReqResp
|
|
79
79
|
sendBeaconBlocksByRange(peerId: PeerIdStr, request: phase0.BeaconBlocksByRangeRequest): Promise<SignedBeaconBlock[]>;
|
|
80
80
|
sendBeaconBlocksByRoot(peerId: PeerIdStr, request: BeaconBlocksByRootRequest): Promise<SignedBeaconBlock[]>;
|
|
81
|
+
sendBeaconBlocksByHead(peerId: PeerIdStr, request: fulu.BeaconBlocksByHeadRequest): Promise<SignedBeaconBlock[]>;
|
|
81
82
|
sendBlobSidecarsByRange(peerId: PeerIdStr, request: deneb.BlobSidecarsByRangeRequest): Promise<deneb.BlobSidecar[]>;
|
|
82
83
|
sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise<deneb.BlobSidecar[]>;
|
|
83
84
|
sendDataColumnSidecarsByRange(
|
package/src/network/network.ts
CHANGED
|
@@ -578,6 +578,18 @@ export class Network implements INetwork {
|
|
|
578
578
|
);
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
+
async sendBeaconBlocksByHead(
|
|
582
|
+
peerId: PeerIdStr,
|
|
583
|
+
request: fulu.BeaconBlocksByHeadRequest
|
|
584
|
+
): Promise<SignedBeaconBlock[]> {
|
|
585
|
+
return collectMaxResponseTypedWithBytes(
|
|
586
|
+
this.sendReqRespRequest(peerId, ReqRespMethod.BeaconBlocksByHead, [Version.V1], request),
|
|
587
|
+
Math.min(request.count, this.config.MAX_REQUEST_BLOCKS_DENEB),
|
|
588
|
+
responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByHead],
|
|
589
|
+
this.chain.serializedCache
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
581
593
|
async sendLightClientBootstrap(peerId: PeerIdStr, request: Root): Promise<LightClientBootstrap> {
|
|
582
594
|
return collectExactOneTyped(
|
|
583
595
|
this.sendReqRespRequest(peerId, ReqRespMethod.LightClientBootstrap, [Version.V1], request),
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
IBlockInput,
|
|
36
36
|
isBlockInputColumns,
|
|
37
37
|
} from "../../chain/blocks/blockInput/index.js";
|
|
38
|
+
import {PayloadError, PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
38
39
|
import {PayloadEnvelopeInput, PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
39
40
|
import {BlobSidecarValidation} from "../../chain/blocks/types.js";
|
|
40
41
|
import {ChainEvent} from "../../chain/emitter.js";
|
|
@@ -1148,7 +1149,40 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1148
1149
|
});
|
|
1149
1150
|
|
|
1150
1151
|
chain.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
|
|
1151
|
-
|
|
1152
|
+
// Adjust verbosity based on error type
|
|
1153
|
+
let logLevel: LogLevel;
|
|
1154
|
+
|
|
1155
|
+
if (e instanceof PayloadError) {
|
|
1156
|
+
switch (e.type.code) {
|
|
1157
|
+
// BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
|
|
1158
|
+
// already verified the block is in fork choice
|
|
1159
|
+
case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
|
|
1160
|
+
case PayloadErrorCode.MISS_BLOCK_STATE:
|
|
1161
|
+
case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
|
|
1162
|
+
// Errors might indicate an issue with our node or the connected EL client
|
|
1163
|
+
logLevel = LogLevel.error;
|
|
1164
|
+
break;
|
|
1165
|
+
// INVALID_SIGNATURE should not happen, signature is verified during gossip validation
|
|
1166
|
+
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1167
|
+
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1168
|
+
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1169
|
+
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
|
|
1170
|
+
// Misbehaving peer, but could highlight an issue in another client
|
|
1171
|
+
logLevel = LogLevel.warn;
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
} else {
|
|
1175
|
+
// Any unexpected error
|
|
1176
|
+
logLevel = LogLevel.error;
|
|
1177
|
+
}
|
|
1178
|
+
metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
|
|
1179
|
+
error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
|
|
1180
|
+
});
|
|
1181
|
+
chain.logger[logLevel](
|
|
1182
|
+
"Error processing execution payload from gossip",
|
|
1183
|
+
{slot, peer: peerIdStr, root: blockRootHex},
|
|
1184
|
+
e as Error
|
|
1185
|
+
);
|
|
1152
1186
|
});
|
|
1153
1187
|
},
|
|
1154
1188
|
[GossipType.payload_attestation_message]: async ({
|
|
@@ -286,6 +286,7 @@ export class ReqRespBeaconNode extends ReqResp {
|
|
|
286
286
|
// instead of protocol version. This is not easily fixable with our current architecture.
|
|
287
287
|
// See https://github.com/ChainSafe/lodestar/pull/8168 for more details.
|
|
288
288
|
[protocols.StatusV2(fork, this.config), this.onStatus.bind(this)],
|
|
289
|
+
[protocols.BeaconBlocksByHead(fork, this.config), this.getHandler(ReqRespMethod.BeaconBlocksByHead)],
|
|
289
290
|
[
|
|
290
291
|
protocols.DataColumnSidecarsByRoot(fork, this.config),
|
|
291
292
|
this.getHandler(ReqRespMethod.DataColumnSidecarsByRoot),
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {PeerId} from "@libp2p/interface";
|
|
2
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
+
import {ForkName, GENESIS_EPOCH, GENESIS_SLOT, isForkPostDeneb} from "@lodestar/params";
|
|
4
|
+
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
|
|
5
|
+
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
6
|
+
import {fulu} from "@lodestar/types";
|
|
7
|
+
import {toRootHex} from "@lodestar/utils";
|
|
8
|
+
import {IBeaconChain} from "../../../chain/index.js";
|
|
9
|
+
import {getParentRootFromSignedBeaconBlockSerialized} from "../../../util/sszBytes.js";
|
|
10
|
+
import {prettyPrintPeerId} from "../../util.js";
|
|
11
|
+
|
|
12
|
+
// See https://github.com/ethereum/consensus-specs/pull/5181
|
|
13
|
+
export async function* onBeaconBlocksByHead(
|
|
14
|
+
request: fulu.BeaconBlocksByHeadRequest,
|
|
15
|
+
chain: IBeaconChain,
|
|
16
|
+
peerId: PeerId,
|
|
17
|
+
peerClient: string
|
|
18
|
+
): AsyncIterable<ResponseOutgoing> {
|
|
19
|
+
const currentFork = chain.config.getForkName(chain.clock.currentSlot);
|
|
20
|
+
const {beaconRoot, count} = validateBeaconBlocksByHeadRequest(currentFork, chain.config, request);
|
|
21
|
+
|
|
22
|
+
const requestedRootHex = toRootHex(beaconRoot);
|
|
23
|
+
let blockRootHex = requestedRootHex;
|
|
24
|
+
const minimumRequestEpoch = Math.max(
|
|
25
|
+
GENESIS_EPOCH,
|
|
26
|
+
chain.clock.currentEpoch - chain.config.MIN_EPOCHS_FOR_BLOCK_REQUESTS
|
|
27
|
+
);
|
|
28
|
+
const minimumRequestSlot = computeStartSlotAtEpoch(minimumRequestEpoch);
|
|
29
|
+
|
|
30
|
+
for (let blocksSent = 0; blocksSent < count; blocksSent++) {
|
|
31
|
+
const blockBytes = await chain.getSerializedBlockByRoot(blockRootHex);
|
|
32
|
+
if (!blockBytes) {
|
|
33
|
+
if (blocksSent === 0) {
|
|
34
|
+
throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, `Unknown block root ${requestedRootHex}`);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (blockBytes.slot < minimumRequestSlot) {
|
|
40
|
+
if (blocksSent === 0) {
|
|
41
|
+
chain.logger.verbose("Peer requested unavailable block for BeaconBlocksByHead", {
|
|
42
|
+
peer: prettyPrintPeerId(peerId),
|
|
43
|
+
client: peerClient,
|
|
44
|
+
requestedRoot: requestedRootHex,
|
|
45
|
+
slot: blockBytes.slot,
|
|
46
|
+
minimumRequestSlot,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
yield {
|
|
53
|
+
data: blockBytes.block,
|
|
54
|
+
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(blockBytes.slot)),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (blockBytes.slot === GENESIS_SLOT) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const parentRootHex = getParentRootFromSignedBeaconBlockSerialized(blockBytes.block);
|
|
62
|
+
if (parentRootHex === null) {
|
|
63
|
+
throw new ResponseError(
|
|
64
|
+
RespStatus.SERVER_ERROR,
|
|
65
|
+
`Invalid block bytes for root ${blockRootHex} slot ${blockBytes.slot}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
blockRootHex = parentRootHex;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function validateBeaconBlocksByHeadRequest(
|
|
73
|
+
fork: ForkName,
|
|
74
|
+
config: BeaconConfig,
|
|
75
|
+
request: fulu.BeaconBlocksByHeadRequest
|
|
76
|
+
): fulu.BeaconBlocksByHeadRequest {
|
|
77
|
+
const {beaconRoot} = request;
|
|
78
|
+
let {count} = request;
|
|
79
|
+
|
|
80
|
+
if (count < 1) {
|
|
81
|
+
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const maxRequestBlocks = isForkPostDeneb(fork) ? config.MAX_REQUEST_BLOCKS_DENEB : config.MAX_REQUEST_BLOCKS;
|
|
85
|
+
|
|
86
|
+
if (count > maxRequestBlocks) {
|
|
87
|
+
count = maxRequestBlocks;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {beaconRoot, count};
|
|
91
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ExecutionPayloadEnvelopesByRootRequestType,
|
|
10
10
|
} from "../../../util/types.js";
|
|
11
11
|
import {GetReqRespHandlerFn, ReqRespMethod} from "../types.js";
|
|
12
|
+
import {onBeaconBlocksByHead} from "./beaconBlocksByHead.js";
|
|
12
13
|
import {onBeaconBlocksByRange} from "./beaconBlocksByRange.js";
|
|
13
14
|
import {onBeaconBlocksByRoot} from "./beaconBlocksByRoot.js";
|
|
14
15
|
import {onBlobSidecarsByRange} from "./blobSidecarsByRange.js";
|
|
@@ -47,6 +48,10 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
|
|
|
47
48
|
const body = BeaconBlocksByRootRequestType(fork, chain.config).deserialize(req.data);
|
|
48
49
|
return onBeaconBlocksByRoot(body, chain);
|
|
49
50
|
},
|
|
51
|
+
[ReqRespMethod.BeaconBlocksByHead]: (req, peerId, peerClient) => {
|
|
52
|
+
const body = ssz.fulu.BeaconBlocksByHeadRequest.deserialize(req.data);
|
|
53
|
+
return onBeaconBlocksByHead(body, chain, peerId, peerClient);
|
|
54
|
+
},
|
|
50
55
|
[ReqRespMethod.BlobSidecarsByRoot]: (req) => {
|
|
51
56
|
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
52
57
|
const body = BlobSidecarsByRootRequestType(fork, chain.config).deserialize(req.data);
|
|
@@ -33,7 +33,7 @@ export enum RespStatus {
|
|
|
33
33
|
*/
|
|
34
34
|
SERVER_ERROR = 2,
|
|
35
35
|
/**
|
|
36
|
-
* The responder does not have requested resource.
|
|
36
|
+
* The responder does not have requested resource. The response payload adheres to the ErrorMessage schema.
|
|
37
37
|
*/
|
|
38
38
|
RESOURCE_UNAVAILABLE = 3,
|
|
39
39
|
/**
|
|
@@ -70,6 +70,12 @@ export const BeaconBlocksByRootV2 = toProtocol({
|
|
|
70
70
|
contextBytesType: ContextBytesType.ForkDigest,
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
export const BeaconBlocksByHead = toProtocol({
|
|
74
|
+
method: ReqRespMethod.BeaconBlocksByHead,
|
|
75
|
+
version: Version.V1,
|
|
76
|
+
contextBytesType: ContextBytesType.ForkDigest,
|
|
77
|
+
});
|
|
78
|
+
|
|
73
79
|
export const BlobSidecarsByRange = toProtocol({
|
|
74
80
|
method: ReqRespMethod.BlobSidecarsByRange,
|
|
75
81
|
version: Version.V1,
|
|
@@ -40,6 +40,10 @@ export const rateLimitQuotas: (fork: ForkName, config: BeaconConfig) => Record<R
|
|
|
40
40
|
},
|
|
41
41
|
getRequestCount: getRequestCountFn(fork, config, ReqRespMethod.BeaconBlocksByRoot, (req) => req.length),
|
|
42
42
|
},
|
|
43
|
+
[ReqRespMethod.BeaconBlocksByHead]: {
|
|
44
|
+
byPeer: {quota: config.MAX_REQUEST_BLOCKS_DENEB, quotaTimeMs: 10_000},
|
|
45
|
+
getRequestCount: getRequestCountFn(fork, config, ReqRespMethod.BeaconBlocksByHead, (req) => req.count),
|
|
46
|
+
},
|
|
43
47
|
[ReqRespMethod.BlobSidecarsByRange]: {
|
|
44
48
|
// Rationale: MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
|
|
45
49
|
byPeer: {
|
|
@@ -46,6 +46,7 @@ export function onOutgoingReqRespError(e: RequestError, method: ReqRespMethod):
|
|
|
46
46
|
return PeerAction.LowToleranceError;
|
|
47
47
|
case ReqRespMethod.BeaconBlocksByRange:
|
|
48
48
|
case ReqRespMethod.BeaconBlocksByRoot:
|
|
49
|
+
case ReqRespMethod.BeaconBlocksByHead:
|
|
49
50
|
case ReqRespMethod.ExecutionPayloadEnvelopesByRoot:
|
|
50
51
|
case ReqRespMethod.ExecutionPayloadEnvelopesByRange:
|
|
51
52
|
return PeerAction.MidToleranceError;
|
|
@@ -42,6 +42,7 @@ export enum ReqRespMethod {
|
|
|
42
42
|
Metadata = "metadata",
|
|
43
43
|
BeaconBlocksByRange = "beacon_blocks_by_range",
|
|
44
44
|
BeaconBlocksByRoot = "beacon_blocks_by_root",
|
|
45
|
+
BeaconBlocksByHead = "beacon_blocks_by_head",
|
|
45
46
|
BlobSidecarsByRange = "blob_sidecars_by_range",
|
|
46
47
|
BlobSidecarsByRoot = "blob_sidecars_by_root",
|
|
47
48
|
DataColumnSidecarsByRange = "data_column_sidecars_by_range",
|
|
@@ -62,6 +63,7 @@ export type RequestBodyByMethod = {
|
|
|
62
63
|
[ReqRespMethod.Metadata]: null;
|
|
63
64
|
[ReqRespMethod.BeaconBlocksByRange]: phase0.BeaconBlocksByRangeRequest;
|
|
64
65
|
[ReqRespMethod.BeaconBlocksByRoot]: BeaconBlocksByRootRequest;
|
|
66
|
+
[ReqRespMethod.BeaconBlocksByHead]: fulu.BeaconBlocksByHeadRequest;
|
|
65
67
|
[ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecarsByRangeRequest;
|
|
66
68
|
[ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequest;
|
|
67
69
|
[ReqRespMethod.DataColumnSidecarsByRange]: fulu.DataColumnSidecarsByRangeRequest;
|
|
@@ -82,6 +84,7 @@ type ResponseBodyByMethod = {
|
|
|
82
84
|
// Do not matter
|
|
83
85
|
[ReqRespMethod.BeaconBlocksByRange]: SignedBeaconBlock;
|
|
84
86
|
[ReqRespMethod.BeaconBlocksByRoot]: SignedBeaconBlock;
|
|
87
|
+
[ReqRespMethod.BeaconBlocksByHead]: SignedBeaconBlock;
|
|
85
88
|
[ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecar;
|
|
86
89
|
[ReqRespMethod.BlobSidecarsByRoot]: deneb.BlobSidecar;
|
|
87
90
|
[ReqRespMethod.DataColumnSidecarsByRange]: DataColumnSidecar;
|
|
@@ -111,6 +114,7 @@ export const requestSszTypeByMethod: (
|
|
|
111
114
|
|
|
112
115
|
[ReqRespMethod.BeaconBlocksByRange]: ssz.phase0.BeaconBlocksByRangeRequest,
|
|
113
116
|
[ReqRespMethod.BeaconBlocksByRoot]: BeaconBlocksByRootRequestType(fork, config),
|
|
117
|
+
[ReqRespMethod.BeaconBlocksByHead]: ssz.fulu.BeaconBlocksByHeadRequest,
|
|
114
118
|
[ReqRespMethod.BlobSidecarsByRange]: ssz.deneb.BlobSidecarsByRangeRequest,
|
|
115
119
|
[ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequestType(fork, config),
|
|
116
120
|
[ReqRespMethod.DataColumnSidecarsByRange]: ssz.fulu.DataColumnSidecarsByRangeRequest,
|
|
@@ -142,6 +146,7 @@ export const responseSszTypeByMethod: {[K in ReqRespMethod]: ResponseTypeGetter<
|
|
|
142
146
|
version === Version.V1 ? ssz.phase0.Metadata : version === Version.V2 ? ssz.altair.Metadata : ssz.fulu.Metadata,
|
|
143
147
|
[ReqRespMethod.BeaconBlocksByRange]: blocksResponseType,
|
|
144
148
|
[ReqRespMethod.BeaconBlocksByRoot]: blocksResponseType,
|
|
149
|
+
[ReqRespMethod.BeaconBlocksByHead]: (fork) => ssz[fork].SignedBeaconBlock,
|
|
145
150
|
[ReqRespMethod.BlobSidecarsByRange]: () => ssz.deneb.BlobSidecar,
|
|
146
151
|
[ReqRespMethod.BlobSidecarsByRoot]: () => ssz.deneb.BlobSidecar,
|
|
147
152
|
[ReqRespMethod.LightClientBootstrap]: (fork) => sszTypesFor(onlyPostAltairFork(fork)).LightClientBootstrap,
|
package/src/sync/constants.ts
CHANGED
|
@@ -7,7 +7,11 @@ export const MIN_FINALIZED_CHAIN_VALIDATED_EPOCHS = 10;
|
|
|
7
7
|
/** The number of times to retry a batch before it is considered failed. */
|
|
8
8
|
export const MAX_BATCH_DOWNLOAD_ATTEMPTS = 5;
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Backoff before assigning more range-sync batches to a peer that rate-limited us.
|
|
12
|
+
*
|
|
13
|
+
* Note: this is used when rate limited due to MAX_CONCURRENT_REQUESTS
|
|
14
|
+
*/
|
|
11
15
|
export const RATE_LIMITED_PEER_BACKOFF_MS = 5_000;
|
|
12
16
|
|
|
13
17
|
/**
|