@lodestar/beacon-node 1.43.0-dev.433e692fd9 → 1.43.0-dev.4fb05c546d

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 (60) hide show
  1. package/lib/chain/GetBlobsTracker.d.ts +1 -1
  2. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  3. package/lib/chain/GetBlobsTracker.js +1 -2
  4. package/lib/chain/GetBlobsTracker.js.map +1 -1
  5. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  6. package/lib/chain/blocks/importBlock.js +3 -7
  7. package/lib/chain/blocks/importBlock.js.map +1 -1
  8. package/lib/chain/blocks/importExecutionPayload.d.ts +1 -1
  9. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  10. package/lib/chain/blocks/importExecutionPayload.js +8 -6
  11. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  12. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +3 -0
  13. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  14. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +20 -0
  15. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  16. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
  17. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  18. package/lib/chain/blocks/payloadEnvelopeProcessor.js +6 -4
  19. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  20. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
  21. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
  22. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +25 -0
  23. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
  24. package/lib/chain/emitter.d.ts +13 -1
  25. package/lib/chain/emitter.d.ts.map +1 -1
  26. package/lib/chain/emitter.js +5 -0
  27. package/lib/chain/emitter.js.map +1 -1
  28. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  29. package/lib/chain/forkChoice/index.js +6 -2
  30. package/lib/chain/forkChoice/index.js.map +1 -1
  31. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  32. package/lib/chain/prepareNextSlot.js +20 -10
  33. package/lib/chain/prepareNextSlot.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 +24 -19
  37. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  38. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  39. package/lib/chain/validation/payloadAttestationMessage.js +4 -3
  40. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  41. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  42. package/lib/network/processor/gossipHandlers.js +19 -3
  43. package/lib/network/processor/gossipHandlers.js.map +1 -1
  44. package/lib/node/nodejs.d.ts.map +1 -1
  45. package/lib/node/nodejs.js +4 -2
  46. package/lib/node/nodejs.js.map +1 -1
  47. package/package.json +16 -16
  48. package/src/chain/GetBlobsTracker.ts +1 -2
  49. package/src/chain/blocks/importBlock.ts +3 -11
  50. package/src/chain/blocks/importExecutionPayload.ts +9 -5
  51. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -0
  52. package/src/chain/blocks/payloadEnvelopeProcessor.ts +6 -5
  53. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +38 -0
  54. package/src/chain/emitter.ts +12 -0
  55. package/src/chain/forkChoice/index.ts +6 -2
  56. package/src/chain/prepareNextSlot.ts +23 -10
  57. package/src/chain/produceBlock/produceBlockBody.ts +34 -14
  58. package/src/chain/validation/payloadAttestationMessage.ts +5 -3
  59. package/src/network/processor/gossipHandlers.ts +23 -7
  60. package/src/node/nodejs.ts +4 -2
@@ -8,8 +8,9 @@ import {
8
8
  computeEpochAtSlot,
9
9
  computeTimeAtSlot,
10
10
  isStatePostBellatrix,
11
+ isStatePostGloas,
11
12
  } from "@lodestar/state-transition";
12
- import {Slot} from "@lodestar/types";
13
+ import {Bytes32, Slot} from "@lodestar/types";
13
14
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
14
15
  import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
15
16
  import {BuilderStatus} from "../execution/builder/http.js";
@@ -81,6 +82,8 @@ export class PrepareNextSlotScheduler {
81
82
  // calling updateHead() here before we produce a block to reduce reorg possibility
82
83
  const headBlock = this.chain.recomputeForkChoiceHead(ForkchoiceCaller.prepareNextSlot);
83
84
  const {slot: headSlot, blockRoot: headRoot} = headBlock;
85
+ // may be updated below if we predict a proposer-boost-reorg
86
+ let updatedHeadRoot = headRoot;
84
87
 
85
88
  // PS: previously this was comparing slots, but that gave no leway on the skipped
86
89
  // slots on epoch bounday. Making it more fluid.
@@ -123,7 +126,6 @@ export class PrepareNextSlotScheduler {
123
126
  const proposerIndex = prepareState.getBeaconProposer(prepareSlot);
124
127
  const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex);
125
128
  let updatedPrepareState = prepareState;
126
- let updatedHeadRoot = headRoot;
127
129
 
128
130
  if (feeRecipient) {
129
131
  // If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not
@@ -156,25 +158,39 @@ export class PrepareNextSlotScheduler {
156
158
  this.logger.error("Builder disabled as the check status api failed", {prepareSlot}, e as Error);
157
159
  });
158
160
  }
