@lodestar/beacon-node 1.43.0-dev.a6d8600051 → 1.43.0-dev.ac258a8def

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 (220) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +10 -0
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/debug/index.d.ts.map +1 -1
  5. package/lib/api/impl/debug/index.js +0 -1
  6. package/lib/api/impl/debug/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +2 -1
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  11. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  12. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  13. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  15. package/lib/chain/blocks/importBlock.js +16 -29
  16. package/lib/chain/blocks/importBlock.js.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
  18. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  19. package/lib/chain/blocks/importExecutionPayload.js +15 -6
  20. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  21. package/lib/chain/blocks/index.d.ts.map +1 -1
  22. package/lib/chain/blocks/index.js +29 -17
  23. package/lib/chain/blocks/index.js.map +1 -1
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  26. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +24 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
  29. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  30. package/lib/chain/blocks/types.d.ts +2 -1
  31. package/lib/chain/blocks/types.d.ts.map +1 -1
  32. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
  33. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  34. package/lib/chain/blocks/utils/chainSegment.js +34 -23
  35. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  36. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyBlock.js +5 -6
  38. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  39. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  40. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  43. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  44. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
  46. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  48. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/chain.d.ts.map +1 -1
  52. package/lib/chain/chain.js +25 -10
  53. package/lib/chain/chain.js.map +1 -1
  54. package/lib/chain/emitter.d.ts +0 -11
  55. package/lib/chain/emitter.d.ts.map +1 -1
  56. package/lib/chain/emitter.js +0 -4
  57. package/lib/chain/emitter.js.map +1 -1
  58. package/lib/chain/errors/proposerPreferences.d.ts +8 -1
  59. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  60. package/lib/chain/errors/proposerPreferences.js +1 -0
  61. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  62. package/lib/chain/initState.d.ts.map +1 -1
  63. package/lib/chain/initState.js +6 -1
  64. package/lib/chain/initState.js.map +1 -1
  65. package/lib/chain/prepareNextSlot.js +1 -1
  66. package/lib/chain/prepareNextSlot.js.map +1 -1
  67. package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -0
  68. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  69. package/lib/chain/produceBlock/produceBlockBody.js +2 -7
  70. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  71. package/lib/chain/regen/queued.d.ts.map +1 -1
  72. package/lib/chain/regen/queued.js +1 -4
  73. package/lib/chain/regen/queued.js.map +1 -1
  74. package/lib/chain/regen/regen.d.ts.map +1 -1
  75. package/lib/chain/regen/regen.js +1 -4
  76. package/lib/chain/regen/regen.js.map +1 -1
  77. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
  78. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  79. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +63 -19
  80. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  81. package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
  82. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  83. package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
  84. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  85. package/lib/chain/validation/executionPayloadBid.js +11 -8
  86. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  87. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  88. package/lib/chain/validation/proposerPreferences.js +39 -17
  89. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  90. package/lib/network/gossip/topic.d.ts +2 -0
  91. package/lib/network/gossip/topic.d.ts.map +1 -1
  92. package/lib/network/interface.d.ts +1 -0
  93. package/lib/network/interface.d.ts.map +1 -1
  94. package/lib/network/network.d.ts +1 -0
  95. package/lib/network/network.d.ts.map +1 -1
  96. package/lib/network/network.js +3 -0
  97. package/lib/network/network.js.map +1 -1
  98. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  99. package/lib/network/processor/gossipHandlers.js +24 -7
  100. package/lib/network/processor/gossipHandlers.js.map +1 -1
  101. package/lib/network/processor/index.js +5 -5
  102. package/lib/network/processor/index.js.map +1 -1
  103. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  104. package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
  105. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  106. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
  107. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
  108. package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
  109. package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
  110. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  111. package/lib/network/reqresp/handlers/index.js +5 -0
  112. package/lib/network/reqresp/handlers/index.js.map +1 -1
  113. package/lib/network/reqresp/interface.d.ts +1 -1
  114. package/lib/network/reqresp/interface.js +1 -1
  115. package/lib/network/reqresp/protocols.d.ts +1 -0
  116. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  117. package/lib/network/reqresp/protocols.js +5 -0
  118. package/lib/network/reqresp/protocols.js.map +1 -1
  119. package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
  120. package/lib/network/reqresp/rateLimit.js +4 -0
  121. package/lib/network/reqresp/rateLimit.js.map +1 -1
  122. package/lib/network/reqresp/score.d.ts.map +1 -1
  123. package/lib/network/reqresp/score.js +1 -0
  124. package/lib/network/reqresp/score.js.map +1 -1
  125. package/lib/network/reqresp/types.d.ts +3 -0
  126. package/lib/network/reqresp/types.d.ts.map +1 -1
  127. package/lib/network/reqresp/types.js +3 -0
  128. package/lib/network/reqresp/types.js.map +1 -1
  129. package/lib/node/nodejs.js +2 -2
  130. package/lib/node/nodejs.js.map +1 -1
  131. package/lib/node/notifier.js +1 -7
  132. package/lib/node/notifier.js.map +1 -1
  133. package/lib/sync/constants.d.ts +7 -1
  134. package/lib/sync/constants.d.ts.map +1 -1
  135. package/lib/sync/constants.js +7 -4
  136. package/lib/sync/constants.js.map +1 -1
  137. package/lib/sync/range/batch.d.ts +23 -3
  138. package/lib/sync/range/batch.d.ts.map +1 -1
  139. package/lib/sync/range/batch.js +191 -36
  140. package/lib/sync/range/batch.js.map +1 -1
  141. package/lib/sync/range/chain.d.ts +16 -2
  142. package/lib/sync/range/chain.d.ts.map +1 -1
  143. package/lib/sync/range/chain.js +99 -8
  144. package/lib/sync/range/chain.js.map +1 -1
  145. package/lib/sync/range/range.d.ts.map +1 -1
  146. package/lib/sync/range/range.js +54 -5
  147. package/lib/sync/range/range.js.map +1 -1
  148. package/lib/sync/sync.d.ts.map +1 -1
  149. package/lib/sync/sync.js +13 -0
  150. package/lib/sync/sync.js.map +1 -1
  151. package/lib/sync/unknownBlock.d.ts +7 -2
  152. package/lib/sync/unknownBlock.d.ts.map +1 -1
  153. package/lib/sync/unknownBlock.js +127 -57
  154. package/lib/sync/unknownBlock.js.map +1 -1
  155. package/lib/sync/utils/downloadByRange.d.ts +36 -9
  156. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  157. package/lib/sync/utils/downloadByRange.js +106 -42
  158. package/lib/sync/utils/downloadByRange.js.map +1 -1
  159. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  160. package/lib/sync/utils/downloadByRoot.js +10 -0
  161. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  162. package/lib/sync/utils/rateLimit.d.ts +2 -0
  163. package/lib/sync/utils/rateLimit.d.ts.map +1 -0
  164. package/lib/sync/utils/rateLimit.js +15 -0
  165. package/lib/sync/utils/rateLimit.js.map +1 -0
  166. package/lib/util/sszBytes.d.ts.map +1 -1
  167. package/lib/util/sszBytes.js +8 -6
  168. package/lib/util/sszBytes.js.map +1 -1
  169. package/package.json +16 -16
  170. package/src/api/impl/beacon/blocks/index.ts +13 -0
  171. package/src/api/impl/debug/index.ts +0 -1
  172. package/src/api/impl/validator/index.ts +2 -1
  173. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  174. package/src/chain/blocks/importBlock.ts +16 -49
  175. package/src/chain/blocks/importExecutionPayload.ts +21 -11
  176. package/src/chain/blocks/index.ts +18 -9
  177. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +32 -2
  178. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
  179. package/src/chain/blocks/types.ts +2 -1
  180. package/src/chain/blocks/utils/chainSegment.ts +35 -24
  181. package/src/chain/blocks/verifyBlock.ts +7 -5
  182. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  183. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
  184. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
  185. package/src/chain/chain.ts +29 -9
  186. package/src/chain/emitter.ts +0 -11
  187. package/src/chain/errors/proposerPreferences.ts +9 -1
  188. package/src/chain/initState.ts +9 -1
  189. package/src/chain/prepareNextSlot.ts +1 -1
  190. package/src/chain/produceBlock/produceBlockBody.ts +3 -7
  191. package/src/chain/regen/queued.ts +2 -7
  192. package/src/chain/regen/regen.ts +2 -7
  193. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +78 -22
  194. package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
  195. package/src/chain/validation/executionPayloadBid.ts +11 -8
  196. package/src/chain/validation/proposerPreferences.ts +37 -18
  197. package/src/network/interface.ts +1 -0
  198. package/src/network/network.ts +12 -0
  199. package/src/network/processor/gossipHandlers.ts +30 -7
  200. package/src/network/processor/index.ts +5 -5
  201. package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
  202. package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
  203. package/src/network/reqresp/handlers/index.ts +5 -0
  204. package/src/network/reqresp/interface.ts +1 -1
  205. package/src/network/reqresp/protocols.ts +6 -0
  206. package/src/network/reqresp/rateLimit.ts +4 -0
  207. package/src/network/reqresp/score.ts +1 -0
  208. package/src/network/reqresp/types.ts +5 -0
  209. package/src/node/nodejs.ts +2 -2
  210. package/src/node/notifier.ts +1 -8
  211. package/src/sync/constants.ts +8 -4
  212. package/src/sync/range/batch.ts +240 -42
  213. package/src/sync/range/chain.ts +118 -9
  214. package/src/sync/range/range.ts +68 -5
  215. package/src/sync/sync.ts +13 -1
  216. package/src/sync/unknownBlock.ts +157 -60
  217. package/src/sync/utils/downloadByRange.ts +174 -44
  218. package/src/sync/utils/downloadByRoot.ts +12 -0
  219. package/src/sync/utils/rateLimit.ts +16 -0
  220. 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
  *
