@lodestar/beacon-node 1.43.0-dev.7622a1076c → 1.43.0-dev.78c66bac71

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.
@@ -214,9 +214,13 @@ export async function produceBlockBody<T extends BlockType>(
214
214
  });
215
215
 
216
216
  // Get execution payload from EL
217
- const parentBlockHash = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot))
217
+ const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot));
218
+ const parentBlockHash = isExtendingPayload
218
219
  ? currentState.latestExecutionPayloadBid.blockHash
219
220
  : currentState.latestExecutionPayloadBid.parentBlockHash;
221
+ const parentExecutionRequests = isExtendingPayload
222
+ ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
223
+ : ssz.electra.ExecutionRequests.defaultValue();
220
224
  const prepareRes = await prepareExecutionPayload(
221
225
  this,
222
226
  this.logger,
@@ -226,7 +230,8 @@ export async function produceBlockBody<T extends BlockType>(
226
230
  safeBlockHash,
227
231
  finalizedBlockHash ?? ZERO_HASH_HEX,
228
232
  currentState,
229
- feeRecipient
233
+ feeRecipient,
234
+ parentExecutionRequests
230
235
  );
231
236
 
232
237
  const {prepType, payloadId} = prepareRes;
@@ -282,7 +287,7 @@ export async function produceBlockBody<T extends BlockType>(
282
287
  gloasBody.signedExecutionPayloadBid = signedBid;
283
288
  // TODO GLOAS: Get payload attestations from pool for previous slot
284
289
  gloasBody.payloadAttestations = [];
285
- // TODO GLOAS: set parentExecutionRequests in the block body
290
+ gloasBody.parentExecutionRequests = parentExecutionRequests;
286
291
  blockBody = gloasBody as AssembledBodyType<T>;
287
292
 
288
293
  // Store execution payload data required to construct execution payload envelope later
@@ -619,7 +624,8 @@ export async function prepareExecutionPayload(
619
624
  safeBlockHash: RootHex,
620
625
  finalizedBlockHash: RootHex,
621
626
  state: IBeaconStateViewBellatrix,
622
- suggestedFeeRecipient: string
627
+ suggestedFeeRecipient: string,
628
+ parentExecutionRequests?: electra.ExecutionRequests
623
629
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
624
630
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
625
631
  const prevRandao = state.getRandaoMix(state.epoch);
@@ -656,6 +662,7 @@ export async function prepareExecutionPayload(
656
662
  parentBlockRoot,
657
663
  parentBlockHash,
658
664
  feeRecipient: suggestedFeeRecipient,
665
+ parentExecutionRequests,
659
666
  });
660
667
 
661
668
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
@@ -714,12 +721,14 @@ export function getPayloadAttributesForSSE(
714
721
  parentBlockRoot,
715
722
  parentBlockHash,
716
723
  feeRecipient,
724
+ parentExecutionRequests,
717
725
  }: {
718
726
  prepareState: IBeaconStateViewBellatrix;
719
727
  prepareSlot: Slot;
720
728
  parentBlockRoot: Root;
721
729
  parentBlockHash: Bytes32;
722
730
  feeRecipient: string;
731
+ parentExecutionRequests?: electra.ExecutionRequests;
723
732
  }
724
733
  ): SSEPayloadAttributes {
725
734
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
@@ -728,6 +737,7 @@ export function getPayloadAttributesForSSE(
728
737
  parentBlockRoot,
729
738
  parentBlockHash,
730
739
  feeRecipient,
740
+ parentExecutionRequests,
731
741
  });
732
742
 
733
743
  let parentBlockNumber: number;
@@ -766,12 +776,14 @@ function preparePayloadAttributes(
766
776
  parentBlockRoot,
767
777
  parentBlockHash,
768
778
  feeRecipient,
779
+ parentExecutionRequests,
769
780
  }: {
770
781
  prepareState: IBeaconStateViewBellatrix;
771
782
  prepareSlot: Slot;
772
783
  parentBlockRoot: Root;
773
784
  parentBlockHash: Bytes32;
774
785
  feeRecipient: string;
786
+ parentExecutionRequests?: electra.ExecutionRequests;
775
787
  }
776
788
  ): SSEPayloadAttributes["payloadAttributes"] {
777
789
  const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
@@ -789,13 +801,20 @@ function preparePayloadAttributes(
789
801
 
790
802
  if (isStatePostGloas(prepareState)) {
791
803
  const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
792
- // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
793
- // already deducted from CL balances but never credited on the EL (the envelope
794
- // was not delivered). The next payload must carry those same withdrawals to
795
- // restore CL/EL consistency, otherwise validators permanently lose that balance.
796
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = isExtendingPayload
797
- ? prepareState.getExpectedWithdrawals().expectedWithdrawals
798
- : prepareState.payloadExpectedWithdrawals;
804
+ if (isExtendingPayload) {
805
+ if (parentExecutionRequests === undefined) {
806
+ throw new Error("parentExecutionRequests required when extending full parent");
807
+ }
808
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
809
+ prepareState.getExpectedWithdrawalsForFullParent(parentExecutionRequests);
810
+ } else {
811
+ // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
812
+ // already deducted from CL balances but never credited on the EL (the envelope
813
+ // was not delivered). The next payload must carry those same withdrawals to
814
+ // restore CL/EL consistency, otherwise validators permanently lose that balance.
815
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
816
+ prepareState.payloadExpectedWithdrawals;
817
+ }
799
818
  } else {
800
819
  // withdrawals logic is now fork aware as it changes on electra fork post capella
801
820
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -1,6 +1,6 @@
1
1
  import {CheckpointWithHex} from "@lodestar/fork-choice";
2
2
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
3
- import {RootHex} from "@lodestar/types";
3
+ import {RootHex, Slot} from "@lodestar/types";
4
4
  import {Logger} from "@lodestar/utils";
5
5
  import {Metrics} from "../../metrics/metrics.js";
6
6
  import {SerializedCache} from "../../util/serializedCache.js";
@@ -21,8 +21,15 @@ export type SeenPayloadEnvelopeInputModules = {
21
21
  /**
22
22
  * Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
23
23
  *
24
- * Created during block import when a block is processed.
25
- * Pruned on finalization and after payload is written to DB.
24
+ * Created during block import when a block is processed. Two pruning paths:
25
+ * - `prepareNextSlot` calls `pruneBelow(headParentSlot)` every slot once the head we'll build
26
+ * on is known.
27
+ * - `onFinalized` calls `pruneBelow(finalizedSlot)` on every finalization for bulk cleanup.
28
+ *
29
+ * Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
30
+ * (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
31
+ * transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
32
+ * ticks; subsequent ticks settle it back.
26
33
  */
27
34
  export class SeenPayloadEnvelopeInput {
28
35
  private readonly chainEvents: ChainEventEmitter;
@@ -58,16 +65,7 @@ export class SeenPayloadEnvelopeInput {
58
65
  }
59
66
 
60
67
  private onFinalized = (checkpoint: CheckpointWithHex): void => {
61
- // Prune all entries with slot < finalized slot
62
- const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
63
- let deletedCount = 0;
64
- for (const [, input] of this.payloadInputs) {
65
- if (input.slot < finalizedSlot) {
66
- this.evictPayloadInput(input);
67
- deletedCount++;
68
- }
69
- }
70
- this.logger?.debug("SeenPayloadEnvelopeInput.onFinalized deleted cached entries", {deletedCount});
68
+ this.pruneBelow(computeStartSlotAtEpoch(checkpoint.epoch));
71
69
  };
72
70
 
73
71
  add(props: CreateFromBlockProps): PayloadEnvelopeInput {
@@ -88,17 +86,21 @@ export class SeenPayloadEnvelopeInput {
88
86
  return this.payloadInputs.get(blockRootHex)?.hasPayloadEnvelope() ?? false;
89
87
  }
90
88
 
91
- prune(blockRootHex: RootHex): void {
92
- const payloadInput = this.payloadInputs.get(blockRootHex);
93
- if (payloadInput) {
94
- this.evictPayloadInput(payloadInput);
95
- }
96
- }
97
-
98
89
  size(): number {
99
90
  return this.payloadInputs.size;
100
91
  }
101
92
 
93
+ pruneBelow(slot: Slot): void {
94
+ let deletedCount = 0;
95
+ for (const [, input] of this.payloadInputs) {
96
+ if (input.slot < slot) {
97
+ this.evictPayloadInput(input);
98
+ deletedCount++;
99
+ }
100
+ }
101
+ this.logger?.debug("SeenPayloadEnvelopeInput.pruneBelow deleted entries", {slot, deletedCount});
102
+ }
103
+
102
104
  private evictPayloadInput(payloadInput: PayloadEnvelopeInput): void {
103
105
  this.serializedCache.delete(payloadInput.getSerializedCacheKeys());
104
106
  this.payloadInputs.delete(payloadInput.blockRootHex);