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

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 (233) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +16 -5
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +45 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/lodestar/index.js +1 -1
  8. package/lib/api/impl/lodestar/index.js.map +1 -1
  9. package/lib/api/impl/validator/index.d.ts.map +1 -1
  10. package/lib/api/impl/validator/index.js +66 -1
  11. package/lib/api/impl/validator/index.js.map +1 -1
  12. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  13. package/lib/chain/blocks/importBlock.js +10 -18
  14. package/lib/chain/blocks/importBlock.js.map +1 -1
  15. package/lib/chain/blocks/importExecutionPayload.d.ts +19 -6
  16. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.js +43 -19
  18. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  19. package/lib/chain/blocks/index.d.ts +5 -3
  20. package/lib/chain/blocks/index.d.ts.map +1 -1
  21. package/lib/chain/blocks/index.js +31 -10
  22. package/lib/chain/blocks/index.js.map +1 -1
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +1 -0
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +5 -1
  26. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
  28. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
  30. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  31. package/lib/chain/blocks/types.d.ts +2 -2
  32. package/lib/chain/blocks/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
  34. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  35. package/lib/chain/blocks/utils/chainSegment.js +81 -12
  36. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  37. package/lib/chain/blocks/verifyBlock.d.ts +5 -3
  38. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  39. package/lib/chain/blocks/verifyBlock.js +51 -7
  40. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
  43. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  44. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  45. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  46. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  47. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  48. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  49. package/lib/chain/chain.d.ts +3 -2
  50. package/lib/chain/chain.d.ts.map +1 -1
  51. package/lib/chain/chain.js +12 -4
  52. package/lib/chain/chain.js.map +1 -1
  53. package/lib/chain/emitter.d.ts +0 -11
  54. package/lib/chain/emitter.d.ts.map +1 -1
  55. package/lib/chain/emitter.js +0 -4
  56. package/lib/chain/emitter.js.map +1 -1
  57. package/lib/chain/errors/blockError.d.ts +8 -1
  58. package/lib/chain/errors/blockError.d.ts.map +1 -1
  59. package/lib/chain/errors/blockError.js +2 -0
  60. package/lib/chain/errors/blockError.js.map +1 -1
  61. package/lib/chain/errors/index.d.ts +1 -0
  62. package/lib/chain/errors/index.d.ts.map +1 -1
  63. package/lib/chain/errors/index.js +1 -0
  64. package/lib/chain/errors/index.js.map +1 -1
  65. package/lib/chain/errors/proposerPreferences.d.ts +33 -0
  66. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  67. package/lib/chain/errors/proposerPreferences.js +13 -0
  68. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  69. package/lib/chain/interface.d.ts +3 -2
  70. package/lib/chain/interface.d.ts.map +1 -1
  71. package/lib/chain/interface.js.map +1 -1
  72. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  73. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  74. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  75. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  76. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  77. package/lib/chain/prepareNextSlot.js +15 -17
  78. package/lib/chain/prepareNextSlot.js.map +1 -1
  79. package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -3
  80. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  81. package/lib/chain/produceBlock/produceBlockBody.js +41 -18
  82. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  83. package/lib/chain/regen/interface.d.ts +1 -0
  84. package/lib/chain/regen/interface.d.ts.map +1 -1
  85. package/lib/chain/regen/interface.js +1 -0
  86. package/lib/chain/regen/interface.js.map +1 -1
  87. package/lib/chain/seenCache/index.d.ts +1 -0
  88. package/lib/chain/seenCache/index.d.ts.map +1 -1
  89. package/lib/chain/seenCache/index.js +1 -0
  90. package/lib/chain/seenCache/index.js.map +1 -1
  91. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +8 -2
  92. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  93. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -4
  94. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  95. package/lib/chain/seenCache/seenProposerPreferences.d.ts +15 -0
  96. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  97. package/lib/chain/seenCache/seenProposerPreferences.js +25 -0
  98. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  99. package/lib/chain/validation/block.d.ts.map +1 -1
  100. package/lib/chain/validation/block.js +1 -0
  101. package/lib/chain/validation/block.js.map +1 -1
  102. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  103. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  104. package/lib/chain/validation/proposerPreferences.js +69 -0
  105. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  106. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  107. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  108. package/lib/metrics/metrics/lodestar.js +4 -0
  109. package/lib/metrics/metrics/lodestar.js.map +1 -1
  110. package/lib/network/gossip/interface.d.ts +7 -1
  111. package/lib/network/gossip/interface.d.ts.map +1 -1
  112. package/lib/network/gossip/interface.js +1 -0
  113. package/lib/network/gossip/interface.js.map +1 -1
  114. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  115. package/lib/network/gossip/scoringParameters.js +12 -1
  116. package/lib/network/gossip/scoringParameters.js.map +1 -1
  117. package/lib/network/gossip/topic.d.ts +8 -0
  118. package/lib/network/gossip/topic.d.ts.map +1 -1
  119. package/lib/network/gossip/topic.js +6 -0
  120. package/lib/network/gossip/topic.js.map +1 -1
  121. package/lib/network/interface.d.ts +1 -0
  122. package/lib/network/interface.d.ts.map +1 -1
  123. package/lib/network/network.d.ts +1 -0
  124. package/lib/network/network.d.ts.map +1 -1
  125. package/lib/network/network.js +5 -0
  126. package/lib/network/network.js.map +1 -1
  127. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  128. package/lib/network/processor/gossipHandlers.js +24 -16
  129. package/lib/network/processor/gossipHandlers.js.map +1 -1
  130. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  131. package/lib/network/processor/gossipQueues/index.js +5 -0
  132. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  133. package/lib/network/processor/index.d.ts.map +1 -1
  134. package/lib/network/processor/index.js +6 -5
  135. package/lib/network/processor/index.js.map +1 -1
  136. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  137. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  138. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  139. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  140. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  141. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  142. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  143. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  144. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  145. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  146. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  147. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  148. package/lib/node/notifier.js +7 -1
  149. package/lib/node/notifier.js.map +1 -1
  150. package/lib/sync/range/batch.d.ts +23 -2
  151. package/lib/sync/range/batch.d.ts.map +1 -1
  152. package/lib/sync/range/batch.js +84 -33
  153. package/lib/sync/range/batch.js.map +1 -1
  154. package/lib/sync/range/chain.d.ts +6 -2
  155. package/lib/sync/range/chain.d.ts.map +1 -1
  156. package/lib/sync/range/chain.js +26 -7
  157. package/lib/sync/range/chain.js.map +1 -1
  158. package/lib/sync/range/range.d.ts.map +1 -1
  159. package/lib/sync/range/range.js +17 -6
  160. package/lib/sync/range/range.js.map +1 -1
  161. package/lib/sync/types.d.ts +34 -0
  162. package/lib/sync/types.d.ts.map +1 -1
  163. package/lib/sync/types.js +34 -0
  164. package/lib/sync/types.js.map +1 -1
  165. package/lib/sync/unknownBlock.d.ts +22 -1
  166. package/lib/sync/unknownBlock.d.ts.map +1 -1
  167. package/lib/sync/unknownBlock.js +602 -53
  168. package/lib/sync/unknownBlock.js.map +1 -1
  169. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  170. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  171. package/lib/sync/utils/downloadByRange.js +153 -24
  172. package/lib/sync/utils/downloadByRange.js.map +1 -1
  173. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  174. package/lib/sync/utils/downloadByRoot.js +16 -2
  175. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  176. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  177. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  178. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  179. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  180. package/package.json +15 -15
  181. package/src/api/impl/beacon/blocks/index.ts +21 -5
  182. package/src/api/impl/beacon/pool/index.ts +83 -1
  183. package/src/api/impl/lodestar/index.ts +1 -1
  184. package/src/api/impl/validator/index.ts +80 -0
  185. package/src/chain/blocks/importBlock.ts +10 -35
  186. package/src/chain/blocks/importExecutionPayload.ts +57 -21
  187. package/src/chain/blocks/index.ts +54 -14
  188. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +6 -1
  189. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
  190. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  191. package/src/chain/blocks/types.ts +2 -2
  192. package/src/chain/blocks/utils/chainSegment.ts +106 -17
  193. package/src/chain/blocks/verifyBlock.ts +68 -9
  194. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
  195. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  196. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  197. package/src/chain/chain.ts +16 -3
  198. package/src/chain/emitter.ts +0 -11
  199. package/src/chain/errors/blockError.ts +4 -1
  200. package/src/chain/errors/index.ts +1 -0
  201. package/src/chain/errors/proposerPreferences.ts +39 -0
  202. package/src/chain/interface.ts +7 -1
  203. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  204. package/src/chain/prepareNextSlot.ts +20 -28
  205. package/src/chain/produceBlock/produceBlockBody.ts +51 -23
  206. package/src/chain/regen/interface.ts +1 -0
  207. package/src/chain/seenCache/index.ts +1 -0
  208. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +25 -5
  209. package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
  210. package/src/chain/validation/block.ts +1 -0
  211. package/src/chain/validation/proposerPreferences.ts +91 -0
  212. package/src/metrics/metrics/lodestar.ts +4 -0
  213. package/src/network/gossip/interface.ts +6 -0
  214. package/src/network/gossip/scoringParameters.ts +14 -1
  215. package/src/network/gossip/topic.ts +6 -0
  216. package/src/network/interface.ts +1 -0
  217. package/src/network/network.ts +11 -0
  218. package/src/network/processor/gossipHandlers.ts +35 -17
  219. package/src/network/processor/gossipQueues/index.ts +5 -0
  220. package/src/network/processor/index.ts +6 -5
  221. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  222. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  223. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  224. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  225. package/src/node/notifier.ts +8 -1
  226. package/src/sync/range/batch.ts +142 -38
  227. package/src/sync/range/chain.ts +37 -9
  228. package/src/sync/range/range.ts +18 -6
  229. package/src/sync/types.ts +72 -0
  230. package/src/sync/unknownBlock.ts +760 -57
  231. package/src/sync/utils/downloadByRange.ts +262 -39
  232. package/src/sync/utils/downloadByRoot.ts +24 -2
  233. 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,88 @@ export function cacheByRangeResponses({
149
185
  }
150
186
  }
