@lodestar/beacon-node 1.43.0-dev.87cbe69c66 → 1.43.0-dev.8ad5d8be4c
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/blockInput/blockInput.d.ts +3 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +4 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +0 -16
- package/lib/chain/blocks/importBlock.js.map +1 -1
- 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 +11 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +23 -1
- 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 +25 -10
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +6 -1
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/prepareNextSlot.js +1 -1
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +51 -17
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +6 -11
- package/lib/chain/seenCache/seenProposerPreferences.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 +45 -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 +7 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +7 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +32 -6
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +215 -41
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +16 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +77 -4
- 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 -10
- 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 +89 -42
- 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/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +0 -18
- package/src/chain/blocks/importExecutionPayload.ts +18 -6
- package/src/chain/blocks/index.ts +5 -7
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +31 -2
- 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 +29 -9
- package/src/chain/initState.ts +9 -1
- package/src/chain/prepareNextSlot.ts +1 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +66 -20
- package/src/chain/seenCache/seenProposerPreferences.ts +7 -12
- 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 +53 -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 +8 -4
- package/src/sync/range/batch.ts +256 -47
- package/src/sync/range/chain.ts +94 -5
- 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 -10
- package/src/sync/utils/downloadByRange.ts +156 -44
- package/src/sync/utils/rateLimit.ts +16 -0
|
@@ -21,6 +21,7 @@ export enum PayloadErrorCode {
|
|
|
21
21
|
EXECUTION_ENGINE_INVALID = "PAYLOAD_ERROR_EXECUTION_ENGINE_INVALID",
|
|
22
22
|
EXECUTION_ENGINE_ERROR = "PAYLOAD_ERROR_EXECUTION_ENGINE_ERROR",
|
|
23
23
|
BLOCK_NOT_IN_FORK_CHOICE = "PAYLOAD_ERROR_BLOCK_NOT_IN_FORK_CHOICE",
|
|
24
|
+
MISS_BLOCK_STATE = "PAYLOAD_ERROR_MISS_BLOCK_STATE",
|
|
24
25
|
ENVELOPE_VERIFICATION_ERROR = "PAYLOAD_ERROR_ENVELOPE_VERIFICATION_ERROR",
|
|
25
26
|
INVALID_SIGNATURE = "PAYLOAD_ERROR_INVALID_SIGNATURE",
|
|
26
27
|
}
|
|
@@ -40,6 +41,10 @@ export type PayloadErrorType =
|
|
|
40
41
|
code: PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE;
|
|
41
42
|
blockRootHex: string;
|
|
42
43
|
}
|
|
44
|
+
| {
|
|
45
|
+
code: PayloadErrorCode.MISS_BLOCK_STATE;
|
|
46
|
+
blockRootHex: string;
|
|
47
|
+
}
|
|
43
48
|
| {
|
|
44
49
|
code: PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR;
|
|
45
50
|
message: string;
|
|
@@ -123,12 +128,19 @@ export async function importExecutionPayload(
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
// 3. Regenerate state for envelope verification
|
|
126
|
-
const blockState = await this.regen
|
|
127
|
-
protoBlock,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
const blockState = await this.regen
|
|
132
|
+
.getBlockSlotState(protoBlock, protoBlock.slot, {dontTransferCache: true}, RegenCaller.processBlock)
|
|
133
|
+
.catch(() =>
|
|
134
|
+
// only happen at the 1st batch of skipped slot checkpoint sync
|
|
135
|
+
this.regen.getClosestHeadState(protoBlock)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (blockState == null) {
|
|
139
|
+
throw new PayloadError({
|
|
140
|
+
code: PayloadErrorCode.MISS_BLOCK_STATE,
|
|
141
|
+
blockRootHex: protoBlock.blockRoot,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
132
144
|
if (!isStatePostGloas(blockState)) {
|
|
133
145
|
throw new PayloadError({
|
|
134
146
|
code: PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR,
|
|
@@ -126,13 +126,11 @@ export async function processBlocks(
|
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// subsequent slots can find their parent payload.
|
|
135
|
-
const slots = Array.from(new Set(blocks.map((b) => b.getBlock().message.slot)));
|
|
129
|
+
const slotSet = new Set<Slot>(blocks.map((b) => b.getBlock().message.slot));
|
|
130
|
+
if (payloadEnvelopes) {
|
|
131
|
+
for (const slot of payloadEnvelopes.keys()) slotSet.add(slot);
|
|
132
|
+
}
|
|
133
|
+
const slots = Array.from(slotSet).sort((a, b) => a - b);
|
|
136
134
|
for (const slot of slots) {
|
|
137
135
|
const fullyVerifiedBlock = verifiedBlocksBySlot.get(slot);
|
|
138
136
|
if (fullyVerifiedBlock !== undefined) {
|
|
@@ -4,7 +4,13 @@ import {toRootHex, withTimeout} from "@lodestar/utils";
|
|
|
4
4
|
import {VersionedHashes} from "../../../execution/index.js";
|
|
5
5
|
import {kzgCommitmentToVersionedHash} from "../../../util/blobs.js";
|
|
6
6
|
import {MissingColumnMeta} from "../blockInput/types.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AddPayloadEnvelopeProps,
|
|
9
|
+
ColumnWithSource,
|
|
10
|
+
CreateFromBidProps,
|
|
11
|
+
CreateFromBlockProps,
|
|
12
|
+
SourceMeta,
|
|
13
|
+
} from "./types.js";
|
|
8
14
|
|
|
9
15
|
export type PayloadEnvelopeInputState =
|
|
10
16
|
| {
|
|
@@ -132,6 +138,26 @@ export class PayloadEnvelopeInput {
|
|
|
132
138
|
});
|
|
133
139
|
}
|
|
134
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Create a `PayloadEnvelopeInput` from a state's `latestExecutionPayloadBid` (the bid
|
|
143
|
+
* recorded in beacon state for the latest imported block). Used when seeding the cache
|
|
144
|
+
* for a checkpoint anchor block — we have the bid via state but not the full
|
|
145
|
+
* SignedBeaconBlock body.
|
|
146
|
+
*/
|
|
147
|
+
static createFromBid(props: CreateFromBidProps): PayloadEnvelopeInput {
|
|
148
|
+
return new PayloadEnvelopeInput({
|
|
149
|
+
blockRootHex: props.blockRootHex,
|
|
150
|
+
slot: props.slot,
|
|
151
|
+
forkName: props.forkName,
|
|
152
|
+
proposerIndex: props.proposerIndex,
|
|
153
|
+
bid: props.bid,
|
|
154
|
+
sampledColumns: props.sampledColumns,
|
|
155
|
+
custodyColumns: props.custodyColumns,
|
|
156
|
+
timeCreatedSec: props.timeCreatedSec,
|
|
157
|
+
daOutOfRange: props.daOutOfRange,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
135
161
|
getBid(): gloas.ExecutionPayloadBid {
|
|
136
162
|
return this.bid;
|
|
137
163
|
}
|
|
@@ -311,8 +337,11 @@ export class PayloadEnvelopeInput {
|
|
|
311
337
|
return this.state.hasAllData;
|
|
312
338
|
}
|
|
313
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Strictly checks missing sampled columns. Does NOT short-circuit on `state.hasAllData`.
|
|
342
|
+
*/
|
|
314
343
|
getMissingSampledColumnMeta(): MissingColumnMeta {
|
|
315
|
-
if (this.state.
|
|
344
|
+
if (this.state.hasComputedAllData) {
|
|
316
345
|
return {missing: [], versionedHashes: this.versionedHashes};
|
|
317
346
|
}
|
|
318
347
|
|
|
@@ -30,6 +30,23 @@ export type CreateFromBlockProps = {
|
|
|
30
30
|
daOutOfRange: boolean;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Used to seed an entry from a state's `latestExecutionPayloadBid` (e.g., when initializing
|
|
35
|
+
* the chain from a checkpoint anchor state — we have the bid via the state but not the
|
|
36
|
+
* full SignedBeaconBlock).
|
|
37
|
+
*/
|
|
38
|
+
export type CreateFromBidProps = {
|
|
39
|
+
blockRootHex: RootHex;
|
|
40
|
+
slot: number;
|
|
41
|
+
forkName: ForkName;
|
|
42
|
+
proposerIndex: number;
|
|
43
|
+
bid: gloas.ExecutionPayloadBid;
|
|
44
|
+
sampledColumns: ColumnIndex[];
|
|
45
|
+
custodyColumns: ColumnIndex[];
|
|
46
|
+
timeCreatedSec: number;
|
|
47
|
+
daOutOfRange: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
33
50
|
export type AddPayloadEnvelopeProps = SourceMeta & {
|
|
34
51
|
envelope: gloas.SignedExecutionPayloadEnvelope;
|
|
35
52
|
};
|
|
@@ -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,
|
|
@@ -335,15 +336,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
335
336
|
metrics,
|
|
336
337
|
logger,
|
|
337
338
|
});
|
|
338
|
-
this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
|
|
339
|
-
config,
|
|
340
|
-
clock,
|
|
341
|
-
chainEvents: emitter,
|
|
342
|
-
signal,
|
|
343
|
-
serializedCache: this.serializedCache,
|
|
344
|
-
metrics,
|
|
345
|
-
logger,
|
|
346
|
-
});
|
|
347
339
|
|
|
348
340
|
this._earliestAvailableSlot = anchorState.slot;
|
|
349
341
|
|
|
@@ -399,6 +391,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
399
391
|
metrics,
|
|
400
392
|
logger
|
|
401
393
|
);
|
|
394
|
+
|
|
402
395
|
const regen = new QueuedStateRegenerator({
|
|
403
396
|
config,
|
|
404
397
|
forkChoice,
|
|
@@ -423,6 +416,33 @@ export class BeaconChain implements IBeaconChain {
|
|
|
423
416
|
this.payloadEnvelopeProcessor = new PayloadEnvelopeProcessor(this, metrics, signal);
|
|
424
417
|
|
|
425
418
|
this.forkChoice = forkChoice;
|
|
419
|
+
|
|
420
|
+
this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
|
|
421
|
+
config,
|
|
422
|
+
clock,
|
|
423
|
+
forkChoice,
|
|
424
|
+
chainEvents: emitter,
|
|
425
|
+
signal,
|
|
426
|
+
serializedCache: this.serializedCache,
|
|
427
|
+
metrics,
|
|
428
|
+
logger,
|
|
429
|
+
});
|
|
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
|
+
|
|
426
446
|
this.clock = clock;
|
|
427
447
|
this.regen = regen;
|
|
428
448
|
this.bls = bls;
|
package/src/chain/initState.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {ZERO_HASH} from "@lodestar/params";
|
|
2
|
+
import {ForkPostGloas, ForkSeq, ZERO_HASH} from "@lodestar/params";
|
|
3
3
|
import {
|
|
4
4
|
BeaconStateAllForks,
|
|
5
|
+
BeaconStateGloas,
|
|
5
6
|
IBeaconStateView,
|
|
6
7
|
computeEpochAtSlot,
|
|
7
8
|
computeStartSlotAtEpoch,
|
|
@@ -52,6 +53,13 @@ export function createGenesisBlock(config: ChainForkConfig, genesisState: Beacon
|
|
|
52
53
|
const genesisBlock = types.SignedBeaconBlock.defaultValue();
|
|
53
54
|
const stateRoot = genesisState.hashTreeRoot();
|
|
54
55
|
genesisBlock.message.stateRoot = stateRoot;
|
|
56
|
+
|
|
57
|
+
if (config.getForkSeq(GENESIS_SLOT) >= ForkSeq.gloas) {
|
|
58
|
+
const gloasBlock = genesisBlock as SignedBeaconBlock<ForkPostGloas>;
|
|
59
|
+
const gloasState = genesisState as BeaconStateGloas;
|
|
60
|
+
gloasBlock.message.body.signedExecutionPayloadBid.message = gloasState.latestExecutionPayloadBid.toValue();
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
return genesisBlock;
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -222,7 +222,7 @@ export class PrepareNextSlotScheduler {
|
|
|
222
222
|
// and head.parent (proposer-boost-reorg fallback). Anything older is evicted.
|
|
223
223
|
const updatedHeadParent = this.chain.forkChoice.getBlockHexDefaultStatus(updatedHead.parentRoot);
|
|
224
224
|
if (updatedHeadParent) {
|
|
225
|
-
this.chain.seenPayloadEnvelopeInputCache.
|
|
225
|
+
this.chain.seenPayloadEnvelopeInputCache.pruneBelowParent(updatedHeadParent);
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
2
|
+
import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
|
|
3
3
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
|
-
import {RootHex
|
|
4
|
+
import {RootHex} from "@lodestar/types";
|
|
5
5
|
import {Logger} from "@lodestar/utils";
|
|
6
6
|
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";
|
|
@@ -16,6 +16,7 @@ export {PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
|
|
|
16
16
|
export type SeenPayloadEnvelopeInputModules = {
|
|
17
17
|
config: ChainForkConfig;
|
|
18
18
|
clock: IClock;
|
|
19
|
+
forkChoice: IForkChoice;
|
|
19
20
|
chainEvents: ChainEventEmitter;
|
|
20
21
|
signal: AbortSignal;
|
|
21
22
|
serializedCache: SerializedCache;
|
|
@@ -26,11 +27,7 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
26
27
|
/**
|
|
27
28
|
* Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
|
|
28
29
|
*
|
|
29
|
-
* Created
|
|
30
|
-
* - `prepareNextSlot` calls `pruneBelow(headParentSlot)` every slot once the head we'll build
|
|
31
|
-
* on is known.
|
|
32
|
-
* - `onFinalized` calls `pruneBelow(finalizedSlot)` on every finalization for bulk cleanup.
|
|
33
|
-
*
|
|
30
|
+
* Created whenever we have a block because it needs block bid.
|
|
34
31
|
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
|
|
35
32
|
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
|
|
36
33
|
* transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
|
|
@@ -39,6 +36,7 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
39
36
|
export class SeenPayloadEnvelopeInput {
|
|
40
37
|
private readonly config: ChainForkConfig;
|
|
41
38
|
private readonly clock: IClock;
|
|
39
|
+
private readonly forkChoice: IForkChoice;
|
|
42
40
|
private readonly chainEvents: ChainEventEmitter;
|
|
43
41
|
private readonly signal: AbortSignal;
|
|
44
42
|
private readonly serializedCache: SerializedCache;
|
|
@@ -46,9 +44,19 @@ export class SeenPayloadEnvelopeInput {
|
|
|
46
44
|
private readonly logger?: Logger;
|
|
47
45
|
private payloadInputs = new Map<RootHex, PayloadEnvelopeInput>();
|
|
48
46
|
|
|
49
|
-
constructor({
|
|
47
|
+
constructor({
|
|
48
|
+
config,
|
|
49
|
+
clock,
|
|
50
|
+
forkChoice,
|
|
51
|
+
chainEvents,
|
|
52
|
+
signal,
|
|
53
|
+
serializedCache,
|
|
54
|
+
metrics,
|
|
55
|
+
logger,
|
|
56
|
+
}: SeenPayloadEnvelopeInputModules) {
|
|
50
57
|
this.config = config;
|
|
51
58
|
this.clock = clock;
|
|
59
|
+
this.forkChoice = forkChoice;
|
|
52
60
|
this.chainEvents = chainEvents;
|
|
53
61
|
this.signal = signal;
|
|
54
62
|
this.serializedCache = serializedCache;
|
|
@@ -67,14 +75,27 @@ export class SeenPayloadEnvelopeInput {
|
|
|
67
75
|
});
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
this.chainEvents.on(ChainEvent.forkChoiceFinalized, this.
|
|
78
|
+
this.chainEvents.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
|
|
71
79
|
this.signal.addEventListener("abort", () => {
|
|
72
|
-
this.chainEvents.off(ChainEvent.forkChoiceFinalized, this.
|
|
80
|
+
this.chainEvents.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
|
|
73
81
|
});
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
private
|
|
77
|
-
|
|
84
|
+
private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
|
|
85
|
+
const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
|
|
86
|
+
let deletedCount = 0;
|
|
87
|
+
for (const [, input] of this.payloadInputs) {
|
|
88
|
+
if (input.slot < finalizedSlot) {
|
|
89
|
+
this.evictPayloadInput(input);
|
|
90
|
+
deletedCount++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.logger?.debug("SeenPayloadEnvelopeInput.pruneFinalized deleted entries", {
|
|
95
|
+
finalizedSlot,
|
|
96
|
+
finalizedRoot: checkpoint.rootHex,
|
|
97
|
+
deletedCount,
|
|
98
|
+
});
|
|
78
99
|
};
|
|
79
100
|
|
|
80
101
|
add(props: Omit<CreateFromBlockProps, "daOutOfRange">): PayloadEnvelopeInput {
|
|
@@ -98,6 +119,27 @@ export class SeenPayloadEnvelopeInput {
|
|
|
98
119
|
return input;
|
|
99
120
|
}
|
|
100
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
|
+
|
|
101
143
|
get(blockRootHex: RootHex): PayloadEnvelopeInput | undefined {
|
|
102
144
|
return this.payloadInputs.get(blockRootHex);
|
|
103
145
|
}
|
|
@@ -110,15 +152,19 @@ export class SeenPayloadEnvelopeInput {
|
|
|
110
152
|
return this.payloadInputs.size;
|
|
111
153
|
}
|
|
112
154
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
155
|
+
pruneBelowParent(parentBlock: ProtoBlock): void {
|
|
156
|
+
for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
|
|
157
|
+
if (block.slot < parentBlock.slot) {
|
|
158
|
+
const input = this.payloadInputs.get(block.blockRoot);
|
|
159
|
+
if (input) {
|
|
160
|
+
this.evictPayloadInput(input);
|
|
161
|
+
this.logger?.verbose("SeenPayloadEnvelopeInput.pruneBelowParent deleted", {
|
|
162
|
+
slot: block.slot,
|
|
163
|
+
root: block.blockRoot,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
119
166
|
}
|
|
120
167
|
}
|
|
121
|
-
this.logger?.debug("SeenPayloadEnvelopeInput.pruneBelow deleted entries", {slot, deletedCount});
|
|
122
168
|
}
|
|
123
169
|
|
|
124
170
|
private evictPayloadInput(payloadInput: PayloadEnvelopeInput): void {
|
|
@@ -5,16 +5,16 @@ import {MapDef} from "@lodestar/utils";
|
|
|
5
5
|
* Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
|
|
6
6
|
*/
|
|
7
7
|
export class SeenProposerPreferences {
|
|
8
|
-
private readonly
|
|
9
|
-
() => new Map<
|
|
8
|
+
private readonly validatorByDependentRootBySlot = new MapDef<Slot, Map<RootHex, ValidatorIndex>>(
|
|
9
|
+
() => new Map<RootHex, ValidatorIndex>()
|
|
10
10
|
);
|
|
11
11
|
|
|
12
12
|
isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
|
|
13
|
-
return this.
|
|
13
|
+
return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
|
|
17
|
-
this.
|
|
17
|
+
this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -23,14 +23,9 @@ export class SeenProposerPreferences {
|
|
|
23
23
|
* on each slot tick.
|
|
24
24
|
*/
|
|
25
25
|
prune(currentSlot: Slot): void {
|
|
26
|
-
for (const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
slotMap.delete(slot);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (slotMap.size === 0) {
|
|
33
|
-
this.validatorBySlotByDependentRoot.delete(dependentRoot);
|
|
26
|
+
for (const slot of this.validatorByDependentRootBySlot.keys()) {
|
|
27
|
+
if (slot < currentSlot) {
|
|
28
|
+
this.validatorByDependentRootBySlot.delete(slot);
|
|
34
29
|
}
|
|
35
30
|
}
|
|
36
31
|
}
|
|
@@ -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),
|