@lodestar/beacon-node 1.43.0-dev.921c57528b → 1.43.0-dev.958956d6ba

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 (212) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -5
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/lodestar/index.js +1 -1
  5. package/lib/api/impl/lodestar/index.js.map +1 -1
  6. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  7. package/lib/chain/blocks/importBlock.js +3 -2
  8. package/lib/chain/blocks/importBlock.js.map +1 -1
  9. package/lib/chain/blocks/importExecutionPayload.d.ts +19 -6
  10. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  11. package/lib/chain/blocks/importExecutionPayload.js +43 -19
  12. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  13. package/lib/chain/blocks/index.d.ts +5 -3
  14. package/lib/chain/blocks/index.d.ts.map +1 -1
  15. package/lib/chain/blocks/index.js +31 -10
  16. package/lib/chain/blocks/index.js.map +1 -1
  17. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +1 -0
  18. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  19. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +4 -1
  20. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  21. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
  22. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  23. package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
  24. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  25. package/lib/chain/blocks/types.d.ts +2 -2
  26. package/lib/chain/blocks/types.d.ts.map +1 -1
  27. package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
  28. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  29. package/lib/chain/blocks/utils/chainSegment.js +81 -12
  30. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  31. package/lib/chain/blocks/verifyBlock.d.ts +5 -3
  32. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  33. package/lib/chain/blocks/verifyBlock.js +51 -7
  34. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  35. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  36. package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
  37. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  38. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  39. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  40. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  41. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  42. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  43. package/lib/chain/chain.d.ts +5 -3
  44. package/lib/chain/chain.d.ts.map +1 -1
  45. package/lib/chain/chain.js +19 -4
  46. package/lib/chain/chain.js.map +1 -1
  47. package/lib/chain/errors/blockError.d.ts +8 -1
  48. package/lib/chain/errors/blockError.d.ts.map +1 -1
  49. package/lib/chain/errors/blockError.js +2 -0
  50. package/lib/chain/errors/blockError.js.map +1 -1
  51. package/lib/chain/errors/index.d.ts +1 -0
  52. package/lib/chain/errors/index.d.ts.map +1 -1
  53. package/lib/chain/errors/index.js +1 -0
  54. package/lib/chain/errors/index.js.map +1 -1
  55. package/lib/chain/errors/proposerPreferences.d.ts +33 -0
  56. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  57. package/lib/chain/errors/proposerPreferences.js +13 -0
  58. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  59. package/lib/chain/interface.d.ts +5 -3
  60. package/lib/chain/interface.d.ts.map +1 -1
  61. package/lib/chain/interface.js.map +1 -1
  62. package/lib/chain/opPools/payloadAttestationPool.d.ts +2 -2
  63. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  64. package/lib/chain/opPools/payloadAttestationPool.js +4 -4
  65. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  66. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  67. package/lib/chain/prepareNextSlot.js +14 -2
  68. package/lib/chain/prepareNextSlot.js.map +1 -1
  69. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  70. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  71. package/lib/chain/produceBlock/produceBlockBody.js +35 -16
  72. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  73. package/lib/chain/regen/interface.d.ts +1 -0
  74. package/lib/chain/regen/interface.d.ts.map +1 -1
  75. package/lib/chain/regen/interface.js +1 -0
  76. package/lib/chain/regen/interface.js.map +1 -1
  77. package/lib/chain/seenCache/index.d.ts +1 -0
  78. package/lib/chain/seenCache/index.d.ts.map +1 -1
  79. package/lib/chain/seenCache/index.js +1 -0
  80. package/lib/chain/seenCache/index.js.map +1 -1
  81. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +8 -2
  82. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  83. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +8 -2
  84. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  85. package/lib/chain/seenCache/seenProposerPreferences.d.ts +15 -0
  86. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  87. package/lib/chain/seenCache/seenProposerPreferences.js +25 -0
  88. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  89. package/lib/chain/validation/block.d.ts.map +1 -1
  90. package/lib/chain/validation/block.js +1 -0
  91. package/lib/chain/validation/block.js.map +1 -1
  92. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  93. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  94. package/lib/chain/validation/proposerPreferences.js +69 -0
  95. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  96. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  97. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  98. package/lib/metrics/metrics/lodestar.js +4 -0
  99. package/lib/metrics/metrics/lodestar.js.map +1 -1
  100. package/lib/network/gossip/interface.d.ts +7 -1
  101. package/lib/network/gossip/interface.d.ts.map +1 -1
  102. package/lib/network/gossip/interface.js +1 -0
  103. package/lib/network/gossip/interface.js.map +1 -1
  104. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  105. package/lib/network/gossip/scoringParameters.js +12 -1
  106. package/lib/network/gossip/scoringParameters.js.map +1 -1
  107. package/lib/network/gossip/topic.d.ts +8 -0
  108. package/lib/network/gossip/topic.d.ts.map +1 -1
  109. package/lib/network/gossip/topic.js +6 -0
  110. package/lib/network/gossip/topic.js.map +1 -1
  111. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  112. package/lib/network/processor/gossipHandlers.js +10 -6
  113. package/lib/network/processor/gossipHandlers.js.map +1 -1
  114. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  115. package/lib/network/processor/gossipQueues/index.js +5 -0
  116. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  117. package/lib/network/processor/index.d.ts.map +1 -1
  118. package/lib/network/processor/index.js +1 -0
  119. package/lib/network/processor/index.js.map +1 -1
  120. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  121. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  122. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  123. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  124. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  125. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  126. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  127. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  128. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  129. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  130. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  131. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  132. package/lib/node/notifier.js +7 -1
  133. package/lib/node/notifier.js.map +1 -1
  134. package/lib/sync/range/batch.d.ts +12 -2
  135. package/lib/sync/range/batch.d.ts.map +1 -1
  136. package/lib/sync/range/batch.js +56 -30
  137. package/lib/sync/range/batch.js.map +1 -1
  138. package/lib/sync/range/chain.d.ts +6 -2
  139. package/lib/sync/range/chain.d.ts.map +1 -1
  140. package/lib/sync/range/chain.js +4 -3
  141. package/lib/sync/range/chain.js.map +1 -1
  142. package/lib/sync/range/range.d.ts.map +1 -1
  143. package/lib/sync/range/range.js +17 -6
  144. package/lib/sync/range/range.js.map +1 -1
  145. package/lib/sync/types.d.ts +34 -0
  146. package/lib/sync/types.d.ts.map +1 -1
  147. package/lib/sync/types.js +34 -0
  148. package/lib/sync/types.js.map +1 -1
  149. package/lib/sync/unknownBlock.d.ts +24 -1
  150. package/lib/sync/unknownBlock.d.ts.map +1 -1
  151. package/lib/sync/unknownBlock.js +649 -53
  152. package/lib/sync/unknownBlock.js.map +1 -1
  153. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  154. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  155. package/lib/sync/utils/downloadByRange.js +147 -24
  156. package/lib/sync/utils/downloadByRange.js.map +1 -1
  157. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  158. package/lib/sync/utils/downloadByRoot.js +6 -2
  159. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  160. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  161. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  162. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  163. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  164. package/package.json +16 -15
  165. package/src/api/impl/beacon/blocks/index.ts +8 -5
  166. package/src/api/impl/lodestar/index.ts +1 -1
  167. package/src/chain/blocks/importBlock.ts +3 -2
  168. package/src/chain/blocks/importExecutionPayload.ts +57 -21
  169. package/src/chain/blocks/index.ts +54 -14
  170. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +5 -1
  171. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
  172. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  173. package/src/chain/blocks/types.ts +2 -2
  174. package/src/chain/blocks/utils/chainSegment.ts +106 -17
  175. package/src/chain/blocks/verifyBlock.ts +68 -9
  176. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
  177. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  178. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  179. package/src/chain/chain.ts +28 -3
  180. package/src/chain/errors/blockError.ts +4 -1
  181. package/src/chain/errors/index.ts +1 -0
  182. package/src/chain/errors/proposerPreferences.ts +39 -0
  183. package/src/chain/interface.ts +9 -1
  184. package/src/chain/opPools/payloadAttestationPool.ts +4 -8
  185. package/src/chain/prepareNextSlot.ts +24 -3
  186. package/src/chain/produceBlock/produceBlockBody.ts +41 -14
  187. package/src/chain/regen/interface.ts +1 -0
  188. package/src/chain/seenCache/index.ts +1 -0
  189. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +13 -3
  190. package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
  191. package/src/chain/validation/block.ts +1 -0
  192. package/src/chain/validation/proposerPreferences.ts +91 -0
  193. package/src/metrics/metrics/lodestar.ts +4 -0
  194. package/src/network/gossip/interface.ts +6 -0
  195. package/src/network/gossip/scoringParameters.ts +14 -1
  196. package/src/network/gossip/topic.ts +6 -0
  197. package/src/network/processor/gossipHandlers.ts +15 -6
  198. package/src/network/processor/gossipQueues/index.ts +5 -0
  199. package/src/network/processor/index.ts +1 -0
  200. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  201. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  202. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  203. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  204. package/src/node/notifier.ts +8 -1
  205. package/src/sync/range/batch.ts +90 -35
  206. package/src/sync/range/chain.ts +13 -5
  207. package/src/sync/range/range.ts +18 -6
  208. package/src/sync/types.ts +72 -0
  209. package/src/sync/unknownBlock.ts +810 -57
  210. package/src/sync/utils/downloadByRange.ts +256 -39
  211. package/src/sync/utils/downloadByRoot.ts +12 -2
  212. package/src/sync/utils/pendingBlocksTree.ts +0 -15
