@lodestar/beacon-node 1.43.0-dev.87cbe69c66 → 1.43.0-dev.8ad5d8be4c

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 (153) hide show
  1. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  2. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  3. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  4. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  5. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  6. package/lib/chain/blocks/importBlock.js +0 -16
  7. package/lib/chain/blocks/importBlock.js.map +1 -1
  8. package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
  9. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  10. package/lib/chain/blocks/importExecutionPayload.js +12 -1
  11. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  12. package/lib/chain/blocks/index.d.ts.map +1 -1
  13. package/lib/chain/blocks/index.js +6 -7
  14. package/lib/chain/blocks/index.js.map +1 -1
  15. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
  16. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  17. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +23 -1
  18. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  19. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
  20. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  21. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
  22. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  23. package/lib/chain/blocks/utils/chainSegment.js +29 -26
  24. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  25. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  26. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +5 -4
  27. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  28. package/lib/chain/chain.d.ts.map +1 -1
  29. package/lib/chain/chain.js +25 -10
  30. package/lib/chain/chain.js.map +1 -1
  31. package/lib/chain/initState.d.ts.map +1 -1
  32. package/lib/chain/initState.js +6 -1
  33. package/lib/chain/initState.js.map +1 -1
  34. package/lib/chain/prepareNextSlot.js +1 -1
  35. package/lib/chain/prepareNextSlot.js.map +1 -1
  36. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
  37. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  38. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +51 -17
  39. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  40. package/lib/chain/seenCache/seenProposerPreferences.d.ts +1 -1
  41. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  42. package/lib/chain/seenCache/seenProposerPreferences.js +6 -11
  43. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  44. package/lib/metrics/metrics/lodestar.d.ts +4 -0
  45. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  46. package/lib/metrics/metrics/lodestar.js +5 -0
  47. package/lib/metrics/metrics/lodestar.js.map +1 -1
  48. package/lib/network/gossip/topic.d.ts +749 -2
  49. package/lib/network/gossip/topic.d.ts.map +1 -1
  50. package/lib/network/interface.d.ts +1 -0
  51. package/lib/network/interface.d.ts.map +1 -1
  52. package/lib/network/network.d.ts +1 -0
  53. package/lib/network/network.d.ts.map +1 -1
  54. package/lib/network/network.js +3 -0
  55. package/lib/network/network.js.map +1 -1
  56. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  57. package/lib/network/processor/gossipHandlers.js +45 -1
  58. package/lib/network/processor/gossipHandlers.js.map +1 -1
  59. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  60. package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
  61. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  62. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
  63. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
  64. package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
  65. package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
  66. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  67. package/lib/network/reqresp/handlers/index.js +5 -0
  68. package/lib/network/reqresp/handlers/index.js.map +1 -1
  69. package/lib/network/reqresp/interface.d.ts +1 -1
  70. package/lib/network/reqresp/interface.js +1 -1
  71. package/lib/network/reqresp/protocols.d.ts +1 -0
  72. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  73. package/lib/network/reqresp/protocols.js +5 -0
  74. package/lib/network/reqresp/protocols.js.map +1 -1
  75. package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
  76. package/lib/network/reqresp/rateLimit.js +4 -0
  77. package/lib/network/reqresp/rateLimit.js.map +1 -1
  78. package/lib/network/reqresp/score.d.ts.map +1 -1
  79. package/lib/network/reqresp/score.js +1 -0
  80. package/lib/network/reqresp/score.js.map +1 -1
  81. package/lib/network/reqresp/types.d.ts +3 -0
  82. package/lib/network/reqresp/types.d.ts.map +1 -1
  83. package/lib/network/reqresp/types.js +3 -0
  84. package/lib/network/reqresp/types.js.map +1 -1
  85. package/lib/sync/constants.d.ts +7 -1
  86. package/lib/sync/constants.d.ts.map +1 -1
  87. package/lib/sync/constants.js +7 -4
  88. package/lib/sync/constants.js.map +1 -1
  89. package/lib/sync/range/batch.d.ts +32 -6
  90. package/lib/sync/range/batch.d.ts.map +1 -1
  91. package/lib/sync/range/batch.js +215 -41
  92. package/lib/sync/range/batch.js.map +1 -1
  93. package/lib/sync/range/chain.d.ts +16 -2
  94. package/lib/sync/range/chain.d.ts.map +1 -1
  95. package/lib/sync/range/chain.js +77 -4
  96. package/lib/sync/range/chain.js.map +1 -1
  97. package/lib/sync/range/range.d.ts.map +1 -1
  98. package/lib/sync/range/range.js +54 -5
  99. package/lib/sync/range/range.js.map +1 -1
  100. package/lib/sync/range/utils/peerBalancer.d.ts +2 -1
  101. package/lib/sync/range/utils/peerBalancer.d.ts.map +1 -1
  102. package/lib/sync/range/utils/peerBalancer.js +8 -4
  103. package/lib/sync/range/utils/peerBalancer.js.map +1 -1
  104. package/lib/sync/sync.d.ts.map +1 -1
  105. package/lib/sync/sync.js +13 -0
  106. package/lib/sync/sync.js.map +1 -1
  107. package/lib/sync/unknownBlock.d.ts +7 -0
  108. package/lib/sync/unknownBlock.d.ts.map +1 -1
  109. package/lib/sync/unknownBlock.js +127 -10
  110. package/lib/sync/unknownBlock.js.map +1 -1
  111. package/lib/sync/utils/downloadByRange.d.ts +36 -9
  112. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  113. package/lib/sync/utils/downloadByRange.js +89 -42
  114. package/lib/sync/utils/downloadByRange.js.map +1 -1
  115. package/lib/sync/utils/rateLimit.d.ts +2 -0
  116. package/lib/sync/utils/rateLimit.d.ts.map +1 -0
  117. package/lib/sync/utils/rateLimit.js +15 -0
  118. package/lib/sync/utils/rateLimit.js.map +1 -0
  119. package/package.json +16 -16
  120. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  121. package/src/chain/blocks/importBlock.ts +0 -18
  122. package/src/chain/blocks/importExecutionPayload.ts +18 -6
  123. package/src/chain/blocks/index.ts +5 -7
  124. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +31 -2
  125. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
  126. package/src/chain/blocks/utils/chainSegment.ts +30 -27
  127. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +7 -4
  128. package/src/chain/chain.ts +29 -9
  129. package/src/chain/initState.ts +9 -1
  130. package/src/chain/prepareNextSlot.ts +1 -1
  131. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +66 -20
  132. package/src/chain/seenCache/seenProposerPreferences.ts +7 -12
  133. package/src/metrics/metrics/lodestar.ts +6 -0
  134. package/src/network/interface.ts +1 -0
  135. package/src/network/network.ts +12 -0
  136. package/src/network/processor/gossipHandlers.ts +53 -1
  137. package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
  138. package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
  139. package/src/network/reqresp/handlers/index.ts +5 -0
  140. package/src/network/reqresp/interface.ts +1 -1
  141. package/src/network/reqresp/protocols.ts +6 -0
  142. package/src/network/reqresp/rateLimit.ts +4 -0
  143. package/src/network/reqresp/score.ts +1 -0
  144. package/src/network/reqresp/types.ts +5 -0
  145. package/src/sync/constants.ts +8 -4
  146. package/src/sync/range/batch.ts +256 -47
  147. package/src/sync/range/chain.ts +94 -5
  148. package/src/sync/range/range.ts +68 -5
  149. package/src/sync/range/utils/peerBalancer.ts +9 -3
  150. package/src/sync/sync.ts +13 -1
  151. package/src/sync/unknownBlock.ts +157 -10
  152. package/src/sync/utils/downloadByRange.ts +156 -44
  153. package/src/sync/utils/rateLimit.ts +16 -0
