@lodestar/beacon-node 1.43.0-dev.66d2c102e3 → 1.43.0-dev.6f485b1b61

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 (195) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +13 -3
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +45 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/debug/index.d.ts.map +1 -1
  8. package/lib/api/impl/debug/index.js +0 -1
  9. package/lib/api/impl/debug/index.js.map +1 -1
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +68 -2
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  14. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  16. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  17. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  18. package/lib/chain/blocks/importBlock.js +16 -31
  19. package/lib/chain/blocks/importBlock.js.map +1 -1
  20. package/lib/chain/blocks/importExecutionPayload.d.ts +9 -3
  21. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  22. package/lib/chain/blocks/importExecutionPayload.js +37 -15
  23. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts.map +1 -1
  25. package/lib/chain/blocks/index.js +35 -21
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  31. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
  32. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/types.d.ts +2 -1
  34. package/lib/chain/blocks/types.d.ts.map +1 -1
  35. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  36. package/lib/chain/blocks/utils/chainSegment.js +8 -0
  37. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  38. package/lib/chain/blocks/verifyBlock.d.ts +2 -1
  39. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  40. package/lib/chain/blocks/verifyBlock.js +30 -12
  41. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  43. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  44. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  45. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  46. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  47. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  48. package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
  49. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  51. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  52. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  54. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  55. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  56. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  57. package/lib/chain/chain.d.ts.map +1 -1
  58. package/lib/chain/chain.js +25 -8
  59. package/lib/chain/chain.js.map +1 -1
  60. package/lib/chain/emitter.d.ts +0 -11
  61. package/lib/chain/emitter.d.ts.map +1 -1
  62. package/lib/chain/emitter.js +0 -4
  63. package/lib/chain/emitter.js.map +1 -1
  64. package/lib/chain/errors/proposerPreferences.d.ts +8 -1
  65. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  66. package/lib/chain/errors/proposerPreferences.js +1 -0
  67. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  68. package/lib/chain/initState.d.ts.map +1 -1
  69. package/lib/chain/initState.js +6 -1
  70. package/lib/chain/initState.js.map +1 -1
  71. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  72. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  73. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  74. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  75. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  76. package/lib/chain/prepareNextSlot.js +16 -18
  77. package/lib/chain/prepareNextSlot.js.map +1 -1
  78. package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
  79. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  80. package/lib/chain/produceBlock/produceBlockBody.js +34 -22
  81. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  82. package/lib/chain/regen/queued.d.ts.map +1 -1
  83. package/lib/chain/regen/queued.js +1 -4
  84. package/lib/chain/regen/queued.js.map +1 -1
  85. package/lib/chain/regen/regen.d.ts.map +1 -1
  86. package/lib/chain/regen/regen.js +1 -4
  87. package/lib/chain/regen/regen.js.map +1 -1
  88. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +21 -11
  89. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  90. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
  91. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  92. package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
  93. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  94. package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
  95. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  96. package/lib/chain/validation/executionPayloadBid.js +11 -8
  97. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  98. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  99. package/lib/chain/validation/proposerPreferences.js +39 -17
  100. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  101. package/lib/network/gossip/topic.d.ts +2 -0
  102. package/lib/network/gossip/topic.d.ts.map +1 -1
  103. package/lib/network/interface.d.ts +1 -0
  104. package/lib/network/interface.d.ts.map +1 -1
  105. package/lib/network/network.d.ts +1 -0
  106. package/lib/network/network.d.ts.map +1 -1
  107. package/lib/network/network.js +5 -0
  108. package/lib/network/network.js.map +1 -1
  109. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  110. package/lib/network/processor/gossipHandlers.js +28 -10
  111. package/lib/network/processor/gossipHandlers.js.map +1 -1
  112. package/lib/network/processor/index.js +5 -5
  113. package/lib/network/processor/index.js.map +1 -1
  114. package/lib/node/nodejs.js +2 -2
  115. package/lib/node/nodejs.js.map +1 -1
  116. package/lib/node/notifier.js +1 -7
  117. package/lib/node/notifier.js.map +1 -1
  118. package/lib/sync/constants.d.ts +3 -1
  119. package/lib/sync/constants.d.ts.map +1 -1
  120. package/lib/sync/constants.js +3 -4
  121. package/lib/sync/constants.js.map +1 -1
  122. package/lib/sync/range/batch.d.ts +23 -3
  123. package/lib/sync/range/batch.d.ts.map +1 -1
  124. package/lib/sync/range/batch.js +191 -36
  125. package/lib/sync/range/batch.js.map +1 -1
  126. package/lib/sync/range/chain.d.ts +13 -2
  127. package/lib/sync/range/chain.d.ts.map +1 -1
  128. package/lib/sync/range/chain.js +61 -9
  129. package/lib/sync/range/chain.js.map +1 -1
  130. package/lib/sync/range/range.d.ts.map +1 -1
  131. package/lib/sync/range/range.js +14 -3
  132. package/lib/sync/range/range.js.map +1 -1
  133. package/lib/sync/sync.d.ts.map +1 -1
  134. package/lib/sync/sync.js +13 -0
  135. package/lib/sync/sync.js.map +1 -1
  136. package/lib/sync/unknownBlock.d.ts +7 -2
  137. package/lib/sync/unknownBlock.d.ts.map +1 -1
  138. package/lib/sync/unknownBlock.js +138 -57
  139. package/lib/sync/unknownBlock.js.map +1 -1
  140. package/lib/sync/utils/downloadByRange.d.ts +29 -8
  141. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  142. package/lib/sync/utils/downloadByRange.js +104 -42
  143. package/lib/sync/utils/downloadByRange.js.map +1 -1
  144. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  145. package/lib/sync/utils/downloadByRoot.js +10 -0
  146. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  147. package/lib/util/sszBytes.d.ts.map +1 -1
  148. package/lib/util/sszBytes.js +8 -6
  149. package/lib/util/sszBytes.js.map +1 -1
  150. package/package.json +15 -15
  151. package/src/api/impl/beacon/blocks/index.ts +16 -3
  152. package/src/api/impl/beacon/pool/index.ts +83 -1
  153. package/src/api/impl/debug/index.ts +0 -1
  154. package/src/api/impl/validator/index.ts +82 -1
  155. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  156. package/src/chain/blocks/importBlock.ts +16 -50
  157. package/src/chain/blocks/importExecutionPayload.ts +51 -20
  158. package/src/chain/blocks/index.ts +32 -15
  159. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
  160. package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
  161. package/src/chain/blocks/types.ts +2 -1
  162. package/src/chain/blocks/utils/chainSegment.ts +8 -0
  163. package/src/chain/blocks/verifyBlock.ts +45 -13
  164. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  165. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
  166. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
  167. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  168. package/src/chain/chain.ts +29 -7
  169. package/src/chain/emitter.ts +0 -11
  170. package/src/chain/errors/proposerPreferences.ts +9 -1
  171. package/src/chain/initState.ts +9 -1
  172. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  173. package/src/chain/prepareNextSlot.ts +21 -29
  174. package/src/chain/produceBlock/produceBlockBody.ts +45 -27
  175. package/src/chain/regen/queued.ts +2 -7
  176. package/src/chain/regen/regen.ts +2 -7
  177. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
  178. package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
  179. package/src/chain/validation/executionPayloadBid.ts +11 -8
  180. package/src/chain/validation/proposerPreferences.ts +37 -18
  181. package/src/network/interface.ts +1 -0
  182. package/src/network/network.ts +11 -0
  183. package/src/network/processor/gossipHandlers.ts +38 -11
  184. package/src/network/processor/index.ts +5 -5
  185. package/src/node/nodejs.ts +2 -2
  186. package/src/node/notifier.ts +1 -8
  187. package/src/sync/constants.ts +4 -4
  188. package/src/sync/range/batch.ts +240 -42
  189. package/src/sync/range/chain.ts +77 -10
  190. package/src/sync/range/range.ts +16 -3
  191. package/src/sync/sync.ts +13 -1
  192. package/src/sync/unknownBlock.ts +170 -60
  193. package/src/sync/utils/downloadByRange.ts +166 -44
  194. package/src/sync/utils/downloadByRoot.ts +12 -0
  195. package/src/util/sszBytes.ts +8 -6
