@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.
Files changed (100) hide show
  1. package/lib/chain/GetBlobsTracker.d.ts +2 -2
  2. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  3. package/lib/chain/GetBlobsTracker.js.map +1 -1
  4. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  5. package/lib/chain/blocks/importBlock.js +17 -47
  6. package/lib/chain/blocks/importBlock.js.map +1 -1
  7. package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
  8. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  9. package/lib/chain/blocks/importExecutionPayload.js +24 -3
  10. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  11. package/lib/chain/blocks/index.d.ts.map +1 -1
  12. package/lib/chain/blocks/index.js +2 -2
  13. package/lib/chain/blocks/index.js.map +1 -1
  14. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +1 -1
  15. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  16. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  17. package/lib/chain/blocks/verifyBlocksSanityChecks.js +4 -0
  18. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  19. package/lib/chain/chain.d.ts +7 -1
  20. package/lib/chain/chain.d.ts.map +1 -1
  21. package/lib/chain/chain.js +53 -3
  22. package/lib/chain/chain.js.map +1 -1
  23. package/lib/chain/forkChoice/index.d.ts +11 -1
  24. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  25. package/lib/chain/forkChoice/index.js +11 -0
  26. package/lib/chain/forkChoice/index.js.map +1 -1
  27. package/lib/chain/forkChoiceExecutionEngine.d.ts +53 -0
  28. package/lib/chain/forkChoiceExecutionEngine.d.ts.map +1 -0
  29. package/lib/chain/forkChoiceExecutionEngine.js +121 -0
  30. package/lib/chain/forkChoiceExecutionEngine.js.map +1 -0
  31. package/lib/chain/interface.d.ts +3 -2
  32. package/lib/chain/interface.d.ts.map +1 -1
  33. package/lib/chain/interface.js.map +1 -1
  34. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  35. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  36. package/lib/chain/produceBlock/produceBlockBody.js +1 -1
  37. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  38. package/lib/chain/regen/interface.d.ts +1 -1
  39. package/lib/chain/regen/interface.d.ts.map +1 -1
  40. package/lib/chain/regen/queued.d.ts +1 -1
  41. package/lib/chain/regen/queued.d.ts.map +1 -1
  42. package/lib/chain/regen/queued.js +5 -3
  43. package/lib/chain/regen/queued.js.map +1 -1
  44. package/lib/execution/engine/http.d.ts +2 -2
  45. package/lib/execution/engine/http.d.ts.map +1 -1
  46. package/lib/execution/engine/http.js +5 -4
  47. package/lib/execution/engine/http.js.map +1 -1
  48. package/lib/execution/engine/interface.d.ts +16 -2
  49. package/lib/execution/engine/interface.d.ts.map +1 -1
  50. package/lib/execution/engine/jsonRpcTransport.d.ts +2 -2
  51. package/lib/execution/engine/jsonRpcTransport.d.ts.map +1 -1
  52. package/lib/execution/engine/jsonRpcTransport.js +1 -1
  53. package/lib/execution/engine/jsonRpcTransport.js.map +1 -1
  54. package/lib/execution/engine/restTransport.d.ts +2 -2
  55. package/lib/execution/engine/restTransport.d.ts.map +1 -1
  56. package/lib/execution/engine/restTransport.js.map +1 -1
  57. package/lib/execution/engine/transport.d.ts +1 -12
  58. package/lib/execution/engine/transport.d.ts.map +1 -1
  59. package/lib/index.d.ts +1 -1
  60. package/lib/index.d.ts.map +1 -1
  61. package/lib/index.js +1 -1
  62. package/lib/index.js.map +1 -1
  63. package/lib/network/network.d.ts +2 -1
  64. package/lib/network/network.d.ts.map +1 -1
  65. package/lib/network/network.js +42 -17
  66. package/lib/network/network.js.map +1 -1
  67. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  68. package/lib/network/processor/gossipHandlers.js +3 -0
  69. package/lib/network/processor/gossipHandlers.js.map +1 -1
  70. package/lib/network/processor/index.d.ts +6 -0
  71. package/lib/network/processor/index.d.ts.map +1 -1
  72. package/lib/network/processor/index.js +29 -3
  73. package/lib/network/processor/index.js.map +1 -1
  74. package/lib/util/execution.d.ts +4 -4
  75. package/lib/util/execution.d.ts.map +1 -1
  76. package/lib/util/execution.js.map +1 -1
  77. package/package.json +16 -16
  78. package/src/chain/GetBlobsTracker.ts +2 -2
  79. package/src/chain/blocks/importBlock.ts +35 -67
  80. package/src/chain/blocks/importExecutionPayload.ts +42 -13
  81. package/src/chain/blocks/index.ts +5 -2
  82. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  83. package/src/chain/blocks/verifyBlocksSanityChecks.ts +4 -1
  84. package/src/chain/chain.ts +62 -3
  85. package/src/chain/forkChoice/index.ts +11 -0
  86. package/src/chain/forkChoiceExecutionEngine.ts +197 -0
  87. package/src/chain/interface.ts +3 -2
  88. package/src/chain/produceBlock/produceBlockBody.ts +4 -2
  89. package/src/chain/regen/interface.ts +1 -1
  90. package/src/chain/regen/queued.ts +6 -4
  91. package/src/execution/engine/http.ts +12 -13
  92. package/src/execution/engine/interface.ts +18 -2
  93. package/src/execution/engine/jsonRpcTransport.ts +10 -2
  94. package/src/execution/engine/restTransport.ts +3 -1
  95. package/src/execution/engine/transport.ts +8 -14
  96. package/src/index.ts +1 -1
  97. package/src/network/network.ts +40 -21
  98. package/src/network/processor/gossipHandlers.ts +3 -0
  99. package/src/network/processor/index.ts +25 -3
  100. package/src/util/execution.ts +6 -6
@@ -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: IExecutionEngine;
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
+ }
@@ -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, IExecutionEngine} from "../execution/index.js";
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: IExecutionEngine;
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, IExecutionEngine, PayloadAttributes, PayloadId} from "../../execution/index.js";
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: IExecutionEngine;
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: IBeaconStateView): void;
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: IBeaconStateView): void {
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.slot,
164
+ maybeHeadSlot: maybeHeadState?.slot,
165
165
  maybeHeadStateRoot,
166
166
  };
167
167
  const headState =
168
- newHeadStateRoot === maybeHeadStateRoot ? maybeHeadState : this.blockStateCache.get(newHeadStateRoot);
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, PayloadStatusResult} from "./transport.js";
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<PayloadId | null> {
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 payloadId;
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 null;
387
+ return result;
385
388
 
386
389
  case ExecutionPayloadStatus.INVALID:
387
- throw Error(
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<PayloadId | null>;
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 {ClientCode, ClientVersion, PayloadAttributes, PayloadId, VersionedHashes} from "./interface.js";
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 {ForkchoiceUpdatedResult, GetPayloadResult, IEngineTransport, PayloadStatusResult} from "./transport.js";
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 {ForkchoiceUpdatedResult, GetPayloadResult, IEngineTransport, PayloadStatusResult} from "./transport.js";
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 {ClientVersion, ExecutionPayloadStatus, PayloadAttributes, PayloadId, VersionedHashes} from "./interface.js";
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,