@lodestar/beacon-node 1.43.0-dev.07875b3e0c → 1.43.0-dev.1540a78cd0
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/lodestar/index.js +1 -1
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +6 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +19 -8
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +31 -20
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts +5 -3
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +28 -9
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +2 -2
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +81 -12
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +3 -2
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +30 -5
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/chain.d.ts +3 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +14 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +8 -1
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -0
- package/lib/chain/errors/blockError.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/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +19 -8
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +27 -13
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +1 -0
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +1 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +4 -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/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +2 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/node/notifier.js +7 -1
- package/lib/node/notifier.js.map +1 -1
- package/lib/sync/range/batch.d.ts +12 -2
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +56 -30
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +6 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +4 -3
- 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 +17 -6
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/types.d.ts +34 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +34 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +24 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +649 -53
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +46 -10
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +147 -24
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +6 -2
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.js +0 -9
- package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
- package/package.json +16 -15
- package/src/api/impl/beacon/blocks/index.ts +5 -2
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/chain/blocks/importBlock.ts +4 -2
- package/src/chain/blocks/importExecutionPayload.ts +36 -21
- package/src/chain/blocks/index.ts +44 -12
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
- package/src/chain/blocks/types.ts +2 -2
- package/src/chain/blocks/utils/chainSegment.ts +106 -17
- package/src/chain/blocks/verifyBlock.ts +35 -6
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
- package/src/chain/chain.ts +23 -3
- package/src/chain/errors/blockError.ts +4 -1
- package/src/chain/interface.ts +7 -1
- package/src/chain/prepareNextSlot.ts +29 -9
- package/src/chain/produceBlock/produceBlockBody.ts +30 -11
- package/src/chain/validation/block.ts +1 -0
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/network/processor/gossipHandlers.ts +4 -1
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
- package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
- package/src/node/notifier.ts +8 -1
- package/src/sync/range/batch.ts +90 -35
- package/src/sync/range/chain.ts +13 -5
- package/src/sync/range/range.ts +18 -6
- package/src/sync/types.ts +72 -0
- package/src/sync/unknownBlock.ts +810 -57
- package/src/sync/utils/downloadByRange.ts +256 -39
- package/src/sync/utils/downloadByRoot.ts +12 -2
- package/src/sync/utils/pendingBlocksTree.ts +0 -15
package/src/chain/interface.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
altair,
|
|
19
19
|
capella,
|
|
20
20
|
deneb,
|
|
21
|
+
electra,
|
|
21
22
|
gloas,
|
|
22
23
|
phase0,
|
|
23
24
|
rewards,
|
|
@@ -231,6 +232,7 @@ export interface IBeaconChain {
|
|
|
231
232
|
blockSlot: Slot,
|
|
232
233
|
blockRootHex: string
|
|
233
234
|
): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
|
|
235
|
+
getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
|
|
234
236
|
|
|
235
237
|
produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
|
|
236
238
|
produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
|
|
@@ -248,7 +250,11 @@ export interface IBeaconChain {
|
|
|
248
250
|
/** Process a block until complete */
|
|
249
251
|
processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void>;
|
|
250
252
|
/** Process a chain of blocks until complete */
|
|
251
|
-
processChainSegment(
|
|
253
|
+
processChainSegment(
|
|
254
|
+
blocks: IBlockInput[],
|
|
255
|
+
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
256
|
+
opts?: ImportBlockOpts
|
|
257
|
+
): Promise<void>;
|
|
252
258
|
|
|
253
259
|
/** Process execution payload envelope: verify, import to fork choice, and persist to DB */
|
|
254
260
|
processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void>;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isStatePostBellatrix,
|
|
11
11
|
isStatePostGloas,
|
|
12
12
|
} from "@lodestar/state-transition";
|
|
13
|
-
import {Bytes32, Slot} from "@lodestar/types";
|
|
13
|
+
import {Bytes32, Slot, electra} from "@lodestar/types";
|
|
14
14
|
import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
|
|
15
15
|
import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
|
|
16
16
|
import {BuilderStatus} from "../execution/builder/http.js";
|
|
@@ -165,14 +165,19 @@ export class PrepareNextSlotScheduler {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
let parentBlockHash: Bytes32;
|
|
168
|
+
let isExtendingPayload = false;
|
|
168
169
|
if (isStatePostGloas(updatedPrepareState)) {
|
|
169
|
-
|
|
170
|
+
isExtendingPayload = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot);
|
|
171
|
+
parentBlockHash = isExtendingPayload
|
|
170
172
|
? updatedPrepareState.latestExecutionPayloadBid.blockHash
|
|
171
173
|
: updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
|
|
172
174
|
} else {
|
|
173
175
|
parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
// Reused by the SSE emit below to avoid a second DB lookup on cache miss
|
|
179
|
+
let parentExecutionRequests: electra.ExecutionRequests | undefined;
|
|
180
|
+
|
|
176
181
|
if (feeRecipient) {
|
|
177
182
|
const preparationTime =
|
|
178
183
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
@@ -182,6 +187,13 @@ export class PrepareNextSlotScheduler {
|
|
|
182
187
|
const finalizedBlockHash =
|
|
183
188
|
this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
|
|
184
189
|
|
|
190
|
+
if (isExtendingPayload) {
|
|
191
|
+
parentExecutionRequests = await this.chain.getParentExecutionRequests(
|
|
192
|
+
updatedHead.slot,
|
|
193
|
+
updatedHead.blockRoot
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
// awaiting here instead of throwing an async call because there is no other task
|
|
186
198
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
187
199
|
// try/catch wrapper here.
|
|
@@ -194,7 +206,8 @@ export class PrepareNextSlotScheduler {
|
|
|
194
206
|
safeBlockHash,
|
|
195
207
|
finalizedBlockHash,
|
|
196
208
|
updatedPrepareState,
|
|
197
|
-
feeRecipient
|
|
209
|
+
feeRecipient,
|
|
210
|
+
parentExecutionRequests
|
|
198
211
|
);
|
|
199
212
|
this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
|
|
200
213
|
prepareSlot,
|
|
@@ -215,19 +228,26 @@ export class PrepareNextSlotScheduler {
|
|
|
215
228
|
|
|
216
229
|
this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
|
|
217
230
|
|
|
218
|
-
// If emitPayloadAttributes is true emit a SSE payloadAttributes event
|
|
231
|
+
// If emitPayloadAttributes is true emit a SSE payloadAttributes event for
|
|
232
|
+
// every slot. Without the flag, only emit the event if we are proposing in the next slot.
|
|
219
233
|
if (
|
|
220
|
-
this.chain.opts.emitPayloadAttributes === true &&
|
|
234
|
+
(feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
|
|
221
235
|
this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
|
|
222
236
|
) {
|
|
237
|
+
// if we didn't fetch above (not proposing), SSE still needs it here
|
|
238
|
+
if (!parentExecutionRequests && isExtendingPayload) {
|
|
239
|
+
parentExecutionRequests = await this.chain.getParentExecutionRequests(
|
|
240
|
+
updatedHead.slot,
|
|
241
|
+
updatedHead.blockRoot
|
|
242
|
+
);
|
|
243
|
+
}
|
|
223
244
|
const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
|
|
224
245
|
prepareState: updatedPrepareState,
|
|
225
246
|
prepareSlot,
|
|
226
|
-
parentBlockRoot: fromHex(
|
|
247
|
+
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
227
248
|
parentBlockHash,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
feeRecipient: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
249
|
+
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
250
|
+
parentExecutionRequests,
|
|
231
251
|
});
|
|
232
252
|
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
233
253
|
}
|
|
@@ -214,9 +214,13 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
// Get execution payload from EL
|
|
217
|
-
const
|
|
217
|
+
const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot));
|
|
218
|
+
const parentBlockHash = isExtendingPayload
|
|
218
219
|
? currentState.latestExecutionPayloadBid.blockHash
|
|
219
220
|
: currentState.latestExecutionPayloadBid.parentBlockHash;
|
|
221
|
+
const parentExecutionRequests = isExtendingPayload
|
|
222
|
+
? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
|
|
223
|
+
: ssz.electra.ExecutionRequests.defaultValue();
|
|
220
224
|
const prepareRes = await prepareExecutionPayload(
|
|
221
225
|
this,
|
|
222
226
|
this.logger,
|
|
@@ -226,7 +230,8 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
226
230
|
safeBlockHash,
|
|
227
231
|
finalizedBlockHash ?? ZERO_HASH_HEX,
|
|
228
232
|
currentState,
|
|
229
|
-
feeRecipient
|
|
233
|
+
feeRecipient,
|
|
234
|
+
parentExecutionRequests
|
|
230
235
|
);
|
|
231
236
|
|
|
232
237
|
const {prepType, payloadId} = prepareRes;
|
|
@@ -282,7 +287,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
282
287
|
gloasBody.signedExecutionPayloadBid = signedBid;
|
|
283
288
|
// TODO GLOAS: Get payload attestations from pool for previous slot
|
|
284
289
|
gloasBody.payloadAttestations = [];
|
|
285
|
-
|
|
290
|
+
gloasBody.parentExecutionRequests = parentExecutionRequests;
|
|
286
291
|
blockBody = gloasBody as AssembledBodyType<T>;
|
|
287
292
|
|
|
288
293
|
// Store execution payload data required to construct execution payload envelope later
|
|
@@ -619,7 +624,8 @@ export async function prepareExecutionPayload(
|
|
|
619
624
|
safeBlockHash: RootHex,
|
|
620
625
|
finalizedBlockHash: RootHex,
|
|
621
626
|
state: IBeaconStateViewBellatrix,
|
|
622
|
-
suggestedFeeRecipient: string
|
|
627
|
+
suggestedFeeRecipient: string,
|
|
628
|
+
parentExecutionRequests?: electra.ExecutionRequests
|
|
623
629
|
): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
|
|
624
630
|
const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
|
|
625
631
|
const prevRandao = state.getRandaoMix(state.epoch);
|
|
@@ -656,6 +662,7 @@ export async function prepareExecutionPayload(
|
|
|
656
662
|
parentBlockRoot,
|
|
657
663
|
parentBlockHash,
|
|
658
664
|
feeRecipient: suggestedFeeRecipient,
|
|
665
|
+
parentExecutionRequests,
|
|
659
666
|
});
|
|
660
667
|
|
|
661
668
|
payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
|
|
@@ -714,12 +721,14 @@ export function getPayloadAttributesForSSE(
|
|
|
714
721
|
parentBlockRoot,
|
|
715
722
|
parentBlockHash,
|
|
716
723
|
feeRecipient,
|
|
724
|
+
parentExecutionRequests,
|
|
717
725
|
}: {
|
|
718
726
|
prepareState: IBeaconStateViewBellatrix;
|
|
719
727
|
prepareSlot: Slot;
|
|
720
728
|
parentBlockRoot: Root;
|
|
721
729
|
parentBlockHash: Bytes32;
|
|
722
730
|
feeRecipient: string;
|
|
731
|
+
parentExecutionRequests?: electra.ExecutionRequests;
|
|
723
732
|
}
|
|
724
733
|
): SSEPayloadAttributes {
|
|
725
734
|
const payloadAttributes = preparePayloadAttributes(fork, chain, {
|
|
@@ -728,6 +737,7 @@ export function getPayloadAttributesForSSE(
|
|
|
728
737
|
parentBlockRoot,
|
|
729
738
|
parentBlockHash,
|
|
730
739
|
feeRecipient,
|
|
740
|
+
parentExecutionRequests,
|
|
731
741
|
});
|
|
732
742
|
|
|
733
743
|
let parentBlockNumber: number;
|
|
@@ -766,12 +776,14 @@ function preparePayloadAttributes(
|
|
|
766
776
|
parentBlockRoot,
|
|
767
777
|
parentBlockHash,
|
|
768
778
|
feeRecipient,
|
|
779
|
+
parentExecutionRequests,
|
|
769
780
|
}: {
|
|
770
781
|
prepareState: IBeaconStateViewBellatrix;
|
|
771
782
|
prepareSlot: Slot;
|
|
772
783
|
parentBlockRoot: Root;
|
|
773
784
|
parentBlockHash: Bytes32;
|
|
774
785
|
feeRecipient: string;
|
|
786
|
+
parentExecutionRequests?: electra.ExecutionRequests;
|
|
775
787
|
}
|
|
776
788
|
): SSEPayloadAttributes["payloadAttributes"] {
|
|
777
789
|
const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
|
|
@@ -789,13 +801,20 @@ function preparePayloadAttributes(
|
|
|
789
801
|
|
|
790
802
|
if (isStatePostGloas(prepareState)) {
|
|
791
803
|
const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
804
|
+
if (isExtendingPayload) {
|
|
805
|
+
if (parentExecutionRequests === undefined) {
|
|
806
|
+
throw new Error("parentExecutionRequests required when extending full parent");
|
|
807
|
+
}
|
|
808
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
809
|
+
prepareState.getExpectedWithdrawalsForFullParent(parentExecutionRequests);
|
|
810
|
+
} else {
|
|
811
|
+
// When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
|
|
812
|
+
// already deducted from CL balances but never credited on the EL (the envelope
|
|
813
|
+
// was not delivered). The next payload must carry those same withdrawals to
|
|
814
|
+
// restore CL/EL consistency, otherwise validators permanently lose that balance.
|
|
815
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
816
|
+
prepareState.payloadExpectedWithdrawals;
|
|
817
|
+
}
|
|
799
818
|
} else {
|
|
800
819
|
// withdrawals logic is now fork aware as it changes on electra fork post capella
|
|
801
820
|
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
@@ -103,6 +103,7 @@ export async function validateGossipBlock(
|
|
|
103
103
|
if (chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHashHex) === null) {
|
|
104
104
|
throw new BlockGossipError(GossipAction.IGNORE, {
|
|
105
105
|
code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
|
|
106
|
+
parentRoot,
|
|
106
107
|
parentBlockHash: parentBlockHashHex,
|
|
107
108
|
});
|
|
108
109
|
}
|
|
@@ -613,6 +613,10 @@ export function createLodestarMetrics(
|
|
|
613
613
|
name: "lodestar_sync_unknown_block_pending_blocks_size",
|
|
614
614
|
help: "Current size of UnknownBlockSync pending blocks cache",
|
|
615
615
|
}),
|
|
616
|
+
pendingPayloads: register.gauge({
|
|
617
|
+
name: "lodestar_sync_unknown_block_pending_payloads_size",
|
|
618
|
+
help: "Current size of UnknownBlockSync pending payloads cache",
|
|
619
|
+
}),
|
|
616
620
|
knownBadBlocks: register.gauge({
|
|
617
621
|
name: "lodestar_sync_unknown_block_known_bad_blocks_size",
|
|
618
622
|
help: "Current size of UnknownBlockSync known bad blocks cache",
|
|
@@ -198,7 +198,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
198
198
|
} catch (e) {
|
|
199
199
|
if (e instanceof BlockGossipError) {
|
|
200
200
|
logger.debug("Gossip block has error", {slot, root: blockShortHex, code: e.type.code});
|
|
201
|
-
if (
|
|
201
|
+
if (
|
|
202
|
+
(e.type.code === BlockErrorCode.PARENT_UNKNOWN || e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN) &&
|
|
203
|
+
blockInput
|
|
204
|
+
) {
|
|
202
205
|
chain.emitter.emit(ChainEvent.blockUnknownParent, {
|
|
203
206
|
blockInput,
|
|
204
207
|
peer: peerIdStr,
|
|
@@ -24,6 +24,10 @@ export async function* onBeaconBlocksByRange(
|
|
|
24
24
|
// in the case of initializing from a non-finalized state, we don't have the finalized block so this api does not work
|
|
25
25
|
// chain.forkChoice.getFinalizeBlock().slot
|
|
26
26
|
const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
|
|
27
|
+
// Blocks are migrated to blockArchive at finalization (including the finalized block itself),
|
|
28
|
+
// so the archive loop serves up to AND INCLUDING finalizedSlot and the headChain loop
|
|
29
|
+
// starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
|
|
30
|
+
const archiveMaxSlot = finalizedSlot;
|
|
27
31
|
|
|
28
32
|
const forkName = chain.config.getForkName(startSlot);
|
|
29
33
|
if (isForkPostFulu(forkName) && startSlot < chain.earliestAvailableSlot) {
|
|
@@ -35,9 +39,12 @@ export async function* onBeaconBlocksByRange(
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
// Finalized range of blocks
|
|
38
|
-
if (startSlot <=
|
|
42
|
+
if (startSlot <= archiveMaxSlot) {
|
|
39
43
|
// Chain of blobs won't change
|
|
40
|
-
for await (const {key, value} of finalized.binaryEntriesStream({
|
|
44
|
+
for await (const {key, value} of finalized.binaryEntriesStream({
|
|
45
|
+
gte: startSlot,
|
|
46
|
+
lt: Math.min(endSlot, archiveMaxSlot + 1),
|
|
47
|
+
})) {
|
|
41
48
|
yield {
|
|
42
49
|
data: value,
|
|
43
50
|
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(finalized.decodeKey(key))),
|
|
@@ -46,19 +53,20 @@ export async function* onBeaconBlocksByRange(
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// Non-finalized range of blocks
|
|
49
|
-
if (endSlot >
|
|
56
|
+
if (endSlot > archiveMaxSlot) {
|
|
50
57
|
const headBlock = chain.forkChoice.getHead();
|
|
51
58
|
const headRoot = headBlock.blockRoot;
|
|
52
59
|
// TODO DENEB: forkChoice should mantain an array of canonical blocks, and change only on reorg
|
|
53
60
|
const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
|
|
54
|
-
// getAllAncestorBlocks
|
|
61
|
+
// `getAllAncestorBlocks` includes both the head and the previous-finalized boundary.
|
|
55
62
|
|
|
56
63
|
// Iterate head chain with ascending block numbers
|
|
57
64
|
for (let i = headChain.length - 1; i >= 0; i--) {
|
|
58
65
|
const block = headChain[i];
|
|
59
66
|
|
|
60
|
-
// Must include only blocks in the range requested
|
|
61
|
-
|
|
67
|
+
// Must include only blocks in the range requested, and skip anything the archive loop
|
|
68
|
+
// above already served via the block.slot > archiveMaxSlot filter.
|
|
69
|
+
if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
|
|
62
70
|
// Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
|
|
63
71
|
// at the time of the start of the request. Spec is clear the chain of blobs must be consistent, but on
|
|
64
72
|
// re-org there's no need to abort the request
|
|
@@ -20,31 +20,37 @@ export async function* onBlobSidecarsByRange(
|
|
|
20
20
|
const finalized = db.blobSidecarsArchive;
|
|
21
21
|
const unfinalized = db.blobSidecars;
|
|
22
22
|
const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
|
|
23
|
+
// Blobs are migrated to blobSidecarsArchive at finalization (including the finalized block
|
|
24
|
+
// itself), so the archive loop serves up to AND INCLUDING finalizedSlot and the headChain
|
|
25
|
+
// loop starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
|
|
26
|
+
const archiveMaxSlot = finalizedSlot;
|
|
23
27
|
|
|
24
28
|
// Finalized range of blobs
|
|
25
|
-
if (startSlot <=
|
|
29
|
+
if (startSlot <= archiveMaxSlot) {
|
|
26
30
|
// Chain of blobs won't change
|
|
27
31
|
for await (const {key, value: blobSideCarsBytesWrapped} of finalized.binaryEntriesStream({
|
|
28
32
|
gte: startSlot,
|
|
29
|
-
lt: endSlot,
|
|
33
|
+
lt: Math.min(endSlot, archiveMaxSlot + 1),
|
|
30
34
|
})) {
|
|
31
35
|
yield* iterateBlobBytesFromWrapper(chain, blobSideCarsBytesWrapped, finalized.decodeKey(key));
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
// Non-finalized range of blobs
|
|
36
|
-
if (endSlot >
|
|
40
|
+
if (endSlot > archiveMaxSlot) {
|
|
37
41
|
const headBlock = chain.forkChoice.getHead();
|
|
38
42
|
const headRoot = headBlock.blockRoot;
|
|
39
43
|
// TODO DENEB: forkChoice should mantain an array of canonical blocks, and change only on reorg
|
|
40
44
|
const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
|
|
45
|
+
// `getAllAncestorBlocks` includes both the head and the previous-finalized boundary.
|
|
41
46
|
|
|
42
47
|
// Iterate head chain with ascending block numbers
|
|
43
48
|
for (let i = headChain.length - 1; i >= 0; i--) {
|
|
44
49
|
const block = headChain[i];
|
|
45
50
|
|
|
46
|
-
// Must include only blobs in the range requested
|
|
47
|
-
|
|
51
|
+
// Must include only blobs in the range requested, and skip anything the archive loop
|
|
52
|
+
// above already served via the block.slot > archiveMaxSlot filter.
|
|
53
|
+
if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
|
|
48
54
|
// Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
|
|
49
55
|
// at the time of the start of the request. Spec is clear the chain of blobs must be consistent, but on
|
|
50
56
|
// re-org there's no need to abort the request
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {PeerId} from "@libp2p/interface";
|
|
2
2
|
import {ChainConfig} from "@lodestar/config";
|
|
3
|
-
import {GENESIS_SLOT} from "@lodestar/params";
|
|
3
|
+
import {ForkSeq, GENESIS_SLOT} from "@lodestar/params";
|
|
4
4
|
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
|
|
5
5
|
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
6
6
|
import {ColumnIndex, Epoch, fulu} from "@lodestar/types";
|
|
@@ -43,10 +43,19 @@ export async function* onDataColumnSidecarsByRange(
|
|
|
43
43
|
|
|
44
44
|
const finalized = db.dataColumnSidecarArchive;
|
|
45
45
|
const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
|
|
46
|
+
// Columns of the last finalized block live in different DBs depending on fork:
|
|
47
|
+
// - Pre-gloas (fulu): migrated to dataColumnSidecarArchive in the same finalization run.
|
|
48
|
+
// - Post-gloas: stay in the hot db (db.dataColumnSidecar) until the next finalization run,
|
|
49
|
+
// because the migration filter requires payloadStatus === FULL for gloas blocks.
|
|
50
|
+
// archiveMaxSlot is the last slot whose columns are served by the archive loop below;
|
|
51
|
+
// anything above it is served by the headChain loop.
|
|
52
|
+
const isPostGloasFinalized = chain.config.getForkSeq(finalizedSlot) >= ForkSeq.gloas;
|
|
53
|
+
const archiveMaxSlot = isPostGloasFinalized ? finalizedSlot - 1 : finalizedSlot;
|
|
46
54
|
|
|
47
55
|
// Finalized range of columns
|
|
48
|
-
if (startSlot <=
|
|
49
|
-
|
|
56
|
+
if (startSlot <= archiveMaxSlot) {
|
|
57
|
+
const archiveEnd = Math.min(endSlot, archiveMaxSlot + 1);
|
|
58
|
+
for (let slot = startSlot; slot < archiveEnd; slot++) {
|
|
50
59
|
const dataColumnSidecars = await finalized.getManyBinary(slot, availableColumns);
|
|
51
60
|
|
|
52
61
|
const unavailableColumnIndices: ColumnIndex[] = [];
|
|
@@ -81,9 +90,12 @@ export async function* onDataColumnSidecarsByRange(
|
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
// Non-finalized range of columns
|
|
84
|
-
if (endSlot >
|
|
93
|
+
if (endSlot > archiveMaxSlot) {
|
|
85
94
|
const headBlock = chain.forkChoice.getHead();
|
|
86
95
|
const headRoot = headBlock.blockRoot;
|
|
96
|
+
// getAllAncestorBlocks includes the last finalized block as its final element.
|
|
97
|
+
// Skip anything the archive loop above already served via the block.slot > archiveMaxSlot
|
|
98
|
+
// filter below (pre-gloas this skips finalizedSlot, post-gloas it keeps it).
|
|
87
99
|
const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
|
|
88
100
|
|
|
89
101
|
// Iterate head chain with ascending block numbers
|
|
@@ -91,7 +103,7 @@ export async function* onDataColumnSidecarsByRange(
|
|
|
91
103
|
const block = headChain[i];
|
|
92
104
|
|
|
93
105
|
// Must include only columns in the range requested
|
|
94
|
-
if (block.slot >= startSlot && block.slot < endSlot) {
|
|
106
|
+
if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
|
|
95
107
|
// Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
|
|
96
108
|
// at the time of the start of the request. Spec is clear the chain of columns must be consistent, but on
|
|
97
109
|
// re-org there's no need to abort the request
|
|
@@ -21,12 +21,15 @@ export async function* onExecutionPayloadEnvelopesByRange(
|
|
|
21
21
|
|
|
22
22
|
const finalized = db.executionPayloadEnvelopeArchive;
|
|
23
23
|
const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
|
|
24
|
+
// The current finalized block's envelope is still in the hot db; archive migration happens
|
|
25
|
+
// in the next finalization run (see migrateExecutionPayloadEnvelopesFromHotToColdDb).
|
|
26
|
+
const archiveMaxSlot = finalizedSlot - 1;
|
|
24
27
|
|
|
25
28
|
// Finalized range of envelopes
|
|
26
|
-
if (startSlot <=
|
|
29
|
+
if (startSlot <= archiveMaxSlot) {
|
|
27
30
|
for await (const {key, value: envelopeBytes} of finalized.binaryEntriesStream({
|
|
28
31
|
gte: startSlot,
|
|
29
|
-
lt: endSlot,
|
|
32
|
+
lt: Math.min(endSlot, archiveMaxSlot + 1),
|
|
30
33
|
})) {
|
|
31
34
|
const slot = finalized.decodeKey(key);
|
|
32
35
|
yield {
|
|
@@ -37,7 +40,7 @@ export async function* onExecutionPayloadEnvelopesByRange(
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// Non-finalized range of envelopes
|
|
40
|
-
if (endSlot >
|
|
43
|
+
if (endSlot > archiveMaxSlot) {
|
|
41
44
|
const headBlock = chain.forkChoice.getHead();
|
|
42
45
|
const headRoot = headBlock.blockRoot;
|
|
43
46
|
const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
|
|
@@ -46,7 +49,7 @@ export async function* onExecutionPayloadEnvelopesByRange(
|
|
|
46
49
|
for (let i = headChain.length - 1; i >= 0; i--) {
|
|
47
50
|
const block = headChain[i];
|
|
48
51
|
|
|
49
|
-
if (block.slot >= startSlot && block.slot < endSlot) {
|
|
52
|
+
if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
|
|
50
53
|
// Skip EMPTY blocks
|
|
51
54
|
if (block.payloadStatus !== PayloadStatus.FULL) {
|
|
52
55
|
continue;
|
package/src/node/notifier.ts
CHANGED
|
@@ -167,7 +167,14 @@ function getHeadExecutionInfo(
|
|
|
167
167
|
return [];
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
// A PayloadSeparated head is a gloas beacon block imported before its payload envelope
|
|
171
|
+
// arrives, in that case the exec-block row surfaces the inherited parent anchor (from the
|
|
172
|
+
// bid), which is already validated. Normalize to "valid" to avoid leaking internal
|
|
173
|
+
// fork-choice bookkeeping into the log. Once the payload envelope arrives and the FULL
|
|
174
|
+
// variant becomes head, executionStatus is Valid/Syncing naturally.
|
|
175
|
+
// TODO GLOAS: revisit once optimistic sync is implemented
|
|
176
|
+
const executionStatusStr =
|
|
177
|
+
headInfo.executionStatus === ExecutionStatus.PayloadSeparated ? "valid" : headInfo.executionStatus.toLowerCase();
|
|
171
178
|
|
|
172
179
|
// Add execution status to notifier only if head is on/post bellatrix
|
|
173
180
|
if (isStatePostBellatrix(headState) && headState.isExecutionStateType) {
|