@@ -8,7 +8,9 @@ import {
8
8
  isForkPostGloas,
9
9
  } from "@lodestar/params";
10
10
  import {
11
+ ColumnIndex,
11
12
  DataColumnSidecar,
13
+ RootHex,
12
14
  SignedBeaconBlock,
13
15
  Slot,
14
16
  deneb,
@@ -45,6 +47,22 @@ export type DownloadByRangeRequests = {
45
47
  blobsRequest?: deneb.BlobSidecarsByRangeRequest;
46
48
  columnsRequest?: fulu.DataColumnSidecarsByRangeRequest;
47
49
  envelopesRequest?: gloas.ExecutionPayloadEnvelopesByRangeRequest;
50
+ /**
51
+ * Post-Gloas only. Fetches the dangling-parent's payload envelope and/or its missing sampled
52
+ * data columns by-root. Set by `Batch` for the first batch of a `SyncChain` after the first
53
+ * block's parentRoot is known.
54
+ */
55
+ parentPayloadRequest?: {
56
+ blockRoot?: Uint8Array;
57
+ columns?: ColumnIndex[];
58
+ envelopeBlockRoot?: Uint8Array;
59
+ };
60
+ };
61
+
62
+ export type ParentPayloadCommitments = {
63
+ blockRoot: Uint8Array;
64
+ blockRootHex: RootHex;
65
+ kzgCommitments: deneb.BlobKzgCommitments;
48
66
  };
49
67
 
50
68
  export type DownloadByRangeResponses = {
@@ -60,6 +78,8 @@ export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
60
78
  logger: Logger;
61
79
  peerIdStr: string;
62
80
  batchBlocks?: IBlockInput[];
81
+ /** Required when `parentPayloadRequest` is set; supplies the data needed to validate the parent's columns. */
82
+ parentPayloadCommitments?: ParentPayloadCommitments;
63
83
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
64
84
  };
65
85
 
@@ -185,33 +205,33 @@ export function cacheByRangeResponses({
185
205
  }
186
206
  }
187
207
 
188
- // Build payloadEnvelopes map for gloas: start from existing (partial download) state.
189
- // The entries are wrappers around (block + envelope + sampled columns) and also seeded into
190
- // seenPayloadEnvelopeInputCache so importBlock can find them without creating a duplicate.
191
- let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null = null;
192
- if (downloadedPayloadEnvelopes !== null) {
193
- payloadEnvelopes = new Map(existingPayloadEnvelopes ?? []);
208
+ // Seed seenPayloadEnvelopeInputCache for every gloas block in the batch, regardless of whether
209
+ // the peer returned its envelope. Without this, a block returned without its envelope would be
210
+ // imported with no cache entry, and later payload-by-root sync would throw
211
+ // "Missing PayloadEnvelopeInput for known block" (see issue #9306).
212
+ for (const blockInput of updatedBatchBlocks.values()) {
213
+ if (!blockInput.hasBlock() || !isForkPostGloas(blockInput.forkName)) continue;
214
+ seenPayloadEnvelopeInputCache.add({
215
+ blockRootHex: blockInput.blockRootHex,
216
+ block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
217
+ forkName: blockInput.forkName,
218
+ sampledColumns: custodyConfig.sampledColumns,
219
+ custodyColumns: custodyConfig.custodyColumns,
220
+ timeCreatedSec: seenTimestampSec,
221
+ });
222
+ }
194
223
 
224
+ let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
225
+ existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
226
+ if (downloadedPayloadEnvelopes !== null) {
227
+ payloadEnvelopes ??= new Map();
195
228
  for (const [slot, envelope] of downloadedPayloadEnvelopes) {
196
- const blockInput = updatedBatchBlocks.get(slot);
197
- if (!blockInput?.hasBlock() || !isForkPostGloas(blockInput.forkName)) {
198
- // No block to pair this envelope with; drop silently
199
- continue;
200
- }
201
- const {blockRootHex} = blockInput;
202
-
203
- // Reuse any existing PayloadEnvelopeInput (e.g. gossip arrived first) to avoid
204
- // duplicate cache entries. If missing, create a fresh one from the block's bid.
205
- let payloadInput = seenPayloadEnvelopeInputCache.get(blockRootHex);
229
+ const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
230
+ const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
206
231
  if (payloadInput === undefined) {
207
- payloadInput = seenPayloadEnvelopeInputCache.add({
208
- blockRootHex,
209
- block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
210
- forkName: blockInput.forkName,
211
- sampledColumns: custodyConfig.sampledColumns,
212
- custodyColumns: custodyConfig.custodyColumns,
213
- timeCreatedSec: seenTimestampSec,
214
- });
232
+ // Unreachable given the loop above seeded an entry for every gloas block in the batch.
233
+ // for the parent block, it's populated at BeaconChain init
234
+ throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
215
235
  }
216
236
 
217
237
  if (!payloadInput.hasPayloadEnvelope()) {
@@ -301,6 +321,8 @@ export async function downloadByRange({
301
321
  blobsRequest,
302
322
  columnsRequest,
303
323
  envelopesRequest,
324
+ parentPayloadRequest,
325
+ parentPayloadCommitments,
304
326
  peerDasMetrics,
305
327
  }: DownloadAndCacheByRangeProps): Promise<
306
328
  WarnResult<
@@ -317,6 +339,7 @@ export async function downloadByRange({
317
339
  blobsRequest,
318
340
  columnsRequest,
319
341
  envelopesRequest,
342
+ parentPayloadRequest,
320
343
  });
321
344
  } catch (err) {
322
345
  throw new DownloadByRangeError({
@@ -333,6 +356,8 @@ export async function downloadByRange({
333
356
  blobsRequest,
334
357
  columnsRequest,
335
358
  envelopesRequest,
359
+ parentPayloadRequest,
360
+ parentPayloadCommitments,
336
361
  peerDasMetrics,
337
362
  ...response,
338
363
  });
@@ -348,14 +373,15 @@ export async function requestByRange({
348
373
  blobsRequest,
349
374
  columnsRequest,
350
375
  envelopesRequest,
376
+ parentPayloadRequest,
351
377
  }: DownloadByRangeRequests & {
352
378
  network: INetwork;
353
379
  peerIdStr: PeerIdStr;
354
380
  }): Promise<DownloadByRangeResponses> {
355
381
  let blocks: undefined | SignedBeaconBlock[];
356
382
  let blobSidecars: undefined | deneb.BlobSidecars;
357
- let columnSidecars: undefined | DataColumnSidecar[];
358
- let payloadEnvelopes: undefined | gloas.SignedExecutionPayloadEnvelope[];
383
+ const columnSidecars: DataColumnSidecar[] = [];
384
+ const payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[] = [];
359
385
 
360
386
  const requests: Promise<unknown>[] = [];
361
387
 
@@ -378,7 +404,7 @@ export async function requestByRange({
378
404
  if (columnsRequest) {
379
405
  requests.push(
380
406
  network.sendDataColumnSidecarsByRange(peerIdStr, columnsRequest).then((columnResponse) => {
381
- columnSidecars = columnResponse;
407
+ columnSidecars.push(...columnResponse);
382
408
  })
383
409
  );
384
410
  }
@@ -386,11 +412,34 @@ export async function requestByRange({
386
412
  if (envelopesRequest) {
387
413
  requests.push(
388
414
  network.sendExecutionPayloadEnvelopesByRange(peerIdStr, envelopesRequest).then((envelopeResponse) => {
389
- payloadEnvelopes = envelopeResponse;
415
+ payloadEnvelopes.push(...envelopeResponse);
390
416
  })
391
417
  );
392
418
  }
393
419
 
420
+ // Only happens on the 1st batch of a SyncChain — fetches whichever pieces of the
421
+ // dangling-parent's payload are still missing from the seen-cache entry.
422
+ if (parentPayloadRequest?.envelopeBlockRoot) {
423
+ requests.push(
424
+ network
425
+ .sendExecutionPayloadEnvelopesByRoot(peerIdStr, [parentPayloadRequest.envelopeBlockRoot])
426
+ .then((envelopeResponse) => {
427
+ payloadEnvelopes.push(...envelopeResponse);
428
+ })
429
+ );
430
+ }
431
+ if (parentPayloadRequest?.blockRoot && parentPayloadRequest.columns && parentPayloadRequest.columns.length > 0) {
432
+ requests.push(
433
+ network
434
+ .sendDataColumnSidecarsByRoot(peerIdStr, [
435
+ {blockRoot: parentPayloadRequest.blockRoot, columns: parentPayloadRequest.columns},
436
+ ])
437
+ .then((columnResponse) => {
438
+ columnSidecars.push(...columnResponse);
439
+ })
440
+ );
441
+ }
442
+
394
443
  await Promise.all(requests);
395
444
 
396
445
  return {
@@ -411,6 +460,8 @@ export async function validateResponses({
411
460
  blobsRequest,
412
461
  columnsRequest,
413
462
  envelopesRequest,
463
+ parentPayloadRequest,
464
+ parentPayloadCommitments,
414
465
  blocks,
415
466
  blobSidecars,
416
467
  columnSidecars,
@@ -420,6 +471,7 @@ export async function validateResponses({
420
471
  DownloadByRangeResponses & {
421
472
  config: ChainForkConfig;
422
473
  batchBlocks?: IBlockInput[];
474
+ parentPayloadCommitments?: ParentPayloadCommitments;
423
475
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
424
476
  }): Promise<
425
477
  WarnResult<
@@ -440,6 +492,11 @@ export async function validateResponses({
440
492
  );
441
493
  }
442
494
 
495
+ // `parentPayloadRequest` and `parentPayloadCommitments` must be supplied together
496
+ if ((parentPayloadRequest === undefined) !== (parentPayloadCommitments === undefined)) {
497
+ throw new Error("Coding error: parentPayloadRequest and parentPayloadCommitments must be both set or both unset");
498
+ }
499
+
443
500
  const validatedResponses: ValidatedResponses = {};
444
501
  let warnings: DownloadByRangeError[] | null = null;
445
502
 
@@ -451,21 +508,30 @@ export async function validateResponses({
451
508
  validatedResponses.validatedBlocks = result.result;
452
509
  }
453
510
 
511
+ const needsEnvelopeValidation = !!envelopesRequest || parentPayloadCommitments !== undefined;
454
512
  const dataRequest = blobsRequest ?? columnsRequest;
455
- if (!dataRequest && !envelopesRequest) {
513
+ if (!dataRequest && !needsEnvelopeValidation) {
456
514
  return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
457
515
  }
458
516
 
459
517
  if (!dataRequest) {
460
- // Only envelope validation needed
461
- let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
462
- if (envelopesRequest) {
463
- validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
464
- validatedResponses.validatedBlocks ?? [],
465
- batchBlocks,
466
- payloadEnvelopes ?? []
518
+ // Only envelope and/or parent-by-root validation needed
519
+ if (parentPayloadCommitments !== undefined) {
520
+ const parentValidated = await validateParentPayloadColumns(
521
+ parentPayloadCommitments,
522
+ columnSidecars ?? [],
523
+ peerDasMetrics
467
524
  );
525
+ if (parentValidated) {
526
+ validatedResponses.validatedColumnSidecars = [parentValidated];
527
+ }
468
528
  }
529
+ const validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
530
+ validatedResponses.validatedBlocks ?? [],
531
+ batchBlocks,
532
+ payloadEnvelopes ?? [],
533
+ parentPayloadCommitments
534
+ );
469
535
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
470
536
  }
471
537
 
@@ -524,19 +590,64 @@ export async function validateResponses({
524
590
  warnings = validatedColumnSidecarsResult.warnings;
525
591
  }
526
592
 
527
- // Validate envelopes if an envelopes request was made
593
+ // Parent columns (by-root): KZG-validate against parent's bid commitments and append.
594
+ if (parentPayloadCommitments !== undefined) {
595
+ const parentValidated = await validateParentPayloadColumns(
596
+ parentPayloadCommitments,
597
+ columnSidecars ?? [],
598
+ peerDasMetrics
599
+ );
600
+ if (parentValidated) {
601
+ validatedResponses.validatedColumnSidecars = [
602
+ ...(validatedResponses.validatedColumnSidecars ?? []),
603
+ parentValidated,
604
+ ];
605
+ }
606
+ }
607
+
528
608
  let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
529
- if (envelopesRequest) {
609
+ if (needsEnvelopeValidation) {
530
610
  validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
531
611
  validatedResponses.validatedBlocks ?? [],
532
612
  batchBlocks,
533
- payloadEnvelopes ?? []
613
+ payloadEnvelopes ?? [],
614
+ parentPayloadCommitments
534
615
  );
535
616
  }
536
617
 
537
618
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
538
619
  }
539
620
 
621
+ async function validateParentPayloadColumns(
622
+ parentPayloadCommitments: ParentPayloadCommitments,
623
+ columnSidecars: DataColumnSidecar[],
624
+ peerDasMetrics?: BeaconMetrics["peerDas"] | null
625
+ ): Promise<ValidatedColumnSidecars | null> {
626
+ const parentColumns: gloas.DataColumnSidecar[] = [];
627
+ for (const cs of columnSidecars) {
628
+ if (isGloasDataColumnSidecar(cs) && byteArrayEquals(cs.beaconBlockRoot, parentPayloadCommitments.blockRoot)) {
629
+ parentColumns.push(cs);
630
+ }
631
+ }
632
+
633
+ if (parentColumns.length === 0) {
634
+ return null;
635
+ }
636
+
637
+ parentColumns.sort((a, b) => a.index - b.index);
638
+ const parentSlot = parentColumns[0].slot;
639
+
640
+ await validateGloasBlockDataColumnSidecars(
641
+ parentSlot,
642
+ parentPayloadCommitments.blockRoot,
643
+ parentPayloadCommitments.kzgCommitments,
644
+ parentColumns,
645
+ peerDasMetrics
646
+ );
647
+
648
+ return {blockRoot: parentPayloadCommitments.blockRoot, columnSidecars: parentColumns};
649
+ }
650
+
540
651
  /**
541
652
  * Should not be called directly. Only exported for unit testing purposes
542
653
  *
@@ -1147,14 +1258,18 @@ export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType
1147
1258
 
1148
1259
  /**
1149
1260
  * Validates SignedExecutionPayloadEnvelopes received for a range request.
1150
- * For each envelope whose slot appears in the downloaded blocks, verifies that
1151
- * envelope.message.beaconBlockRoot matches the corresponding block's root.
1152
- * Envelopes for slots not in the batch (orphaned payloads) are silently ignored.
1261
+ *
1262
+ * Three categories of envelope slots:
1263
+ * - In-batch slot whose block we have: verify envelope.beaconBlockRoot matches.
1264
+ * - Dangling-parent envelope (only when `parentPayloadCommitments` is set, i.e. the first
1265
+ * batch of a `SyncChain`): keep if `envelope.beaconBlockRoot === parentPayloadCommitments.blockRoot`.
1266
+ * - Other "orphan" envelopes (e.g. unrelated slots): ignored.
1153
1267
  */
1154
1268
  export function validateEnvelopesByRangeResponse(
1155
1269
  validatedBlocks: ValidatedBlock[],
1156
1270
  batchBlocks: IBlockInput[] | undefined,
1157
- payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
1271
+ payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
1272
+ parentPayloadCommitments?: ParentPayloadCommitments
1158
1273
  ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1159
1274
  // Build a map of slot -> blockRoot for all blocks in the batch
1160
1275
  const batchBlockRoots = new Map<Slot, Uint8Array>();
@@ -1173,8 +1288,15 @@ export function validateEnvelopesByRangeResponse(
1173
1288
  const slot = payloadEnvelope.message.payload.slotNumber;
1174
1289
  const batchBlockRoot = batchBlockRoots.get(slot);
1175
1290
 
1176
- // Envelopes for slots not in the batch are silently ignored (orphaned payloads)
1177
1291
  if (batchBlockRoot === undefined) {
1292
+ // Keep the requested dangling-parent envelope only when its beaconBlockRoot matches
1293
+ // exactly. All other unrelated envelopes are dropped.
1294
+ if (
1295
+ parentPayloadCommitments !== undefined &&
1296
+ byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayloadCommitments.blockRoot)
1297
+ ) {
1298
+ payloadEnvelopeMap.set(slot, payloadEnvelope);
1299
+ }
1178
1300
  continue;
1179
1301
  }
1180
1302
 
@@ -3,6 +3,7 @@ import {ChainForkConfig} from "@lodestar/config";
3
3
  import {
4
4
  ForkPostDeneb,
5
5
  ForkPostFulu,
6
+ ForkPostGloas,
6
7
  ForkPreFulu,
7
8
  isForkPostDeneb,
8
9
  isForkPostFulu,
@@ -114,6 +115,17 @@ export async function downloadByRoot({
114
115
  });
115
116
  }
116
117
 
118
+ if (isForkPostGloas(blockInput.forkName)) {
119
+ chain.seenPayloadEnvelopeInputCache.add({
120
+ blockRootHex: rootHex,
121
+ block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
122
+ forkName: blockInput.forkName,
123
+ sampledColumns: chain.custodyConfig.sampledColumns,
124
+ custodyColumns: chain.custodyConfig.custodyColumns,
125
+ timeCreatedSec: Date.now() / 1000,
126
+ });
127
+ }
128
+
117
129
  const hasAllDataPreDownload = blockInput.hasBlockAndAllData();
118
130
 
119
131
  if (isBlockInputBlobs(blockInput) && !hasAllDataPreDownload) {
@@ -558,9 +558,10 @@ export function getBeaconBlockRootFromDataColumnSidecarSerialized(data: Uint8Arr
558
558
  * └─ ExecutionPayloadEnvelope (starts at byte 100):
559
559
  * ├─ 4 bytes: payload offset
560
560
  * ├─ 4 bytes: executionRequests offset
561
- * ├─ 8 bytes: builderIndex (offset 108-115)
562
- * ├─ 32 bytes: beaconBlockRoot (offset 116-147)
563
- * └─ variable: payload data (starts at envelope + 48)
561
+ * ├─ 8 bytes: builderIndex (offset 108-115)
562
+ * ├─ 32 bytes: beaconBlockRoot (offset 116-147)
563
+ * ├─ 32 bytes: parentBeaconBlockRoot (offset 148-179) new in Gloas alpha.6 (consensus-specs#5152)
564
+ * └─ variable: payload data (starts at envelope + 80)
564
565
  * └─ ExecutionPayload fixed portion includes slotNumber at offset 532
565
566
  */
566
567
  const SIGNED_EXECUTION_PAYLOAD_ENVELOPE_MESSAGE_OFFSET = 4;
@@ -576,12 +577,13 @@ const BEACON_BLOCK_ROOT_OFFSET_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE =
576
577
  EXECUTION_PAYLOAD_ENVELOPE_REQUESTS_OFFSET +
577
578
  EXECUTION_PAYLOAD_ENVELOPE_BUILDER_INDEX_SIZE; // 116
578
579
 
579
- // Envelope fixed portion (without slot): payload_offset(4) + requests_offset(4) + builderIndex(8) + beaconBlockRoot(32) = 48
580
+ // Envelope fixed portion: payload_offset(4) + requests_offset(4) + builderIndex(8) + beaconBlockRoot(32) + parentBeaconBlockRoot(32) = 80
580
581
  const EXECUTION_PAYLOAD_ENVELOPE_FIXED_SIZE =
581
582
  EXECUTION_PAYLOAD_ENVELOPE_PAYLOAD_OFFSET +
582
583
  EXECUTION_PAYLOAD_ENVELOPE_REQUESTS_OFFSET +
583
584
  EXECUTION_PAYLOAD_ENVELOPE_BUILDER_INDEX_SIZE +
584
- ROOT_SIZE; // 48
585
+ ROOT_SIZE +
586
+ ROOT_SIZE; // 80
585
587
 
586
588
  // slotNumber offset within ExecutionPayload fixed portion:
587
589
  // parentHash(32) + feeRecipient(20) + stateRoot(32) + receiptsRoot(32) + logsBloom(256) +
@@ -595,7 +597,7 @@ const ENVELOPE_START_IN_SIGNED =
595
597
  SIGNED_EXECUTION_PAYLOAD_ENVELOPE_MESSAGE_OFFSET + SIGNED_EXECUTION_PAYLOAD_ENVELOPE_SIGNATURE_SIZE; // 100
596
598
 
597
599
  const SLOT_OFFSET_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE =
598
- ENVELOPE_START_IN_SIGNED + EXECUTION_PAYLOAD_ENVELOPE_FIXED_SIZE + SLOT_NUMBER_OFFSET_IN_EXECUTION_PAYLOAD; // 100 + 48 + 532 = 680
600
+ ENVELOPE_START_IN_SIGNED + EXECUTION_PAYLOAD_ENVELOPE_FIXED_SIZE + SLOT_NUMBER_OFFSET_IN_EXECUTION_PAYLOAD; // 100 + 80 + 532 = 712
599
601
 
600
602
  export function getSlotFromExecutionPayloadEnvelopeSerialized(data: Uint8Array): Slot | null {
601
603
  if (data.length < SLOT_OFFSET_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE + SLOT_SIZE) {