@lodestar/beacon-node 1.43.0-dev.a45ba75824 → 1.43.0-dev.a58ad80359

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 (156) hide show
  1. package/lib/api/impl/debug/index.d.ts.map +1 -1
  2. package/lib/api/impl/debug/index.js +0 -1
  3. package/lib/api/impl/debug/index.js.map +1 -1
  4. package/lib/api/impl/validator/index.d.ts.map +1 -1
  5. package/lib/api/impl/validator/index.js +2 -1
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  8. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  9. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  10. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  11. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  12. package/lib/chain/blocks/importBlock.js +14 -18
  13. package/lib/chain/blocks/importBlock.js.map +1 -1
  14. package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
  15. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  16. package/lib/chain/blocks/importExecutionPayload.js +15 -6
  17. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  18. package/lib/chain/blocks/index.d.ts.map +1 -1
  19. package/lib/chain/blocks/index.js +29 -17
  20. package/lib/chain/blocks/index.js.map +1 -1
  21. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
  22. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +23 -1
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
  26. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  27. package/lib/chain/blocks/types.d.ts +2 -1
  28. package/lib/chain/blocks/types.d.ts.map +1 -1
  29. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  30. package/lib/chain/blocks/utils/chainSegment.js +8 -0
  31. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  32. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  33. package/lib/chain/blocks/verifyBlock.js +5 -6
  34. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  35. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  36. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  38. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  39. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  40. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
  42. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  43. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  44. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  45. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
  46. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  47. package/lib/chain/chain.d.ts.map +1 -1
  48. package/lib/chain/chain.js +25 -10
  49. package/lib/chain/chain.js.map +1 -1
  50. package/lib/chain/errors/proposerPreferences.d.ts +8 -1
  51. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  52. package/lib/chain/errors/proposerPreferences.js +1 -0
  53. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  54. package/lib/chain/initState.d.ts.map +1 -1
  55. package/lib/chain/initState.js +6 -1
  56. package/lib/chain/initState.js.map +1 -1
  57. package/lib/chain/prepareNextSlot.js +1 -1
  58. package/lib/chain/prepareNextSlot.js.map +1 -1
  59. package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -0
  60. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  61. package/lib/chain/produceBlock/produceBlockBody.js +2 -7
  62. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  63. package/lib/chain/regen/queued.d.ts.map +1 -1
  64. package/lib/chain/regen/queued.js +1 -4
  65. package/lib/chain/regen/queued.js.map +1 -1
  66. package/lib/chain/regen/regen.d.ts.map +1 -1
  67. package/lib/chain/regen/regen.js +1 -4
  68. package/lib/chain/regen/regen.js.map +1 -1
  69. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
  70. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  71. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +51 -17
  72. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  73. package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
  74. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  75. package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
  76. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  77. package/lib/chain/validation/executionPayloadBid.js +11 -8
  78. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  79. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  80. package/lib/chain/validation/proposerPreferences.js +39 -17
  81. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  82. package/lib/network/gossip/topic.d.ts +2 -0
  83. package/lib/network/gossip/topic.d.ts.map +1 -1
  84. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  85. package/lib/network/processor/gossipHandlers.js +14 -0
  86. package/lib/network/processor/gossipHandlers.js.map +1 -1
  87. package/lib/node/nodejs.js +2 -2
  88. package/lib/node/nodejs.js.map +1 -1
  89. package/lib/node/notifier.js +1 -7
  90. package/lib/node/notifier.js.map +1 -1
  91. package/lib/sync/constants.d.ts +3 -1
  92. package/lib/sync/constants.d.ts.map +1 -1
  93. package/lib/sync/constants.js +3 -4
  94. package/lib/sync/constants.js.map +1 -1
  95. package/lib/sync/range/batch.d.ts +12 -3
  96. package/lib/sync/range/batch.d.ts.map +1 -1
  97. package/lib/sync/range/batch.js +163 -33
  98. package/lib/sync/range/batch.js.map +1 -1
  99. package/lib/sync/range/chain.d.ts +13 -2
  100. package/lib/sync/range/chain.d.ts.map +1 -1
  101. package/lib/sync/range/chain.js +38 -4
  102. package/lib/sync/range/chain.js.map +1 -1
  103. package/lib/sync/range/range.d.ts.map +1 -1
  104. package/lib/sync/range/range.js +14 -3
  105. package/lib/sync/range/range.js.map +1 -1
  106. package/lib/sync/sync.d.ts.map +1 -1
  107. package/lib/sync/sync.js +13 -0
  108. package/lib/sync/sync.js.map +1 -1
  109. package/lib/sync/unknownBlock.d.ts +7 -0
  110. package/lib/sync/unknownBlock.d.ts.map +1 -1
  111. package/lib/sync/unknownBlock.js +138 -10
  112. package/lib/sync/unknownBlock.js.map +1 -1
  113. package/lib/sync/utils/downloadByRange.d.ts +29 -8
  114. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  115. package/lib/sync/utils/downloadByRange.js +87 -31
  116. package/lib/sync/utils/downloadByRange.js.map +1 -1
  117. package/lib/util/sszBytes.d.ts.map +1 -1
  118. package/lib/util/sszBytes.js +8 -6
  119. package/lib/util/sszBytes.js.map +1 -1
  120. package/package.json +16 -16
  121. package/src/api/impl/debug/index.ts +0 -1
  122. package/src/api/impl/validator/index.ts +2 -1
  123. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  124. package/src/chain/blocks/importBlock.ts +14 -21
  125. package/src/chain/blocks/importExecutionPayload.ts +21 -11
  126. package/src/chain/blocks/index.ts +18 -9
  127. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +31 -2
  128. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
  129. package/src/chain/blocks/types.ts +2 -1
  130. package/src/chain/blocks/utils/chainSegment.ts +8 -0
  131. package/src/chain/blocks/verifyBlock.ts +7 -5
  132. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  133. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
  134. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
  135. package/src/chain/chain.ts +29 -9
  136. package/src/chain/errors/proposerPreferences.ts +9 -1
  137. package/src/chain/initState.ts +9 -1
  138. package/src/chain/prepareNextSlot.ts +1 -1
  139. package/src/chain/produceBlock/produceBlockBody.ts +3 -7
  140. package/src/chain/regen/queued.ts +2 -7
  141. package/src/chain/regen/regen.ts +2 -7
  142. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +66 -20
  143. package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
  144. package/src/chain/validation/executionPayloadBid.ts +11 -8
  145. package/src/chain/validation/proposerPreferences.ts +37 -18
  146. package/src/network/processor/gossipHandlers.ts +18 -0
  147. package/src/node/nodejs.ts +2 -2
  148. package/src/node/notifier.ts +1 -8
  149. package/src/sync/constants.ts +4 -4
  150. package/src/sync/range/batch.ts +187 -38
  151. package/src/sync/range/chain.ts +52 -5
  152. package/src/sync/range/range.ts +16 -3
  153. package/src/sync/sync.ts +13 -1
  154. package/src/sync/unknownBlock.ts +170 -10
  155. package/src/sync/utils/downloadByRange.ts +149 -33
  156. 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
 
