@lodestar/beacon-node 1.43.0-dev.ca1fc40294 → 1.43.0-dev.dfb984e779
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 +26 -14
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +73 -77
- 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 -10
- 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 +14 -20
- 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/blocks/verifyExecutionPayloadEnvelope.d.ts +24 -0
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -0
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +76 -0
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -0
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +2 -11
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.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/errors/executionPayloadBid.d.ts +5 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +5 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +1 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -2
- package/lib/chain/forkChoice/index.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 +30 -10
- 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 +34 -13
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +11 -4
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -18
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.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/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +13 -1
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +11 -1
- package/lib/chain/validation/executionPayloadEnvelope.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/processor/gossipHandlers.js +4 -6
- 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 +92 -97
- package/src/chain/blocks/index.ts +44 -13
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
- package/src/chain/blocks/types.ts +14 -25
- 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/blocks/verifyExecutionPayloadEnvelope.ts +129 -0
- package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +9 -18
- package/src/chain/chain.ts +23 -3
- package/src/chain/errors/blockError.ts +4 -1
- package/src/chain/errors/executionPayloadBid.ts +6 -0
- package/src/chain/errors/executionPayloadEnvelope.ts +6 -0
- package/src/chain/forkChoice/index.ts +2 -2
- package/src/chain/interface.ts +7 -1
- package/src/chain/prepareNextSlot.ts +42 -12
- package/src/chain/produceBlock/produceBlockBody.ts +37 -11
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +22 -20
- package/src/chain/validation/block.ts +1 -0
- package/src/chain/validation/executionPayloadBid.ts +14 -0
- package/src/chain/validation/executionPayloadEnvelope.ts +12 -2
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/network/processor/gossipHandlers.ts +6 -6
- 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
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
ssz,
|
|
50
50
|
} from "@lodestar/types";
|
|
51
51
|
import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
|
|
52
|
-
import {ZERO_HASH_HEX} from "../../constants/index.js";
|
|
52
|
+
import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js";
|
|
53
53
|
import {numToQuantity} from "../../execution/engine/utils.js";
|
|
54
54
|
import {
|
|
55
55
|
IExecutionBuilder,
|
|
@@ -214,9 +214,19 @@ 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
|
+
let parentBlockHash = isExtendingPayload
|
|
218
219
|
? currentState.latestExecutionPayloadBid.blockHash
|
|
219
220
|
: currentState.latestExecutionPayloadBid.parentBlockHash;
|
|
221
|
+
// At gloas genesis the committed bid has no prior EL block to reference
|
|
222
|
+
// (`bid.parentBlockHash` is zero). Fall back to `bid.blockHash` (= eth1 genesis hash) so the
|
|
223
|
+
// FCU to the EL carries a valid head. Post-genesis bids always reference a non-zero parent.
|
|
224
|
+
if (isStatePostGloas(currentState) && byteArrayEquals(parentBlockHash, ZERO_HASH)) {
|
|
225
|
+
parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
|
|
226
|
+
}
|
|
227
|
+
const parentExecutionRequests = isExtendingPayload
|
|
228
|
+
? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
|
|
229
|
+
: ssz.electra.ExecutionRequests.defaultValue();
|
|
220
230
|
const prepareRes = await prepareExecutionPayload(
|
|
221
231
|
this,
|
|
222
232
|
this.logger,
|
|
@@ -226,7 +236,8 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
226
236
|
safeBlockHash,
|
|
227
237
|
finalizedBlockHash ?? ZERO_HASH_HEX,
|
|
228
238
|
currentState,
|
|
229
|
-
feeRecipient
|
|
239
|
+
feeRecipient,
|
|
240
|
+
parentExecutionRequests
|
|
230
241
|
);
|
|
231
242
|
|
|
232
243
|
const {prepType, payloadId} = prepareRes;
|
|
@@ -282,6 +293,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
282
293
|
gloasBody.signedExecutionPayloadBid = signedBid;
|
|
283
294
|
// TODO GLOAS: Get payload attestations from pool for previous slot
|
|
284
295
|
gloasBody.payloadAttestations = [];
|
|
296
|
+
gloasBody.parentExecutionRequests = parentExecutionRequests;
|
|
285
297
|
blockBody = gloasBody as AssembledBodyType<T>;
|
|
286
298
|
|
|
287
299
|
// Store execution payload data required to construct execution payload envelope later
|
|
@@ -618,7 +630,8 @@ export async function prepareExecutionPayload(
|
|
|
618
630
|
safeBlockHash: RootHex,
|
|
619
631
|
finalizedBlockHash: RootHex,
|
|
620
632
|
state: IBeaconStateViewBellatrix,
|
|
621
|
-
suggestedFeeRecipient: string
|
|
633
|
+
suggestedFeeRecipient: string,
|
|
634
|
+
parentExecutionRequests?: electra.ExecutionRequests
|
|
622
635
|
): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
|
|
623
636
|
const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
|
|
624
637
|
const prevRandao = state.getRandaoMix(state.epoch);
|
|
@@ -655,6 +668,7 @@ export async function prepareExecutionPayload(
|
|
|
655
668
|
parentBlockRoot,
|
|
656
669
|
parentBlockHash,
|
|
657
670
|
feeRecipient: suggestedFeeRecipient,
|
|
671
|
+
parentExecutionRequests,
|
|
658
672
|
});
|
|
659
673
|
|
|
660
674
|
payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
|
|
@@ -713,12 +727,14 @@ export function getPayloadAttributesForSSE(
|
|
|
713
727
|
parentBlockRoot,
|
|
714
728
|
parentBlockHash,
|
|
715
729
|
feeRecipient,
|
|
730
|
+
parentExecutionRequests,
|
|
716
731
|
}: {
|
|
717
732
|
prepareState: IBeaconStateViewBellatrix;
|
|
718
733
|
prepareSlot: Slot;
|
|
719
734
|
parentBlockRoot: Root;
|
|
720
735
|
parentBlockHash: Bytes32;
|
|
721
736
|
feeRecipient: string;
|
|
737
|
+
parentExecutionRequests?: electra.ExecutionRequests;
|
|
722
738
|
}
|
|
723
739
|
): SSEPayloadAttributes {
|
|
724
740
|
const payloadAttributes = preparePayloadAttributes(fork, chain, {
|
|
@@ -727,6 +743,7 @@ export function getPayloadAttributesForSSE(
|
|
|
727
743
|
parentBlockRoot,
|
|
728
744
|
parentBlockHash,
|
|
729
745
|
feeRecipient,
|
|
746
|
+
parentExecutionRequests,
|
|
730
747
|
});
|
|
731
748
|
|
|
732
749
|
let parentBlockNumber: number;
|
|
@@ -765,12 +782,14 @@ function preparePayloadAttributes(
|
|
|
765
782
|
parentBlockRoot,
|
|
766
783
|
parentBlockHash,
|
|
767
784
|
feeRecipient,
|
|
785
|
+
parentExecutionRequests,
|
|
768
786
|
}: {
|
|
769
787
|
prepareState: IBeaconStateViewBellatrix;
|
|
770
788
|
prepareSlot: Slot;
|
|
771
789
|
parentBlockRoot: Root;
|
|
772
790
|
parentBlockHash: Bytes32;
|
|
773
791
|
feeRecipient: string;
|
|
792
|
+
parentExecutionRequests?: electra.ExecutionRequests;
|
|
774
793
|
}
|
|
775
794
|
): SSEPayloadAttributes["payloadAttributes"] {
|
|
776
795
|
const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
|
|
@@ -788,13 +807,20 @@ function preparePayloadAttributes(
|
|
|
788
807
|
|
|
789
808
|
if (isStatePostGloas(prepareState)) {
|
|
790
809
|
const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
810
|
+
if (isExtendingPayload) {
|
|
811
|
+
if (parentExecutionRequests === undefined) {
|
|
812
|
+
throw new Error("parentExecutionRequests required when extending full parent");
|
|
813
|
+
}
|
|
814
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
815
|
+
prepareState.getExpectedWithdrawalsForFullParent(parentExecutionRequests);
|
|
816
|
+
} else {
|
|
817
|
+
// When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
|
|
818
|
+
// already deducted from CL balances but never credited on the EL (the envelope
|
|
819
|
+
// was not delivered). The next payload must carry those same withdrawals to
|
|
820
|
+
// restore CL/EL consistency, otherwise validators permanently lose that balance.
|
|
821
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
822
|
+
prepareState.payloadExpectedWithdrawals;
|
|
823
|
+
}
|
|
798
824
|
} else {
|
|
799
825
|
// withdrawals logic is now fork aware as it changes on electra fork post capella
|
|
800
826
|
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
2
2
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
3
|
-
import {RootHex} from "@lodestar/types";
|
|
3
|
+
import {RootHex, Slot} from "@lodestar/types";
|
|
4
4
|
import {Logger} from "@lodestar/utils";
|
|
5
5
|
import {Metrics} from "../../metrics/metrics.js";
|
|
6
6
|
import {SerializedCache} from "../../util/serializedCache.js";
|
|
@@ -21,8 +21,15 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
21
21
|
/**
|
|
22
22
|
* Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
|
|
23
23
|
*
|
|
24
|
-
* Created during block import when a block is processed.
|
|
25
|
-
*
|
|
24
|
+
* Created during block import when a block is processed. Two pruning paths:
|
|
25
|
+
* - `prepareNextSlot` calls `pruneBelow(headParentSlot)` every slot once the head we'll build
|
|
26
|
+
* on is known.
|
|
27
|
+
* - `onFinalized` calls `pruneBelow(finalizedSlot)` on every finalization for bulk cleanup.
|
|
28
|
+
*
|
|
29
|
+
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
|
|
30
|
+
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
|
|
31
|
+
* transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
|
|
32
|
+
* ticks; subsequent ticks settle it back.
|
|
26
33
|
*/
|
|
27
34
|
export class SeenPayloadEnvelopeInput {
|
|
28
35
|
private readonly chainEvents: ChainEventEmitter;
|
|
@@ -58,16 +65,7 @@ export class SeenPayloadEnvelopeInput {
|
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
private onFinalized = (checkpoint: CheckpointWithHex): void => {
|
|
61
|
-
|
|
62
|
-
const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
|
|
63
|
-
let deletedCount = 0;
|
|
64
|
-
for (const [, input] of this.payloadInputs) {
|
|
65
|
-
if (input.slot < finalizedSlot) {
|
|
66
|
-
this.evictPayloadInput(input);
|
|
67
|
-
deletedCount++;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
this.logger?.debug("SeenPayloadEnvelopeInput.onFinalized deleted cached entries", {deletedCount});
|
|
68
|
+
this.pruneBelow(computeStartSlotAtEpoch(checkpoint.epoch));
|
|
71
69
|
};
|
|
72
70
|
|
|
73
71
|
add(props: CreateFromBlockProps): PayloadEnvelopeInput {
|
|
@@ -88,17 +86,21 @@ export class SeenPayloadEnvelopeInput {
|
|
|
88
86
|
return this.payloadInputs.get(blockRootHex)?.hasPayloadEnvelope() ?? false;
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
prune(blockRootHex: RootHex): void {
|
|
92
|
-
const payloadInput = this.payloadInputs.get(blockRootHex);
|
|
93
|
-
if (payloadInput) {
|
|
94
|
-
this.evictPayloadInput(payloadInput);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
89
|
size(): number {
|
|
99
90
|
return this.payloadInputs.size;
|
|
100
91
|
}
|
|
101
92
|
|
|
93
|
+
pruneBelow(slot: Slot): void {
|
|
94
|
+
let deletedCount = 0;
|
|
95
|
+
for (const [, input] of this.payloadInputs) {
|
|
96
|
+
if (input.slot < slot) {
|
|
97
|
+
this.evictPayloadInput(input);
|
|
98
|
+
deletedCount++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.logger?.debug("SeenPayloadEnvelopeInput.pruneBelow deleted entries", {slot, deletedCount});
|
|
102
|
+
}
|
|
103
|
+
|
|
102
104
|
private evictPayloadInput(payloadInput: PayloadEnvelopeInput): void {
|
|
103
105
|
this.serializedCache.delete(payloadInput.getSerializedCacheKeys());
|
|
104
106
|
this.payloadInputs.delete(payloadInput.blockRootHex);
|
|
@@ -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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {PublicKey} from "@chainsafe/blst";
|
|
2
2
|
import {
|
|
3
|
+
computeEpochAtSlot,
|
|
3
4
|
createSingleSignatureSetFromComponents,
|
|
4
5
|
getExecutionPayloadBidSigningRoot,
|
|
5
6
|
isActiveBuilder,
|
|
@@ -76,6 +77,19 @@ async function validateExecutionPayloadBid(
|
|
|
76
77
|
// `SignedProposerPreferences` associated with `bid.slot`.
|
|
77
78
|
// TODO GLOAS: Implement this along with proposer preference
|
|
78
79
|
|
|
80
|
+
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
81
|
+
// consensus layer -- i.e. validate that
|
|
82
|
+
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
83
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
84
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
85
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
86
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
87
|
+
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
88
|
+
blobKzgCommitmentsLen,
|
|
89
|
+
commitmentLimit: maxBlobsPerBlock,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
// [IGNORE] this is the first signed bid seen with a valid signature from the given builder for this slot.
|
|
80
94
|
if (chain.seenExecutionPayloadBids.isKnown(bid.slot, bid.builderIndex)) {
|
|
81
95
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
getExecutionPayloadEnvelopeSignatureSet,
|
|
5
5
|
isStatePostGloas,
|
|
6
6
|
} from "@lodestar/state-transition";
|
|
7
|
-
import {gloas} from "@lodestar/types";
|
|
8
|
-
import {toRootHex} from "@lodestar/utils";
|
|
7
|
+
import {gloas, ssz} from "@lodestar/types";
|
|
8
|
+
import {byteArrayEquals, toRootHex} from "@lodestar/utils";
|
|
9
9
|
import {ExecutionPayloadEnvelopeError, ExecutionPayloadEnvelopeErrorCode, GossipAction} from "../errors/index.js";
|
|
10
10
|
import {IBeaconChain} from "../index.js";
|
|
11
11
|
import {RegenCaller} from "../regen/index.js";
|
|
@@ -107,6 +107,16 @@ async function validateExecutionPayloadEnvelope(
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// [REJECT] `hash_tree_root(envelope.execution_requests) == bid.execution_requests_root`
|
|
111
|
+
const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
|
|
112
|
+
if (!byteArrayEquals(requestsRoot, payloadInput.getBid().executionRequestsRoot)) {
|
|
113
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
114
|
+
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_ROOT_MISMATCH,
|
|
115
|
+
envelopeRequestsRoot: toRootHex(requestsRoot),
|
|
116
|
+
bidRequestsRoot: toRootHex(payloadInput.getBid().executionRequestsRoot),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
110
120
|
// Get the block state to verify the builder's signature.
|
|
111
121
|
const blockState = await chain.regen
|
|
112
122
|
.getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
|
|
@@ -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,
|
|
@@ -1057,10 +1060,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1057
1060
|
const signedEnvelope = sszDeserialize(topic, serializedData);
|
|
1058
1061
|
const envelope = signedEnvelope.message;
|
|
1059
1062
|
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
// the problem now is we cannot create a PayloadEnvelopeInput without the beacon block being known, we need at least the proposer index
|
|
1063
|
-
// we can achieve that by looking into the EpochCache
|
|
1063
|
+
// unlike BlockInput, we send the envelope into UnknownBlockInput sync
|
|
1064
|
+
// inside the sync it'll reconcile into PayloadEnvelopeInput and share the same cache with gossip
|
|
1064
1065
|
try {
|
|
1065
1066
|
await validateGossipExecutionPayloadEnvelope(chain, signedEnvelope);
|
|
1066
1067
|
} catch (e) {
|
|
@@ -1069,7 +1070,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1069
1070
|
const slot = signedEnvelope.message.payload.slotNumber;
|
|
1070
1071
|
logger.debug("Gossip envelope has error", {slot, root: toRootHex(beaconBlockRoot), code: e.type.code});
|
|
1071
1072
|
if (e.type.code === ExecutionPayloadEnvelopeErrorCode.BLOCK_ROOT_UNKNOWN) {
|
|
1072
|
-
// TODO GLOAS: UnknownBlockSync to handle this
|
|
1073
1073
|
chain.emitter.emit(ChainEvent.envelopeUnknownBlock, {
|
|
1074
1074
|
envelope: signedEnvelope,
|
|
1075
1075
|
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) {
|