@@ -1,12 +1,14 @@
1
1
  import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice";
2
- import {ForkName, isForkPostFulu} from "@lodestar/params";
2
+ import {ForkName, ForkSeq, isForkPostFulu} from "@lodestar/params";
3
3
  import {DataAvailabilityStatus, IBeaconStateView, computeEpochAtSlot} from "@lodestar/state-transition";
4
- import {IndexedAttestation, deneb} from "@lodestar/types";
4
+ import {IndexedAttestation, Slot, deneb} from "@lodestar/types";
5
+ import {getBlobKzgCommitments} from "../../util/dataColumns.js";
5
6
  import type {BeaconChain} from "../chain.js";
6
7
  import {BlockError, BlockErrorCode} from "../errors/index.js";
7
8
  import {BlockProcessOpts} from "../options.js";
8
9
  import {RegenCaller} from "../regen/index.js";
9
10
  import {DAType, IBlockInput} from "./blockInput/index.js";
11
+ import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js";
10
12
  import {ImportBlockOpts} from "./types.js";
11
13
  import {DENEB_BLOWFISH_BANNER} from "./utils/blowfishBanner.js";
12
14
  import {ELECTRA_GIRAFFE_BANNER} from "./utils/giraffeBanner.js";
@@ -16,6 +18,7 @@ import {verifyBlocksDataAvailability} from "./verifyBlocksDataAvailability.js";
16
18
  import {SegmentExecStatus, verifyBlocksExecutionPayload} from "./verifyBlocksExecutionPayloads.js";