161
+ }
162
+
163
+ if (!isStatePostBellatrix(updatedPrepareState)) {
164
+ throw new Error("Expected Bellatrix state for payload attributes");
165
+ }
166
+
167
+ let parentBlockHash: Bytes32;
168
+ if (isStatePostGloas(updatedPrepareState)) {
169
+ parentBlockHash = this.chain.forkChoice.shouldExtendPayload(updatedHeadRoot)
170
+ ? updatedPrepareState.latestExecutionPayloadBid.blockHash
171
+ : updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
172
+ } else {
173
+ parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
174
+ }
159
175
 
176
+ if (feeRecipient) {
160
177
  const preparationTime =
161
178
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
162
179
  this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
163
- if (!isStatePostBellatrix(updatedPrepareState)) {
164
- throw new Error("Expected Bellatrix state for payload preparation");
165
- }
166
180
 
167
181
  const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice);
168
182
  const finalizedBlockHash =
169
183
  this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
184
+
170
185
  // awaiting here instead of throwing an async call because there is no other task
171
- // left for scheduler and this gives nice sematics to catch and log errors in the
186
+ // left for scheduler and this gives nice semantics to catch and log errors in the
172
187
  // try/catch wrapper here.
173
188
  await prepareExecutionPayload(
174
189
  this.chain,
175
190
  this.logger,
176
191
  fork as ForkPostBellatrix, // State is of execution type
177
192
  fromHex(updatedHeadRoot),
193
+ parentBlockHash,
178
194
  safeBlockHash,
179
195
  finalizedBlockHash,
180
196
  updatedPrepareState,
@@ -187,10 +203,6 @@ export class PrepareNextSlotScheduler {
187
203
  });
188
204
  }
189
205
 
190
- if (!isStatePostBellatrix(updatedPrepareState)) {
191
- throw new Error("Expected Bellatrix state for payload attributes");
192
- }
193
-
194
206
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
195
207
 
196
208
  // If emitPayloadAttributes is true emit a SSE payloadAttributes event