151
187
 
188
+ // Seed seenPayloadEnvelopeInputCache for every gloas block in the batch, regardless of whether
189
+ // the peer returned its envelope. Without this, a block returned without its envelope would be
190
+ // imported with no cache entry, and later payload-by-root sync would throw
191
+ // "Missing PayloadEnvelopeInput for known block" (see issue #9306).
192
+ for (const blockInput of updatedBatchBlocks.values()) {
193
+ if (!blockInput.hasBlock() || !isForkPostGloas(blockInput.forkName)) continue;
194
+ seenPayloadEnvelopeInputCache.add({
195
+ blockRootHex: blockInput.blockRootHex,
196
+ block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
197
+ forkName: blockInput.forkName,
198
+ sampledColumns: custodyConfig.sampledColumns,
199
+ custodyColumns: custodyConfig.custodyColumns,
200
+ timeCreatedSec: seenTimestampSec,
201
+ });
202
+ }
203
+
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;
207
+ if (downloadedPayloadEnvelopes !== null) {
208
+ payloadEnvelopes = new Map(existingPayloadEnvelopes ?? []);
209
+
210
+ 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);
218
+ if (payloadInput === undefined) {
219
+ // Unreachable given the loop above seeded an entry for every gloas block in the batch.
220
+ continue;
221
+ }
222
+
223
+ if (!payloadInput.hasPayloadEnvelope()) {
224
+ payloadInput.addPayloadEnvelope({
225
+ envelope,
226
+ source: PayloadEnvelopeInputSource.byRange,
227
+ seenTimestampSec,
228
+ peerIdStr,
229
+ });
230
+ }
231
+
232
+ payloadEnvelopes.set(slot, payloadInput);
233
+ }
234
+ }
235
+
152
236
  for (const {blockRoot, columnSidecars} of responses.validatedColumnSidecars ?? []) {
153
- const dataSlot = columnSidecars.at(0)?.signedBlockHeader.message.slot;
154
- if (dataSlot === undefined) {
237
+ const firstColumn = columnSidecars[0];
238
+ if (!firstColumn) {
155
239
  throw new Error(
156
240
  `Coding Error: empty columnSidecars returned for blockRoot=${toRootHex(blockRoot)} from validation functions`
157
241
  );
158
242
  }
159
- const existing = updatedBatchBlocks.get(dataSlot);
243
+
160
244
  const blockRootHex = toRootHex(blockRoot);
161
245
 
246
+ if (isGloasDataColumnSidecar(firstColumn)) {
247
+ // Gloas columns are attached to the matching PayloadEnvelopeInput, NOT to IBlockInput.
248
+ // Gloas DataColumnSidecar has `slot` directly (no signedBlockHeader).
249
+ const dataSlot = firstColumn.slot;
250
+ const payloadInput = payloadEnvelopes?.get(dataSlot);
251
+ if (!payloadInput) {
252
+ // Should not happen: we built payloadInputs for all gloas blocks above
253
+ continue;
254
+ }
255
+ for (const columnSidecar of columnSidecars as gloas.DataColumnSidecar[]) {
256
+ payloadInput.addColumn({
257
+ columnSidecar,
258
+ seenTimestampSec,
259
+ peerIdStr,
260
+ source: PayloadEnvelopeInputSource.byRange,
261
+ });
262
+ }
263
+ continue;
264
+ }
265
+
266
+ const fuluColumns = columnSidecars as fulu.DataColumnSidecar[];
267
+ const dataSlot = fuluColumns[0].signedBlockHeader.message.slot;
268
+ const existing = updatedBatchBlocks.get(dataSlot);
269
+
162
270
  if (!existing) {
163
271
  throw new Error("Coding error: blockInput must exist when adding columns");
164
272
  }
@@ -172,7 +280,7 @@ export function cacheByRangeResponses({
172
280
  actual: existing.type,
173
281
  });
174
282
  }
175
- for (const columnSidecar of columnSidecars) {
283
+ for (const columnSidecar of fuluColumns) {
176
284
  // will throw if root hex does not match (meaning we are following the wrong chain)
177
285
  existing.addColumn(
178
286
  {
@@ -187,7 +295,7 @@ export function cacheByRangeResponses({
187
295
  }
188
296
  }
189
297
 
190
- return Array.from(updatedBatchBlocks.values());
298
+ return {blocks: Array.from(updatedBatchBlocks.values()), payloadEnvelopes};
191
299
  }
192
300
 
193
301
  export async function downloadByRange({
@@ -198,8 +306,14 @@ export async function downloadByRange({
198
306
  blocksRequest,
199
307
  blobsRequest,
200
308
  columnsRequest,
309
+ envelopesRequest,
201
310
  peerDasMetrics,
202
- }: DownloadAndCacheByRangeProps): Promise<WarnResult<ValidatedResponses, DownloadByRangeError>> {
311
+ }: DownloadAndCacheByRangeProps): Promise<
312
+ WarnResult<
313
+ {responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null},
314
+ DownloadByRangeError
315
+ >
316
+ > {
203
317
  let response: DownloadByRangeResponses;
204
318
  try {
205
319
  response = await requestByRange({
@@ -208,6 +322,7 @@ export async function downloadByRange({
208
322
  blocksRequest,
209
323
  blobsRequest,
210
324
  columnsRequest,
325
+ envelopesRequest,
211
326
  });
212
327
  } catch (err) {
213
328
  throw new DownloadByRangeError({
@@ -217,17 +332,16 @@ export async function downloadByRange({
217
332
  });
218
333
  }
219
334
 
220
- const validated = await validateResponses({
335
+ return validateResponses({
221
336
  config,
222
337
  batchBlocks,
223
338
  blocksRequest,
224
339
  blobsRequest,
225
340
  columnsRequest,
341
+ envelopesRequest,
226
342
  peerDasMetrics,
227
343
  ...response,
228
344
  });
229
-
230
- return validated;
231
345
  }
232
346
 
233
347
  /**
@@ -239,13 +353,15 @@ export async function requestByRange({
239
353
  blocksRequest,
240
354
  blobsRequest,
241
355
  columnsRequest,
356
+ envelopesRequest,
242
357
  }: DownloadByRangeRequests & {
243
358
  network: INetwork;
244
359
  peerIdStr: PeerIdStr;
245
360
  }): Promise<DownloadByRangeResponses> {
246
361
  let blocks: undefined | SignedBeaconBlock[];
247
362
  let blobSidecars: undefined | deneb.BlobSidecars;
248
- let columnSidecars: undefined | fulu.DataColumnSidecar[];
363
+ let columnSidecars: undefined | DataColumnSidecar[];
364
+ let payloadEnvelopes: undefined | gloas.SignedExecutionPayloadEnvelope[];
249
365
 
250
366
  const requests: Promise<unknown>[] = [];
251
367
 
@@ -268,7 +384,15 @@ export async function requestByRange({
268
384
  if (columnsRequest) {
269
385
  requests.push(
270
386
  network.sendDataColumnSidecarsByRange(peerIdStr, columnsRequest).then((columnResponse) => {
271
- columnSidecars = columnResponse as fulu.DataColumnSidecar[];
387
+ columnSidecars = columnResponse;
388
+ })
389
+ );
390
+ }
391
+
392
+ if (envelopesRequest) {
393
+ requests.push(
394
+ network.sendExecutionPayloadEnvelopesByRange(peerIdStr, envelopesRequest).then((envelopeResponse) => {
395
+ payloadEnvelopes = envelopeResponse;
272
396
  })
273
397
  );
274
398
  }
@@ -279,6 +403,7 @@ export async function requestByRange({
279
403
  blocks,
280
404
  blobSidecars,
281
405
  columnSidecars,
406
+ payloadEnvelopes,
282
407
  };
283
408
  }
284
409
 
@@ -291,16 +416,23 @@ export async function validateResponses({
291
416
  blocksRequest,
292
417
  blobsRequest,
293
418
  columnsRequest,
419
+ envelopesRequest,
294
420
  blocks,
295
421
  blobSidecars,
296
422
  columnSidecars,
423
+ payloadEnvelopes,
297
424
  peerDasMetrics,
298
425
  }: DownloadByRangeRequests &
299
426
  DownloadByRangeResponses & {
300
427
  config: ChainForkConfig;
301
428
  batchBlocks?: IBlockInput[];
302
429
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
303
- }): Promise<WarnResult<ValidatedResponses, DownloadByRangeError>> {
430
+ }): Promise<
431
+ WarnResult<
432
+ {responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null},
433
+ DownloadByRangeError
434
+ >
435
+ > {
304
436
  // Blocks are always required for blob/column validation
305
437
  // If a blocksRequest is provided, blocks have just been downloaded
306
438
  // If no blocksRequest is provided, batchBlocks must have been provided from cache
@@ -326,8 +458,21 @@ export async function validateResponses({
326
458
  }
327
459
 
328
460
  const dataRequest = blobsRequest ?? columnsRequest;
461
+ if (!dataRequest && !envelopesRequest) {
462
+ return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
463
+ }
464
+
329
465
  if (!dataRequest) {
330
- return {result: validatedResponses, warnings};
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 ?? []
473
+ );
474
+ }
475
+ return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
331
476
  }
332
477
 
333
478
  const blocksForDataValidation = getBlocksForDataValidation(
@@ -385,7 +530,17 @@ export async function validateResponses({
385
530
  warnings = validatedColumnSidecarsResult.warnings;
386
531
  }
387
532
 
388
- return {result: validatedResponses, warnings};
533
+ // Validate envelopes if an envelopes request was made
534
+ let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
535
+ if (envelopesRequest) {
536
+ validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
537
+ validatedResponses.validatedBlocks ?? [],
538
+ batchBlocks,
539
+ payloadEnvelopes ?? []
540
+ );
541
+ }
542
+
543
+ return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
389
544
  }
390
545
 
391
546
  /**
@@ -615,19 +770,19 @@ export async function validateColumnsByRangeResponse(
615
770
  config: ChainForkConfig,
616
771
  request: fulu.DataColumnSidecarsByRangeRequest,
617
772
  blocks: ValidatedBlock[],
618
- columnSidecars: fulu.DataColumnSidecar[],
773
+ columnSidecars: DataColumnSidecar[],
619
774
  peerDasMetrics?: BeaconMetrics["peerDas"] | null
620
775
  ): Promise<WarnResult<ValidatedColumnSidecars[], DownloadByRangeError>> {
621
776
  const warnings: DownloadByRangeError[] = [];
622
777
 
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>>();
778
+ const seenColumns = new Map<Slot, Map<number, DataColumnSidecar>>();
626
779
  let currentSlot = -1;
627
780
  let currentIndex = -1;
628
781
  // Check for duplicates and order
629
782
  for (const columnSidecar of columnSidecars) {
630
- const slot = columnSidecar.signedBlockHeader.message.slot;
783
+ const slot = isGloasDataColumnSidecar(columnSidecar)
784
+ ? columnSidecar.slot
785
+ : columnSidecar.signedBlockHeader.message.slot;
631
786
  let seenSlotColumns = seenColumns.get(slot);
632
787
  if (!seenSlotColumns) {
633
788
  seenSlotColumns = new Map();
@@ -686,20 +841,20 @@ export async function validateColumnsByRangeResponse(
686
841
  const slot = block.message.slot;
687
842
  const rootHex = toRootHex(blockRoot);
688
843
  const forkName = config.getForkName(slot);
689
- const columnSidecarsMap: Map<number, fulu.DataColumnSidecar> = seenColumns.get(slot) ?? new Map();
844
+ const columnSidecarsMap: Map<number, DataColumnSidecar> = seenColumns.get(slot) ?? new Map();
690
845
  const columnSidecars = Array.from(columnSidecarsMap.values()).sort((a, b) => a.index - b.index);
691
846
 
692
847
  let blobCount: number;
693
848
  if (!isForkPostFulu(forkName)) {
694
- const dataSlot = columnSidecars.at(0)?.signedBlockHeader.message.slot;
695
849
  throw new DownloadByRangeError({
696
850
  code: DownloadByRangeErrorCode.MISMATCH_BLOCK_FORK,
697
851
  slot,
698
852
  blockFork: forkName,
699
- dataFork: dataSlot ? config.getForkName(dataSlot) : "unknown",
853
+ dataFork: "unknown",
700
854
  });
701
855
  }
702
- blobCount = getBlobKzgCommitments(forkName, block as SignedBeaconBlock<ForkPostFulu>).length;
856
+ const kzgCommitments = getBlobKzgCommitments(forkName, block as SignedBeaconBlock<ForkPostFulu>);
857
+ blobCount = kzgCommitments.length;
703
858
 
704
859
  if (columnSidecars.length === 0) {
705
860
  if (!blobCount) {
@@ -768,15 +923,25 @@ export async function validateColumnsByRangeResponse(
768
923
  );
769
924
  }
770
925
 
926
+ const validatePromise = isForkPostGloas(forkName)
927
+ ? validateGloasBlockDataColumnSidecars(
928
+ slot,
929
+ blockRoot,
930
+ kzgCommitments,
931
+ columnSidecars as gloas.DataColumnSidecar[],
932
+ peerDasMetrics
933
+ )
934
+ : validateFuluBlockDataColumnSidecars(
935
+ null, // do not pass chain here so we do not validate header signature
936
+ slot,
937
+ blockRoot,
938
+ blobCount,
939
+ columnSidecars as fulu.DataColumnSidecar[],
940
+ peerDasMetrics
941
+ );
942
+
771
943
  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(() => ({
944
+ validatePromise.then(() => ({
780
945
  blockRoot,
781
946
  columnSidecars,
782
947
  }))
@@ -882,6 +1047,9 @@ export enum DownloadByRangeErrorCode {
882
1047
  /** Cached block input type mismatches new data */
883
1048
  MISMATCH_BLOCK_FORK = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_FORK",
884
1049
  MISMATCH_BLOCK_INPUT_TYPE = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_INPUT_TYPE",
1050
+
1051
+ /** Envelope beaconBlockRoot does not match the block's root */
1052
+ INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
885
1053
  }
886
1054
 
887
1055
  export type DownloadByRangeErrorType =
@@ -973,6 +1141,61 @@ export type DownloadByRangeErrorType =
973
1141
  blockRoot: string;
974
1142
  expected: DAType;
975
1143
  actual: DAType;
1144
+ }
1145
+ | {
1146
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT;
1147
+ slot: Slot;
1148
+ expected: string;
1149
+ actual: string;
976
1150
  };
977
1151
 
978
1152
  export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType> {}
1153
+
1154
+ /**
1155
+ * 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.
1159
+ */
1160
+ export function validateEnvelopesByRangeResponse(
1161
+ validatedBlocks: ValidatedBlock[],
1162
+ batchBlocks: IBlockInput[] | undefined,
1163
+ payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
1164
+ ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1165
+ // Build a map of slot -> blockRoot for all blocks in the batch
1166
+ const batchBlockRoots = new Map<Slot, Uint8Array>();
1167
+ if (batchBlocks) {
1168
+ for (const blockInput of batchBlocks) {
1169
+ batchBlockRoots.set(blockInput.slot, fromHex(blockInput.blockRootHex));
1170
+ }
1171
+ }
1172
+ for (const {block, blockRoot} of validatedBlocks) {
1173
+ batchBlockRoots.set(block.message.slot, blockRoot);
1174
+ }
1175
+
1176
+ const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
1177
+
1178
+ for (const payloadEnvelope of payloadEnvelopes) {
1179
+ const slot = payloadEnvelope.message.payload.slotNumber;
1180
+ const batchBlockRoot = batchBlockRoots.get(slot);
1181
+
1182
+ // Envelopes for slots not in the batch are silently ignored (orphaned payloads)
1183
+ if (batchBlockRoot === undefined) {
1184
+ continue;
1185
+ }
1186
+
1187
+ // Verify beaconBlockRoot matches the block's root
1188
+ if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlockRoot)) {
1189
+ throw new DownloadByRangeError({
1190
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
1191
+ slot,
1192
+ expected: toRootHex(batchBlockRoot),
1193
+ actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
1194
+ });
1195
+ }
1196
+
1197
+ payloadEnvelopeMap.set(slot, payloadEnvelope);
1198
+ }
1199
+
1200
+ return payloadEnvelopeMap;
1201
+ }
@@ -1,6 +1,14 @@
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
+ ForkPostGloas,
7
+ ForkPreFulu,
8
+ isForkPostDeneb,
9
+ isForkPostFulu,
10
+ isForkPostGloas,
11
+ } from "@lodestar/params";
4
12
  import {BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
5
13
  import {LodestarError, byteArrayEquals, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
6
14
  import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
@@ -107,6 +115,17 @@ export async function downloadByRoot({
107
115
  });
108
116
  }
109
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
+
110
129
  const hasAllDataPreDownload = blockInput.hasBlockAndAllData();
111
130
 
112
131
  if (isBlockInputBlobs(blockInput) && !hasAllDataPreDownload) {
@@ -263,7 +282,10 @@ export async function fetchByRoot({
263
282
  blockRoot,
264
283
  });
265
284
  const forkName = config.getForkName(block.message.slot);
266
- if (isForkPostFulu(forkName)) {
285
+ if (isForkPostGloas(forkName)) {
286
+ // Post-gloas block sync only needs the block body. Payload columns stay on the
287
+ // payload/envelope path and are queued independently in the network processor.
288
+ } else if (isForkPostFulu(forkName)) {
267
289
  columnSidecarResult = await fetchAndValidateColumns({
268
290
  config,
269
291
  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[];