@lodestar/beacon-node 1.43.0-dev.ca1fc40294 → 1.43.0-dev.dfb984e779

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 (170) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +3 -2
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/lodestar/index.js +1 -1
  5. package/lib/api/impl/lodestar/index.js.map +1 -1
  6. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  7. package/lib/chain/blocks/importBlock.js +6 -3
  8. package/lib/chain/blocks/importBlock.js.map +1 -1
  9. package/lib/chain/blocks/importExecutionPayload.d.ts +26 -14
  10. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  11. package/lib/chain/blocks/importExecutionPayload.js +73 -77
  12. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  13. package/lib/chain/blocks/index.d.ts +5 -3
  14. package/lib/chain/blocks/index.d.ts.map +1 -1
  15. package/lib/chain/blocks/index.js +28 -10
  16. package/lib/chain/blocks/index.js.map +1 -1
  17. package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
  18. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  19. package/lib/chain/blocks/types.d.ts +14 -20
  20. package/lib/chain/blocks/types.d.ts.map +1 -1
  21. package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
  22. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  23. package/lib/chain/blocks/utils/chainSegment.js +81 -12
  24. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  25. package/lib/chain/blocks/verifyBlock.d.ts +3 -2
  26. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  27. package/lib/chain/blocks/verifyBlock.js +30 -5
  28. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  29. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
  31. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  32. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +24 -0
  33. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -0
  34. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +76 -0
  35. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -0
  36. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts +1 -1
  37. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -1
  38. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +2 -11
  39. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -1
  40. package/lib/chain/chain.d.ts +3 -2
  41. package/lib/chain/chain.d.ts.map +1 -1
  42. package/lib/chain/chain.js +14 -3
  43. package/lib/chain/chain.js.map +1 -1
  44. package/lib/chain/errors/blockError.d.ts +8 -1
  45. package/lib/chain/errors/blockError.d.ts.map +1 -1
  46. package/lib/chain/errors/blockError.js +2 -0
  47. package/lib/chain/errors/blockError.js.map +1 -1
  48. package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
  49. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  50. package/lib/chain/errors/executionPayloadBid.js +1 -0
  51. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  52. package/lib/chain/errors/executionPayloadEnvelope.d.ts +5 -0
  53. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  54. package/lib/chain/errors/executionPayloadEnvelope.js +1 -0
  55. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  56. package/lib/chain/forkChoice/index.js +2 -2
  57. package/lib/chain/forkChoice/index.js.map +1 -1
  58. package/lib/chain/interface.d.ts +3 -2
  59. package/lib/chain/interface.d.ts.map +1 -1
  60. package/lib/chain/interface.js.map +1 -1
  61. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  62. package/lib/chain/prepareNextSlot.js +30 -10
  63. package/lib/chain/prepareNextSlot.js.map +1 -1
  64. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  65. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  66. package/lib/chain/produceBlock/produceBlockBody.js +34 -13
  67. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  68. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +11 -4
  69. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  70. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -18
  71. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  72. package/lib/chain/validation/block.d.ts.map +1 -1
  73. package/lib/chain/validation/block.js +1 -0
  74. package/lib/chain/validation/block.js.map +1 -1
  75. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  76. package/lib/chain/validation/executionPayloadBid.js +13 -1
  77. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  78. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  79. package/lib/chain/validation/executionPayloadEnvelope.js +11 -1
  80. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  81. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  82. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  83. package/lib/metrics/metrics/lodestar.js +4 -0
  84. package/lib/metrics/metrics/lodestar.js.map +1 -1
  85. package/lib/network/processor/gossipHandlers.js +4 -6
  86. package/lib/network/processor/gossipHandlers.js.map +1 -1
  87. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  88. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  89. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  90. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  91. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  92. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  93. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  94. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  95. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  96. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  97. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  98. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  99. package/lib/node/notifier.js +7 -1
  100. package/lib/node/notifier.js.map +1 -1
  101. package/lib/sync/range/batch.d.ts +12 -2
  102. package/lib/sync/range/batch.d.ts.map +1 -1
  103. package/lib/sync/range/batch.js +56 -30
  104. package/lib/sync/range/batch.js.map +1 -1
  105. package/lib/sync/range/chain.d.ts +6 -2
  106. package/lib/sync/range/chain.d.ts.map +1 -1
  107. package/lib/sync/range/chain.js +4 -3
  108. package/lib/sync/range/chain.js.map +1 -1
  109. package/lib/sync/range/range.d.ts.map +1 -1
  110. package/lib/sync/range/range.js +17 -6
  111. package/lib/sync/range/range.js.map +1 -1
  112. package/lib/sync/types.d.ts +34 -0
  113. package/lib/sync/types.d.ts.map +1 -1
  114. package/lib/sync/types.js +34 -0
  115. package/lib/sync/types.js.map +1 -1
  116. package/lib/sync/unknownBlock.d.ts +24 -1
  117. package/lib/sync/unknownBlock.d.ts.map +1 -1
  118. package/lib/sync/unknownBlock.js +649 -53
  119. package/lib/sync/unknownBlock.js.map +1 -1
  120. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  121. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  122. package/lib/sync/utils/downloadByRange.js +147 -24
  123. package/lib/sync/utils/downloadByRange.js.map +1 -1
  124. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  125. package/lib/sync/utils/downloadByRoot.js +6 -2
  126. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  127. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  128. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  129. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  130. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  131. package/package.json +16 -15
  132. package/src/api/impl/beacon/blocks/index.ts +5 -2
  133. package/src/api/impl/lodestar/index.ts +1 -1
  134. package/src/chain/blocks/importBlock.ts +4 -2
  135. package/src/chain/blocks/importExecutionPayload.ts +92 -97
  136. package/src/chain/blocks/index.ts +44 -13
  137. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  138. package/src/chain/blocks/types.ts +14 -25
  139. package/src/chain/blocks/utils/chainSegment.ts +106 -17
  140. package/src/chain/blocks/verifyBlock.ts +35 -6
  141. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
  142. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +129 -0
  143. package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +9 -18
  144. package/src/chain/chain.ts +23 -3
  145. package/src/chain/errors/blockError.ts +4 -1
  146. package/src/chain/errors/executionPayloadBid.ts +6 -0
  147. package/src/chain/errors/executionPayloadEnvelope.ts +6 -0
  148. package/src/chain/forkChoice/index.ts +2 -2
  149. package/src/chain/interface.ts +7 -1
  150. package/src/chain/prepareNextSlot.ts +42 -12
  151. package/src/chain/produceBlock/produceBlockBody.ts +37 -11
  152. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +22 -20
  153. package/src/chain/validation/block.ts +1 -0
  154. package/src/chain/validation/executionPayloadBid.ts +14 -0
  155. package/src/chain/validation/executionPayloadEnvelope.ts +12 -2
  156. package/src/metrics/metrics/lodestar.ts +4 -0
  157. package/src/network/processor/gossipHandlers.ts +6 -6
  158. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  159. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  160. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  161. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  162. package/src/node/notifier.ts +8 -1
  163. package/src/sync/range/batch.ts +90 -35
  164. package/src/sync/range/chain.ts +13 -5
  165. package/src/sync/range/range.ts +18 -6
  166. package/src/sync/types.ts +72 -0
  167. package/src/sync/unknownBlock.ts +810 -57
  168. package/src/sync/utils/downloadByRange.ts +256 -39
  169. package/src/sync/utils/downloadByRoot.ts +12 -2
  170. package/src/sync/utils/pendingBlocksTree.ts +0 -15
