@lodestar/beacon-node 1.51.0-dev.e21949cfcf → 1.51.0-dev.e2bf489dd3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chain/GetBlobsTracker.d.ts +2 -2
- package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
- package/lib/chain/GetBlobsTracker.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +17 -47
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +24 -3
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +2 -2
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +4 -0
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/chain.d.ts +7 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +53 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts +11 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +11 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/forkChoiceExecutionEngine.d.ts +53 -0
- package/lib/chain/forkChoiceExecutionEngine.d.ts.map +1 -0
- package/lib/chain/forkChoiceExecutionEngine.js +121 -0
- package/lib/chain/forkChoiceExecutionEngine.js.map +1 -0
- 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/produceBlock/produceBlockBody.d.ts +3 -2
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +1 -1
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/queued.d.ts +1 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +5 -3
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/execution/engine/http.d.ts +2 -2
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +5 -4
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +16 -2
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/jsonRpcTransport.d.ts +2 -2
- package/lib/execution/engine/jsonRpcTransport.d.ts.map +1 -1
- package/lib/execution/engine/jsonRpcTransport.js +1 -1
- package/lib/execution/engine/jsonRpcTransport.js.map +1 -1
- package/lib/execution/engine/restTransport.d.ts +2 -2
- package/lib/execution/engine/restTransport.d.ts.map +1 -1
- package/lib/execution/engine/restTransport.js.map +1 -1
- package/lib/execution/engine/transport.d.ts +1 -12
- package/lib/execution/engine/transport.d.ts.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/network/network.d.ts +2 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +42 -17
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +3 -0
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts +6 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +29 -3
- package/lib/network/processor/index.js.map +1 -1
- package/lib/util/execution.d.ts +4 -4
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js.map +1 -1
- package/package.json +16 -16
- package/src/chain/GetBlobsTracker.ts +2 -2
- package/src/chain/blocks/importBlock.ts +35 -67
- package/src/chain/blocks/importExecutionPayload.ts +42 -13
- package/src/chain/blocks/index.ts +5 -2
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +4 -1
- package/src/chain/chain.ts +62 -3
- package/src/chain/forkChoice/index.ts +11 -0
- package/src/chain/forkChoiceExecutionEngine.ts +197 -0
- package/src/chain/interface.ts +3 -2
- package/src/chain/produceBlock/produceBlockBody.ts +4 -2
- package/src/chain/regen/interface.ts +1 -1
- package/src/chain/regen/queued.ts +6 -4
- package/src/execution/engine/http.ts +12 -13
- package/src/execution/engine/interface.ts +18 -2
- package/src/execution/engine/jsonRpcTransport.ts +10 -2
- package/src/execution/engine/restTransport.ts +3 -1
- package/src/execution/engine/transport.ts +8 -14
- package/src/index.ts +1 -1
- package/src/network/network.ts +40 -21
- package/src/network/processor/gossipHandlers.ts +3 -0
- package/src/network/processor/index.ts +25 -3
- package/src/util/execution.ts +6 -6
package/src/chain/chain.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {Type} from "@chainsafe/ssz";
|
|
|
4
4
|
import {routes} from "@lodestar/api";
|
|
5
5
|
import {BeaconConfig} from "@lodestar/config";
|
|
6
6
|
import {
|
|
7
|
+
AncestorStatus,
|
|
7
8
|
CheckpointWithHex,
|
|
8
9
|
EpochDifference,
|
|
9
10
|
ForkChoiceStateGetter,
|
|
@@ -90,8 +91,9 @@ import {persistPayloadEnvelopeInput} from "./blocks/writePayloadEnvelopeInputToD
|
|
|
90
91
|
import {BlsMultiThreadWorkerPool, BlsSingleThreadVerifier, IBlsVerifier} from "./bls/index.js";
|
|
91
92
|
import {BuilderCircuitBreaker} from "./builderCircuitBreaker.js";
|
|
92
93
|
import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
|
|
93
|
-
import {ChainEvent, ChainEventEmitter} from "./emitter.js";
|
|
94
|
+
import {ChainEvent, ChainEventEmitter, ReorgEventData} from "./emitter.js";
|
|
94
95
|
import {ForkchoiceCaller, initializeForkChoice} from "./forkChoice/index.js";
|
|
96
|
+
import {ForkChoiceExecutionEngine} from "./forkChoiceExecutionEngine.js";
|
|
95
97
|
import {GetBlobsTracker} from "./GetBlobsTracker.js";
|
|
96
98
|
import {CommonBlockBody, FindHeadFnName, IBeaconChain, ProposerPreparationData, StateGetOpts} from "./interface.js";
|
|
97
99
|
import {LightClientServer} from "./lightClient/index.js";
|
|
@@ -166,7 +168,7 @@ const DEFAULT_MAX_PENDING_UNFINALIZED_PAYLOAD_ENVELOPE_WRITES = 16;
|
|
|
166
168
|
export class BeaconChain implements IBeaconChain {
|
|
167
169
|
readonly genesisTime: UintNum64;
|
|
168
170
|
readonly genesisValidatorsRoot: Root;
|
|
169
|
-
readonly executionEngine:
|
|
171
|
+
readonly executionEngine: ForkChoiceExecutionEngine;
|
|
170
172
|
readonly executionBuilder?: IExecutionBuilder;
|
|
171
173
|
readonly builderCircuitBreaker: BuilderCircuitBreaker;
|
|
172
174
|
readonly builderApiClient: BuilderApiClient;
|
|
@@ -317,7 +319,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
317
319
|
this.genesisTime = anchorState.genesisTime;
|
|
318
320
|
this.anchorStateLatestBlockSlot = anchorState.latestBlockHeader.slot;
|
|
319
321
|
this.genesisValidatorsRoot = anchorState.genesisValidatorsRoot;
|
|
320
|
-
this.executionEngine = executionEngine;
|
|
321
322
|
this.executionBuilder = executionBuilder;
|
|
322
323
|
const signal = this.abortController.signal;
|
|
323
324
|
const emitter = new ChainEventEmitter();
|
|
@@ -457,6 +458,12 @@ export class BeaconChain implements IBeaconChain {
|
|
|
457
458
|
this.payloadEnvelopeProcessor = new PayloadEnvelopeProcessor(this, metrics, signal);
|
|
458
459
|
|
|
459
460
|
this.forkChoice = forkChoice;
|
|
461
|
+
this.executionEngine = new ForkChoiceExecutionEngine(executionEngine, {
|
|
462
|
+
forkChoice,
|
|
463
|
+
recomputeHeadAfterELInvalidResponse: () => this.recomputeHeadAfterELInvalidResponse(),
|
|
464
|
+
logger,
|
|
465
|
+
metrics,
|
|
466
|
+
});
|
|
460
467
|
|
|
461
468
|
this.builderCircuitBreaker = new BuilderCircuitBreaker(
|
|
462
469
|
{faultInspectionWindow: opts.faultInspectionWindow, allowedFaults: opts.allowedFaults},
|
|
@@ -1430,6 +1437,58 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1430
1437
|
}
|
|
1431
1438
|
}
|
|
1432
1439
|
|
|
1440
|
+
/**
|
|
1441
|
+
* Apply chain side effects of a fork choice head change: head state, head metrics, and reorg detection
|
|
1442
|
+
*/
|
|
1443
|
+
onHeadChanged(oldHead: ProtoBlock, newHead: ProtoBlock, maybeHeadState?: IBeaconStateView): void {
|
|
1444
|
+
// Set head state as strong reference
|
|
1445
|
+
this.regen.updateHeadState(newHead, maybeHeadState);
|
|
1446
|
+
|
|
1447
|
+
const delaySec = this.clock.secFromSlot(newHead.slot);
|
|
1448
|
+
this.logger.verbose("New chain head", {
|
|
1449
|
+
slot: newHead.slot,
|
|
1450
|
+
root: newHead.blockRoot,
|
|
1451
|
+
payloadStatus: newHead.payloadStatus,
|
|
1452
|
+
delaySec,
|
|
1453
|
+
});
|
|
1454
|
+
this.metrics?.headSlot.set(newHead.slot);
|
|
1455
|
+
|
|
1456
|
+
this.onNewHead(newHead);
|
|
1457
|
+
this.metrics?.forkChoice.changedHead.inc();
|
|
1458
|
+
|
|
1459
|
+
const ancestorResult = this.forkChoice.getCommonAncestorDepth(oldHead, newHead);
|
|
1460
|
+
if (ancestorResult.code === AncestorStatus.CommonAncestor) {
|
|
1461
|
+
// CommonAncestor = chain reorg, old head and new head not direct descendants
|
|
1462
|
+
|
|
1463
|
+
const forkChoiceReorgEventData: ReorgEventData = {
|
|
1464
|
+
depth: ancestorResult.depth,
|
|
1465
|
+
epoch: computeEpochAtSlot(newHead.slot),
|
|
1466
|
+
slot: newHead.slot,
|
|
1467
|
+
newHeadBlock: newHead.blockRoot,
|
|
1468
|
+
oldHeadBlock: oldHead.blockRoot,
|
|
1469
|
+
newHeadState: newHead.stateRoot,
|
|
1470
|
+
oldHeadState: oldHead.stateRoot,
|
|
1471
|
+
executionOptimistic: isOptimisticBlock(newHead),
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
this.emitter.emit(routes.events.EventType.chainReorg, forkChoiceReorgEventData);
|
|
1475
|
+
this.logger.verbose("Chain reorg", forkChoiceReorgEventData);
|
|
1476
|
+
|
|
1477
|
+
this.metrics?.forkChoice.reorg.inc();
|
|
1478
|
+
this.metrics?.forkChoice.reorgDistance.observe(ancestorResult.depth);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
private recomputeHeadAfterELInvalidResponse(): void {
|
|
1483
|
+
// validateLatestHash only re-scores proto-array, the cached head is still the old one
|
|
1484
|
+
const oldHead = this.forkChoice.getHead();
|
|
1485
|
+
const newHead = this.recomputeForkChoiceHead(ForkchoiceCaller.executionInvalid);
|
|
1486
|
+
if (newHead.blockRoot === oldHead.blockRoot && newHead.payloadStatus === oldHead.payloadStatus) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
this.onHeadChanged(oldHead, newHead);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1433
1492
|
/**
|
|
1434
1493
|
* Emit `head` and `head_v2` when the fork choice head differs between two observations. Every caller that
|
|
1435
1494
|
* can move the head, `updateAndGetHead()` and `updateTime()`, must capture the head before and pass both.
|
|
@@ -34,6 +34,17 @@ export type ForkChoiceOpts = RawForkChoiceOpts & {
|
|
|
34
34
|
export enum ForkchoiceCaller {
|
|
35
35
|
prepareNextSlot = "prepare_next_slot",
|
|
36
36
|
importBlock = "import_block",
|
|
37
|
+
executionInvalid = "execution_invalid",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Where an execution engine INVALID verdict comes from. Only `blockSegment` verdicts are about the payload the engine
|
|
42
|
+
* just processed and keep the consensus failure semantics in fork choice, the others are applied leniently.
|
|
43
|
+
*/
|
|
44
|
+
export enum ExecutionInvalidSource {
|
|
45
|
+
forkchoiceUpdated = "forkchoice_updated",
|
|
46
|
+
newPayload = "new_payload",
|
|
47
|
+
blockSegment = "block_segment",
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
/**
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {ExecutionStatus, IForkChoice, LVHInvalidResponse} from "@lodestar/fork-choice";
|
|
2
|
+
import {ForkName, ForkPostFulu, ForkPreFulu, isForkPostFulu} from "@lodestar/params";
|
|
3
|
+
import {RootHex, bellatrix} from "@lodestar/types";
|
|
4
|
+
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
5
|
+
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
6
|
+
import {Logger} from "@lodestar/utils";
|
|
7
|
+
import {ExecutionPayloadBodyV2} from "../execution/engine/types.js";
|
|
8
|
+
import {DATA} from "../execution/engine/utils.js";
|
|
9
|
+
import {
|
|
10
|
+
ClientVersion,
|
|
11
|
+
ExecutionEngineState,
|
|
12
|
+
ExecutionPayloadStatus,
|
|
13
|
+
ForkchoiceUpdatedResult,
|
|
14
|
+
IExecutionEngine,
|
|
15
|
+
PayloadAttributes,
|
|
16
|
+
PayloadId,
|
|
17
|
+
PayloadIdCache,
|
|
18
|
+
VersionedHashes,
|
|
19
|
+
} from "../execution/index.js";
|
|
20
|
+
import {Metrics} from "../metrics/index.js";
|
|
21
|
+
import {ExecutionInvalidSource} from "./forkChoice/index.js";
|
|
22
|
+
|
|
23
|
+
export type ForkChoiceExecutionEngineModules = {
|
|
24
|
+
forkChoice: IForkChoice;
|
|
25
|
+
/** Recompute and switch the chain head, fork choice is already updated when this runs */
|
|
26
|
+
recomputeHeadAfterELInvalidResponse: () => void;
|
|
27
|
+
logger: Logger;
|
|
28
|
+
metrics: Metrics | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The only handle on the execution engine. Payloads are imported optimistically, so every engine verdict that can
|
|
33
|
+
* change fork choice is applied here.
|
|
34
|
+
*/
|
|
35
|
+
export class ForkChoiceExecutionEngine {
|
|
36
|
+
private readonly forkChoice: IForkChoice;
|
|
37
|
+
private readonly recomputeHeadAfterELInvalidResponse: () => void;
|
|
38
|
+
private readonly logger: Logger;
|
|
39
|
+
private readonly metrics: Metrics | null;
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
private readonly engine: IExecutionEngine,
|
|
43
|
+
modules: ForkChoiceExecutionEngineModules
|
|
44
|
+
) {
|
|
45
|
+
this.forkChoice = modules.forkChoice;
|
|
46
|
+
this.recomputeHeadAfterELInvalidResponse = modules.recomputeHeadAfterELInvalidResponse;
|
|
47
|
+
this.logger = modules.logger;
|
|
48
|
+
this.metrics = modules.metrics;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get state(): ExecutionEngineState {
|
|
52
|
+
return this.engine.state;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get clientVersion(): ClientVersion | null | undefined {
|
|
56
|
+
return this.engine.clientVersion;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get payloadIdCache(): PayloadIdCache {
|
|
60
|
+
return this.engine.payloadIdCache;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
notifyNewPayload(
|
|
64
|
+
...args: Parameters<IExecutionEngine["notifyNewPayload"]>
|
|
65
|
+
): ReturnType<IExecutionEngine["notifyNewPayload"]> {
|
|
66
|
+
return this.engine.notifyNewPayload(...args);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `headBlockRoot` is the root of the node whose payload hash is sent as `headBlockHash`, captured by the caller
|
|
71
|
+
* before the call: root and hash identify the exact fork choice node, gloas variant included.
|
|
72
|
+
*
|
|
73
|
+
* The verdict is applied to fork choice before returning. An INVALID verdict throws when preparing a payload so
|
|
74
|
+
* block production fails, otherwise it resolves to null.
|
|
75
|
+
*/
|
|
76
|
+
async notifyForkchoiceUpdate(
|
|
77
|
+
fork: ForkName,
|
|
78
|
+
headBlockRoot: RootHex,
|
|
79
|
+
headBlockHash: RootHex,
|
|
80
|
+
safeBlockHash: RootHex,
|
|
81
|
+
finalizedBlockHash: RootHex,
|
|
82
|
+
payloadAttributes?: PayloadAttributes
|
|
83
|
+
): Promise<PayloadId | null> {
|
|
84
|
+
const result = await this.engine.notifyForkchoiceUpdate(
|
|
85
|
+
fork,
|
|
86
|
+
headBlockHash,
|
|
87
|
+
safeBlockHash,
|
|
88
|
+
finalizedBlockHash,
|
|
89
|
+
payloadAttributes
|
|
90
|
+
);
|
|
91
|
+
this.applyForkchoiceUpdatedResult(headBlockRoot, headBlockHash, result);
|
|
92
|
+
|
|
93
|
+
if (result.payloadStatus.status === ExecutionPayloadStatus.INVALID) {
|
|
94
|
+
if (payloadAttributes) {
|
|
95
|
+
throw Error(`Invalid prepare payload, validationError=${result.payloadStatus.validationError ?? ""}`);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return result.payloadId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getPayload(...args: Parameters<IExecutionEngine["getPayload"]>): ReturnType<IExecutionEngine["getPayload"]> {
|
|
103
|
+
return this.engine.getPayload(...args);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getInclusionList(): Promise<bellatrix.Transactions> {
|
|
107
|
+
return this.engine.getInclusionList();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getPayloadBodiesByHashV2(blockHashes: DATA[]): Promise<(ExecutionPayloadBodyV2 | null)[]> {
|
|
111
|
+
return this.engine.getPayloadBodiesByHashV2(blockHashes);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getBlobs(
|
|
115
|
+
fork: ForkPostFulu,
|
|
116
|
+
versionedHashes: VersionedHashes,
|
|
117
|
+
buffers?: Uint8Array[]
|
|
118
|
+
): Promise<BlobAndProofV2[] | null>;
|
|
119
|
+
getBlobs(
|
|
120
|
+
fork: ForkPreFulu,
|
|
121
|
+
versionedHashes: VersionedHashes,
|
|
122
|
+
buffers?: Uint8Array[]
|
|
123
|
+
): Promise<(BlobAndProof | null)[]>;
|
|
124
|
+
getBlobs(
|
|
125
|
+
fork: ForkName,
|
|
126
|
+
versionedHashes: VersionedHashes,
|
|
127
|
+
buffers?: Uint8Array[]
|
|
128
|
+
): Promise<BlobAndProofV2[] | (BlobAndProof | null)[] | null> {
|
|
129
|
+
if (isForkPostFulu(fork)) {
|
|
130
|
+
return this.engine.getBlobs(fork, versionedHashes, buffers);
|
|
131
|
+
}
|
|
132
|
+
return this.engine.getBlobs(fork as ForkPreFulu, versionedHashes, buffers);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Apply an execution engine INVALID verdict to fork choice and move the chain head off the invalid chain
|
|
137
|
+
*/
|
|
138
|
+
invalidateExecutionPayloads(lvh: LVHInvalidResponse, source: ExecutionInvalidSource): void {
|
|
139
|
+
const logCtx = {
|
|
140
|
+
source,
|
|
141
|
+
blockRoot: lvh.invalidateFromParentBlockRoot,
|
|
142
|
+
blockHash: lvh.invalidateFromParentBlockHash,
|
|
143
|
+
latestValidHash: lvh.latestValidExecHash ?? "null",
|
|
144
|
+
};
|
|
145
|
+
const applied = this.forkChoice.validateLatestHash(lvh, {strict: source === ExecutionInvalidSource.blockSegment});
|
|
146
|
+
if (!applied) {
|
|
147
|
+
this.logger.warn("Execution engine INVALID verdict not applied to fork choice", logCtx);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.metrics?.forkChoice.executionInvalidations.inc({source});
|
|
151
|
+
this.logger.warn("Invalidated execution payloads in fork choice", logCtx);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
this.recomputeHeadAfterELInvalidResponse();
|
|
155
|
+
} catch (e) {
|
|
156
|
+
this.logger.error("Error recomputing head after execution payload invalidation", logCtx, e as Error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private applyForkchoiceUpdatedResult(
|
|
161
|
+
headBlockRoot: RootHex,
|
|
162
|
+
headBlockHash: RootHex,
|
|
163
|
+
result: ForkchoiceUpdatedResult
|
|
164
|
+
): void {
|
|
165
|
+
const {status, latestValidHash, validationError} = result.payloadStatus;
|
|
166
|
+
const executionStatus = this.forkChoice.getBlockHexAndBlockHash(headBlockRoot, headBlockHash)?.executionStatus;
|
|
167
|
+
try {
|
|
168
|
+
if (status === ExecutionPayloadStatus.INVALID) {
|
|
169
|
+
if (executionStatus === ExecutionStatus.Invalid) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.logger.error("Execution engine reported INVALID forkchoice head", {
|
|
173
|
+
headBlockRoot,
|
|
174
|
+
headBlockHash,
|
|
175
|
+
latestValidHash: latestValidHash ?? "null",
|
|
176
|
+
validationError: validationError ?? "",
|
|
177
|
+
});
|
|
178
|
+
this.invalidateExecutionPayloads(
|
|
179
|
+
{
|
|
180
|
+
executionStatus: ExecutionStatus.Invalid,
|
|
181
|
+
latestValidExecHash: latestValidHash,
|
|
182
|
+
invalidateFromParentBlockRoot: headBlockRoot,
|
|
183
|
+
invalidateFromParentBlockHash: headBlockHash,
|
|
184
|
+
},
|
|
185
|
+
ExecutionInvalidSource.forkchoiceUpdated
|
|
186
|
+
);
|
|
187
|
+
} else if (status === ExecutionPayloadStatus.VALID && executionStatus === ExecutionStatus.Syncing) {
|
|
188
|
+
this.forkChoice.validateLatestHash(
|
|
189
|
+
{executionStatus: ExecutionStatus.Valid, latestValidExecHash: headBlockHash, blockRoot: headBlockRoot},
|
|
190
|
+
{strict: false}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {
|
|
194
|
+
this.logger.error("Error applying forkchoice update result", {headBlockRoot, headBlockHash, status}, e as Error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
package/src/chain/interface.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "@lodestar/types";
|
|
26
26
|
import {Logger} from "@lodestar/utils";
|
|
27
27
|
import {BuilderApiClient} from "../execution/builder/apiClient.js";
|
|
28
|
-
import {IExecutionBuilder
|
|
28
|
+
import {IExecutionBuilder} from "../execution/index.js";
|
|
29
29
|
import {Metrics} from "../metrics/metrics.js";
|
|
30
30
|
import {BufferPool} from "../util/bufferPool.js";
|
|
31
31
|
import {IClock} from "../util/clock.js";
|
|
@@ -42,6 +42,7 @@ import {BuilderCircuitBreaker} from "./builderCircuitBreaker.js";
|
|
|
42
42
|
import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
|
|
43
43
|
import {ChainEventEmitter} from "./emitter.js";
|
|
44
44
|
import {ForkchoiceCaller} from "./forkChoice/index.js";
|
|
45
|
+
import {ForkChoiceExecutionEngine} from "./forkChoiceExecutionEngine.js";
|
|
45
46
|
import {GetBlobsTracker} from "./GetBlobsTracker.js";
|
|
46
47
|
import {LightClientServer} from "./lightClient/index.js";
|
|
47
48
|
import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool.js";
|
|
@@ -98,7 +99,7 @@ export interface IBeaconChain {
|
|
|
98
99
|
readonly genesisTime: UintNum64;
|
|
99
100
|
readonly genesisValidatorsRoot: Root;
|
|
100
101
|
earliestAvailableSlot: Slot;
|
|
101
|
-
readonly executionEngine:
|
|
102
|
+
readonly executionEngine: ForkChoiceExecutionEngine;
|
|
102
103
|
readonly executionBuilder?: IExecutionBuilder;
|
|
103
104
|
readonly builderCircuitBreaker: BuilderCircuitBreaker;
|
|
104
105
|
readonly builderApiClient: BuilderApiClient;
|
|
@@ -72,12 +72,13 @@ import {
|
|
|
72
72
|
} from "@lodestar/utils";
|
|
73
73
|
import {ZERO_HASH_HEX} from "../../constants/index.js";
|
|
74
74
|
import {numToQuantity} from "../../execution/engine/utils.js";
|
|
75
|
-
import {IExecutionBuilder,
|
|
75
|
+
import {IExecutionBuilder, PayloadAttributes, PayloadId} from "../../execution/index.js";
|
|
76
76
|
import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
|
|
77
77
|
import {fromGraffitiBytes} from "../../util/graffiti.js";
|
|
78
78
|
import {kzg} from "../../util/kzg.js";
|
|
79
79
|
import {BeaconProposerCache} from "../beaconProposerCache.js";
|
|
80
80
|
import type {BeaconChain} from "../chain.js";
|
|
81
|
+
import {ForkChoiceExecutionEngine} from "../forkChoiceExecutionEngine.js";
|
|
81
82
|
import {CommonBlockBody} from "../interface.js";
|
|
82
83
|
import {InclusionListStore, ProposerPreferencesPool} from "../opPools/index.js";
|
|
83
84
|
import {validateBlobsAndKzgCommitments, validateCellsAndKzgCommitments} from "./validateBlobsAndKzgCommitments.js";
|
|
@@ -742,7 +743,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
742
743
|
*/
|
|
743
744
|
export async function prepareExecutionPayload(
|
|
744
745
|
chain: {
|
|
745
|
-
executionEngine:
|
|
746
|
+
executionEngine: ForkChoiceExecutionEngine;
|
|
746
747
|
config: ChainForkConfig;
|
|
747
748
|
forkChoice: IForkChoice;
|
|
748
749
|
proposerPreferencesPool: ProposerPreferencesPool;
|
|
@@ -807,6 +808,7 @@ export async function prepareExecutionPayload(
|
|
|
807
808
|
|
|
808
809
|
payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
|
|
809
810
|
fork,
|
|
811
|
+
toRootHex(parentBlockRoot),
|
|
810
812
|
toRootHex(parentBlockHash),
|
|
811
813
|
safeBlockHash,
|
|
812
814
|
finalizedBlockHash,
|
|
@@ -52,7 +52,7 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
|
|
|
52
52
|
onCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
|
|
53
53
|
processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
|
|
54
54
|
addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
|
|
55
|
-
updateHeadState(newHead: ProtoBlock, maybeHeadState
|
|
55
|
+
updateHeadState(newHead: ProtoBlock, maybeHeadState?: IBeaconStateView): void;
|
|
56
56
|
updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -154,18 +154,20 @@ export class QueuedStateRegenerator implements IStateRegenerator {
|
|
|
154
154
|
this.checkpointStateCache.add(cp, item);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
updateHeadState(newHead: ProtoBlock, maybeHeadState
|
|
157
|
+
updateHeadState(newHead: ProtoBlock, maybeHeadState?: IBeaconStateView): void {
|
|
158
158
|
const {stateRoot: newHeadStateRoot, blockRoot: newHeadBlockRoot, slot: newHeadSlot} = newHead;
|
|
159
|
-
const maybeHeadStateRoot = toRootHex(maybeHeadState.hashTreeRoot());
|
|
159
|
+
const maybeHeadStateRoot = maybeHeadState ? toRootHex(maybeHeadState.hashTreeRoot()) : undefined;
|
|
160
160
|
const logCtx = {
|
|
161
161
|
newHeadSlot,
|
|
162
162
|
newHeadBlockRoot,
|
|
163
163
|
newHeadStateRoot,
|
|
164
|
-
maybeHeadSlot: maybeHeadState
|
|
164
|
+
maybeHeadSlot: maybeHeadState?.slot,
|
|
165
165
|
maybeHeadStateRoot,
|
|
166
166
|
};
|
|
167
167
|
const headState =
|
|
168
|
-
newHeadStateRoot === maybeHeadStateRoot
|
|
168
|
+
maybeHeadState && newHeadStateRoot === maybeHeadStateRoot
|
|
169
|
+
? maybeHeadState
|
|
170
|
+
: this.blockStateCache.get(newHeadStateRoot);
|
|
169
171
|
|
|
170
172
|
if (headState) {
|
|
171
173
|
this.blockStateCache.setHeadState(headState);
|
|
@@ -22,9 +22,11 @@ import {
|
|
|
22
22
|
ExecutePayloadResponse,
|
|
23
23
|
ExecutionEngineState,
|
|
24
24
|
ExecutionPayloadStatus,
|
|
25
|
+
ForkchoiceUpdatedResult,
|
|
25
26
|
IExecutionEngine,
|
|
26
27
|
PayloadAttributes,
|
|
27
28
|
PayloadId,
|
|
29
|
+
PayloadStatusResult,
|
|
28
30
|
VersionedHashes,
|
|
29
31
|
} from "./interface.js";
|
|
30
32
|
import {ErrorJsonRpcResponse, HttpRpcError, JsonRpcHttpClientEvent} from "./jsonRpcHttpClient.js";
|
|
@@ -32,7 +34,7 @@ import {PayloadIdCache} from "./payloadIdCache.js";
|
|
|
32
34
|
import {EngineRestError, EngineRestResponseError, isRetryableEngineRestError} from "./restHttpClient.js";
|
|
33
35
|
import {EngineCapabilities, RestEngineTransport} from "./restTransport.js";
|
|
34
36
|
import {executionForkName} from "./sszTypes.js";
|
|
35
|
-
import {IEngineTransport
|
|
37
|
+
import {IEngineTransport} from "./transport.js";
|
|
36
38
|
import {ExecutionPayloadBodyV2, serializePayloadAttributes} from "./types.js";
|
|
37
39
|
import {getExecutionEngineState} from "./utils.js";
|
|
38
40
|
|
|
@@ -342,15 +344,16 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
342
344
|
safeBlockHash: RootHex,
|
|
343
345
|
finalizedBlockHash: RootHex,
|
|
344
346
|
payloadAttributes?: PayloadAttributes
|
|
345
|
-
): Promise<
|
|
346
|
-
const
|
|
347
|
-
payloadStatus: {status, validationError},
|
|
348
|
-
payloadId,
|
|
349
|
-
} = await this.enqueue(() =>
|
|
347
|
+
): Promise<ForkchoiceUpdatedResult> {
|
|
348
|
+
const result = await this.enqueue(() =>
|
|
350
349
|
this.withTransport(fork, undefined, (transport) =>
|
|
351
350
|
transport.forkchoiceUpdated(fork, headBlockHash, safeBlockHash, finalizedBlockHash, payloadAttributes)
|
|
352
351
|
)
|
|
353
352
|
);
|
|
353
|
+
const {
|
|
354
|
+
payloadStatus: {status},
|
|
355
|
+
payloadId,
|
|
356
|
+
} = result;
|
|
354
357
|
|
|
355
358
|
this.updateEngineState(getExecutionEngineState({payloadStatus: status, oldState: this.state}));
|
|
356
359
|
this.metrics?.engineNotifyForkchoiceUpdateResult.inc({result: status});
|
|
@@ -374,21 +377,17 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
374
377
|
);
|
|
375
378
|
void this.prunePayloadIdCache();
|
|
376
379
|
}
|
|
377
|
-
return
|
|
380
|
+
return result;
|
|
378
381
|
|
|
379
382
|
case ExecutionPayloadStatus.SYNCING:
|
|
380
383
|
// Throw error on syncing if requested to produce a block, else silently ignore
|
|
381
384
|
if (payloadAttributes) {
|
|
382
385
|
throw Error("Execution Layer Syncing");
|
|
383
386
|
}
|
|
384
|
-
return
|
|
387
|
+
return result;
|
|
385
388
|
|
|
386
389
|
case ExecutionPayloadStatus.INVALID:
|
|
387
|
-
|
|
388
|
-
`Invalid ${payloadAttributes ? "prepare payload" : "forkchoice request"}, validationError=${
|
|
389
|
-
validationError ?? ""
|
|
390
|
-
}`
|
|
391
|
-
);
|
|
390
|
+
return result;
|
|
392
391
|
|
|
393
392
|
default:
|
|
394
393
|
throw Error(`Unknown status ${status}`);
|
|
@@ -99,6 +99,19 @@ export type ExecutePayloadResponse =
|
|
|
99
99
|
validationError: string;
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
export type PayloadStatusResult = {
|
|
103
|
+
status: ExecutionPayloadStatus;
|
|
104
|
+
latestValidHash: RootHex | null;
|
|
105
|
+
validationError: string | null;
|
|
106
|
+
/** PayloadStatusV2: whether a VALID payload satisfied the inclusion list constraints, null otherwise */
|
|
107
|
+
inclusionListSatisfied?: boolean | null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type ForkchoiceUpdatedResult = {
|
|
111
|
+
payloadStatus: PayloadStatusResult;
|
|
112
|
+
payloadId: PayloadId | null;
|
|
113
|
+
};
|
|
114
|
+
|
|
102
115
|
export type ForkChoiceUpdateStatus =
|
|
103
116
|
| ExecutionPayloadStatus.VALID
|
|
104
117
|
| ExecutionPayloadStatus.INVALID
|
|
@@ -166,7 +179,10 @@ export interface IExecutionEngine {
|
|
|
166
179
|
* The call of the notify_forkchoice_updated function maps on the POS_FORKCHOICE_UPDATED event defined in the EIP-3675.
|
|
167
180
|
* https://github.com/ethereum/consensus-specs/blob/v1.1.7/specs/merge/fork-choice.md#notify_forkchoice_updated
|
|
168
181
|
*
|
|
169
|
-
* Should be called in response to fork-choice head and finalized events
|
|
182
|
+
* Should be called in response to fork-choice head and finalized events.
|
|
183
|
+
*
|
|
184
|
+
* An INVALID verdict is returned, not thrown: the caller feeds it back into fork choice together with the
|
|
185
|
+
* `latestValidHash`. Only SYNCING while building a payload is an error.
|
|
170
186
|
*/
|
|
171
187
|
notifyForkchoiceUpdate(
|
|
172
188
|
fork: ForkName,
|
|
@@ -174,7 +190,7 @@ export interface IExecutionEngine {
|
|
|
174
190
|
safeBlockHash: RootHex,
|
|
175
191
|
finalizedBlockHash: RootHex,
|
|
176
192
|
payloadAttributes?: PayloadAttributes
|
|
177
|
-
): Promise<
|
|
193
|
+
): Promise<ForkchoiceUpdatedResult>;
|
|
178
194
|
|
|
179
195
|
/**
|
|
180
196
|
* Given the payload_id, get_payload returns the most recent version of the execution payload that has been built
|
|
@@ -3,9 +3,17 @@ import {ExecutionPayload, ExecutionRequests, Root, RootHex, bellatrix} from "@lo
|
|
|
3
3
|
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
4
4
|
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
5
5
|
import {strip0xPrefix} from "@lodestar/utils";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ClientCode,
|
|
8
|
+
ClientVersion,
|
|
9
|
+
ForkchoiceUpdatedResult,
|
|
10
|
+
PayloadAttributes,
|
|
11
|
+
PayloadId,
|
|
12
|
+
PayloadStatusResult,
|
|
13
|
+
VersionedHashes,
|
|
14
|
+
} from "./interface.js";
|
|
7
15
|
import {IJsonRpcHttpClient, JsonRpcHttpClientEventEmitter, ReqOpts} from "./jsonRpcHttpClient.js";
|
|
8
|
-
import {
|
|
16
|
+
import {GetPayloadResult, IEngineTransport} from "./transport.js";
|
|
9
17
|
import {
|
|
10
18
|
BLOB_AND_PROOF_V2_RPC_BYTES,
|
|
11
19
|
EngineApiRpcParamTypes,
|
|
@@ -19,8 +19,10 @@ import {
|
|
|
19
19
|
ClientCode,
|
|
20
20
|
ClientVersion,
|
|
21
21
|
ExecutionPayloadStatus,
|
|
22
|
+
ForkchoiceUpdatedResult,
|
|
22
23
|
PayloadAttributes,
|
|
23
24
|
PayloadId,
|
|
25
|
+
PayloadStatusResult,
|
|
24
26
|
VersionedHashes,
|
|
25
27
|
} from "./interface.js";
|
|
26
28
|
import {JsonRpcHttpClientEventEmitter, ReqOpts} from "./jsonRpcHttpClient.js";
|
|
@@ -57,7 +59,7 @@ import {
|
|
|
57
59
|
PayloadStatusSsz,
|
|
58
60
|
executionForkName,
|
|
59
61
|
} from "./sszTypes.js";
|
|
60
|
-
import {
|
|
62
|
+
import {GetPayloadResult, IEngineTransport} from "./transport.js";
|
|
61
63
|
import {EngineTransportMetrics} from "./transportMetrics.js";
|
|
62
64
|
import {
|
|
63
65
|
ExecutionPayloadBodyV2,
|
|
@@ -2,23 +2,17 @@ import {ForkName} from "@lodestar/params";
|
|
|
2
2
|
import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, bellatrix} from "@lodestar/types";
|
|
3
3
|
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
4
4
|
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ClientVersion,
|
|
7
|
+
ForkchoiceUpdatedResult,
|
|
8
|
+
PayloadAttributes,
|
|
9
|
+
PayloadId,
|
|
10
|
+
PayloadStatusResult,
|
|
11
|
+
VersionedHashes,
|
|
12
|
+
} from "./interface.js";
|
|
6
13
|
import {JsonRpcHttpClientEventEmitter} from "./jsonRpcHttpClient.js";
|
|
7
14
|
import {ExecutionPayloadBodyV2} from "./types.js";
|
|
8
15
|
|
|
9
|
-
export type PayloadStatusResult = {
|
|
10
|
-
status: ExecutionPayloadStatus;
|
|
11
|
-
latestValidHash: RootHex | null;
|
|
12
|
-
validationError: string | null;
|
|
13
|
-
/** PayloadStatusV2: whether a VALID payload satisfied the inclusion list constraints, null otherwise */
|
|
14
|
-
inclusionListSatisfied?: boolean | null;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type ForkchoiceUpdatedResult = {
|
|
18
|
-
payloadStatus: PayloadStatusResult;
|
|
19
|
-
payloadId: PayloadId | null;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
16
|
export type GetPayloadResult = {
|
|
23
17
|
executionPayload: ExecutionPayload;
|
|
24
18
|
executionPayloadValue: Wei;
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export {DbCPStateDatastore} from "./chain/stateCache/datastore/db.js";
|
|
|
7
7
|
export {FileCPStateDatastore} from "./chain/stateCache/datastore/file.js";
|
|
8
8
|
export {BeaconDb, type IBeaconDb} from "./db/index.js";
|
|
9
9
|
// Export execution engine to make it usable by builder
|
|
10
|
-
export {initializeExecutionEngine} from "./execution/index.js";
|
|
10
|
+
export {ExecutionPayloadStatus, initializeExecutionEngine} from "./execution/index.js";
|
|
11
11
|
// Export metrics utilities to de-duplicate validator metrics
|
|
12
12
|
export {
|
|
13
13
|
type HttpMetricsServer,
|