17
19
  import {verifyBlocksSignatures} from "./verifyBlocksSignatures.js";
18
20
  import {verifyBlocksStateTransitionOnly} from "./verifyBlocksStateTransitionOnly.js";
21
+ import {verifyPayloadsDataAvailability} from "./verifyPayloadsDataAvailability.js";
19
22
 
20
23
  /**
21
24
  * Verifies 1 or more blocks are fully valid; from a linear sequence of blocks.
@@ -32,12 +35,14 @@ export async function verifyBlocksInEpoch(
32
35
  this: BeaconChain,
33
36
  parentBlock: ProtoBlock,
34
37
  blockInputs: IBlockInput[],
38
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
35
39
  opts: BlockProcessOpts & ImportBlockOpts
36
40
  ): Promise<{
37
41
  postStates: IBeaconStateView[];
38
42
  proposerBalanceDeltas: number[];
39
43
  segmentExecStatus: SegmentExecStatus;
40
- dataAvailabilityStatuses: DataAvailabilityStatus[];
44
+ blockDAStatuses: DataAvailabilityStatus[];
45
+ payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
41
46
  indexedAttestationsByBlock: IndexedAttestation[][];
42
47
  }> {
43
48
  const blocks = blockInputs.map((blockInput) => blockInput.getBlock());
@@ -110,17 +115,59 @@ export async function verifyBlocksInEpoch(
110
115
  });
111
116
  }
112
117
 
118
+ // Pick the data-availability source by fork:
119
+ // - Pre-Gloas: blob/Fulu-column data lives in IBlockInput → verifyBlocksDataAvailability.
120
+ // - Post-Gloas: verifyPayloadsDataAvailability (payload-level DA, keyed by slot).
121
+ const daAvailabilityPromise: Promise<{
122
+ blockDAStatuses: DataAvailabilityStatus[];
123
+ payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
124
+ availableTime: number;
125
+ }> =
126
+ fork >= ForkSeq.gloas
127
+ ? (async () => {
128
+ const payloadInputsForDa: PayloadEnvelopeInput[] = [];
129
+ for (const input of blockInputs) {
130
+ const pi = payloadEnvelopes?.get(input.slot);
131
+ if (pi !== undefined) payloadInputsForDa.push(pi);
132
+ }
133
+ const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
134
+ payloadInputsForDa,
135
+ abortController.signal
136
+ );
137
+ const payloadDAStatuses = new Map<Slot, DataAvailabilityStatus>();
138
+ for (let i = 0; i < payloadInputsForDa.length; i++) {
139
+ payloadDAStatuses.set(payloadInputsForDa[i].slot, dataAvailabilityStatuses[i]);
140
+ }
141
+ return {
142
+ // post-gloas, DataAvailabilityStatus is NotRequired for forkChoice.onBlock() ProtoBlock
143
+ blockDAStatuses: blockInputs.map(() => DataAvailabilityStatus.NotRequired),
144
+ payloadDAStatuses,
145
+ availableTime,
146
+ };
147
+ })()
148
+ : (async () => {
149
+ const {dataAvailabilityStatuses, availableTime} = await verifyBlocksDataAvailability(
150
+ blockInputs,
151
+ abortController.signal
152
+ );
153
+ return {
154
+ blockDAStatuses: dataAvailabilityStatuses,
155
+ payloadDAStatuses: new Map<Slot, DataAvailabilityStatus>(),
156
+ availableTime,
157
+ };
158
+ })();
159
+
113
160
  // batch all I/O operations to reduce overhead
114
161
  const [
115
162
  segmentExecStatus,
116
- {dataAvailabilityStatuses, availableTime},
163
+ {blockDAStatuses, payloadDAStatuses, availableTime},
117
164
  {postStates, proposerBalanceDeltas, verifyStateTime},
118
165
  {verifySignaturesTime},
119
166
  ] = await Promise.all([
120
167
  verifyExecutionPayloadsPromise,
121
168
 
122
- // data availability for the blobs
123
- verifyBlocksDataAvailability(blockInputs, abortController.signal),
169
+ // data availability (fork-specific; see daAvailabilityPromise above)
170
+ daAvailabilityPromise,
124
171
 
125
172
  // Run state transition only
126
173
  // TODO: Ensure it yields to allow flushing to workers and engine API
@@ -149,6 +196,9 @@ export async function verifyBlocksInEpoch(
149
196
  opts
150
197
  )
151
198
  : Promise.resolve({verifySignaturesTime: Date.now()}),
199
+
200
+ // TODO GLOAS: can verify payload signatures in batch too
201
+ // maybe chain with the above verifyBlocksSignatures()
152
202
  ]);
153
203
 
154
204
  if (opts.verifyOnly !== true) {
@@ -200,7 +250,9 @@ export async function verifyBlocksInEpoch(
200
250
  blockInputs.length === 1 &&
201
251
  // gossip blocks have seenTimestampSec
202
252
  opts.seenTimestampSec !== undefined &&
253
+ // PreData (pre-deneb) and NoData (gloas) carry no blob data on the block — skip metric
203
254
  blockInputs[0].type !== DAType.PreData &&
255
+ blockInputs[0].type !== DAType.NoData &&
204
256
  executionStatuses[0] === ExecutionStatus.Valid
205
257
  ) {
206
258
  // Find the max time when the block was actually verified
@@ -209,8 +261,8 @@ export async function verifyBlocksInEpoch(
209
261
  this.metrics?.gossipBlock.receivedToFullyVerifiedTime.observe(recvTofullyVerifedTime);
210
262
 
211
263
  const verifiedToBlobsAvailabiltyTime = Math.max(availableTime - fullyVerifiedTime, 0) / 1000;
212
- const block = blockInputs[0].getBlock() as deneb.SignedBeaconBlock;
213
- const numBlobs = block.message.body.blobKzgCommitments.length;
264
+ const block = blockInputs[0].getBlock();
265
+ const numBlobs = getBlobKzgCommitments(blockInputs[0].forkName, block as deneb.SignedBeaconBlock).length;
214
266
 
215
267
  this.metrics?.gossipBlock.verifiedToBlobsAvailabiltyTime.observe({numBlobs}, verifiedToBlobsAvailabiltyTime);
216
268
  this.logger.verbose("Verified blockInput fully with blobs availability", {
@@ -229,7 +281,14 @@ export async function verifyBlocksInEpoch(
229
281
  );
230
282
  }
231
283
 
232
- return {postStates, dataAvailabilityStatuses, proposerBalanceDeltas, segmentExecStatus, indexedAttestationsByBlock};
284
+ return {
285
+ postStates,
286
+ blockDAStatuses,
287
+ payloadDAStatuses,
288
+ proposerBalanceDeltas,
289
+ segmentExecStatus,
290
+ indexedAttestationsByBlock,
291
+ };
233
292
  } finally {
234
293
  abortController.abort();
235
294
  }
@@ -90,15 +90,24 @@ export function verifyBlocksSanityChecks(
90
90
  } else {
91
91
  // When importing a block segment, only the first NON-IGNORED block must be known to the fork-choice.
92
92
  const parentRoot = toRootHex(block.message.parentRoot);
93
- parentBlock = isGloasBeaconBlock(block.message)
94
- ? chain.forkChoice.getBlockHexAndBlockHash(
95
- parentRoot,
96
- toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash)
97
- )
98
- : chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
99
- if (!parentBlock) {
93
+ const parentBlockDefaultStatus = chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
94
+ if (!parentBlockDefaultStatus) {
100
95
  throw new BlockError(block, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
101
96
  }
97
+
98
+ parentBlock = parentBlockDefaultStatus;
99
+ if (isGloasBeaconBlock(block.message)) {
100
+ const parentBlockHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
101
+ const parentBlockWithPayload = chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash);
102
+ if (!parentBlockWithPayload) {
103
+ throw new BlockError(block, {
104
+ code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
105
+ parentRoot,
106
+ parentBlockHash,
107
+ });
108
+ }
109
+ parentBlock = parentBlockWithPayload;
110
+ }
102
111
  // Parent is known to the fork-choice
103
112
  parentBlockSlot = parentBlock.slot;
104
113
  }
@@ -32,8 +32,8 @@ export function verifyExecutionPayloadEnvelope(
32
32
  const payload = envelope.payload;
33
33
 
34
34
  // Verify consistency with the beacon block.
35
- // Compute header root on a copy of latestBlockHeader to avoid mutating state.
36
- const headerValue = {...state.latestBlockHeader};
35
+ // Compute header root on a clone of latestBlockHeader to avoid mutating state.
36
+ const headerValue = ssz.phase0.BeaconBlockHeader.clone(state.latestBlockHeader);
37
37
  if (byteArrayEquals(headerValue.stateRoot, ssz.Root.defaultValue())) {
38
38
  headerValue.stateRoot = state.hashTreeRoot();
39
39
  }
@@ -28,11 +28,14 @@ export async function verifyPayloadsDataAvailability(
28
28
  await Promise.all(promises);
29
29
 
30
30
  const availableTime = Math.max(0, Math.max(...payloadInputs.map((payloadInput) => payloadInput.getTimeComplete())));
31
- const dataAvailabilityStatuses: DataAvailabilityStatus[] = payloadInputs.map((payloadInput) =>
32
- payloadInput.getBlobKzgCommitments().length === 0
31
+ const dataAvailabilityStatuses: DataAvailabilityStatus[] = payloadInputs.map((payloadInput) => {
32
+ if (payloadInput.daOutOfRange) {
33
+ return DataAvailabilityStatus.OutOfRange;
34
+ }
35
+ return payloadInput.getBlobKzgCommitments().length === 0
33
36
  ? DataAvailabilityStatus.NotRequired
34
- : DataAvailabilityStatus.Available
35
- );
37
+ : DataAvailabilityStatus.Available;
38
+ });
36
39
 
37
40
  return {dataAvailabilityStatuses, availableTime};
38
41
  }
@@ -39,6 +39,7 @@ import {
39
39
  ValidatorIndex,
40
40
  Wei,
41
41
  deneb,
42
+ electra,
42
43
  gloas,
43
44
  isBlindedBeaconBlock,
44
45
  phase0,
@@ -105,6 +106,7 @@ import {
105
106
  SeenExecutionPayloadBids,
106
107
  SeenPayloadAttesters,
107
108
  SeenPayloadEnvelopeInput,
109
+ SeenProposerPreferences,
108
110
  SeenSyncCommitteeMessages,
109
111
  } from "./seenCache/index.js";
110
112
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -185,6 +187,7 @@ export class BeaconChain implements IBeaconChain {
185
187
  readonly seenPayloadAttesters = new SeenPayloadAttesters();
186
188
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
187
189
  readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
190
+ readonly seenProposerPreferences = new SeenProposerPreferences();
188
191
  readonly seenBlockProposers = new SeenBlockProposers();
189
192
  readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
190
193
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -333,6 +336,8 @@ export class BeaconChain implements IBeaconChain {
333
336
  logger,
334
337
  });
335
338
  this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
339
+ config,
340
+ clock,
336
341
  chainEvents: emitter,
337
342
  signal,
338
343
  serializedCache: this.serializedCache,
@@ -886,6 +891,21 @@ export class BeaconChain implements IBeaconChain {
886
891
  );
887
892
  }
888
893
 
894
+ async getParentExecutionRequests(
895
+ parentBlockSlot: Slot,
896
+ parentBlockRootHex: RootHex
897
+ ): Promise<electra.ExecutionRequests> {
898
+ // at the fork boundary, parent is pre-gloas
899
+ if (!isForkPostGloas(this.config.getForkName(parentBlockSlot))) {
900
+ return ssz.electra.ExecutionRequests.defaultValue();
901
+ }
902
+ const envelope = await this.getExecutionPayloadEnvelope(parentBlockSlot, parentBlockRootHex);
903
+ if (envelope === null) {
904
+ throw Error(`Parent execution payload envelope not found slot=${parentBlockSlot}, root=${parentBlockRootHex}`);
905
+ }
906
+ return envelope.message.executionRequests;
907
+ }
908
+
889
909
  async getDataColumnSidecars(blockSlot: Slot, blockRootHex: string): Promise<DataColumnSidecar[]> {
890
910
  const fork = this.config.getForkName(blockSlot);
891
911
 
@@ -1082,11 +1102,15 @@ export class BeaconChain implements IBeaconChain {
1082
1102
  }
1083
1103
 
1084
1104
  async processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void> {
1085
- return this.blockProcessor.processBlocksJob([block], opts);
1105
+ return this.blockProcessor.processBlocksJob([block], null, opts);
1086
1106
  }
1087
1107
 
1088
- async processChainSegment(blocks: IBlockInput[], opts?: ImportBlockOpts): Promise<void> {
1089
- return this.blockProcessor.processBlocksJob(blocks, opts);
1108
+ async processChainSegment(
1109
+ blocks: IBlockInput[],
1110
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
1111
+ opts?: ImportBlockOpts
1112
+ ): Promise<void> {
1113
+ await this.blockProcessor.processBlocksJob(blocks, payloadEnvelopes, opts);
1090
1114
  }
1091
1115
 
1092
1116
  async processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void> {
@@ -1417,6 +1441,7 @@ export class BeaconChain implements IBeaconChain {
1417
1441
  this.payloadAttestationPool.prune(slot);
1418
1442
  this.executionPayloadBidPool.prune(slot);
1419
1443
  this.seenExecutionPayloadBids.prune(slot);
1444
+ this.seenProposerPreferences.prune(slot);
1420
1445
  this.seenAttestationDatas.onSlot(slot);
1421
1446
  this.reprocessController.onSlot(slot);
1422
1447
 
@@ -74,6 +74,8 @@ export enum BlockErrorCode {
74
74
  PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
75
75
  /** The block's parent execution payload (defined by bid.parent_block_hash) has not been seen */