@@ -1,6 +1,22 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ForkPostDeneb, ForkPostFulu, ForkPreFulu, isForkPostFulu} from "@lodestar/params";
3
- import {SignedBeaconBlock, Slot, deneb, fulu, phase0} from "@lodestar/types";
2
+ import {
3
+ ForkPostDeneb,
4
+ ForkPostFulu,
5
+ ForkPostGloas,
6
+ ForkPreFulu,
7
+ isForkPostFulu,
8
+ isForkPostGloas,
9
+ } from "@lodestar/params";
10
+ import {
11
+ DataColumnSidecar,
12
+ SignedBeaconBlock,
13
+ Slot,
14
+ deneb,
15
+ fulu,
16
+ gloas,
17
+ isGloasDataColumnSidecar,
18
+ phase0,
19
+ } from "@lodestar/types";
4
20
  import {LodestarError, Logger, byteArrayEquals, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
5
21
  import {
6
22
  BlockInputSource,
@@ -9,12 +25,18 @@ import {
9
25
  isBlockInputBlobs,
10
26
  isBlockInputColumns,
11
27
  } from "../../chain/blocks/blockInput/index.js";
28
+ import {PayloadEnvelopeInput} from "../../chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
29
+ import {PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/types.js";
12
30
  import {SeenBlockInput} from "../../chain/seenCache/seenGossipBlockInput.js";
31
+ import {SeenPayloadEnvelopeInput} from "../../chain/seenCache/seenPayloadEnvelopeInput.js";
13
32
  import {validateBlockBlobSidecars} from "../../chain/validation/blobSidecar.js";
14
- import {validateFuluBlockDataColumnSidecars} from "../../chain/validation/dataColumnSidecar.js";
33
+ import {
34
+ validateFuluBlockDataColumnSidecars,
35
+ validateGloasBlockDataColumnSidecars,
36
+ } from "../../chain/validation/dataColumnSidecar.js";
15
37
  import {BeaconMetrics} from "../../metrics/metrics/beacon.js";
16
38
  import {INetwork} from "../../network/index.js";
17
- import {getBlobKzgCommitments} from "../../util/dataColumns.js";
39
+ import {CustodyConfig, getBlobKzgCommitments} from "../../util/dataColumns.js";
18
40
  import {PeerIdStr} from "../../util/peerId.js";
19
41
  import {WarnResult} from "../../util/wrapError.js";
20
42
 
@@ -22,12 +44,14 @@ export type DownloadByRangeRequests = {
22
44
  blocksRequest?: phase0.BeaconBlocksByRangeRequest;
23
45
  blobsRequest?: deneb.BlobSidecarsByRangeRequest;
24
46
  columnsRequest?: fulu.DataColumnSidecarsByRangeRequest;
47
+ envelopesRequest?: gloas.ExecutionPayloadEnvelopesByRangeRequest;
25
48
  };
26
49
 
27
50
  export type DownloadByRangeResponses = {
28
51
  blocks?: SignedBeaconBlock[];
29
52
  blobSidecars?: deneb.BlobSidecars;
30
- columnSidecars?: fulu.DataColumnSidecar[];
53
+ columnSidecars?: DataColumnSidecar[];
54
+ payloadEnvelopes?: gloas.SignedExecutionPayloadEnvelope[];
31
55
  };
32
56
 
33
57
  export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
@@ -41,9 +65,17 @@ export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
41
65
 
42
66
  export type CacheByRangeResponsesProps = {
43
67
  cache: SeenBlockInput;
68
+ seenPayloadEnvelopeInputCache: SeenPayloadEnvelopeInput;
44
69
  peerIdStr: string;
45
70
  responses: ValidatedResponses;
46
71
  batchBlocks: IBlockInput[];
72
+ /** Raw envelopes downloaded in this batch, keyed by slot (from downloadByRange return) */
73
+ downloadedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null;
74
+ /** Envelopes already wrapped from previous partial downloads on this batch */
75
+ existingPayloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
76
+ /** Sampled/custody column indices for building PayloadEnvelopeInputs */
77
+ custodyConfig: Pick<CustodyConfig, "sampledColumns" | "custodyColumns">;
78
+ seenTimestampSec: number;
47
79
  };
48
80
 
49
81
  export type ValidatedBlock = {
@@ -58,7 +90,7 @@ export type ValidatedBlobSidecars = {
58
90
 
59
91
  export type ValidatedColumnSidecars = {
60
92
  blockRoot: Uint8Array;
61
- columnSidecars: fulu.DataColumnSidecar[];
93
+ columnSidecars: DataColumnSidecar[];
62
94
  };
63
95
 
64
96
  export type ValidatedResponses = {
@@ -72,12 +104,16 @@ export type ValidatedResponses = {
72
104
  */
73
105
  export function cacheByRangeResponses({
74
106
  cache,
107
+ seenPayloadEnvelopeInputCache,
75
108
  peerIdStr,
76
109
  responses,
77
110
  batchBlocks,
78
- }: CacheByRangeResponsesProps): IBlockInput[] {
111
+ downloadedPayloadEnvelopes,
112
+ existingPayloadEnvelopes,
113
+ custodyConfig,
114
+ seenTimestampSec,
115
+ }: CacheByRangeResponsesProps): {blocks: IBlockInput[]; payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null} {
79
116
  const source = BlockInputSource.byRange;
80
- const seenTimestampSec = Date.now() / 1000;
81
117
  const updatedBatchBlocks = new Map<Slot, IBlockInput>(batchBlocks.map((block) => [block.slot, block]));
82
118
 
83
119
  const blocks = responses.validatedBlocks ?? [];
@@ -149,16 +185,82 @@ export function cacheByRangeResponses({
149
185
  }
150
186
  }
151
187
 
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 ?? []);
194
+
195
+ 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);
206
+ 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
+ });
215
+ }
216
+
217
+ if (!payloadInput.hasPayloadEnvelope()) {
218
+ payloadInput.addPayloadEnvelope({
219
+ envelope,
220
+ source: PayloadEnvelopeInputSource.byRange,
221
+ seenTimestampSec,
222
+ peerIdStr,
223
+ });
224
+ }
225
+
226
+ payloadEnvelopes.set(slot, payloadInput);
227
+ }
228
+ }
229
+
152
230
  for (const {blockRoot, columnSidecars} of responses.validatedColumnSidecars ?? []) {
153
- const dataSlot = columnSidecars.at(0)?.signedBlockHeader.message.slot;
154
- if (dataSlot === undefined) {
231
+ const firstColumn = columnSidecars[0];
232
+ if (!firstColumn) {
155
233
  throw new Error(
156
234
  `Coding Error: empty columnSidecars returned for blockRoot=${toRootHex(blockRoot)} from validation functions`
157
235
  );
158
236
  }
159
- const existing = updatedBatchBlocks.get(dataSlot);
237
+
160
238
  const blockRootHex = toRootHex(blockRoot);
161
239
 
240
+ if (isGloasDataColumnSidecar(firstColumn)) {
241
+ // Gloas columns are attached to the matching PayloadEnvelopeInput, NOT to IBlockInput.
242
+ // Gloas DataColumnSidecar has `slot` directly (no signedBlockHeader).
243
+ const dataSlot = firstColumn.slot;
244
+ const payloadInput = payloadEnvelopes?.get(dataSlot);
245
+ if (!payloadInput) {
246
+ // Should not happen: we built payloadInputs for all gloas blocks above
247
+ continue;
248
+ }
249
+ for (const columnSidecar of columnSidecars as gloas.DataColumnSidecar[]) {
250
+ payloadInput.addColumn({
251
+ columnSidecar,
252
+ seenTimestampSec,
253
+ peerIdStr,
254
+ source: PayloadEnvelopeInputSource.byRange,
255
+ });
256
+ }
257
+ continue;
258
+ }
259
+
260
+ const fuluColumns = columnSidecars as fulu.DataColumnSidecar[];
261
+ const dataSlot = fuluColumns[0].signedBlockHeader.message.slot;
262
+ const existing = updatedBatchBlocks.get(dataSlot);
263
+
162
264
  if (!existing) {
163
265
  throw new Error("Coding error: blockInput must exist when adding columns");
164
266
  }
@@ -172,7 +274,7 @@ export function cacheByRangeResponses({
172
274
  actual: existing.type,
173
275
  });
174
276
  }
175
- for (const columnSidecar of columnSidecars) {
277
+ for (const columnSidecar of fuluColumns) {
176
278
  // will throw if root hex does not match (meaning we are following the wrong chain)
177
279
  existing.addColumn(
178
280
  {
@@ -187,7 +289,7 @@ export function cacheByRangeResponses({
187
289
  }
188
290
  }
189
291
 
190
- return Array.from(updatedBatchBlocks.values());
292
+ return {blocks: Array.from(updatedBatchBlocks.values()), payloadEnvelopes};
191
293
  }
192
294
 
193
295
  export async function downloadByRange({
@@ -198,8 +300,14 @@ export async function downloadByRange({
198
300
  blocksRequest,
199
301
  blobsRequest,
200
302
  columnsRequest,
303
+ envelopesRequest,
201
304
  peerDasMetrics,
202
- }: DownloadAndCacheByRangeProps): Promise<WarnResult<ValidatedResponses, DownloadByRangeError>> {
305
+ }: DownloadAndCacheByRangeProps): Promise<
306
+ WarnResult<
307
+ {responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null},
308
+ DownloadByRangeError
309
+ >
310
+ > {
203
311
  let response: DownloadByRangeResponses;
204
312
  try {
205
313
  response = await requestByRange({
@@ -208,6 +316,7 @@ export async function downloadByRange({
208
316
  blocksRequest,
209
317
  blobsRequest,
210
318
  columnsRequest,
319
+ envelopesRequest,
211
320
  });
212
321
  } catch (err) {
213
322
  throw new DownloadByRangeError({
@@ -217,17 +326,16 @@ export async function downloadByRange({
217
326
  });
218
327
  }
219
328
 
220
- const validated = await validateResponses({
329
+ return validateResponses({
221
330
  config,
222
331
  batchBlocks,
223
332
  blocksRequest,
224
333
  blobsRequest,
225
334
  columnsRequest,
335
+ envelopesRequest,
226
336
  peerDasMetrics,
227
337
  ...response,
228
338
  });
229
-
230
- return validated;
231
339
  }
232
340
 
233
341
  /**
@@ -239,13 +347,15 @@ export async function requestByRange({
239
347
  blocksRequest,
240
348
  blobsRequest,
241
349
  columnsRequest,
350
+ envelopesRequest,
242
351
  }: DownloadByRangeRequests & {
243
352
  network: INetwork;
244
353
  peerIdStr: PeerIdStr;
245
354
  }): Promise<DownloadByRangeResponses> {
246
355
  let blocks: undefined | SignedBeaconBlock[];
247
356
  let blobSidecars: undefined | deneb.BlobSidecars;
248
- let columnSidecars: undefined | fulu.DataColumnSidecar[];
357
+ let columnSidecars: undefined | DataColumnSidecar[];
358
+ let payloadEnvelopes: undefined | gloas.SignedExecutionPayloadEnvelope[];
249
359
 
250
360
  const requests: Promise<unknown>[] = [];
251
361
 
@@ -268,7 +378,15 @@ export async function requestByRange({
268
378
  if (columnsRequest) {
269
379
  requests.push(
270
380
  network.sendDataColumnSidecarsByRange(peerIdStr, columnsRequest).then((columnResponse) => {
271
- columnSidecars = columnResponse as fulu.DataColumnSidecar[];
381
+ columnSidecars = columnResponse;
382
+ })
383
+ );
384
+ }
385
+
386
+ if (envelopesRequest) {
387
+ requests.push(
388
+ network.sendExecutionPayloadEnvelopesByRange(peerIdStr, envelopesRequest).then((envelopeResponse) => {
389
+ payloadEnvelopes = envelopeResponse;
272
390
  })
273
391
  );
274
392
  }
@@ -279,6 +397,7 @@ export async function requestByRange({
279
397
  blocks,
280
398
  blobSidecars,
281
399
  columnSidecars,
400
+ payloadEnvelopes,
282
401
  };
283
402
  }
284
403
 
@@ -291,16 +410,23 @@ export async function validateResponses({
291
410
  blocksRequest,
292
411
  blobsRequest,
293
412
  columnsRequest,
413
+ envelopesRequest,
294
414
  blocks,
295
415
  blobSidecars,
296
416
  columnSidecars,
417
+ payloadEnvelopes,
297
418
  peerDasMetrics,
298
419
  }: DownloadByRangeRequests &
299
420
  DownloadByRangeResponses & {
300
421
  config: ChainForkConfig;
301
422
  batchBlocks?: IBlockInput[];
302
423
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
303
- }): Promise<WarnResult<ValidatedResponses, DownloadByRangeError>> {
424
+ }): Promise<
425
+ WarnResult<
426
+ {responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null},
427
+ DownloadByRangeError
428
+ >
429
+ > {
304
430
  // Blocks are always required for blob/column validation
305
431
  // If a blocksRequest is provided, blocks have just been downloaded
306
432
  // If no blocksRequest is provided, batchBlocks must have been provided from cache
@@ -326,8 +452,21 @@ export async function validateResponses({
326
452
  }
327
453
 
328
454
  const dataRequest = blobsRequest ?? columnsRequest;
455
+ if (!dataRequest && !envelopesRequest) {
456
+ return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
457
+ }
458
+
329
459
  if (!dataRequest) {
330
- return {result: validatedResponses, warnings};
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 ?? []
467
+ );
468
+ }
469
+ return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
331
470
  }
332
471
 
333
472
  const blocksForDataValidation = getBlocksForDataValidation(
@@ -385,7 +524,17 @@ export async function validateResponses({
385
524
  warnings = validatedColumnSidecarsResult.warnings;
386
525
  }
387
526
 
388
- return {result: validatedResponses, warnings};
527
+ // Validate envelopes if an envelopes request was made
528
+ let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
529
+ if (envelopesRequest) {
530
+ validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
531
+ validatedResponses.validatedBlocks ?? [],
532
+ batchBlocks,
533
+ payloadEnvelopes ?? []
534
+ );
535
+ }
536
+
537
+ return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
389
538
  }
390
539
 
391
540
  /**
@@ -615,19 +764,19 @@ export async function validateColumnsByRangeResponse(
615
764
  config: ChainForkConfig,
616
765
  request: fulu.DataColumnSidecarsByRangeRequest,
617
766
  blocks: ValidatedBlock[],
618
- columnSidecars: fulu.DataColumnSidecar[],
767
+ columnSidecars: DataColumnSidecar[],
619
768
  peerDasMetrics?: BeaconMetrics["peerDas"] | null
620
769
  ): Promise<WarnResult<ValidatedColumnSidecars[], DownloadByRangeError>> {
621
770
  const warnings: DownloadByRangeError[] = [];
622
771
 
623
- // TODO GLOAS: Extend by range column sync to support gloas.DataColumnSidecar and
624
- // validate against the block bid commitments instead of the fulu signed header shape
625
- const seenColumns = new Map<Slot, Map<number, fulu.DataColumnSidecar>>();
772
+ const seenColumns = new Map<Slot, Map<number, DataColumnSidecar>>();
626
773
  let currentSlot = -1;
627
774
  let currentIndex = -1;
628
775
  // Check for duplicates and order
629
776
  for (const columnSidecar of columnSidecars) {
630
- const slot = columnSidecar.signedBlockHeader.message.slot;
777
+ const slot = isGloasDataColumnSidecar(columnSidecar)
778
+ ? columnSidecar.slot
779
+ : columnSidecar.signedBlockHeader.message.slot;
631
780
  let seenSlotColumns = seenColumns.get(slot);
632
781
  if (!seenSlotColumns) {
633
782
  seenSlotColumns = new Map();
@@ -686,20 +835,20 @@ export async function validateColumnsByRangeResponse(
686
835
  const slot = block.message.slot;
687
836
  const rootHex = toRootHex(blockRoot);
688
837
  const forkName = config.getForkName(slot);
689
- const columnSidecarsMap: Map<number, fulu.DataColumnSidecar> = seenColumns.get(slot) ?? new Map();
838
+ const columnSidecarsMap: Map<number, DataColumnSidecar> = seenColumns.get(slot) ?? new Map();
690
839
  const columnSidecars = Array.from(columnSidecarsMap.values()).sort((a, b) => a.index - b.index);
691
840
 
692
841
  let blobCount: number;
693
842
  if (!isForkPostFulu(forkName)) {
694
- const dataSlot = columnSidecars.at(0)?.signedBlockHeader.message.slot;
695
843
  throw new DownloadByRangeError({
696
844
  code: DownloadByRangeErrorCode.MISMATCH_BLOCK_FORK,
697
845
  slot,
698
846
  blockFork: forkName,
699
- dataFork: dataSlot ? config.getForkName(dataSlot) : "unknown",
847
+ dataFork: "unknown",
700
848
  });
701
849
  }
702
- blobCount = getBlobKzgCommitments(forkName, block as SignedBeaconBlock<ForkPostFulu>).length;
850
+ const kzgCommitments = getBlobKzgCommitments(forkName, block as SignedBeaconBlock<ForkPostFulu>);
851
+ blobCount = kzgCommitments.length;
703
852
 
704
853
  if (columnSidecars.length === 0) {
705
854
  if (!blobCount) {
@@ -768,15 +917,25 @@ export async function validateColumnsByRangeResponse(
768
917
  );
769
918
  }
770
919
 
920
+ const validatePromise = isForkPostGloas(forkName)
921
+ ? validateGloasBlockDataColumnSidecars(
922
+ slot,
923
+ blockRoot,
924
+ kzgCommitments,
925
+ columnSidecars as gloas.DataColumnSidecar[],
926
+ peerDasMetrics
927
+ )
928
+ : validateFuluBlockDataColumnSidecars(
929
+ null, // do not pass chain here so we do not validate header signature
930
+ slot,
931
+ blockRoot,
932
+ blobCount,
933
+ columnSidecars as fulu.DataColumnSidecar[],
934
+ peerDasMetrics
935
+ );
936
+
771
937
  validationPromises.push(
772
- validateFuluBlockDataColumnSidecars(
773
- null, // do not pass chain here so we do not validate header signature
774
- slot,
775
- blockRoot,
776
- blobCount,
777
- columnSidecars,
778
- peerDasMetrics
779
- ).then(() => ({
938
+ validatePromise.then(() => ({
780
939
  blockRoot,
781
940
  columnSidecars,
782
941
  }))
@@ -882,6 +1041,9 @@ export enum DownloadByRangeErrorCode {
882
1041
  /** Cached block input type mismatches new data */
883
1042
  MISMATCH_BLOCK_FORK = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_FORK",
884
1043
  MISMATCH_BLOCK_INPUT_TYPE = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_INPUT_TYPE",
1044
+
1045
+ /** Envelope beaconBlockRoot does not match the block's root */
1046
+ INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
885
1047
  }
886
1048
 
887
1049
  export type DownloadByRangeErrorType =
@@ -973,6 +1135,61 @@ export type DownloadByRangeErrorType =
973
1135
  blockRoot: string;
974
1136
  expected: DAType;
975
1137
  actual: DAType;
1138
+ }
1139
+ | {
1140
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT;
1141
+ slot: Slot;
1142
+ expected: string;
1143
+ actual: string;
976
1144
  };
977
1145
 
978
1146
  export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType> {}
1147
+
1148
+ /**
1149
+ * 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.
1153
+ */
1154
+ export function validateEnvelopesByRangeResponse(
1155
+ validatedBlocks: ValidatedBlock[],
1156
+ batchBlocks: IBlockInput[] | undefined,
1157
+ payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
1158
+ ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1159
+ // Build a map of slot -> blockRoot for all blocks in the batch
1160
+ const batchBlockRoots = new Map<Slot, Uint8Array>();
1161
+ if (batchBlocks) {
1162
+ for (const blockInput of batchBlocks) {
1163
+ batchBlockRoots.set(blockInput.slot, fromHex(blockInput.blockRootHex));
1164
+ }
1165
+ }
1166
+ for (const {block, blockRoot} of validatedBlocks) {
1167
+ batchBlockRoots.set(block.message.slot, blockRoot);
1168
+ }
1169
+
1170
+ const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
1171
+
1172
+ for (const payloadEnvelope of payloadEnvelopes) {
1173
+ const slot = payloadEnvelope.message.payload.slotNumber;
1174
+ const batchBlockRoot = batchBlockRoots.get(slot);
1175
+
1176
+ // Envelopes for slots not in the batch are silently ignored (orphaned payloads)
1177
+ if (batchBlockRoot === undefined) {
1178
+ continue;
1179
+ }
1180
+
1181
+ // Verify beaconBlockRoot matches the block's root
1182
+ if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlockRoot)) {
1183
+ throw new DownloadByRangeError({
1184
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
1185
+ slot,
1186
+ expected: toRootHex(batchBlockRoot),
1187
+ actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
1188
+ });
1189
+ }
1190
+
1191
+ payloadEnvelopeMap.set(slot, payloadEnvelope);
1192
+ }
1193
+
1194
+ return payloadEnvelopeMap;
1195
+ }
@@ -1,6 +1,13 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
- import {ForkPostDeneb, ForkPostFulu, ForkPreFulu, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
3
+ import {
4
+ ForkPostDeneb,
5
+ ForkPostFulu,
6
+ ForkPreFulu,
7
+ isForkPostDeneb,
8
+ isForkPostFulu,
9
+ isForkPostGloas,
10
+ } from "@lodestar/params";
4
11
  import {BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
5
12
  import {LodestarError, byteArrayEquals, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
6
13
  import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
@@ -263,7 +270,10 @@ export async function fetchByRoot({
263
270
  blockRoot,
264
271
  });
265
272
  const forkName = config.getForkName(block.message.slot);
266
- if (isForkPostFulu(forkName)) {
273
+ if (isForkPostGloas(forkName)) {
274
+ // Post-gloas block sync only needs the block body. Payload columns stay on the
275
+ // payload/envelope path and are queued independently in the network processor.
276
+ } else if (isForkPostFulu(forkName)) {
267
277
  columnSidecarResult = await fetchAndValidateColumns({
268
278
  config,
269
279
  chain,
@@ -40,21 +40,6 @@ function addToDescendantBlocks(
40
40
  return descendantBlocks;
41
41
  }
42
42
 
43
- export function getDescendantBlocks(
44
- blockRootHex: RootHex,
45
- blocks: Map<RootHex, BlockInputSyncCacheItem>
46
- ): BlockInputSyncCacheItem[] {
47
- const descendantBlocks: BlockInputSyncCacheItem[] = [];
48
-
49
- for (const block of blocks.values()) {
50
- if ((isPendingBlockInput(block) ? block.blockInput.parentRootHex : undefined) === blockRootHex) {
51
- descendantBlocks.push(block);
52
- }
53
- }
54
-
55
- return descendantBlocks;
56
- }
57
-
58
43
  export type UnknownAndAncestorBlocks = {
59
44
  unknowns: BlockInputSyncCacheItem[];
60
45
  ancestors: PendingBlockInput[];