@@ -1044,6 +1155,9 @@ export enum DownloadByRangeErrorCode {
1044
1155
 
1045
1156
  /** Envelope beaconBlockRoot does not match the block's root */
1046
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",
1047
1161
  }
1048
1162
 
1049
1163
  export type DownloadByRangeErrorType =
@@ -1141,20 +1255,29 @@ export type DownloadByRangeErrorType =
1141
1255
  slot: Slot;
1142
1256
  expected: string;
1143
1257
  actual: string;
1258
+ }
1259
+ | {
1260
+ code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT;
1261
+ slot: Slot;
1262
+ reason: string;
1144
1263
  };
1145
1264
 
1146
1265
  export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType> {}
1147
1266
 
1148
1267
  /**
1149
1268
  * 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.
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.
1153
1275
  */
1154
1276
  export function validateEnvelopesByRangeResponse(
1155
1277
  validatedBlocks: ValidatedBlock[],
1156
1278
  batchBlocks: IBlockInput[] | undefined,
1157
- payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
1279
+ payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
1280
+ parentPayloadCommitments?: ParentPayloadCommitments
1158
1281
  ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1159
1282
  // Build a map of slot -> blockRoot for all blocks in the batch
1160
1283
  const batchBlockRoots = new Map<Slot, Uint8Array>();
@@ -1173,8 +1296,15 @@ export function validateEnvelopesByRangeResponse(
1173
1296
  const slot = payloadEnvelope.message.payload.slotNumber;
1174
1297
  const batchBlockRoot = batchBlockRoots.get(slot);
1175
1298
 
1176
- // Envelopes for slots not in the batch are silently ignored (orphaned payloads)
1177
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
+ }
1178
1308
  continue;
1179
1309
  }
1180
1310
 
@@ -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) {
@@ -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
+ }
@@ -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) {