@lodestar/beacon-node 1.43.0-dev.6641fd750e → 1.43.0-dev.9c8becae00

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 (154) hide show
  1. package/lib/api/impl/lodestar/attesterSlashing.d.ts +8 -0
  2. package/lib/api/impl/lodestar/attesterSlashing.d.ts.map +1 -0
  3. package/lib/api/impl/lodestar/attesterSlashing.js +29 -0
  4. package/lib/api/impl/lodestar/attesterSlashing.js.map +1 -0
  5. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  6. package/lib/api/impl/lodestar/index.js +36 -1
  7. package/lib/api/impl/lodestar/index.js.map +1 -1
  8. package/lib/api/impl/validator/index.d.ts.map +1 -1
  9. package/lib/api/impl/validator/index.js +4 -3
  10. package/lib/api/impl/validator/index.js.map +1 -1
  11. package/lib/chain/GetBlobsTracker.d.ts +1 -1
  12. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  13. package/lib/chain/GetBlobsTracker.js +1 -2
  14. package/lib/chain/GetBlobsTracker.js.map +1 -1
  15. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  16. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
  17. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  18. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  19. package/lib/chain/blocks/importBlock.js +27 -35
  20. package/lib/chain/blocks/importBlock.js.map +1 -1
  21. package/lib/chain/blocks/importExecutionPayload.d.ts +1 -1
  22. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  23. package/lib/chain/blocks/importExecutionPayload.js +10 -8
  24. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  25. package/lib/chain/blocks/index.js +1 -1
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +3 -0
  28. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +20 -0
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  31. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
  32. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  33. package/lib/chain/blocks/payloadEnvelopeProcessor.js +6 -4
  34. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  35. package/lib/chain/blocks/types.d.ts +1 -1
  36. package/lib/chain/blocks/types.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
  38. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
  39. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +25 -0
  40. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
  41. package/lib/chain/chain.d.ts.map +1 -1
  42. package/lib/chain/chain.js +17 -37
  43. package/lib/chain/chain.js.map +1 -1
  44. package/lib/chain/emitter.d.ts +13 -1
  45. package/lib/chain/emitter.d.ts.map +1 -1
  46. package/lib/chain/emitter.js +5 -0
  47. package/lib/chain/emitter.js.map +1 -1
  48. package/lib/chain/errors/attestationError.d.ts +8 -1
  49. package/lib/chain/errors/attestationError.d.ts.map +1 -1
  50. package/lib/chain/errors/attestationError.js +4 -0
  51. package/lib/chain/errors/attestationError.js.map +1 -1
  52. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  53. package/lib/chain/forkChoice/index.js +12 -4
  54. package/lib/chain/forkChoice/index.js.map +1 -1
  55. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  56. package/lib/chain/prepareNextSlot.js +22 -16
  57. package/lib/chain/prepareNextSlot.js.map +1 -1
  58. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
  59. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  60. package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
  61. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  62. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -8
  63. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  64. package/lib/chain/produceBlock/produceBlockBody.js +24 -19
  65. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  66. package/lib/chain/regen/errors.d.ts +1 -11
  67. package/lib/chain/regen/errors.d.ts.map +1 -1
  68. package/lib/chain/regen/errors.js +0 -2
  69. package/lib/chain/regen/errors.js.map +1 -1
  70. package/lib/chain/regen/interface.d.ts +6 -12
  71. package/lib/chain/regen/interface.d.ts.map +1 -1
  72. package/lib/chain/regen/queued.d.ts +6 -11
  73. package/lib/chain/regen/queued.d.ts.map +1 -1
  74. package/lib/chain/regen/queued.js +8 -40
  75. package/lib/chain/regen/queued.js.map +1 -1
  76. package/lib/chain/regen/regen.d.ts +0 -5
  77. package/lib/chain/regen/regen.d.ts.map +1 -1
  78. package/lib/chain/regen/regen.js +7 -34
  79. package/lib/chain/regen/regen.js.map +1 -1
  80. package/lib/chain/stateCache/datastore/db.d.ts +5 -4
  81. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  82. package/lib/chain/stateCache/datastore/db.js +10 -32
  83. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  84. package/lib/chain/stateCache/datastore/file.d.ts +1 -1
  85. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  86. package/lib/chain/stateCache/datastore/file.js +5 -5
  87. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  88. package/lib/chain/stateCache/datastore/types.d.ts +1 -1
  89. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  90. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
  91. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  92. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
  93. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  94. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
  95. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  96. package/lib/chain/stateCache/persistentCheckpointsCache.js +116 -215
  97. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  98. package/lib/chain/stateCache/types.d.ts +8 -15
  99. package/lib/chain/stateCache/types.d.ts.map +1 -1
  100. package/lib/chain/stateCache/types.js.map +1 -1
  101. package/lib/chain/validation/aggregateAndProof.js +12 -0
  102. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  103. package/lib/chain/validation/attestation.d.ts.map +1 -1
  104. package/lib/chain/validation/attestation.js +12 -0
  105. package/lib/chain/validation/attestation.js.map +1 -1
  106. package/lib/chain/validation/executionPayloadEnvelope.js +2 -2
  107. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  108. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  109. package/lib/chain/validation/payloadAttestationMessage.js +4 -3
  110. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  111. package/lib/network/gossip/topic.d.ts +2 -729
  112. package/lib/network/gossip/topic.d.ts.map +1 -1
  113. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  114. package/lib/network/processor/gossipHandlers.js +19 -3
  115. package/lib/network/processor/gossipHandlers.js.map +1 -1
  116. package/lib/node/nodejs.d.ts.map +1 -1
  117. package/lib/node/nodejs.js +4 -2
  118. package/lib/node/nodejs.js.map +1 -1
  119. package/package.json +16 -16
  120. package/src/api/impl/lodestar/attesterSlashing.ts +43 -0
  121. package/src/api/impl/lodestar/index.ts +48 -2
  122. package/src/api/impl/validator/index.ts +6 -5
  123. package/src/chain/GetBlobsTracker.ts +1 -2
  124. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +2 -4
  125. package/src/chain/blocks/importBlock.ts +26 -39
  126. package/src/chain/blocks/importExecutionPayload.ts +11 -7
  127. package/src/chain/blocks/index.ts +1 -1
  128. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -0
  129. package/src/chain/blocks/payloadEnvelopeProcessor.ts +6 -5
  130. package/src/chain/blocks/types.ts +1 -1
  131. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +38 -0
  132. package/src/chain/chain.ts +16 -47
  133. package/src/chain/emitter.ts +12 -0
  134. package/src/chain/errors/attestationError.ts +6 -1
  135. package/src/chain/forkChoice/index.ts +12 -4
  136. package/src/chain/prepareNextSlot.ts +25 -16
  137. package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
  138. package/src/chain/produceBlock/produceBlockBody.ts +34 -20
  139. package/src/chain/regen/errors.ts +1 -6
  140. package/src/chain/regen/interface.ts +6 -12
  141. package/src/chain/regen/queued.ts +12 -48
  142. package/src/chain/regen/regen.ts +8 -36
  143. package/src/chain/stateCache/datastore/db.ts +10 -33
  144. package/src/chain/stateCache/datastore/file.ts +5 -6
  145. package/src/chain/stateCache/datastore/types.ts +2 -3
  146. package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
  147. package/src/chain/stateCache/persistentCheckpointsCache.ts +135 -246
  148. package/src/chain/stateCache/types.ts +8 -14
  149. package/src/chain/validation/aggregateAndProof.ts +13 -0
  150. package/src/chain/validation/attestation.ts +13 -0
  151. package/src/chain/validation/executionPayloadEnvelope.ts +2 -2
  152. package/src/chain/validation/payloadAttestationMessage.ts +5 -3
  153. package/src/network/processor/gossipHandlers.ts +23 -7
  154. package/src/node/nodejs.ts +4 -2