@@ -202,6 +214,7 @@ export class PrepareNextSlotScheduler {
202
214
  prepareState: updatedPrepareState,
203
215
  prepareSlot,
204
216
  parentBlockRoot: fromHex(headRoot),
217
+ parentBlockHash,
205
218
  // The likely consumers of this API are builders and will anyway ignore the
206
219
  // feeRecipient, so just pass zero hash for now till a real use case arises
207
220
  feeRecipient: "0x0000000000000000000000000000000000000000000000000000000000000000",
@@ -19,7 +19,6 @@ import {
19
19
  IBeaconStateView,
20
20
  type IBeaconStateViewBellatrix,
21
21
  computeTimeAtSlot,
22
- isParentBlockFull,
23
22
  isStatePostBellatrix,
24
23
  isStatePostCapella,
25
24
  isStatePostGloas,
@@ -48,7 +47,7 @@ import {
48
47
  fulu,
49
48
  gloas,
50
49
  } from "@lodestar/types";
51
- import {Logger, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
50
+ import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
52
51
  import {ZERO_HASH_HEX} from "../../constants/index.js";
53
52
  import {numToQuantity} from "../../execution/engine/utils.js";
54
53
  import {
@@ -214,11 +213,15 @@ export async function produceBlockBody<T extends BlockType>(
214
213
  });
215
214
 
216
215
  // Get execution payload from EL
216
+ const parentBlockHash = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot))
217
+ ? currentState.latestExecutionPayloadBid.blockHash
218
+ : currentState.latestExecutionPayloadBid.parentBlockHash;
217
219
  const prepareRes = await prepareExecutionPayload(
218
220
  this,
219
221
  this.logger,
220
222
  fork,
221
223
  parentBlockRoot,
224
+ parentBlockHash,
222
225
  safeBlockHash,
223
226
  finalizedBlockHash ?? ZERO_HASH_HEX,
224
227
  currentState,
@@ -255,8 +258,8 @@ export async function produceBlockBody<T extends BlockType>(
255
258
 
256
259
  // Create self-build execution payload bid
257
260
  const bid: gloas.ExecutionPayloadBid = {
258
- parentBlockHash: currentState.latestBlockHash,
259
- parentBlockRoot: parentBlockRoot,
261
+ parentBlockHash,
262
+ parentBlockRoot,
260
263
  blockHash: executionPayload.blockHash,
261
264
  prevRandao: currentState.getRandaoMix(currentState.epoch),
262
265
  feeRecipient: executionPayload.feeRecipient,
@@ -334,6 +337,7 @@ export async function produceBlockBody<T extends BlockType>(
334
337
  this.logger,
335
338
  fork,
336
339
  parentBlockRoot,
340
+ currentState.latestExecutionPayloadHeader.blockHash,
337
341
  safeBlockHash,
338
342
  finalizedBlockHash ?? ZERO_HASH_HEX,
339
343
  currentState,
@@ -442,6 +446,7 @@ export async function produceBlockBody<T extends BlockType>(
442
446
  this.logger,
443
447
  fork,
444
448
  parentBlockRoot,
449
+ currentState.latestExecutionPayloadHeader.blockHash,
445
450
  safeBlockHash,
446
451
  finalizedBlockHash ?? ZERO_HASH_HEX,
447
452
  currentState,
@@ -607,17 +612,17 @@ export async function prepareExecutionPayload(
607
612
  logger: Logger,
608
613
  fork: ForkPostBellatrix,
609
614
  parentBlockRoot: Root,
615
+ parentBlockHash: Bytes32,
610
616
  safeBlockHash: RootHex,
611
617
  finalizedBlockHash: RootHex,
612
618
  state: IBeaconStateViewBellatrix,
613
619
  suggestedFeeRecipient: string
614
620
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
615
- const parentHash = state.latestBlockHash;
616
621
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
617
622
  const prevRandao = state.getRandaoMix(state.epoch);
618
623
 
619
624
  const payloadIdCached = chain.executionEngine.payloadIdCache.get({
620
- headBlockHash: toRootHex(parentHash),
625
+ headBlockHash: toRootHex(parentBlockHash),
621
626
  finalizedBlockHash,
622
627
  timestamp: numToQuantity(timestamp),
623
628
  prevRandao: toHex(prevRandao),
@@ -646,12 +651,13 @@ export async function prepareExecutionPayload(
646
651
  prepareState: state,
647
652
  prepareSlot: state.slot,
648
653
  parentBlockRoot,
654
+ parentBlockHash,
649
655
  feeRecipient: suggestedFeeRecipient,
650
656
  });
651
657
 
652
658
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
653
659
  fork,
654
- toRootHex(parentHash),
660
+ toRootHex(parentBlockHash),
655
661
  safeBlockHash,
656
662
  finalizedBlockHash,
657
663
  attributes
@@ -703,20 +709,30 @@ export function getPayloadAttributesForSSE(
703
709
  prepareState,
704
710
  prepareSlot,
705
711
  parentBlockRoot,
712
+ parentBlockHash,
706
713
  feeRecipient,
707
- }: {prepareState: IBeaconStateViewBellatrix; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string}
714
+ }: {
715
+ prepareState: IBeaconStateViewBellatrix;
716
+ prepareSlot: Slot;
717
+ parentBlockRoot: Root;
718
+ parentBlockHash: Bytes32;
719
+ feeRecipient: string;
720
+ }
708
721
  ): SSEPayloadAttributes {
709
- const parentHash = prepareState.latestBlockHash;
710
722
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
711
723
  prepareState,
712
724
  prepareSlot,
713
725
  parentBlockRoot,
726
+ parentBlockHash,
714
727
  feeRecipient,
715
728
  });
716
729
 
717
730
  let parentBlockNumber: number;
718
731
  if (isForkPostGloas(fork)) {
719
- const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(toRootHex(parentBlockRoot), toRootHex(parentHash));
732
+ const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(
733
+ toRootHex(parentBlockRoot),
734
+ toRootHex(parentBlockHash)
735
+ );
720
736
  if (parentBlock?.executionPayloadBlockHash == null) {
721
737
  throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
722
738
  }
@@ -730,7 +746,7 @@ export function getPayloadAttributesForSSE(
730
746
  proposalSlot: prepareSlot,
731
747
  parentBlockNumber,
732
748
  parentBlockRoot,
733
- parentBlockHash: parentHash,
749
+ parentBlockHash,
734
750
  payloadAttributes,
735
751
  };
736
752
  return ssePayloadAttributes;
@@ -745,11 +761,13 @@ function preparePayloadAttributes(
745
761
  prepareState,
746
762
  prepareSlot,
747
763
  parentBlockRoot,
764
+ parentBlockHash,
748
765
  feeRecipient,
749
766
  }: {
750
767
  prepareState: IBeaconStateViewBellatrix;
751
768
  prepareSlot: Slot;
752
769
  parentBlockRoot: Root;
770
+ parentBlockHash: Bytes32;
753
771
  feeRecipient: string;
754
772
  }
755
773
  ): SSEPayloadAttributes["payloadAttributes"] {
@@ -766,13 +784,15 @@ function preparePayloadAttributes(
766
784
  throw new Error("Expected Capella state for withdrawals");
767
785
  }
768
786
 
769
- if (isStatePostGloas(prepareState) && !isParentBlockFull(prepareState)) {
787
+ if (isStatePostGloas(prepareState)) {
788
+ const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
770
789
  // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
771
790
  // already deducted from CL balances but never credited on the EL (the envelope
772
791
  // was not delivered). The next payload must carry those same withdrawals to
773
792
  // restore CL/EL consistency, otherwise validators permanently lose that balance.
774
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
775
- prepareState.payloadExpectedWithdrawals;
793
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = isExtendingPayload
794
+ ? prepareState.getExpectedWithdrawals().expectedWithdrawals
795
+ : prepareState.payloadExpectedWithdrawals;
776
796
  } else {
777
797
  // withdrawals logic is now fork aware as it changes on electra fork post capella
778
798
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -18,7 +18,8 @@ export async function validateApiPayloadAttestationMessage(
18
18
  chain: IBeaconChain,
19
19
  payloadAttestationMessage: gloas.PayloadAttestationMessage
20
20
  ): Promise<PayloadAttestationValidationResult> {
21
- return validatePayloadAttestationMessage(chain, payloadAttestationMessage);
21
+ const prioritizeBls = true;
22
+ return validatePayloadAttestationMessage(chain, payloadAttestationMessage, prioritizeBls);
22
23
  }
23
24
 
24
25
  export async function validateGossipPayloadAttestationMessage(
@@ -30,7 +31,8 @@ export async function validateGossipPayloadAttestationMessage(
30
31
 
31
32
  async function validatePayloadAttestationMessage(
32
33
  chain: IBeaconChain,
33
- payloadAttestationMessage: gloas.PayloadAttestationMessage
34
+ payloadAttestationMessage: gloas.PayloadAttestationMessage,
35
+ prioritizeBls = false
34
36
  ): Promise<PayloadAttestationValidationResult> {
35
37
  const {data, validatorIndex} = payloadAttestationMessage;
36
38
  const epoch = computeEpochAtSlot(data.slot);
@@ -102,7 +104,7 @@ async function validatePayloadAttestationMessage(
102
104
  payloadAttestationMessage.signature
103
105
  );
104
106
 
105
- if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
107
+ if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
106
108
  throw new PayloadAttestationError(GossipAction.REJECT, {
107
109
  code: PayloadAttestationErrorCode.INVALID_SIGNATURE,
108
110
  });
@@ -745,13 +745,29 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
745
745
  });
746
746
  }
747
747
 
748
- chain.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
749
- chain.logger.debug(
750
- "Error processing execution payload from gossip data column",
751
- {slot: dataColumnSlot, root: payloadInput.blockRootHex},
752
- e as Error
753
- );
754
- });
748
+ // NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
749
+ // envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
750
+ // payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
751
+
752
+ if (!payloadInput.isComplete()) {
753
+ const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
754
+ // do not await here to not delay gossip validation
755
+ payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
756
+ chain.logger.debug(
757
+ "Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope",
758
+ {
759
+ dataColumnIndex: index,
760
+ ...payloadInputMeta,
761
+ }
762
+ );
763
+ // TODO GLOAS: UnknownBlockSync to handle this event
764
+ chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
765
+ payloadInput,
766
+ peer: peerIdStr,
767
+ source: BlockInputSource.gossip,
768
+ });
769
+ });
770
+ }
755
771
  } else {
756
772
  if (config.getForkSeq(dataColumnSlot) < ForkSeq.fulu) {
757
773
  throw new GossipActionError(GossipAction.REJECT, {code: "PRE_FULU_BLOCK"});
@@ -6,7 +6,7 @@ import {BeaconApiMethods} from "@lodestar/api/beacon/server";
6
6
  import {BeaconConfig} from "@lodestar/config";
7
7
  import type {LoggerNode} from "@lodestar/logger/node";
8
8
  import {ZERO_HASH_HEX} from "@lodestar/params";
9
- import {IBeaconStateView, PubkeyCache, isStatePostBellatrix} from "@lodestar/state-transition";
9
+ import {IBeaconStateView, PubkeyCache, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
10
10
  import {phase0} from "@lodestar/types";
11
11
  import {sleep, toRootHex} from "@lodestar/utils";
12
12
  import {ProcessShutdownCallback} from "@lodestar/validator";
@@ -223,7 +223,9 @@ export class BeaconNode {
223
223
  if (opts.executionEngine.mode === "mock") {
224
224
  const eth1BlockHash =
225
225
  isStatePostBellatrix(anchorState) && anchorState.isExecutionStateType
226
- ? toRootHex(anchorState.latestBlockHash)
226
+ ? isStatePostGloas(anchorState)
227
+ ? toRootHex(anchorState.latestBlockHash)
228
+ : toRootHex(anchorState.latestExecutionPayloadHeader.blockHash)
227
229
  : undefined;
228
230
  executionEngineOpts = {
229
231
  ...opts.executionEngine,