@lodestar/beacon-node 1.42.0-dev.4411584fd8 → 1.42.0-dev.70938e1eec
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 +35 -16
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/chain/blocks/blockInput/types.d.ts +3 -3
- package/lib/chain/blocks/blockInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +18 -2
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +48 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -0
- package/lib/chain/blocks/importExecutionPayload.js +159 -0
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/index.d.ts +3 -0
- package/lib/chain/blocks/payloadEnvelopeInput/index.d.ts.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/index.js +3 -0
- package/lib/chain/blocks/payloadEnvelopeInput/index.js.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +80 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +248 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +29 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.js +11 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +15 -0
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -0
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +46 -0
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -0
- package/lib/chain/blocks/types.d.ts +7 -0
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts +12 -0
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -0
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +40 -0
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -0
- package/lib/chain/chain.d.ts +7 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +28 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +12 -2
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +3 -1
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +0 -10
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +6 -3
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +6 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +2 -0
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +2 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +1 -1
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +1 -1
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +2 -2
- package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +38 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +76 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -0
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +30 -19
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +2 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +3 -0
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +28 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +74 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +727 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/processor/extractSlotRootFns.d.ts.map +1 -1
- package/lib/network/processor/extractSlotRootFns.js +14 -4
- package/lib/network/processor/extractSlotRootFns.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +31 -3
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/util/sszBytes.d.ts +4 -1
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +69 -12
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +36 -17
- package/src/chain/blocks/blockInput/types.ts +3 -3
- package/src/chain/blocks/importBlock.ts +36 -2
- package/src/chain/blocks/importExecutionPayload.ts +241 -0
- package/src/chain/blocks/payloadEnvelopeInput/index.ts +2 -0
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +336 -0
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +33 -0
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +61 -0
- package/src/chain/blocks/types.ts +8 -0
- package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +55 -0
- package/src/chain/chain.ts +37 -3
- package/src/chain/errors/executionPayloadEnvelope.ts +6 -2
- package/src/chain/forkChoice/index.ts +0 -10
- package/src/chain/interface.ts +6 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +6 -1
- package/src/chain/regen/interface.ts +2 -0
- package/src/chain/seenCache/index.ts +1 -1
- package/src/chain/seenCache/seenGossipBlockInput.ts +2 -2
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +106 -0
- package/src/chain/validation/executionPayloadEnvelope.ts +38 -25
- package/src/chain/validatorMonitor.ts +10 -0
- package/src/metrics/metrics/lodestar.ts +77 -0
- package/src/network/processor/extractSlotRootFns.ts +18 -5
- package/src/network/processor/gossipHandlers.ts +37 -1
- package/src/util/sszBytes.ts +90 -10
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.d.ts +0 -15
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.d.ts.map +0 -1
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.js +0 -28
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.js.map +0 -1
- package/src/chain/seenCache/seenExecutionPayloadEnvelope.ts +0 -34
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from "@lodestar/types";
|
|
36
36
|
import {fromHex, sleep, toHex, toRootHex} from "@lodestar/utils";
|
|
37
37
|
import {BlockInputSource, isBlockInputBlobs, isBlockInputColumns} from "../../../../chain/blocks/blockInput/index.js";
|
|
38
|
+
import {PayloadEnvelopeInputSource} from "../../../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
38
39
|
import {ImportBlockOpts} from "../../../../chain/blocks/types.js";
|
|
39
40
|
import {verifyBlocksInEpoch} from "../../../../chain/blocks/verifyBlock.js";
|
|
40
41
|
import {BeaconChain} from "../../../../chain/chain.js";
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
ProduceFullGloas,
|
|
49
50
|
} from "../../../../chain/produceBlock/index.js";
|
|
50
51
|
import {validateGossipBlock} from "../../../../chain/validation/block.js";
|
|
52
|
+
import {validateApiExecutionPayloadEnvelope} from "../../../../chain/validation/executionPayloadEnvelope.js";
|
|
51
53
|
import {OpSource} from "../../../../chain/validatorMonitor.js";
|
|
52
54
|
import {
|
|
53
55
|
computePreFuluKzgCommitmentsInclusionProof,
|
|
@@ -659,6 +661,8 @@ export function getBeaconBlockApi({
|
|
|
659
661
|
throw new ApiError(400, `Envelope slot ${slot} does not match block slot ${block.slot}`);
|
|
660
662
|
}
|
|
661
663
|
|
|
664
|
+
await validateApiExecutionPayloadEnvelope(chain, signedExecutionPayloadEnvelope);
|
|
665
|
+
|
|
662
666
|
const isSelfBuild = envelope.builderIndex === BUILDER_INDEX_SELF_BUILD;
|
|
663
667
|
let dataColumnSidecars: gloas.DataColumnSidecars = [];
|
|
664
668
|
|
|
@@ -676,6 +680,7 @@ export function getBeaconBlockApi({
|
|
|
676
680
|
}
|
|
677
681
|
|
|
678
682
|
if (cachedResult.cells && cachedResult.blobsBundle.commitments.length > 0) {
|
|
683
|
+
const timer = metrics?.peerDas.dataColumnSidecarComputationTime.startTimer();
|
|
679
684
|
const cellsAndProofs = cachedResult.cells.map((rowCells, rowIndex) => ({
|
|
680
685
|
cells: rowCells,
|
|
681
686
|
proofs: cachedResult.blobsBundle.proofs.slice(
|
|
@@ -685,24 +690,42 @@ export function getBeaconBlockApi({
|
|
|
685
690
|
}));
|
|
686
691
|
|
|
687
692
|
dataColumnSidecars = getDataColumnSidecarsForGloas(slot, envelope.beaconBlockRoot, cellsAndProofs);
|
|
693
|
+
timer?.();
|
|
688
694
|
}
|
|
689
695
|
} else {
|
|
690
696
|
// TODO GLOAS: will this api be used by builders or only for self-building?
|
|
691
697
|
}
|
|
692
698
|
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
699
|
+
// If called near a slot boundary (e.g. late in slot N-1), hold briefly so gossip aligns with slot N.
|
|
700
|
+
const msToBlockSlot = computeTimeAtSlot(config, slot, chain.genesisTime) * 1000 - Date.now();
|
|
701
|
+
if (msToBlockSlot <= MAX_API_CLOCK_DISPARITY_MS && msToBlockSlot > 0) {
|
|
702
|
+
await sleep(msToBlockSlot);
|
|
703
|
+
}
|
|
697
704
|
|
|
698
|
-
// TODO GLOAS:
|
|
699
|
-
//
|
|
705
|
+
// TODO GLOAS: if block and payload are submitted in parallel, payloadInput may not yet exist.
|
|
706
|
+
// A queuing mechanism is needed to handle this case. See https://github.com/ChainSafe/lodestar/issues/8915
|
|
707
|
+
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
|
|
708
|
+
if (!payloadInput) {
|
|
709
|
+
throw new ApiError(404, `PayloadEnvelopeInput not found for block root ${blockRootHex}`);
|
|
710
|
+
}
|
|
700
711
|
|
|
701
|
-
|
|
702
|
-
|
|
712
|
+
payloadInput.addPayloadEnvelope({
|
|
713
|
+
envelope: signedExecutionPayloadEnvelope,
|
|
714
|
+
source: PayloadEnvelopeInputSource.api,
|
|
715
|
+
seenTimestampSec,
|
|
716
|
+
peerIdStr: undefined,
|
|
717
|
+
});
|
|
703
718
|
|
|
704
|
-
|
|
705
|
-
|
|
719
|
+
if (dataColumnSidecars.length > 0) {
|
|
720
|
+
for (const columnSidecar of dataColumnSidecars) {
|
|
721
|
+
payloadInput.addColumn({
|
|
722
|
+
columnSidecar,
|
|
723
|
+
source: PayloadEnvelopeInputSource.api,
|
|
724
|
+
seenTimestampSec,
|
|
725
|
+
peerIdStr: undefined,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
706
729
|
|
|
707
730
|
const valLogMeta = {
|
|
708
731
|
slot,
|
|
@@ -712,23 +735,19 @@ export function getBeaconBlockApi({
|
|
|
712
735
|
dataColumns: dataColumnSidecars.length,
|
|
713
736
|
};
|
|
714
737
|
|
|
715
|
-
// If called near a slot boundary (e.g. late in slot N-1), hold briefly so gossip aligns with slot N.
|
|
716
|
-
const msToBlockSlot = computeTimeAtSlot(config, slot, chain.genesisTime) * 1000 - Date.now();
|
|
717
|
-
if (msToBlockSlot <= MAX_API_CLOCK_DISPARITY_MS && msToBlockSlot > 0) {
|
|
718
|
-
await sleep(msToBlockSlot);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
738
|
const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
|
|
722
739
|
metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.api}, delaySec);
|
|
740
|
+
chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.api, delaySec, signedExecutionPayloadEnvelope);
|
|
723
741
|
|
|
724
742
|
chain.logger.info("Publishing execution payload envelope", valLogMeta);
|
|
725
743
|
|
|
726
|
-
// Publish envelope and data columns
|
|
727
744
|
const publishPromises = [
|
|
728
745
|
// Gossip the signed execution payload envelope first
|
|
729
746
|
() => network.publishSignedExecutionPayloadEnvelope(signedExecutionPayloadEnvelope),
|
|
730
747
|
// For self-builds, publish all data column sidecars
|
|
731
748
|
...dataColumnSidecars.map((dataColumnSidecar) => () => network.publishDataColumnSidecar(dataColumnSidecar)),
|
|
749
|
+
// Import execution payload. Signature already verified above
|
|
750
|
+
() => chain.processExecutionPayload(payloadInput, {validSignature: true}),
|
|
732
751
|
];
|
|
733
752
|
|
|
734
753
|
const sentPeersArr = await promiseAllMaybeAsync<number | void>(publishPromises);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ForkName} from "@lodestar/params";
|
|
2
|
-
import {ColumnIndex, RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
|
|
2
|
+
import {ColumnIndex, DataColumnSidecars, RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
|
|
3
3
|
import {VersionedHashes} from "../../../execution/index.js";
|
|
4
4
|
|
|
5
5
|
export enum DAType {
|
|
@@ -107,9 +107,9 @@ export type MissingColumnMeta = {
|
|
|
107
107
|
export interface IDataColumnsInput {
|
|
108
108
|
readonly slot: Slot;
|
|
109
109
|
readonly blockRootHex: string;
|
|
110
|
-
getCustodyColumns():
|
|
110
|
+
getCustodyColumns(): DataColumnSidecars;
|
|
111
111
|
hasComputedAllData(): boolean;
|
|
112
|
-
waitForComputedAllData(timeout: number, signal?: AbortSignal): Promise<
|
|
112
|
+
waitForComputedAllData(timeout: number, signal?: AbortSignal): Promise<DataColumnSidecars>;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
@@ -9,7 +9,14 @@ import {
|
|
|
9
9
|
getSafeExecutionBlockHash,
|
|
10
10
|
isGloasBlock,
|
|
11
11
|
} from "@lodestar/fork-choice";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ForkPostAltair,
|
|
14
|
+
ForkPostElectra,
|
|
15
|
+
ForkPostGloas,
|
|
16
|
+
ForkSeq,
|
|
17
|
+
MAX_SEED_LOOKAHEAD,
|
|
18
|
+
SLOTS_PER_EPOCH,
|
|
19
|
+
} from "@lodestar/params";
|
|
13
20
|
import {
|
|
14
21
|
CachedBeaconStateAltair,
|
|
15
22
|
EpochCache,
|
|
@@ -21,7 +28,17 @@ import {
|
|
|
21
28
|
isStartSlotOfEpoch,
|
|
22
29
|
isStateValidatorsNodesPopulated,
|
|
23
30
|
} from "@lodestar/state-transition";
|
|
24
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
Attestation,
|
|
33
|
+
BeaconBlock,
|
|
34
|
+
SignedBeaconBlock,
|
|
35
|
+
altair,
|
|
36
|
+
capella,
|
|
37
|
+
electra,
|
|
38
|
+
isGloasBeaconBlock,
|
|
39
|
+
phase0,
|
|
40
|
+
ssz,
|
|
41
|
+
} from "@lodestar/types";
|
|
25
42
|
import {isErrorAborted, toRootHex} from "@lodestar/utils";
|
|
26
43
|
import {ZERO_HASH_HEX} from "../../constants/index.js";
|
|
27
44
|
import {callInNextEventLoop} from "../../util/eventLoop.js";
|
|
@@ -123,6 +140,23 @@ export async function importBlock(
|
|
|
123
140
|
// processState manages both block state and payload state variants together for memory/disk management
|
|
124
141
|
this.regen.processBlockState(blockRootHex, postState);
|
|
125
142
|
|
|
143
|
+
// For Gloas blocks, create PayloadEnvelopeInput so it's available for later payload import
|
|
144
|
+
if (fork >= ForkSeq.gloas) {
|
|
145
|
+
this.seenPayloadEnvelopeInputCache.add({
|
|
146
|
+
blockRootHex,
|
|
147
|
+
block: block as SignedBeaconBlock<ForkPostGloas>,
|
|
148
|
+
sampledColumns: this.custodyConfig.sampledColumns,
|
|
149
|
+
custodyColumns: this.custodyConfig.custodyColumns,
|
|
150
|
+
timeCreatedSec: fullyVerifiedBlock.seenTimestampSec,
|
|
151
|
+
});
|
|
152
|
+
this.logger.debug("Created PayloadEnvelopeInput for block", {
|
|
153
|
+
slot: blockSlot,
|
|
154
|
+
root: blockRootHex,
|
|
155
|
+
source: source.source,
|
|
156
|
+
...(opts.seenTimestampSec !== undefined ? {recvToImport: Date.now() / 1000 - opts.seenTimestampSec} : {}),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
this.metrics?.importBlock.bySource.inc({source: source.source});
|
|
127
161
|
this.logger.verbose("Added block to forkchoice and state cache", {slot: blockSlot, root: blockRootHex});
|
|
128
162
|
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import {routes} from "@lodestar/api";
|
|
2
|
+
import {ForkName} from "@lodestar/params";
|
|
3
|
+
import {
|
|
4
|
+
BeaconStateView,
|
|
5
|
+
CachedBeaconStateGloas,
|
|
6
|
+
getExecutionPayloadEnvelopeSignatureSet,
|
|
7
|
+
} from "@lodestar/state-transition";
|
|
8
|
+
import {processExecutionPayloadEnvelope} from "@lodestar/state-transition/block";
|
|
9
|
+
import {byteArrayEquals, fromHex, toRootHex} from "@lodestar/utils";
|
|
10
|
+
import {ExecutionPayloadStatus} from "../../execution/index.js";
|
|
11
|
+
import {isQueueErrorAborted} from "../../util/queue/index.js";
|
|
12
|
+
import {BeaconChain} from "../chain.js";
|
|
13
|
+
import {RegenCaller} from "../regen/interface.js";
|
|
14
|
+
import {PayloadEnvelopeInput} from "../seenCache/seenPayloadEnvelopeInput.js";
|
|
15
|
+
import {ImportPayloadOpts} from "./types.js";
|
|
16
|
+
|
|
17
|
+
const EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS = 64;
|
|
18
|
+
|
|
19
|
+
export enum PayloadErrorCode {
|
|
20
|
+
EXECUTION_ENGINE_INVALID = "PAYLOAD_ERROR_EXECUTION_ENGINE_INVALID",
|
|
21
|
+
EXECUTION_ENGINE_ERROR = "PAYLOAD_ERROR_EXECUTION_ENGINE_ERROR",
|
|
22
|
+
BLOCK_NOT_IN_FORK_CHOICE = "PAYLOAD_ERROR_BLOCK_NOT_IN_FORK_CHOICE",
|
|
23
|
+
STATE_TRANSITION_ERROR = "PAYLOAD_ERROR_STATE_TRANSITION_ERROR",
|
|
24
|
+
INVALID_SIGNATURE = "PAYLOAD_ERROR_INVALID_SIGNATURE",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type PayloadErrorType =
|
|
28
|
+
| {
|
|
29
|
+
code: PayloadErrorCode.EXECUTION_ENGINE_INVALID;
|
|
30
|
+
execStatus: ExecutionPayloadStatus;
|
|
31
|
+
errorMessage: string;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
code: PayloadErrorCode.EXECUTION_ENGINE_ERROR;
|
|
35
|
+
execStatus: ExecutionPayloadStatus;
|
|
36
|
+
errorMessage: string;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
code: PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE;
|
|
40
|
+
blockRootHex: string;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
code: PayloadErrorCode.STATE_TRANSITION_ERROR;
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
code: PayloadErrorCode.INVALID_SIGNATURE;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export class PayloadError extends Error {
|
|
51
|
+
type: PayloadErrorType;
|
|
52
|
+
|
|
53
|
+
constructor(type: PayloadErrorType, message?: string) {
|
|
54
|
+
super(message ?? type.code);
|
|
55
|
+
this.type = type;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Import an execution payload envelope after all data is available.
|
|
61
|
+
*
|
|
62
|
+
* This function:
|
|
63
|
+
* 1. Gets the ProtoBlock from fork choice
|
|
64
|
+
* 2. Applies write-queue backpressure (waitForSpace) early, before verification
|
|
65
|
+
* 3. Regenerates the block state
|
|
66
|
+
* 4. Runs EL verification (notifyNewPayload) in parallel with signature verification and processExecutionPayloadEnvelope
|
|
67
|
+
* 5. Persists verified payload envelope to hot DB
|
|
68
|
+
* 6. Updates fork choice
|
|
69
|
+
* 7. Caches the post-execution payload state
|
|
70
|
+
* 8. Records metrics for column sources
|
|
71
|
+
*
|
|
72
|
+
*/
|
|
73
|
+
export async function importExecutionPayload(
|
|
74
|
+
this: BeaconChain,
|
|
75
|
+
payloadInput: PayloadEnvelopeInput,
|
|
76
|
+
opts: ImportPayloadOpts = {}
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const envelope = payloadInput.getPayloadEnvelope();
|
|
79
|
+
const blockRootHex = payloadInput.blockRootHex;
|
|
80
|
+
|
|
81
|
+
// 1. Get ProtoBlock for parent root lookup
|
|
82
|
+
const protoBlock = this.forkChoice.getBlockHexDefaultStatus(blockRootHex);
|
|
83
|
+
if (!protoBlock) {
|
|
84
|
+
throw new PayloadError({
|
|
85
|
+
code: PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE,
|
|
86
|
+
blockRootHex,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 2. Apply backpressure from the write queue early, before doing verification work.
|
|
91
|
+
// The actual DB write is deferred until after verification succeeds.
|
|
92
|
+
await this.unfinalizedPayloadEnvelopeWrites.waitForSpace();
|
|
93
|
+
|
|
94
|
+
// 3. Get pre-state for processExecutionPayloadEnvelope
|
|
95
|
+
// We need the block state (post-block, pre-payload) to process the envelope
|
|
96
|
+
const blockState = (await this.regen.getBlockSlotState(
|
|
97
|
+
protoBlock,
|
|
98
|
+
protoBlock.slot,
|
|
99
|
+
{dontTransferCache: true},
|
|
100
|
+
RegenCaller.processBlock
|
|
101
|
+
)) as CachedBeaconStateGloas;
|
|
102
|
+
|
|
103
|
+
// 4. Run verification steps in parallel
|
|
104
|
+
// Note: No data availability check needed here - importExecutionPayload is only
|
|
105
|
+
// called when payloadInput.isComplete() is true, so all data is already available.
|
|
106
|
+
const [execResult, signatureValid, postPayloadResult] = await Promise.all([
|
|
107
|
+
this.executionEngine.notifyNewPayload(
|
|
108
|
+
ForkName.gloas,
|
|
109
|
+
envelope.message.payload,
|
|
110
|
+
payloadInput.getVersionedHashes(),
|
|
111
|
+
fromHex(protoBlock.parentRoot),
|
|
112
|
+
envelope.message.executionRequests
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
opts.validSignature === true
|
|
116
|
+
? Promise.resolve(true)
|
|
117
|
+
: (async () => {
|
|
118
|
+
const signatureSet = getExecutionPayloadEnvelopeSignatureSet(
|
|
119
|
+
this.config,
|
|
120
|
+
blockState.epochCtx.pubkeyCache,
|
|
121
|
+
new BeaconStateView(blockState),
|
|
122
|
+
envelope,
|
|
123
|
+
payloadInput.proposerIndex
|
|
124
|
+
);
|
|
125
|
+
return this.bls.verifySignatureSets([signatureSet]);
|
|
126
|
+
})(),
|
|
127
|
+
|
|
128
|
+
// Signature verified separately above.
|
|
129
|
+
// State root check is done separately below with better error typing (matching block pipeline pattern).
|
|
130
|
+
(async () => {
|
|
131
|
+
try {
|
|
132
|
+
return {
|
|
133
|
+
postPayloadState: processExecutionPayloadEnvelope(blockState, envelope, {
|
|
134
|
+
verifySignature: false,
|
|
135
|
+
verifyStateRoot: false,
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
} catch (e) {
|
|
139
|
+
throw new PayloadError(
|
|
140
|
+
{
|
|
141
|
+
code: PayloadErrorCode.STATE_TRANSITION_ERROR,
|
|
142
|
+
message: (e as Error).message,
|
|
143
|
+
},
|
|
144
|
+
`State transition error: ${(e as Error).message}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
})(),
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
// 4b. Check signature verification result
|
|
151
|
+
if (!signatureValid) {
|
|
152
|
+
throw new PayloadError({code: PayloadErrorCode.INVALID_SIGNATURE});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 5. Handle EL response
|
|
156
|
+
switch (execResult.status) {
|
|
157
|
+
case ExecutionPayloadStatus.VALID:
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case ExecutionPayloadStatus.INVALID:
|
|
161
|
+
throw new PayloadError({
|
|
162
|
+
code: PayloadErrorCode.EXECUTION_ENGINE_INVALID,
|
|
163
|
+
execStatus: execResult.status,
|
|
164
|
+
errorMessage: execResult.validationError ?? "",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
case ExecutionPayloadStatus.ACCEPTED:
|
|
168
|
+
case ExecutionPayloadStatus.SYNCING:
|
|
169
|
+
// TODO GLOAS: Handle optimistic import for payload - for now treat as error
|
|
170
|
+
throw new PayloadError({
|
|
171
|
+
code: PayloadErrorCode.EXECUTION_ENGINE_ERROR,
|
|
172
|
+
execStatus: execResult.status,
|
|
173
|
+
errorMessage: execResult.validationError ?? "EL syncing, payload not yet validated",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
case ExecutionPayloadStatus.INVALID_BLOCK_HASH:
|
|
177
|
+
case ExecutionPayloadStatus.ELERROR:
|
|
178
|
+
case ExecutionPayloadStatus.UNAVAILABLE:
|
|
179
|
+
throw new PayloadError({
|
|
180
|
+
code: PayloadErrorCode.EXECUTION_ENGINE_ERROR,
|
|
181
|
+
execStatus: execResult.status,
|
|
182
|
+
errorMessage: execResult.validationError ?? "",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 5b. Verify envelope state root matches post-state
|
|
187
|
+
const postPayloadState = postPayloadResult.postPayloadState;
|
|
188
|
+
const postPayloadStateRoot = postPayloadState.hashTreeRoot();
|
|
189
|
+
if (!byteArrayEquals(envelope.message.stateRoot, postPayloadStateRoot)) {
|
|
190
|
+
throw new PayloadError({
|
|
191
|
+
code: PayloadErrorCode.STATE_TRANSITION_ERROR,
|
|
192
|
+
message: `Envelope state root mismatch expected=${toRootHex(envelope.message.stateRoot)} actual=${toRootHex(postPayloadStateRoot)}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 5c. Persist payload envelope to hot DB (performed asynchronously to avoid blocking)
|
|
197
|
+
this.unfinalizedPayloadEnvelopeWrites.push(payloadInput).catch((e) => {
|
|
198
|
+
if (!isQueueErrorAborted(e)) {
|
|
199
|
+
this.logger.error(
|
|
200
|
+
"Error pushing payload envelope to unfinalized write queue",
|
|
201
|
+
{slot: payloadInput.slot, root: blockRootHex},
|
|
202
|
+
e as Error
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 6. Update fork choice
|
|
208
|
+
this.forkChoice.onExecutionPayload(
|
|
209
|
+
blockRootHex,
|
|
210
|
+
payloadInput.getBlockHashHex(),
|
|
211
|
+
envelope.message.payload.blockNumber,
|
|
212
|
+
toRootHex(postPayloadStateRoot)
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// 7. Cache payload state
|
|
216
|
+
// TODO GLOAS: Enable when PR #8868 merged (adds processPayloadState)
|
|
217
|
+
// this.regen.processPayloadState(postPayloadState);
|
|
218
|
+
// if epoch boundary also call
|
|
219
|
+
// this.regen.addCheckpointState(cp, checkpointState, true);
|
|
220
|
+
|
|
221
|
+
// 8. Record metrics for payload envelope and column sources
|
|
222
|
+
this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
|
|
223
|
+
for (const {source} of payloadInput.getSampledColumnsWithSource()) {
|
|
224
|
+
this.metrics?.importPayload.columnsBySource.inc({source});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.logger.verbose("Execution payload imported", {
|
|
228
|
+
slot: payloadInput.slot,
|
|
229
|
+
root: blockRootHex,
|
|
230
|
+
blockHash: payloadInput.getBlockHashHex(),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// 9. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
|
|
234
|
+
const currentSlot = this.clock.currentSlot;
|
|
235
|
+
if (currentSlot - payloadInput.slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
|
|
236
|
+
this.emitter.emit(routes.events.EventType.executionPayloadAvailable, {
|
|
237
|
+
slot: payloadInput.slot,
|
|
238
|
+
blockRoot: blockRootHex,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|