76
76
  PARENT_PAYLOAD_UNKNOWN = "BLOCK_ERROR_PARENT_PAYLOAD_UNKNOWN",
77
+ /** An execution payload envelope in the chain segment references a block root that does not match its slot's block */
78
+ ENVELOPE_BLOCK_ROOT_MISMATCH = "BLOCK_ERROR_ENVELOPE_BLOCK_ROOT_MISMATCH",
77
79
  }
78
80
 
79
81
  type ExecutionErrorStatus = Exclude<
@@ -107,6 +109,7 @@ export type BlockErrorType =
107
109
  | {code: BlockErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
108
110
  | {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS}
109
111
  | {code: BlockErrorCode.NON_LINEAR_SLOTS}
112
+ | {code: BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH; envelopeBlockRoot: RootHex; blockRoot: RootHex}
110
113
  | {code: BlockErrorCode.PER_BLOCK_PROCESSING_ERROR; error: Error}
111
114
  | {code: BlockErrorCode.BEACON_CHAIN_ERROR; error: Error}
112
115
  | {code: BlockErrorCode.KNOWN_BAD_BLOCK}
@@ -120,7 +123,7 @@ export type BlockErrorType =
120
123
  | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
121
124
  | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
122
125
  | {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
123
- | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentBlockHash: RootHex};
126
+ | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex};
124
127
 