@@ -201,23 +221,17 @@ export function cacheByRangeResponses({
201
221
  });
202
222
  }
203
223
 
204
- // Attach envelopes to entries whose envelope was returned by the peer. The returned
205
- // payloadEnvelopes map only contains entries with envelopes ready for importExecutionPayload.
206
- let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null = null;
224
+ let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
225
+ existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
207
226
  if (downloadedPayloadEnvelopes !== null) {
208
- payloadEnvelopes = new Map(existingPayloadEnvelopes ?? []);
209
-
227
+ payloadEnvelopes ??= new Map();
210
228
  for (const [slot, envelope] of downloadedPayloadEnvelopes) {
211
- const blockInput = updatedBatchBlocks.get(slot);
212
- if (!blockInput?.hasBlock() || !isForkPostGloas(blockInput.forkName)) {
213
- // No block to pair this envelope with; drop silently
214
- continue;
215
- }
216
-
217
- const payloadInput = seenPayloadEnvelopeInputCache.get(blockInput.blockRootHex);
229
+ const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
230
+ const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
218
231
  if (payloadInput === undefined) {
219
232
  // Unreachable given the loop above seeded an entry for every gloas block in the batch.
220
- continue;
233
+ // for the parent block, it's populated at BeaconChain init
234
+ throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
221
235
  }
222
236
 
223
237
  if (!payloadInput.hasPayloadEnvelope()) {
@@ -307,6 +321,8 @@ export async function downloadByRange({
307
321
  blobsRequest,
308
322
  columnsRequest,
309
323
  envelopesRequest,
324
+ parentPayloadRequest,
325
+ parentPayloadCommitments,
310
326
  peerDasMetrics,
311
327
  }: DownloadAndCacheByRangeProps): Promise<
312
328
  WarnResult<
@@ -323,6 +339,7 @@ export async function downloadByRange({
323
339
  blobsRequest,
324
340
  columnsRequest,
325
341
  envelopesRequest,
342
+ parentPayloadRequest,
326
343
  });
327
344
  } catch (err) {
328
345
  throw new DownloadByRangeError({
@@ -339,6 +356,8 @@ export async function downloadByRange({
339
356
  blobsRequest,
340
357
  columnsRequest,
341
358
  envelopesRequest,
359
+ parentPayloadRequest,
360
+ parentPayloadCommitments,
342
361
  peerDasMetrics,
343
362
  ...response,
344
363
  });
@@ -354,14 +373,15 @@ export async function requestByRange({
354
373
  blobsRequest,
355
374
  columnsRequest,
356
375
  envelopesRequest,
376
+ parentPayloadRequest,
357
377
  }: DownloadByRangeRequests & {
358
378
  network: INetwork;
359
379
  peerIdStr: PeerIdStr;
360
380
  }): Promise<DownloadByRangeResponses> {
361
381
  let blocks: undefined | SignedBeaconBlock[];
362
382
  let blobSidecars: undefined | deneb.BlobSidecars;
363
- let columnSidecars: undefined | DataColumnSidecar[];
364
- let payloadEnvelopes: undefined | gloas.SignedExecutionPayloadEnvelope[];
383
+ const columnSidecars: DataColumnSidecar[] = [];
384
+ const payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[] = [];
365
385
 
366
386
  const requests: Promise<unknown>[] = [];
367
387
 
@@ -384,7 +404,7 @@ export async function requestByRange({
384
404
  if (columnsRequest) {
385
405
  requests.push(
386
406
  network.sendDataColumnSidecarsByRange(peerIdStr, columnsRequest).then((columnResponse) => {
387
- columnSidecars = columnResponse;
407
+ columnSidecars.push(...columnResponse);
388
408
  })
389
409
  );
390
410
  }
@@ -392,11 +412,34 @@ export async function requestByRange({
392
412
  if (envelopesRequest) {
393
413
  requests.push(
394
414
  network.sendExecutionPayloadEnvelopesByRange(peerIdStr, envelopesRequest).then((envelopeResponse) => {
395
- payloadEnvelopes = envelopeResponse;
415
+ payloadEnvelopes.push(...envelopeResponse);
396
416
  })
397
417
  );
398
418
  }
399
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
+
400
443
  await Promise.all(requests);
401
444
 
402
445
  return {
@@ -417,6 +460,8 @@ export async function validateResponses({
417
460
  blobsRequest,
418
461
  columnsRequest,
419
462
  envelopesRequest,
463
+ parentPayloadRequest,
464
+ parentPayloadCommitments,
420
465
  blocks,
421
466
  blobSidecars,
422
467
  columnSidecars,
@@ -426,6 +471,7 @@ export async function validateResponses({
426
471
  DownloadByRangeResponses & {
427
472
  config: ChainForkConfig;
428
473
  batchBlocks?: IBlockInput[];
474
+ parentPayloadCommitments?: ParentPayloadCommitments;
429
475
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
430
476
  }): Promise<
431
477
  WarnResult<
@@ -446,6 +492,11 @@ export async function validateResponses({
446
492
  );
447
493
  }
448
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
+
449
500
  const validatedResponses: ValidatedResponses = {};
450
501
  let warnings: DownloadByRangeError[] | null = null;
451
502
 
@@ -457,21 +508,30 @@ export async function validateResponses({
457
508
  validatedResponses.validatedBlocks = result.result;
458
509
  }
459
510
 
511
+ const needsEnvelopeValidation = !!envelopesRequest || parentPayloadCommitments !== undefined;
460
512
  const dataRequest = blobsRequest ?? columnsRequest;
461
- if (!dataRequest && !envelopesRequest) {
513
+ if (!dataRequest && !needsEnvelopeValidation) {
462
514
  return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
463
515
  }
464
516
 
465
517
  if (!dataRequest) {
466
- // Only envelope validation needed
467
- let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
468
- if (envelopesRequest) {
469
- validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
470
- validatedResponses.validatedBlocks ?? [],
471
- batchBlocks,
472
- 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
473
524
  );
525
+ if (parentValidated) {
526
+ validatedResponses.validatedColumnSidecars = [parentValidated];
527
+ }
474
528
  }
529
+ const validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
530
+ validatedResponses.validatedBlocks ?? [],
531
+ batchBlocks,
532
+ payloadEnvelopes ?? [],
533
+ parentPayloadCommitments
534
+ );
475
535
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
476
536
  }
477
537
 
@@ -530,19 +590,64 @@ export async function validateResponses({
530
590
  warnings = validatedColumnSidecarsResult.warnings;
531
591
  }
532
592
 
533
- // 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
+
534
608
  let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
535
- if (envelopesRequest) {
609
+ if (needsEnvelopeValidation) {
536
610
  validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
537
611
  validatedResponses.validatedBlocks ?? [],
538
612
  batchBlocks,
539
- payloadEnvelopes ?? []
613
+ payloadEnvelopes ?? [],
614
+ parentPayloadCommitments
540
615
  );
541
616
  }
542
617
 
543
618
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
544
619
  }
545
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
+
546
651
  /**
547
652
  * Should not be called directly. Only exported for unit testing purposes
548
653
  *
@@ -1153,14 +1258,18 @@ export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType
1153
1258
 
1154
1259
  /**
1155
1260
  * Validates SignedExecutionPayloadEnvelopes received for a range request.
1156
- * For each envelope whose slot appears in the downloaded blocks, verifies that
1157
- * envelope.message.beaconBlockRoot matches the corresponding block's root.
1158
- * 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.
1159
1267
  */
1160
1268
  export function validateEnvelopesByRangeResponse(
1161
1269
  validatedBlocks: ValidatedBlock[],
1162
1270
  batchBlocks: IBlockInput[] | undefined,
1163
- payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
1271
+ payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
1272
+ parentPayloadCommitments?: ParentPayloadCommitments
1164
1273
  ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1165
1274
  // Build a map of slot -> blockRoot for all blocks in the batch
1166
1275
  const batchBlockRoots = new Map<Slot, Uint8Array>();
@@ -1179,8 +1288,15 @@ export function validateEnvelopesByRangeResponse(
1179
1288
  const slot = payloadEnvelope.message.payload.slotNumber;
1180
1289
  const batchBlockRoot = batchBlockRoots.get(slot);
1181
1290
 
1182
- // Envelopes for slots not in the batch are silently ignored (orphaned payloads)
1183
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
+ }
1184
1300
  continue;
1185
1301
  }
1186
1302
 
@@ -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) {