@@ -8,13 +8,14 @@ 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,
15
17
  fulu,
16
18
  gloas,
17
- isGloasBeaconBlock,
18
19
  isGloasDataColumnSidecar,
19
20
  phase0,
20
21
  } from "@lodestar/types";
@@ -46,6 +47,22 @@ export type DownloadByRangeRequests = {
46
47
  blobsRequest?: deneb.BlobSidecarsByRangeRequest;
47
48
  columnsRequest?: fulu.DataColumnSidecarsByRangeRequest;
48
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;
49
66
  };
50
67
 
51
68
  export type DownloadByRangeResponses = {
@@ -61,6 +78,8 @@ export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
61
78
  logger: Logger;
62
79
  peerIdStr: string;
63
80
  batchBlocks?: IBlockInput[];
81
+ /** Required when `parentPayloadRequest` is set; supplies the data needed to validate the parent's columns. */
82
+ parentPayloadCommitments?: ParentPayloadCommitments;
64
83
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
65
84
  };
66
85
 
@@ -202,23 +221,17 @@ export function cacheByRangeResponses({
202
221
  });
203
222
  }
204
223
 
205
- // Attach envelopes to entries whose envelope was returned by the peer. The returned
206
- // payloadEnvelopes map only contains entries with envelopes ready for importExecutionPayload.
207
- let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null = null;
224
+ let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
225
+ existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
208
226
  if (downloadedPayloadEnvelopes !== null) {
209
- payloadEnvelopes = new Map(existingPayloadEnvelopes ?? []);
210
-
227
+ payloadEnvelopes ??= new Map();
211
228
  for (const [slot, envelope] of downloadedPayloadEnvelopes) {
212
- const blockInput = updatedBatchBlocks.get(slot);
213
- if (!blockInput?.hasBlock() || !isForkPostGloas(blockInput.forkName)) {
214
- // No block to pair this envelope with; drop silently
215
- continue;
216
- }
217
-
218
- const payloadInput = seenPayloadEnvelopeInputCache.get(blockInput.blockRootHex);
229
+ const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
230
+ const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
219
231
  if (payloadInput === undefined) {
220
232
  // Unreachable given the loop above seeded an entry for every gloas block in the batch.
221
- continue;
233
+ // for the parent block, it's populated at BeaconChain init
234
+ throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
222
235
  }
223
236
 
224
237
  if (!payloadInput.hasPayloadEnvelope()) {
@@ -308,6 +321,8 @@ export async function downloadByRange({
308
321
  blobsRequest,
309
322
  columnsRequest,
310
323
  envelopesRequest,
324
+ parentPayloadRequest,
325
+ parentPayloadCommitments,
311
326
  peerDasMetrics,
312
327
  }: DownloadAndCacheByRangeProps): Promise<
313
328
  WarnResult<
@@ -324,6 +339,7 @@ export async function downloadByRange({
324
339
  blobsRequest,
325
340
  columnsRequest,
326
341
  envelopesRequest,
342
+ parentPayloadRequest,
327
343
  });
328
344
  } catch (err) {
329
345
  throw new DownloadByRangeError({
@@ -340,6 +356,8 @@ export async function downloadByRange({
340
356
  blobsRequest,
341
357
  columnsRequest,
342
358
  envelopesRequest,
359
+ parentPayloadRequest,
360
+ parentPayloadCommitments,
343
361
  peerDasMetrics,
344
362
  ...response,
345
363
  });
@@ -355,13 +373,14 @@ export async function requestByRange({
355
373
  blobsRequest,
356
374
  columnsRequest,
357
375
  envelopesRequest,
376
+ parentPayloadRequest,
358
377
  }: DownloadByRangeRequests & {
359
378
  network: INetwork;
360
379
  peerIdStr: PeerIdStr;
361
380
  }): Promise<DownloadByRangeResponses> {
362
381
  let blocks: undefined | SignedBeaconBlock[];
363
382
  let blobSidecars: undefined | deneb.BlobSidecars;
364
- let columnSidecars: undefined | DataColumnSidecar[];
383
+ const columnSidecars: DataColumnSidecar[] = [];
365
384
  const payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[] = [];
366
385
 
367
386
  const requests: Promise<unknown>[] = [];
@@ -370,17 +389,6 @@ export async function requestByRange({
370
389
  requests.push(
371
390
  network.sendBeaconBlocksByRange(peerIdStr, blocksRequest).then((blockResponse) => {
372
391
  blocks = blockResponse;
373
- const firstBlock = blockResponse.at(0);
374
- if (firstBlock && isGloasBeaconBlock(firstBlock.message)) {
375
- return network
376
- .sendExecutionPayloadEnvelopesByRoot(peerIdStr, [
377
- firstBlock.message.body.signedExecutionPayloadBid.message.parentBlockRoot,
378
- ])
379
- .then((envelopeResponse) => {
380
- payloadEnvelopes?.unshift(...envelopeResponse);
381
- });
382
- }
383
- return undefined;
384
392
  })
385
393
  );
386
394
  }
@@ -396,7 +404,7 @@ export async function requestByRange({
396
404
  if (columnsRequest) {
397
405
  requests.push(
398
406
  network.sendDataColumnSidecarsByRange(peerIdStr, columnsRequest).then((columnResponse) => {
399
- columnSidecars = columnResponse;
407
+ columnSidecars.push(...columnResponse);
400
408
  })
401
409
  );
402
410
  }
@@ -404,11 +412,34 @@ export async function requestByRange({
404
412
  if (envelopesRequest) {
405
413
  requests.push(
406
414
  network.sendExecutionPayloadEnvelopesByRange(peerIdStr, envelopesRequest).then((envelopeResponse) => {
407
- payloadEnvelopes?.push(...envelopeResponse);
415
+ payloadEnvelopes.push(...envelopeResponse);
408
416
  })
409
417
  );
410
418
  }
411
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
+
412
443
  await Promise.all(requests);
413
444
 
414
445
  return {
@@ -429,6 +460,8 @@ export async function validateResponses({
429
460
  blobsRequest,
430
461
  columnsRequest,
431
462
  envelopesRequest,
463
+ parentPayloadRequest,
464
+ parentPayloadCommitments,
432
465
  blocks,
433
466
  blobSidecars,
434
467
  columnSidecars,
@@ -438,6 +471,7 @@ export async function validateResponses({
438
471
  DownloadByRangeResponses & {
439
472
  config: ChainForkConfig;
440
473
  batchBlocks?: IBlockInput[];
474
+ parentPayloadCommitments?: ParentPayloadCommitments;
441
475
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
442
476
  }): Promise<
443
477
  WarnResult<
@@ -458,6 +492,11 @@ export async function validateResponses({
458
492
  );
459
493
  }
460
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
+
461
500
  const validatedResponses: ValidatedResponses = {};
462
501
  let warnings: DownloadByRangeError[] | null = null;
463
502
 
@@ -469,21 +508,30 @@ export async function validateResponses({
469
508
  validatedResponses.validatedBlocks = result.result;
470
509
  }
471
510
 
511
+ const needsEnvelopeValidation = !!envelopesRequest || parentPayloadCommitments !== undefined;
472
512
  const dataRequest = blobsRequest ?? columnsRequest;
473
- if (!dataRequest && !envelopesRequest) {
513
+ if (!dataRequest && !needsEnvelopeValidation) {
474
514
  return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
475
515
  }
476
516
 
477
517
  if (!dataRequest) {
478
- // Only envelope validation needed
479
- let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
480
- if (envelopesRequest) {
481
- validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
482
- validatedResponses.validatedBlocks ?? [],
483
- batchBlocks,
484
- 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
485
524
  );
525
+ if (parentValidated) {
526
+ validatedResponses.validatedColumnSidecars = [parentValidated];
527
+ }
486
528
  }
529
+ const validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
530
+ validatedResponses.validatedBlocks ?? [],
531
+ batchBlocks,
532
+ payloadEnvelopes ?? [],
533
+ parentPayloadCommitments
534
+ );
487
535
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
488
536
  }
489
537
 
@@ -542,19 +590,64 @@ export async function validateResponses({
542
590
  warnings = validatedColumnSidecarsResult.warnings;
543
591
  }
544
592
 
545
- // 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
+
546
608
  let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
547
- if (envelopesRequest) {
609
+ if (needsEnvelopeValidation) {
548
610
  validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
549
611
  validatedResponses.validatedBlocks ?? [],
550
612
  batchBlocks,
551
- payloadEnvelopes ?? []
613
+ payloadEnvelopes ?? [],
614
+ parentPayloadCommitments
552
615
  );
553
616
  }
554
617
 
555
618
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
556
619
  }
557
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
+
558
651
  /**
559
652
  * Should not be called directly. Only exported for unit testing purposes
560
653
  *
@@ -1062,6 +1155,9 @@ export enum DownloadByRangeErrorCode {
1062
1155
 
1063
1156
  /** Envelope beaconBlockRoot does not match the block's root */
1064
1157
  INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
1158
+
1159
+ /** Block segment + envelopes failed chain-segment linearity / FULL-chain checks */
1160
+ INVALID_CHAIN_SEGMENT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_CHAIN_SEGMENT",
1065
1161
  }
1066
1162
 
1067
1163
  export type DownloadByRangeErrorType =
@@ -1159,20 +1255,29 @@ export type DownloadByRangeErrorType =
1159
1255
  slot: Slot;
1160
1256
  expected: string;
1161
1257
  actual: string;
1258
+ }
1259
+ | {
1260
+ code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT;
1261
+ slot: Slot;
1262
+ reason: string;
1162
1263
  };
1163
1264
 
1164
1265
  export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType> {}
1165
1266
 
1166
1267
  /**
1167
1268
  * Validates SignedExecutionPayloadEnvelopes received for a range request.
1168
- * For each envelope whose slot appears in the downloaded blocks, verifies that
1169
- * envelope.message.beaconBlockRoot matches the corresponding block's root.
1170
- * Envelopes for slots not in the batch (orphaned payloads) are silently ignored.
1269
+ *
1270
+ * Three categories of envelope slots:
1271
+ * - In-batch slot whose block we have: verify envelope.beaconBlockRoot matches.
1272
+ * - Dangling-parent envelope (only when `parentPayloadCommitments` is set, i.e. the first
1273
+ * batch of a `SyncChain`): keep if `envelope.beaconBlockRoot === parentPayloadCommitments.blockRoot`.
1274
+ * - Other "orphan" envelopes (e.g. unrelated slots): ignored.
1171
1275
  */
1172
1276
  export function validateEnvelopesByRangeResponse(
1173
1277
  validatedBlocks: ValidatedBlock[],
1174
1278
  batchBlocks: IBlockInput[] | undefined,
1175
- payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
1279
+ payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
1280
+ parentPayloadCommitments?: ParentPayloadCommitments
1176
1281
  ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1177
1282
  // Build a map of slot -> blockRoot for all blocks in the batch
1178
1283
  const batchBlockRoots = new Map<Slot, Uint8Array>();
@@ -1191,8 +1296,15 @@ export function validateEnvelopesByRangeResponse(
1191
1296
  const slot = payloadEnvelope.message.payload.slotNumber;
1192
1297
  const batchBlockRoot = batchBlockRoots.get(slot);
1193
1298
 
1194
- // Envelopes for slots not in the batch are silently ignored (orphaned payloads or a parent payload)
1195
1299
  if (batchBlockRoot === undefined) {
1300
+ // Keep the requested dangling-parent envelope only when its beaconBlockRoot matches
1301
+ // exactly. All other unrelated envelopes are dropped.
1302
+ if (
1303
+ parentPayloadCommitments !== undefined &&
1304
+ byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayloadCommitments.blockRoot)
1305
+ ) {
1306
+ payloadEnvelopeMap.set(slot, payloadEnvelope);
1307
+ }
1196
1308
  continue;
1197
1309
  }
1198
1310
 
@@ -0,0 +1,16 @@
1
+ import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
2
+ import {RATE_LIMITED_PEER_BACKOFF_MS} from "../constants.js";
3
+
4
+ export function getRateLimitedUntilMs(e: unknown): number | null {
5
+ if (!(e instanceof RequestError)) {
6
+ return null;
7
+ }
8
+
9
+ switch (e.type.code) {
10
+ case RequestErrorCode.RESP_RATE_LIMITED:
11
+ case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
12
+ return e.type.rateLimitedUntilMs ?? Date.now() + RATE_LIMITED_PEER_BACKOFF_MS;
13
+ default:
14
+ return null;
15
+ }
16
+ }