125
128
  export class BlockGossipError extends GossipActionError<BlockErrorType> {}
126
129
 
@@ -8,6 +8,7 @@ export * from "./executionPayloadBid.js";
8
8
  export * from "./executionPayloadEnvelope.js";
9
9
  export * from "./gossipValidation.js";
10
10
  export * from "./payloadAttestation.js";
11
+ export * from "./proposerPreferences.js";
11
12
  export * from "./proposerSlashingError.js";
12
13
  export * from "./syncCommitteeError.js";
13
14
  export * from "./voluntaryExitError.js";
@@ -0,0 +1,39 @@
1
+ import {Slot, ValidatorIndex} from "@lodestar/types";
2
+ import {GossipActionError} from "./gossipValidation.js";
3
+
4
+ export enum ProposerPreferencesErrorCode {
5
+ INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH",
6
+ PROPOSAL_SLOT_PASSED = "PROPOSER_PREFERENCES_ERROR_PROPOSAL_SLOT_PASSED",
7
+ INVALID_PROPOSER = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSER",
8
+ ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_ALREADY_KNOWN",
9
+ INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE",
10
+ }
11
+
12
+ export type ProposerPreferencesErrorType =
13
+ | {
14
+ code: ProposerPreferencesErrorCode.INVALID_EPOCH;
15
+ proposalSlot: Slot;
16
+ currentEpoch: number;
17
+ }
18
+ | {
19
+ code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED;
20
+ proposalSlot: Slot;
21
+ currentSlot: Slot;
22
+ }
23
+ | {
24
+ code: ProposerPreferencesErrorCode.INVALID_PROPOSER;
25
+ proposalSlot: Slot;
26
+ validatorIndex: ValidatorIndex;
27
+ }
28
+ | {
29
+ code: ProposerPreferencesErrorCode.ALREADY_KNOWN;
30
+ proposalSlot: Slot;
31
+ validatorIndex: ValidatorIndex;
32
+ }
33
+ | {
34
+ code: ProposerPreferencesErrorCode.INVALID_SIGNATURE;
35
+ proposalSlot: Slot;
36
+ validatorIndex: ValidatorIndex;
37
+ };
38
+
39
+ export class ProposerPreferencesError extends GossipActionError<ProposerPreferencesErrorType> {}
@@ -18,6 +18,7 @@ import {
18
18
  altair,
19
19
  capella,
20
20
  deneb,
21
+ electra,
21
22
  gloas,
22
23
  phase0,
23
24
  rewards,
@@ -60,6 +61,7 @@ import {
60
61
  SeenContributionAndProof,
61
62
  SeenExecutionPayloadBids,
62
63
  SeenPayloadAttesters,
64
+ SeenProposerPreferences,
63
65
  SeenSyncCommitteeMessages,
64
66
  } from "./seenCache/index.js";
65
67
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -130,6 +132,7 @@ export interface IBeaconChain {
130
132
  readonly seenPayloadAttesters: SeenPayloadAttesters;
131
133
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
132
134
  readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
135
+ readonly seenProposerPreferences: SeenProposerPreferences;
133
136
  readonly seenBlockProposers: SeenBlockProposers;
134
137
  readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
135
138
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -231,6 +234,7 @@ export interface IBeaconChain {
231
234
  blockSlot: Slot,
232
235
  blockRootHex: string
233
236
  ): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
237
+ getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
234
238
 
235
239
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
236
240
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
@@ -248,7 +252,11 @@ export interface IBeaconChain {
248
252
  /** Process a block until complete */
249
253
  processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void>;
250
254
  /** Process a chain of blocks until complete */
251
- processChainSegment(blocks: IBlockInput[], opts?: ImportBlockOpts): Promise<void>;
255
+ processChainSegment(
256
+ blocks: IBlockInput[],
257
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
258
+ opts?: ImportBlockOpts
259
+ ): Promise<void>;
252
260
 
253
261
  /** Process execution payload envelope: verify, import to fork choice, and persist to DB */
254
262
  processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import {Signature, aggregateSignatures} from "@chainsafe/blst";
2
2
  import {BitArray} from "@chainsafe/ssz";
3
3
  import {ChainForkConfig} from "@lodestar/config";
4
- import {MAX_COMMITTEES_PER_SLOT, PTC_SIZE} from "@lodestar/params";
4
+ import {MAX_COMMITTEES_PER_SLOT, MAX_PAYLOAD_ATTESTATIONS, PTC_SIZE} from "@lodestar/params";
5
5
  import {RootHex, Slot, gloas} from "@lodestar/types";
6
6
  import {MapDef, toRootHex} from "@lodestar/utils";
7
7
  import {Metrics} from "../../metrics/metrics.js";
@@ -95,13 +95,9 @@ export class PayloadAttestationPool {
95
95
 
96
96
  /**
97
97
  * Get payload attestations to be included in a block.
98
- * Pick the top `maxAttestation` number of attestations with the most votes
98
+ * Pick the top `MAX_PAYLOAD_ATTESTATIONS` aggregates with the most votes.
99
99
  */
100
- getPayloadAttestationsForBlock(
101
- beaconBlockRoot: BlockRootHex,
102
- slot: Slot,
103
- maxAttestation: number
104
- ): gloas.PayloadAttestation[] {
100
+ getPayloadAttestationsForBlock(beaconBlockRoot: BlockRootHex, slot: Slot): gloas.PayloadAttestation[] {
105
101
  const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot);
106
102
 
107
103
  if (!aggregateByDataRootByBlockRoot) {
@@ -119,7 +115,7 @@ export class PayloadAttestationPool {
119
115
  return Array.from(aggregateByDataRoot.values())
120
116
  .slice()
121
117
  .sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length)
122
- .slice(0, maxAttestation)
118
+ .slice(0, MAX_PAYLOAD_ATTESTATIONS)
123
119
  .map(fastToPayloadAttestation);
124
120
  }
125
121
 
@@ -10,7 +10,7 @@ import {
10
10
  isStatePostBellatrix,
11
11
  isStatePostGloas,
12
12
  } from "@lodestar/state-transition";
13
- import {Bytes32, Slot} from "@lodestar/types";
13
+ import {Bytes32, Slot, electra} from "@lodestar/types";
14
14
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
15
15
  import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
16
16
  import {BuilderStatus} from "../execution/builder/http.js";
@@ -165,14 +165,19 @@ export class PrepareNextSlotScheduler {
165
165
  }
166
166
 
167
167
  let parentBlockHash: Bytes32;
168
+ let isExtendingPayload = false;
168
169
  if (isStatePostGloas(updatedPrepareState)) {
169
- parentBlockHash = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)
170
+ isExtendingPayload = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot);
171
+ parentBlockHash = isExtendingPayload
170
172
  ? updatedPrepareState.latestExecutionPayloadBid.blockHash
171
173
  : updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
172
174
  } else {
173
175
  parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
174
176
  }
175
177
 
178
+ // Reused by the SSE emit below to avoid a second DB lookup on cache miss
179
+ let parentExecutionRequests: electra.ExecutionRequests | undefined;
180
+
176
181
  if (feeRecipient) {
177
182
  const preparationTime =
178
183
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
@@ -182,6 +187,13 @@ export class PrepareNextSlotScheduler {
182
187
  const finalizedBlockHash =
183
188
  this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
184
189
 
190
+ if (isExtendingPayload) {
191
+ parentExecutionRequests = await this.chain.getParentExecutionRequests(
192
+ updatedHead.slot,
193
+ updatedHead.blockRoot
194
+ );
195
+ }
196
+
185
197
  // awaiting here instead of throwing an async call because there is no other task
186
198
  // left for scheduler and this gives nice semantics to catch and log errors in the
187
199
  // try/catch wrapper here.
@@ -194,7 +206,8 @@ export class PrepareNextSlotScheduler {
194
206
  safeBlockHash,
195
207
  finalizedBlockHash,
196
208
  updatedPrepareState,
197
- feeRecipient
209
+ feeRecipient,
210
+ parentExecutionRequests
198
211
  );
199
212
  this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
200
213
  prepareSlot,
@@ -221,12 +234,20 @@ export class PrepareNextSlotScheduler {
221
234
  (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
222
235
  this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
223
236
  ) {
237
+ // if we didn't fetch above (not proposing), SSE still needs it here
238
+ if (!parentExecutionRequests && isExtendingPayload) {
239
+ parentExecutionRequests = await this.chain.getParentExecutionRequests(
240
+ updatedHead.slot,
241
+ updatedHead.blockRoot
242
+ );
243
+ }
224
244
  const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
225
245
  prepareState: updatedPrepareState,
226
246
  prepareSlot,
227
247
  parentBlockRoot: fromHex(updatedHead.blockRoot),
228
248
  parentBlockHash,
229
249
  feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
250
+ parentExecutionRequests,
230
251
  });
231
252
  this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
232
253
  }
@@ -49,7 +49,7 @@ import {
49
49
  ssz,
50
50
  } from "@lodestar/types";
51
51
  import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
52
- import {ZERO_HASH_HEX} from "../../constants/index.js";
52
+ import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js";
53
53
  import {numToQuantity} from "../../execution/engine/utils.js";
54
54
  import {
55
55
  IExecutionBuilder,
@@ -214,9 +214,19 @@ 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
+ let parentBlockHash = isExtendingPayload
218
219
  ? currentState.latestExecutionPayloadBid.blockHash
219
220
  : currentState.latestExecutionPayloadBid.parentBlockHash;
221
+ // At gloas genesis the committed bid has no prior EL block to reference
222
+ // (`bid.parentBlockHash` is zero). Fall back to `bid.blockHash` (= eth1 genesis hash) so the
223
+ // FCU to the EL carries a valid head. Post-genesis bids always reference a non-zero parent.
224
+ if (isStatePostGloas(currentState) && byteArrayEquals(parentBlockHash, ZERO_HASH)) {
225
+ parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
226
+ }
227
+ const parentExecutionRequests = isExtendingPayload
228
+ ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
229
+ : ssz.electra.ExecutionRequests.defaultValue();
220
230
  const prepareRes = await prepareExecutionPayload(
221
231
  this,
222
232
  this.logger,
@@ -226,7 +236,8 @@ export async function produceBlockBody<T extends BlockType>(
226
236
  safeBlockHash,
227
237
  finalizedBlockHash ?? ZERO_HASH_HEX,
228
238
  currentState,
229
- feeRecipient
239
+ feeRecipient,
240
+ parentExecutionRequests
230
241
  );
231
242
 
232
243
  const {prepType, payloadId} = prepareRes;
@@ -280,9 +291,11 @@ export async function produceBlockBody<T extends BlockType>(
280
291
  const commonBlockBody = await commonBlockBodyPromise;
281
292
  const gloasBody = Object.assign({}, commonBlockBody) as gloas.BeaconBlockBody;
282
293
  gloasBody.signedExecutionPayloadBid = signedBid;
283
- // TODO GLOAS: Get payload attestations from pool for previous slot
284
- gloasBody.payloadAttestations = [];
285
- // TODO GLOAS: set parentExecutionRequests in the block body
294
+ gloasBody.payloadAttestations = this.payloadAttestationPool.getPayloadAttestationsForBlock(
295
+ parentBlock.blockRoot,
296
+ blockSlot - 1
297
+ );
298
+ gloasBody.parentExecutionRequests = parentExecutionRequests;
286
299
  blockBody = gloasBody as AssembledBodyType<T>;
287
300
 
288
301
  // Store execution payload data required to construct execution payload envelope later
@@ -619,7 +632,8 @@ export async function prepareExecutionPayload(
619
632
  safeBlockHash: RootHex,
620
633
  finalizedBlockHash: RootHex,
621
634
  state: IBeaconStateViewBellatrix,
622
- suggestedFeeRecipient: string
635
+ suggestedFeeRecipient: string,
636
+ parentExecutionRequests?: electra.ExecutionRequests
623
637
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
624
638
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
625
639
  const prevRandao = state.getRandaoMix(state.epoch);
@@ -656,6 +670,7 @@ export async function prepareExecutionPayload(
656
670
  parentBlockRoot,
657
671
  parentBlockHash,
658
672
  feeRecipient: suggestedFeeRecipient,
673
+ parentExecutionRequests,
659
674
  });
660
675
 
661
676
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
@@ -714,12 +729,14 @@ export function getPayloadAttributesForSSE(
714
729
  parentBlockRoot,
715
730
  parentBlockHash,
716
731
  feeRecipient,
732
+ parentExecutionRequests,
717
733
  }: {
718
734
  prepareState: IBeaconStateViewBellatrix;
719
735
  prepareSlot: Slot;
720
736
  parentBlockRoot: Root;
721
737
  parentBlockHash: Bytes32;
722
738
  feeRecipient: string;
739
+ parentExecutionRequests?: electra.ExecutionRequests;
723
740
  }
724
741
  ): SSEPayloadAttributes {
725
742
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
@@ -728,6 +745,7 @@ export function getPayloadAttributesForSSE(
728
745
  parentBlockRoot,
729
746
  parentBlockHash,
730
747
  feeRecipient,
748
+ parentExecutionRequests,
731
749
  });
732
750
 
733
751
  let parentBlockNumber: number;
@@ -766,12 +784,14 @@ function preparePayloadAttributes(
766
784
  parentBlockRoot,
767
785
  parentBlockHash,
768
786
  feeRecipient,
787
+ parentExecutionRequests,
769
788
  }: {
770
789
  prepareState: IBeaconStateViewBellatrix;
771
790
  prepareSlot: Slot;
772
791
  parentBlockRoot: Root;
773
792
  parentBlockHash: Bytes32;
774
793
  feeRecipient: string;
794
+ parentExecutionRequests?: electra.ExecutionRequests;
775
795
  }
776
796
  ): SSEPayloadAttributes["payloadAttributes"] {
777
797
  const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
@@ -789,13 +809,20 @@ function preparePayloadAttributes(
789
809
 
790
810
  if (isStatePostGloas(prepareState)) {
791
811
  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;
812
+ if (isExtendingPayload) {
813
+ if (parentExecutionRequests === undefined) {
814
+ throw new Error("parentExecutionRequests required when extending full parent");
815
+ }
816
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
817
+ prepareState.getExpectedWithdrawalsForFullParent(parentExecutionRequests);
818
+ } else {
819
+ // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
820
+ // already deducted from CL balances but never credited on the EL (the envelope
821
+ // was not delivered). The next payload must carry those same withdrawals to
822
+ // restore CL/EL consistency, otherwise validators permanently lose that balance.
823
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
824
+ prepareState.payloadExpectedWithdrawals;
825
+ }
799
826
  } else {
800
827
  // withdrawals logic is now fork aware as it changes on electra fork post capella
801
828
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -21,6 +21,7 @@ export enum RegenCaller {
21
21
  validateGossipAttestation = "validateGossipAttestation",
22
22
  validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
23
23
  validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
24
+ validateGossipProposerPreferences = "validateGossipProposerPreferences",
24
25
  onForkChoiceFinalized = "onForkChoiceFinalized",
25
26
  restApi = "restApi",
26
27
  }