@@ -1,6 +1,6 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
- import {PayloadStatus, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
3
+ import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
4
4
  import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix} from "@lodestar/params";
5
5
  import {
6
6
  IBeaconStateView,
@@ -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",
@@ -217,11 +230,7 @@ export class PrepareNextSlotScheduler {
217
230
  // + if next slot is a skipped slot, it'd help getting target checkpoint state faster to validate attestations
218
231
  if (isEpochTransition) {
219
232
  this.metrics?.precomputeNextEpochTransition.count.inc({result: "success"}, 1);
220
- // Determine payloadPresent from head block's payload status
221
- // Pre-Gloas: payloadStatus is always FULL → payloadPresent = true
222
- // Post-Gloas: FULL → true, EMPTY → false, PENDING → false (conservative, treat as block state)
223
- const payloadPresent = headBlock.payloadStatus === PayloadStatus.FULL;
224
- const previousHits = this.chain.regen.updatePreComputedCheckpoint(headRoot, nextEpoch, payloadPresent);
233
+ const previousHits = this.chain.regen.updatePreComputedCheckpoint(headRoot, nextEpoch);
225
234
  if (previousHits === 0) {
226
235
  this.metrics?.precomputeNextEpochTransition.waste.inc();
227
236
  }
@@ -1,12 +1,10 @@
1
1
  import {
2
2
  DataAvailabilityStatus,
3
3
  ExecutionPayloadStatus,
4
- G2_POINT_AT_INFINITY,
5
4
  IBeaconStateView,
6
- IBeaconStateViewGloas,
7
5
  StateHashTreeRootSource,
8
6
  } from "@lodestar/state-transition";
9
- import {BeaconBlock, BlindedBeaconBlock, Gwei, Root, gloas} from "@lodestar/types";
7
+ import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types";
10
8
  import {ZERO_HASH} from "../../constants/index.js";
11
9
  import {Metrics} from "../../metrics/index.js";
12
10
 
@@ -19,11 +17,11 @@ export function computeNewStateRoot(
19
17
  metrics: Metrics | null,
20
18
  state: IBeaconStateView,
21
19
  block: BeaconBlock | BlindedBeaconBlock
22
- ): {newStateRoot: Root; proposerReward: Gwei; postBlockState: IBeaconStateView} {
20
+ ): {newStateRoot: Root; proposerReward: Gwei; postState: IBeaconStateView} {
23
21
  // Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type
24
22
  const blockEmptySig = {message: block, signature: ZERO_HASH};
25
23
 
26
- const postBlockState = state.stateTransition(
24
+ const postState = state.stateTransition(
27
25
  blockEmptySig,
28
26
  {
29
27
  // ExecutionPayloadStatus.valid: Assume payload valid, it has been produced by a trusted EL
@@ -42,49 +40,14 @@ export function computeNewStateRoot(
42
40
  {metrics}
43
41
  );
44
42
 
45
- const {attestations, syncAggregate, slashing} = postBlockState.proposerRewards;
43
+ const {attestations, syncAggregate, slashing} = postState.proposerRewards;
46
44
  const proposerReward = BigInt(attestations + syncAggregate + slashing);
47
45
 
48
46
  const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
49
47
  source: StateHashTreeRootSource.computeNewStateRoot,
50
48
  });
51
- const newStateRoot = postBlockState.hashTreeRoot();
49
+ const newStateRoot = postState.hashTreeRoot();
52
50
  hashTreeRootTimer?.();
53
51
 
54
- return {newStateRoot, proposerReward, postBlockState};
55
- }
56
-
57
- /**
58
- * Compute the state root after processing an execution payload envelope.
59
- * Similar to `computeNewStateRoot` but for payload envelope processing.
60
- *
61
- */
62
- export function computePayloadEnvelopeStateRoot(
63
- metrics: Metrics | null,
64
- postBlockState: IBeaconStateViewGloas,
65
- envelope: gloas.ExecutionPayloadEnvelope
66
- ): Root {
67
- const signedEnvelope: gloas.SignedExecutionPayloadEnvelope = {
68
- message: envelope,
69
- signature: G2_POINT_AT_INFINITY,
70
- };
71
-
72
- const processEnvelopeTimer = metrics?.blockPayload.executionPayloadEnvelopeProcessingTime.startTimer();
73
- const postPayloadState = postBlockState.processExecutionPayloadEnvelope(signedEnvelope, {
74
- // Signature is zero-ed (G2_POINT_AT_INFINITY), skip verification
75
- verifySignature: false,
76
- // State root is being computed here, the envelope doesn't have it yet
77
- verifyStateRoot: false,
78
- // Preserve cache in source state, since the resulting state is not added to the state cache
79
- dontTransferCache: true,
80
- });
81
- processEnvelopeTimer?.();
82
-
83
- const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
84
- source: StateHashTreeRootSource.computePayloadEnvelopeStateRoot,
85
- });
86
- const stateRoot = postPayloadState.hashTreeRoot();
87
- hashTreeRootTimer?.();
88
-
89
- return stateRoot;
52
+ return {newStateRoot, proposerReward, postState};
90
53
  }
@@ -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 {
@@ -111,12 +110,6 @@ export type ProduceFullGloas = {
111
110
  executionRequests: electra.ExecutionRequests;
112
111
  blobsBundle: BlobsBundle<ForkPostGloas>;
113
112
  cells: fulu.Cell[][];
114
- /**
115
- * Cached payload envelope state root computed during block production.
116
- * This is the state root after running `processExecutionPayloadEnvelope` on the
117
- * post-block state, and later used to construct the `ExecutionPayloadEnvelope`.
118
- */
119
- payloadEnvelopeStateRoot: Root;
120
113
  };
121
114
  export type ProduceFullFulu = {
122
115
  type: BlockType.Full;
@@ -220,11 +213,15 @@ export async function produceBlockBody<T extends BlockType>(
220
213
  });
221
214
 
222
215
  // Get execution payload from EL
216
+ const parentBlockHash = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot))
217
+ ? currentState.latestExecutionPayloadBid.blockHash
218
+ : currentState.latestExecutionPayloadBid.parentBlockHash;
223
219
  const prepareRes = await prepareExecutionPayload(
224
220
  this,
225
221
  this.logger,
226
222
  fork,
227
223
  parentBlockRoot,
224
+ parentBlockHash,
228
225
  safeBlockHash,
229
226
  finalizedBlockHash ?? ZERO_HASH_HEX,
230
227
  currentState,
@@ -261,8 +258,8 @@ export async function produceBlockBody<T extends BlockType>(
261
258
 
262
259
  // Create self-build execution payload bid
263
260
  const bid: gloas.ExecutionPayloadBid = {
264
- parentBlockHash: currentState.latestBlockHash,
265
- parentBlockRoot: parentBlockRoot,
261
+ parentBlockHash,
262
+ parentBlockRoot,
266
263
  blockHash: executionPayload.blockHash,
267
264
  prevRandao: currentState.getRandaoMix(currentState.epoch),
268
265
  feeRecipient: executionPayload.feeRecipient,
@@ -340,6 +337,7 @@ export async function produceBlockBody<T extends BlockType>(
340
337
  this.logger,
341
338
  fork,
342
339
  parentBlockRoot,
340
+ currentState.latestExecutionPayloadHeader.blockHash,
343
341
  safeBlockHash,
344
342
  finalizedBlockHash ?? ZERO_HASH_HEX,
345
343
  currentState,
@@ -448,6 +446,7 @@ export async function produceBlockBody<T extends BlockType>(
448
446
  this.logger,
449
447
  fork,
450
448
  parentBlockRoot,
449
+ currentState.latestExecutionPayloadHeader.blockHash,
451
450
  safeBlockHash,
452
451
  finalizedBlockHash ?? ZERO_HASH_HEX,
453
452
  currentState,
@@ -613,17 +612,17 @@ export async function prepareExecutionPayload(
613
612
  logger: Logger,
614
613
  fork: ForkPostBellatrix,
615
614
  parentBlockRoot: Root,
615
+ parentBlockHash: Bytes32,
616
616
  safeBlockHash: RootHex,
617
617
  finalizedBlockHash: RootHex,
618
618
  state: IBeaconStateViewBellatrix,
619
619
  suggestedFeeRecipient: string
620
620
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
621
- const parentHash = state.latestBlockHash;
622
621
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
623
622
  const prevRandao = state.getRandaoMix(state.epoch);
624
623
 
625
624
  const payloadIdCached = chain.executionEngine.payloadIdCache.get({
626
- headBlockHash: toRootHex(parentHash),
625
+ headBlockHash: toRootHex(parentBlockHash),
627
626
  finalizedBlockHash,
628
627
  timestamp: numToQuantity(timestamp),
629
628
  prevRandao: toHex(prevRandao),
@@ -652,12 +651,13 @@ export async function prepareExecutionPayload(
652
651
  prepareState: state,
653
652
  prepareSlot: state.slot,
654
653
  parentBlockRoot,
654
+ parentBlockHash,
655
655
  feeRecipient: suggestedFeeRecipient,
656
656
  });
657
657
 
658
658
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
659
659
  fork,
660
- toRootHex(parentHash),
660
+ toRootHex(parentBlockHash),
661
661
  safeBlockHash,
662
662
  finalizedBlockHash,
663
663
  attributes
@@ -709,20 +709,30 @@ export function getPayloadAttributesForSSE(
709
709
  prepareState,
710
710
  prepareSlot,
711
711
  parentBlockRoot,
712
+ parentBlockHash,
712
713
  feeRecipient,
713
- }: {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
+ }
714
721
  ): SSEPayloadAttributes {
715
- const parentHash = prepareState.latestBlockHash;
716
722
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
717
723
  prepareState,
718
724
  prepareSlot,
719
725
  parentBlockRoot,
726
+ parentBlockHash,
720
727
  feeRecipient,
721
728
  });
722
729
 
723
730
  let parentBlockNumber: number;
724
731
  if (isForkPostGloas(fork)) {
725
- const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(toRootHex(parentBlockRoot), toRootHex(parentHash));
732
+ const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(
733
+ toRootHex(parentBlockRoot),
734
+ toRootHex(parentBlockHash)
735
+ );
726
736
  if (parentBlock?.executionPayloadBlockHash == null) {
727
737
  throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
728
738
  }
@@ -736,7 +746,7 @@ export function getPayloadAttributesForSSE(
736
746
  proposalSlot: prepareSlot,
737
747
  parentBlockNumber,
738
748
  parentBlockRoot,
739
- parentBlockHash: parentHash,
749
+ parentBlockHash,
740
750
  payloadAttributes,
741
751
  };
742
752
  return ssePayloadAttributes;
@@ -751,11 +761,13 @@ function preparePayloadAttributes(
751
761
  prepareState,
752
762
  prepareSlot,
753
763
  parentBlockRoot,
764
+ parentBlockHash,
754
765
  feeRecipient,
755
766
  }: {
756
767
  prepareState: IBeaconStateViewBellatrix;
757
768
  prepareSlot: Slot;
758
769
  parentBlockRoot: Root;
770
+ parentBlockHash: Bytes32;
759
771
  feeRecipient: string;
760
772
  }
761
773
  ): SSEPayloadAttributes["payloadAttributes"] {
@@ -772,13 +784,15 @@ function preparePayloadAttributes(
772
784
  throw new Error("Expected Capella state for withdrawals");
773
785
  }
774
786
 
775
- if (isStatePostGloas(prepareState) && !isParentBlockFull(prepareState)) {
787
+ if (isStatePostGloas(prepareState)) {
788
+ const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
776
789
  // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
777
790
  // already deducted from CL balances but never credited on the EL (the envelope
778
791
  // was not delivered). The next payload must carry those same withdrawals to
779
792
  // restore CL/EL consistency, otherwise validators permanently lose that balance.
780
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
781
- prepareState.payloadExpectedWithdrawals;
793
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = isExtendingPayload
794
+ ? prepareState.getExpectedWithdrawals().expectedWithdrawals
795
+ : prepareState.payloadExpectedWithdrawals;
782
796
  } else {
783
797
  // withdrawals logic is now fork aware as it changes on electra fork post capella
784
798
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -1,4 +1,3 @@
1
- import {PayloadStatus} from "@lodestar/fork-choice";
2
1
  import {Root, RootHex, Slot} from "@lodestar/types";
3
2
 
4
3
  export enum RegenErrorCode {
@@ -10,8 +9,6 @@ export enum RegenErrorCode {
10
9
  BLOCK_NOT_IN_DB = "REGEN_ERROR_BLOCK_NOT_IN_DB",
11
10
  STATE_TRANSITION_ERROR = "REGEN_ERROR_STATE_TRANSITION_ERROR",
12
11
  INVALID_STATE_ROOT = "REGEN_ERROR_INVALID_STATE_ROOT",
13
- UNEXPECTED_PAYLOAD_STATUS = "REGEN_ERROR_UNEXPECTED_PAYLOAD_STATUS",
14
- INTERNAL_ERROR = "REGEN_ERROR_INTERNAL_ERROR",
15
12
  }
16
13
 
17
14
  export type RegenErrorType =
@@ -22,9 +19,7 @@ export type RegenErrorType =
22
19
  | {code: RegenErrorCode.TOO_MANY_BLOCK_PROCESSED; stateRoot: RootHex | Root}
23
20
  | {code: RegenErrorCode.BLOCK_NOT_IN_DB; blockRoot: RootHex | Root}
24
21
  | {code: RegenErrorCode.STATE_TRANSITION_ERROR; error: Error}
25
- | {code: RegenErrorCode.INVALID_STATE_ROOT; slot: Slot; expected: RootHex; actual: RootHex}
26
- | {code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS; blockRoot: RootHex | Root; payloadStatus: PayloadStatus}
27
- | {code: RegenErrorCode.INTERNAL_ERROR; message: string};
22
+ | {code: RegenErrorCode.INVALID_STATE_ROOT; slot: Slot; expected: RootHex; actual: RootHex};
28
23
 
29
24
  export class RegenError extends Error {
30
25
  type: RegenErrorType;
@@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {IBeaconStateView} from "@lodestar/state-transition";
4
4
  import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
- import {CheckpointHexPayload} from "../stateCache/types.js";
5
+ import {CheckpointHex} from "../stateCache/types.js";
6
6
 
7
7
  export enum RegenCaller {
8
8
  getDuties = "getDuties",
@@ -40,21 +40,15 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
40
40
  dumpCacheSummary(): routes.lodestar.StateCacheItem[];
41
41
  getStateSync(stateRoot: RootHex): IBeaconStateView | null;
42
42
  getPreStateSync(block: BeaconBlock): IBeaconStateView | null;
43
- getCheckpointStateOrBytes(cp: CheckpointHexPayload): Promise<IBeaconStateView | Uint8Array | null>;
44
- getCheckpointStateSync(cp: CheckpointHexPayload): IBeaconStateView | null;
43
+ getCheckpointStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null>;
44
+ getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null;
45
45
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null;
46
46
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
47
47
  pruneOnFinalized(finalizedEpoch: Epoch): void;
48
- processBlockState(blockRootHex: RootHex, postState: IBeaconStateView): void;
49
- processPayloadState(payloadState: IBeaconStateView): void;
50
- /**
51
- * payloadPresent is true if this is payload state, false if block state.
52
- * payloadPresent is always true for pre-gloas.
53
- */
54
- addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView, payloadPresent: boolean): void;
48
+ processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
49
+ addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
55
50
  updateHeadState(newHead: ProtoBlock, maybeHeadState: IBeaconStateView): void;
56
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null;
57
- upgradeForGloas(epoch: Epoch): void;
51
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
58
52
  }
59
53
 
60
54
  /**
@@ -1,11 +1,11 @@
1
1
  import {routes} from "@lodestar/api";
2
- import {IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {IBeaconStateView, computeEpochAtSlot} from "@lodestar/state-transition";
4
4
  import {BeaconBlock, Epoch, RootHex, Slot, isGloasBeaconBlock, phase0} from "@lodestar/types";
5
- import {Logger, fromHex, toRootHex} from "@lodestar/utils";
5
+ import {Logger, toRootHex} from "@lodestar/utils";
6
6
  import {Metrics} from "../../metrics/index.js";
7
7
  import {JobItemQueue} from "../../util/queue/index.js";
8
- import {BlockStateCache, CheckpointHexPayload, CheckpointStateCache} from "../stateCache/types.js";
8
+ import {BlockStateCache, CheckpointHex, CheckpointStateCache} from "../stateCache/types.js";
9
9
  import {RegenError, RegenErrorCode} from "./errors.js";
10
10
  import {
11
11
  IStateRegenerator,
@@ -104,19 +104,9 @@ export class QueuedStateRegenerator implements IStateRegenerator {
104
104
  const parentEpoch = computeEpochAtSlot(parentBlock.slot);
105
105
  const blockEpoch = computeEpochAtSlot(block.slot);
106
106
 
107
- // Convert PayloadStatus to payloadPresent boolean
108
- if (parentBlock.payloadStatus === PayloadStatus.PENDING) {
109
- throw new RegenError({
110
- code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS,
111
- blockRoot: block.parentRoot,
112
- payloadStatus: parentBlock.payloadStatus,
113
- });
114
- }
115
- const payloadPresent = parentBlock.payloadStatus === PayloadStatus.FULL;
116
-
117
107
  // Check the checkpoint cache (if the pre-state is a checkpoint state)
118
108
  if (parentEpoch < blockEpoch) {
119
- const checkpointState = this.checkpointStateCache.getLatest(parentRoot, blockEpoch, payloadPresent);
109
+ const checkpointState = this.checkpointStateCache.getLatest(parentRoot, blockEpoch);
120
110
  if (checkpointState && computeEpochAtSlot(checkpointState.slot) === blockEpoch) {
121
111
  return checkpointState;
122
112
  }
@@ -135,14 +125,14 @@ export class QueuedStateRegenerator implements IStateRegenerator {
135
125
  return null;
136
126
  }
137
127
 
138
- async getCheckpointStateOrBytes(cp: CheckpointHexPayload): Promise<IBeaconStateView | Uint8Array | null> {
128
+ async getCheckpointStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null> {
139
129
  return this.checkpointStateCache.getStateOrBytes(cp);
140
130
  }
141
131
 
142
132
  /**
143
133
  * Get checkpoint state from cache
144
134
  */
145
- getCheckpointStateSync(cp: CheckpointHexPayload): IBeaconStateView | null {
135
+ getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null {
146
136
  return this.checkpointStateCache.get(cp);
147
137
  }
148
138
 
@@ -150,19 +140,7 @@ export class QueuedStateRegenerator implements IStateRegenerator {
150
140
  * Get state closest to head
151
141
  */
152
142
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null {
153
- // Convert PayloadStatus to payloadPresent boolean
154
- if (head.payloadStatus === PayloadStatus.PENDING) {
155
- throw new RegenError({
156
- code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS,
157
- blockRoot: fromHex(head.blockRoot),
158
- payloadStatus: head.payloadStatus,
159
- });
160
- }
161
- const payloadPresent = head.payloadStatus === PayloadStatus.FULL;
162
- return (
163
- this.checkpointStateCache.getLatest(head.blockRoot, Infinity, payloadPresent) ||
164
- this.blockStateCache.get(head.stateRoot)
165
- );
143
+ return this.checkpointStateCache.getLatest(head.blockRoot, Infinity) || this.blockStateCache.get(head.stateRoot);
166
144
  }
167
145
 
168
146
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void {
@@ -175,24 +153,15 @@ export class QueuedStateRegenerator implements IStateRegenerator {
175
153
  this.blockStateCache.deleteAllBeforeEpoch(finalizedEpoch);
176
154
  }
177
155
 
178
- processBlockState(blockRootHex: RootHex, postState: IBeaconStateView): void {
156
+ processState(blockRootHex: RootHex, postState: IBeaconStateView): void {
179
157
  this.blockStateCache.add(postState);
180
158
  this.checkpointStateCache.processState(blockRootHex, postState).catch((e) => {
181
159
  this.logger.debug("Error processing block state", {blockRootHex, slot: postState.slot}, e);
182
160
  });
183
161
  }
184
162
 
185
- /**
186
- * Process payload state for caching after importing execution payload.
187
- */
188
- processPayloadState(payloadState: IBeaconStateView): void {
189
- // Add payload state to block state cache (keyed by payload state root)
190
- this.blockStateCache.add(payloadState);
191
- }
192
-
193
- // TODO GLOAS: This should also be called when importing execution payload after we implement it
194
- addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView, payloadPresent: boolean): void {
195
- this.checkpointStateCache.add(cp, item, payloadPresent);
163
+ addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void {
164
+ this.checkpointStateCache.add(cp, item);
196
165
  }
197
166
 
198
167
  updateHeadState(newHead: ProtoBlock, maybeHeadState: IBeaconStateView): void {
@@ -228,13 +197,8 @@ export class QueuedStateRegenerator implements IStateRegenerator {
228
197
  }
229
198
  }
230
199
 
231
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null {
232
- return this.checkpointStateCache.updatePreComputedCheckpoint(rootHex, epoch, payloadPresent);
233
- }
234
-
235
- upgradeForGloas(epoch: Epoch): void {
236
- this.logger.verbose("Upgrading block state cache for Gloas fork", {epoch});
237
- this.blockStateCache.upgradeToGloas();
200
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null {
201
+ return this.checkpointStateCache.updatePreComputedCheckpoint(rootHex, epoch);
238
202
  }
239
203
 
240
204
  /**
@@ -1,6 +1,6 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
3
- import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
3
+ import {SLOTS_PER_EPOCH} from "@lodestar/params";
4
4
  import {
5
5
  DataAvailabilityStatus,
6
6
  ExecutionPayloadStatus,
@@ -110,19 +110,9 @@ export class StateRegenerator implements IStateRegeneratorInternal {
110
110
  const {checkpointStateCache} = this.modules;
111
111
  const epoch = computeEpochAtSlot(slot);
112
112
 
113
- // Convert PayloadStatus to payloadPresent boolean
114
- if (block.payloadStatus === PayloadStatus.PENDING) {
115
- throw new RegenError({
116
- code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS,
117
- blockRoot: fromHex(blockRoot),
118
- payloadStatus: block.payloadStatus,
119
- });
120
- }
121
- const payloadPresent = block.payloadStatus === PayloadStatus.FULL;
122
-
123
113
  const latestCheckpointStateCtx = allowDiskReload
124
- ? await checkpointStateCache.getOrReloadLatest(blockRoot, epoch, payloadPresent)
125
- : checkpointStateCache.getLatest(blockRoot, epoch, payloadPresent);
114
+ ? await checkpointStateCache.getOrReloadLatest(blockRoot, epoch)
115
+ : checkpointStateCache.getLatest(blockRoot, epoch);
126
116
 
127
117
  // If a checkpoint state exists with the given checkpoint root, it either is in requested epoch
128
118
  // or needs to have empty slots processed until the requested epoch
@@ -176,18 +166,9 @@ export class StateRegenerator implements IStateRegeneratorInternal {
176
166
  if (!lastBlockToReplay) continue;
177
167
  const epoch = computeEpochAtSlot(lastBlockToReplay.slot - 1);
178
168
 
179
- // Convert PayloadStatus to payloadPresent boolean
180
- if (b.payloadStatus === PayloadStatus.PENDING) {
181
- throw new RegenError({
182
- code: RegenErrorCode.INTERNAL_ERROR,
183
- message: `Unexpected PENDING payloadStatus for ancestor block ${b.blockRoot} at slot ${b.slot}`,
184
- });
185
- }
186
- const payloadPresent = b.payloadStatus === PayloadStatus.FULL;
187
-
188
169
  state = allowDiskReload
189
- ? await checkpointStateCache.getOrReloadLatest(b.blockRoot, epoch, payloadPresent)
190
- : checkpointStateCache.getLatest(b.blockRoot, epoch, payloadPresent);
170
+ ? await checkpointStateCache.getOrReloadLatest(b.blockRoot, epoch)
171
+ : checkpointStateCache.getLatest(b.blockRoot, epoch);
191
172
  if (state) {
192
173
  break;
193
174
  }
@@ -351,11 +332,6 @@ async function processSlotsByCheckpoint(
351
332
  * emitting "checkpoint" events after every epoch processed.
352
333
  *
353
334
  * Stops processing after no more full epochs can be processed.
354
- *
355
- * Output state variant:
356
- * - Post-Gloas: If slots are processed, returns block state (payloadPresent=false).
357
- * If no slots processed, returns preState as-is (preserves variant).
358
- * - Pre-Gloas: Always payloadPresent=true (no block/payload distinction).
359
335
  */
360
336
  export async function processSlotsToNearestCheckpoint(
361
337
  modules: {
@@ -375,7 +351,7 @@ export async function processSlotsToNearestCheckpoint(
375
351
  const postSlot = slot;
376
352
  const preEpoch = computeEpochAtSlot(preSlot);
377
353
  let postState = preState;
378
- const {config, checkpointStateCache, emitter, metrics, logger} = modules;
354
+ const {checkpointStateCache, emitter, metrics, logger} = modules;
379
355
  let count = 0;
380
356
 
381
357
  for (
@@ -399,11 +375,7 @@ export async function processSlotsToNearestCheckpoint(
399
375
  // This may becomes the "official" checkpoint state if the 1st block of epoch is skipped
400
376
  const checkpointState = postState;
401
377
  const cp = getCheckpointFromState(checkpointState);
402
- // processSlots() only does epoch transitions, never processes payloads
403
- // Pre-Gloas: payloadPresent is always true (execution payload embedded in block)
404
- // Post-Gloas: result is a block state (payloadPresent=false)
405
- const isPayloadPresent = config.getForkSeq(checkpointState.slot) < ForkSeq.gloas;
406
- checkpointStateCache.add(cp, checkpointState, isPayloadPresent);
378
+ checkpointStateCache.add(cp, checkpointState);
407
379
  // consumers should not mutate state ever
408
380
  emitter?.emit(ChainEvent.checkpoint, cp, checkpointState);
409
381