@lodestar/beacon-node 1.43.0-dev.07875b3e0c → 1.43.0-dev.1213f9c92d

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 +6 -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 +3 -2
  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 +4 -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 +5 -3
  50. package/lib/chain/chain.d.ts.map +1 -1
  51. package/lib/chain/chain.js +19 -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 +5 -3
  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 +20 -11
  78. package/lib/chain/prepareNextSlot.js.map +1 -1
  79. package/lib/chain/produceBlock/produceBlockBody.d.ts +10 -1
  80. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  81. package/lib/chain/produceBlock/produceBlockBody.js +53 -16
  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 +8 -2
  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 +14 -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 +147 -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 +6 -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 +16 -15
  181. package/src/api/impl/beacon/blocks/index.ts +8 -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 +3 -2
  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 +5 -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 +28 -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 +9 -1
  203. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  204. package/src/chain/prepareNextSlot.ts +23 -11
  205. package/src/chain/produceBlock/produceBlockBody.ts +62 -15
  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 +13 -3
  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 +23 -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 +256 -39
  232. package/src/sync/utils/downloadByRoot.ts +12 -2
  233. package/src/sync/utils/pendingBlocksTree.ts +0 -15
@@ -76,6 +76,7 @@ type WorkOpts = {
76
76
  */
77
77
  const executeGossipWorkOrderObj: Record<GossipType, WorkOpts> = {
78
78
  [GossipType.beacon_block]: {bypassQueue: true},
79
+ [GossipType.execution_payload]: {bypassQueue: true},
79
80
  [GossipType.blob_sidecar]: {bypassQueue: true},
80
81
  [GossipType.data_column_sidecar]: {bypassQueue: true},
81
82
  [GossipType.beacon_aggregate_and_proof]: {},
@@ -88,9 +89,9 @@ const executeGossipWorkOrderObj: Record<GossipType, WorkOpts> = {
88
89
  [GossipType.sync_committee]: {},
89
90
  [GossipType.light_client_finality_update]: {},
90
91
  [GossipType.light_client_optimistic_update]: {},
91
- [GossipType.execution_payload]: {bypassQueue: true},
92
92
  [GossipType.payload_attestation_message]: {},
93
93
  [GossipType.execution_payload_bid]: {},
94
+ [GossipType.proposer_preferences]: {},
94
95
  };
95
96
  const executeGossipWorkOrder = Object.keys(executeGossipWorkOrderObj) as (keyof typeof executeGossipWorkOrderObj)[];
96
97
 
@@ -443,8 +444,7 @@ export class NetworkProcessor {
443
444
  }
444
445
  case GossipType.execution_payload: {
445
446
  // extractBlockSlotRootFn does not return a root for this topic.
446
- // Extract beacon_block_root directly and proactively trigger block sync if missing.
447
- // Do NOT await the block — the handler runs immediately; BlockInputSync handles recovery.
447
+ // Extract beacon_block_root directly
448
448
  const blockRoot = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(message.msg.data);
449
449
  if (blockRoot && !this.chain.forkChoice.hasBlockHexUnsafe(blockRoot)) {
450
450
  this.searchUnknownBlock(
@@ -452,9 +452,10 @@ export class NetworkProcessor {
452
452
  BlockInputSource.network_processor,
453
453
  message.propagationSource.toString()
454
454
  );
455
+ // We always want to await the block
456
+ // This allows us to properly forward the payload envelope
457
+ preprocessResult = {action: PreprocessAction.AwaitBlock, root: blockRoot};
455
458
  }
456
- // do not await the block, we want UnknownBlockSync to handle it.
457
- preprocessResult = {action: PreprocessAction.PushToQueue};
458
459
  break;
459
460
  }
460
461
  case GossipType.execution_payload_bid: {
@@ -24,6 +24,10 @@ export async function* onBeaconBlocksByRange(
24
24
  // in the case of initializing from a non-finalized state, we don't have the finalized block so this api does not work
25
25
  // chain.forkChoice.getFinalizeBlock().slot
26
26
  const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
27
+ // Blocks are migrated to blockArchive at finalization (including the finalized block itself),
28
+ // so the archive loop serves up to AND INCLUDING finalizedSlot and the headChain loop
29
+ // starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
30
+ const archiveMaxSlot = finalizedSlot;
27
31
 
28
32
  const forkName = chain.config.getForkName(startSlot);
29
33
  if (isForkPostFulu(forkName) && startSlot < chain.earliestAvailableSlot) {
@@ -35,9 +39,12 @@ export async function* onBeaconBlocksByRange(
35
39
  }
36
40
 
37
41
  // Finalized range of blocks
38
- if (startSlot <= finalizedSlot) {
42
+ if (startSlot <= archiveMaxSlot) {
39
43
  // Chain of blobs won't change
40
- for await (const {key, value} of finalized.binaryEntriesStream({gte: startSlot, lt: endSlot})) {
44
+ for await (const {key, value} of finalized.binaryEntriesStream({
45
+ gte: startSlot,
46
+ lt: Math.min(endSlot, archiveMaxSlot + 1),
47
+ })) {
41
48
  yield {
42
49
  data: value,
43
50
  boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(finalized.decodeKey(key))),
@@ -46,19 +53,20 @@ export async function* onBeaconBlocksByRange(
46
53
  }
47
54
 
48
55
  // Non-finalized range of blocks
49
- if (endSlot > finalizedSlot) {
56
+ if (endSlot > archiveMaxSlot) {
50
57
  const headBlock = chain.forkChoice.getHead();
51
58
  const headRoot = headBlock.blockRoot;
52
59
  // TODO DENEB: forkChoice should mantain an array of canonical blocks, and change only on reorg
53
60
  const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
54
- // getAllAncestorBlocks response includes the head node, so it's the full chain.
61
+ // `getAllAncestorBlocks` includes both the head and the previous-finalized boundary.
55
62
 
56
63
  // Iterate head chain with ascending block numbers
57
64
  for (let i = headChain.length - 1; i >= 0; i--) {
58
65
  const block = headChain[i];
59
66
 
60
- // Must include only blocks in the range requested
61
- if (block.slot >= startSlot && block.slot < endSlot) {
67
+ // Must include only blocks in the range requested, and skip anything the archive loop
68
+ // above already served via the block.slot > archiveMaxSlot filter.
69
+ if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
62
70
  // Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
63
71
  // at the time of the start of the request. Spec is clear the chain of blobs must be consistent, but on
64
72
  // re-org there's no need to abort the request
@@ -20,31 +20,37 @@ export async function* onBlobSidecarsByRange(
20
20
  const finalized = db.blobSidecarsArchive;
21
21
  const unfinalized = db.blobSidecars;
22
22
  const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
23
+ // Blobs are migrated to blobSidecarsArchive at finalization (including the finalized block
24
+ // itself), so the archive loop serves up to AND INCLUDING finalizedSlot and the headChain
25
+ // loop starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
26
+ const archiveMaxSlot = finalizedSlot;
23
27
 
24
28
  // Finalized range of blobs
25
- if (startSlot <= finalizedSlot) {
29
+ if (startSlot <= archiveMaxSlot) {
26
30
  // Chain of blobs won't change
27
31
  for await (const {key, value: blobSideCarsBytesWrapped} of finalized.binaryEntriesStream({
28
32
  gte: startSlot,
29
- lt: endSlot,
33
+ lt: Math.min(endSlot, archiveMaxSlot + 1),
30
34
  })) {
31
35
  yield* iterateBlobBytesFromWrapper(chain, blobSideCarsBytesWrapped, finalized.decodeKey(key));
32
36
  }
33
37
  }
34
38
 
35
39
  // Non-finalized range of blobs
36
- if (endSlot > finalizedSlot) {
40
+ if (endSlot > archiveMaxSlot) {
37
41
  const headBlock = chain.forkChoice.getHead();
38
42
  const headRoot = headBlock.blockRoot;
39
43
  // TODO DENEB: forkChoice should mantain an array of canonical blocks, and change only on reorg
40
44
  const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
45
+ // `getAllAncestorBlocks` includes both the head and the previous-finalized boundary.
41
46
 
42
47
  // Iterate head chain with ascending block numbers
43
48
  for (let i = headChain.length - 1; i >= 0; i--) {
44
49
  const block = headChain[i];
45
50
 
46
- // Must include only blobs in the range requested
47
- if (block.slot >= startSlot && block.slot < endSlot) {
51
+ // Must include only blobs in the range requested, and skip anything the archive loop
52
+ // above already served via the block.slot > archiveMaxSlot filter.
53
+ if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
48
54
  // Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
49
55
  // at the time of the start of the request. Spec is clear the chain of blobs must be consistent, but on
50
56
  // re-org there's no need to abort the request
@@ -1,6 +1,6 @@
1
1
  import {PeerId} from "@libp2p/interface";
2
2
  import {ChainConfig} from "@lodestar/config";
3
- import {GENESIS_SLOT} from "@lodestar/params";
3
+ import {ForkSeq, GENESIS_SLOT} from "@lodestar/params";
4
4
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
5
5
  import {computeEpochAtSlot} from "@lodestar/state-transition";
6
6
  import {ColumnIndex, Epoch, fulu} from "@lodestar/types";
@@ -43,10 +43,19 @@ export async function* onDataColumnSidecarsByRange(
43
43
 
44
44
  const finalized = db.dataColumnSidecarArchive;
45
45
  const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
46
+ // Columns of the last finalized block live in different DBs depending on fork:
47
+ // - Pre-gloas (fulu): migrated to dataColumnSidecarArchive in the same finalization run.
48
+ // - Post-gloas: stay in the hot db (db.dataColumnSidecar) until the next finalization run,
49
+ // because the migration filter requires payloadStatus === FULL for gloas blocks.
50
+ // archiveMaxSlot is the last slot whose columns are served by the archive loop below;
51
+ // anything above it is served by the headChain loop.
52
+ const isPostGloasFinalized = chain.config.getForkSeq(finalizedSlot) >= ForkSeq.gloas;
53
+ const archiveMaxSlot = isPostGloasFinalized ? finalizedSlot - 1 : finalizedSlot;
46
54
 
47
55
  // Finalized range of columns
48
- if (startSlot <= finalizedSlot) {
49
- for (let slot = startSlot; slot < endSlot; slot++) {
56
+ if (startSlot <= archiveMaxSlot) {
57
+ const archiveEnd = Math.min(endSlot, archiveMaxSlot + 1);
58
+ for (let slot = startSlot; slot < archiveEnd; slot++) {
50
59
  const dataColumnSidecars = await finalized.getManyBinary(slot, availableColumns);
51
60
 
52
61
  const unavailableColumnIndices: ColumnIndex[] = [];
@@ -81,9 +90,12 @@ export async function* onDataColumnSidecarsByRange(
81
90
  }
82
91
 
83
92
  // Non-finalized range of columns
84
- if (endSlot > finalizedSlot) {
93
+ if (endSlot > archiveMaxSlot) {
85
94
  const headBlock = chain.forkChoice.getHead();
86
95
  const headRoot = headBlock.blockRoot;
96
+ // getAllAncestorBlocks includes the last finalized block as its final element.
97
+ // Skip anything the archive loop above already served via the block.slot > archiveMaxSlot
98
+ // filter below (pre-gloas this skips finalizedSlot, post-gloas it keeps it).
87
99
  const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
88
100
 
89
101
  // Iterate head chain with ascending block numbers
@@ -91,7 +103,7 @@ export async function* onDataColumnSidecarsByRange(
91
103
  const block = headChain[i];
92
104
 
93
105
  // Must include only columns in the range requested
94
- if (block.slot >= startSlot && block.slot < endSlot) {
106
+ if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
95
107
  // Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
96
108
  // at the time of the start of the request. Spec is clear the chain of columns must be consistent, but on
97
109
  // re-org there's no need to abort the request
@@ -21,12 +21,15 @@ export async function* onExecutionPayloadEnvelopesByRange(
21
21
 
22
22
  const finalized = db.executionPayloadEnvelopeArchive;
23
23
  const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
24
+ // The current finalized block's envelope is still in the hot db; archive migration happens
25
+ // in the next finalization run (see migrateExecutionPayloadEnvelopesFromHotToColdDb).
26
+ const archiveMaxSlot = finalizedSlot - 1;
24
27
 
25
28
  // Finalized range of envelopes
26
- if (startSlot <= finalizedSlot) {
29
+ if (startSlot <= archiveMaxSlot) {
27
30
  for await (const {key, value: envelopeBytes} of finalized.binaryEntriesStream({
28
31
  gte: startSlot,
29
- lt: endSlot,
32
+ lt: Math.min(endSlot, archiveMaxSlot + 1),
30
33
  })) {
31
34
  const slot = finalized.decodeKey(key);
32
35
  yield {
@@ -37,7 +40,7 @@ export async function* onExecutionPayloadEnvelopesByRange(
37
40
  }
38
41
 
39
42
  // Non-finalized range of envelopes
40
- if (endSlot > finalizedSlot) {
43
+ if (endSlot > archiveMaxSlot) {
41
44
  const headBlock = chain.forkChoice.getHead();
42
45
  const headRoot = headBlock.blockRoot;
43
46
  const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
@@ -46,7 +49,7 @@ export async function* onExecutionPayloadEnvelopesByRange(
46
49
  for (let i = headChain.length - 1; i >= 0; i--) {
47
50
  const block = headChain[i];
48
51
 
49
- if (block.slot >= startSlot && block.slot < endSlot) {
52
+ if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
50
53
  // Skip EMPTY blocks
51
54
  if (block.payloadStatus !== PayloadStatus.FULL) {
52
55
  continue;
@@ -167,7 +167,14 @@ function getHeadExecutionInfo(
167
167
  return [];
168
168
  }
169
169
 
170
- const executionStatusStr = headInfo.executionStatus.toLowerCase();
170
+ // A PayloadSeparated head is a gloas beacon block imported before its payload envelope
171
+ // arrives, in that case the exec-block row surfaces the inherited parent anchor (from the
172
+ // bid), which is already validated. Normalize to "valid" to avoid leaking internal
173
+ // fork-choice bookkeeping into the log. Once the payload envelope arrives and the FULL
174
+ // variant becomes head, executionStatus is Valid/Syncing naturally.
175
+ // TODO GLOAS: revisit once optimistic sync is implemented
176
+ const executionStatusStr =
177
+ headInfo.executionStatus === ExecutionStatus.PayloadSeparated ? "valid" : headInfo.executionStatus.toLowerCase();
171
178
 
172
179
  // Add execution status to notifier only if head is on/post bellatrix
173
180
  if (isStatePostBellatrix(headState) && headState.isExecutionStateType) {
@@ -1,10 +1,11 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ForkName, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
2
+ import {ForkName, isForkPostDeneb, isForkPostFulu, isForkPostGloas} from "@lodestar/params";
3
3
  import {Epoch, RootHex, Slot, phase0} from "@lodestar/types";
4
- import {LodestarError} from "@lodestar/utils";
4
+ import {LodestarError, prettyPrintIndices} from "@lodestar/utils";
5
5
  import {isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
6
6
  import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
7
7
  import {isDaOutOfRange} from "../../chain/blocks/blockInput/utils.js";
8
+ import {PayloadEnvelopeInput} from "../../chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
8
9
  import {BlockError, BlockErrorCode} from "../../chain/errors/index.js";
9
10
  import {PeerSyncMeta} from "../../network/peers/peersData.js";
10
11
  import {IClock} from "../../util/clock.js";
@@ -46,25 +47,68 @@ export type Attempt = {
46
47
  export type AwaitingDownloadState = {
47
48
  status: BatchStatus.AwaitingDownload;
48
49
  blocks: IBlockInput[];
50
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
49
51
  };
50
52
 
51
53
  export type DownloadSuccessState = {
52
54
  status: BatchStatus.AwaitingProcessing;
53
55
  blocks: IBlockInput[];
56
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
54
57
  };
55
58
 
56
59
  export type BatchState =
57
60
  | AwaitingDownloadState
58
- | {status: BatchStatus.Downloading; peer: PeerIdStr; blocks: IBlockInput[]}
61
+ | {
62
+ status: BatchStatus.Downloading;
63
+ peer: PeerIdStr;
64
+ blocks: IBlockInput[];
65
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
66
+ }
59
67
  | DownloadSuccessState
60
- | {status: BatchStatus.Processing; blocks: IBlockInput[]; attempt: Attempt}
61
- | {status: BatchStatus.AwaitingValidation; blocks: IBlockInput[]; attempt: Attempt};
68
+ | {
69
+ status: BatchStatus.Processing;
70
+ blocks: IBlockInput[];
71
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
72
+ attempt: Attempt;
73
+ }
74
+ | {
75
+ status: BatchStatus.AwaitingValidation;
76
+ blocks: IBlockInput[];
77
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
78
+ attempt: Attempt;
79
+ };
62
80
 
63
81
  export type BatchMetadata = {
82
+ // Batch-level slot window (always present)
64
83
  startEpoch: Epoch;
84
+ startSlot: Slot;
85
+ count: number;
65
86
  status: BatchStatus;
87
+
88
+ // Per-type outstanding request shapes; only present when that sub-request exists.
89
+ // Format: "startSlot=<n>,count=<n>" (plus ",cols=<indices>" for columns).
90
+ blocksReq?: string;
91
+ blobsReq?: string;
92
+ columnsReq?: string;
93
+ envelopesReq?: string;
94
+
95
+ // Retry counters
96
+ downloadAttempts: number;
97
+ processingAttempts: number;
98
+
99
+ // Cumulative peer attribution for failed attempts (only present when non-empty)
100
+ failedDownloadPeers?: string;
101
+ failedProcessingPeers?: string;
66
102
  };
67
103
 
104
+ function formatRangeReq(req: {startSlot: Slot; count: number}): string {
105
+ return `startSlot=${req.startSlot},count=${req.count}`;
106
+ }
107
+
108
+ function formatColumnsReq(req: {startSlot: Slot; count: number; columns: number[]}): string {
109
+ return `startSlot=${req.startSlot},count=${req.count},cols=${prettyPrintIndices(req.columns)}`;
110
+ }
111
+
68
112
  /**
69
113
  * Batches are downloaded at the first block of the epoch.
70
114
  *
@@ -85,7 +129,7 @@ export class Batch {
85
129
  /** Block, blob and column requests that are used to determine the best peer and are used in downloadByRange */
86
130
  requests: DownloadByRangeRequests;
87
131
  /** State of the batch. */
88
- state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: []};
132
+ state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
89
133
  /** Peers that provided good data */
90
134
  goodPeers: PeerIdStr[] = [];
91
135
  /** The `Attempts` that have been made and failed to send us this batch. */
@@ -129,35 +173,33 @@ export class Batch {
129
173
  count: this.count,
130
174
  step: 1,
131
175
  };
132
- if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
133
- return {
134
- blocksRequest,
135
- columnsRequest: {
136
- startSlot: this.startSlot,
137
- count: this.count,
138
- columns: this.custodyConfig.sampledColumns,
139
- },
140
- };
176
+ const requests: DownloadByRangeRequests = {blocksRequest};
177
+
178
+ // Post-Gloas envelopes are required for block processing, independent of DA retention window.
179
+ if (isForkPostGloas(this.forkName)) {
180
+ requests.envelopesRequest = {startSlot: this.startSlot, count: this.count};
141
181
  }
142
- if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
143
- return {
144
- blocksRequest,
145
- blobsRequest: {
146
- startSlot: this.startSlot,
147
- count: this.count,
148
- },
182
+
183
+ if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
184
+ requests.columnsRequest = {
185
+ startSlot: this.startSlot,
186
+ count: this.count,
187
+ columns: this.custodyConfig.sampledColumns,
149
188
  };
189
+ } else if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
190
+ requests.blobsRequest = {startSlot: this.startSlot, count: this.count};
150
191
  }
151
- return {
152
- blocksRequest,
153
- };
192
+
193
+ return requests;
154
194
  }
155
195
 
156
196
  // subsequent request where part of the epoch has already been downloaded. Need to figure out what is the beginning
157
197
  // of the range where download needs to resume
158
198
  let blockStartSlot = this.startSlot;
159
199
  let dataStartSlot = this.startSlot;
200
+ let envelopeStartSlot = this.startSlot;
160
201
  const neededColumns = new Set<number>();
202
+ const envelopesBySlot = this.state.payloadEnvelopes ?? new Map<Slot, PayloadEnvelopeInput>();
161
203
 
162
204
  // ensure blocks are in slot-wise order
163
205
  for (const blockInput of blocks) {
@@ -175,6 +217,13 @@ export class Batch {
175
217
  if (blockInput.hasBlock() && blockStartSlot === blockSlot) {
176
218
  blockStartSlot = blockSlot + 1;
177
219
  }
220
+ if (
221
+ blockInput.hasBlock() &&
222
+ envelopeStartSlot === blockSlot &&
223
+ envelopesBySlot.get(blockSlot)?.hasPayloadEnvelope()
224
+ ) {
225
+ envelopeStartSlot = blockSlot + 1;
226
+ }
178
227
  if (!blockInput.hasAllData()) {
179
228
  if (isBlockInputColumns(blockInput)) {
180
229
  for (const index of blockInput.getMissingSampledColumnMeta().missing) {
@@ -216,6 +265,13 @@ export class Batch {
216
265
  // dataSlot will still have a value but do not create a request for preDeneb forks
217
266
  }
218
267
 
268
+ if (isForkPostGloas(this.forkName) && envelopeStartSlot <= endSlot) {
269
+ requests.envelopesRequest = {
270
+ startSlot: envelopeStartSlot,
271
+ count: endSlot - envelopeStartSlot + 1,
272
+ };
273
+ }
274
+
219
275
  return requests;
220
276
  }
221
277
 
@@ -256,13 +312,36 @@ export class Batch {
256
312
  }
257
313
 
258
314
  getMetadata(): BatchMetadata {
259
- return {startEpoch: this.startEpoch, status: this.state.status};
315
+ const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
316
+ const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
317
+ return {
318
+ startEpoch: this.startEpoch,
319
+ startSlot: this.startSlot,
320
+ count: this.count,
321
+ status: this.state.status,
322
+ ...(blocksRequest && {blocksReq: formatRangeReq(blocksRequest)}),
323
+ ...(blobsRequest && {blobsReq: formatRangeReq(blobsRequest)}),
324
+ ...(columnsRequest && {columnsReq: formatColumnsReq(columnsRequest)}),
325
+ ...(envelopesRequest && {envelopesReq: formatRangeReq(envelopesRequest)}),
326
+ downloadAttempts: this.failedDownloadAttempts.length,
327
+ processingAttempts: this.failedProcessingAttempts.length,
328
+ ...(this.failedDownloadAttempts.length > 0 && {
329
+ failedDownloadPeers: this.failedDownloadAttempts.join(","),
330
+ }),
331
+ ...(failedProcessingPeerList.length > 0 && {
332
+ failedProcessingPeers: failedProcessingPeerList.join(","),
333
+ }),
334
+ };
260
335
  }
261
336
 
262
337
  getBlocks(): IBlockInput[] {
263
338
  return this.state.blocks;
264
339
  }
265
340
 
341
+ getPayloadEnvelopes(): Map<Slot, PayloadEnvelopeInput> | null {
342
+ return this.state.payloadEnvelopes;
343
+ }
344
+
266
345
  /**
267
346
  * AwaitingDownload -> Downloading
268
347
  */
@@ -271,13 +350,22 @@ export class Batch {
271
350
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingDownload));
272
351
  }
273
352
 
274
- this.state = {status: BatchStatus.Downloading, peer, blocks: this.state.blocks};
353
+ this.state = {
354
+ status: BatchStatus.Downloading,
355
+ peer,
356
+ blocks: this.state.blocks,
357
+ payloadEnvelopes: this.state.payloadEnvelopes,
358
+ };
275
359
  }
276
360
 
277
361
  /**
278
362
  * Downloading -> AwaitingProcessing
279
363
  */
280
- downloadingSuccess(peer: PeerIdStr, blocks: IBlockInput[]): DownloadSuccessState {
364
+ downloadingSuccess(
365
+ peer: PeerIdStr,
366
+ blocks: IBlockInput[],
367
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null
368
+ ): DownloadSuccessState {
281
369
  if (this.state.status !== BatchStatus.Downloading) {
282
370
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
283
371
  }
@@ -305,11 +393,13 @@ export class Batch {
305
393
  status: this.state.status,
306
394
  });
307
395
  }
396
+ const newPayloadEnvelopes = payloadEnvelopes ?? this.state.payloadEnvelopes;
397
+
308
398
  if (allComplete) {
309
- this.state = {status: BatchStatus.AwaitingProcessing, blocks};
399
+ this.state = {status: BatchStatus.AwaitingProcessing, blocks, payloadEnvelopes: newPayloadEnvelopes};
310
400
  } else {
311
401
  this.requests = this.getRequests(blocks);
312
- this.state = {status: BatchStatus.AwaitingDownload, blocks};
402
+ this.state = {status: BatchStatus.AwaitingDownload, blocks, payloadEnvelopes: newPayloadEnvelopes};
313
403
  }
314
404
 
315
405
  return this.state as DownloadSuccessState;
@@ -328,25 +418,34 @@ export class Batch {
328
418
  throw new BatchError(this.errorType({code: BatchErrorCode.MAX_DOWNLOAD_ATTEMPTS}));
329
419
  }
330
420
 
331
- this.state = {status: BatchStatus.AwaitingDownload, blocks: this.state.blocks};
421
+ this.state = {
422
+ status: BatchStatus.AwaitingDownload,
423
+ blocks: this.state.blocks,
424
+ payloadEnvelopes: this.state.payloadEnvelopes,
425
+ };
332
426
  }
333
427
 
334
428
  /**
335
429
  * AwaitingProcessing -> Processing
336
430
  */
337
- startProcessing(): IBlockInput[] {
431
+ startProcessing(): {
432
+ blocks: IBlockInput[];
433
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
434
+ peers: PeerIdStr[];
435
+ } {
338
436
  if (this.state.status !== BatchStatus.AwaitingProcessing) {
339
437
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingProcessing));
340
438
  }
341
439
 
342
440
  const blocks = this.state.blocks;
441
+ const payloadEnvelopes = this.state.payloadEnvelopes;
343
442
  const hash = hashBlocks(blocks, this.config); // tracks blocks to report peer on processing error
344
443
  // Reset goodPeers in case another download attempt needs to be made. When Attempt is successful or not the peers
345
444
  // that the data came from will be handled by the Attempt that goes for processing
346
445
  const peers = this.goodPeers;
347
446
  this.goodPeers = [];
348
- this.state = {status: BatchStatus.Processing, blocks, attempt: {peers, hash}};
349
- return blocks;
447
+ this.state = {status: BatchStatus.Processing, blocks, payloadEnvelopes, attempt: {peers, hash}};
448
+ return {blocks, payloadEnvelopes, peers};
350
449
  }
351
450
 
352
451
  /**
@@ -357,7 +456,12 @@ export class Batch {
357
456
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.Processing));
358
457
  }
359
458
 
360
- this.state = {status: BatchStatus.AwaitingValidation, blocks: this.state.blocks, attempt: this.state.attempt};
459
+ this.state = {
460
+ status: BatchStatus.AwaitingValidation,
461
+ blocks: this.state.blocks,
462
+ payloadEnvelopes: this.state.payloadEnvelopes,
463
+ attempt: this.state.attempt,
464
+ };
361
465
  }
362
466
 
363
467
  /**
@@ -408,7 +512,7 @@ export class Batch {
408
512
 
409
513
  // remove any downloaded blocks and re-attempt
410
514
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
411
- this.state = {status: BatchStatus.AwaitingDownload, blocks: []};
515
+ this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
412
516
  }
413
517
 
414
518
  private onProcessingError(attempt: Attempt): void {
@@ -419,12 +523,12 @@ export class Batch {
419
523
 
420
524
  // remove any downloaded blocks and re-attempt
421
525
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
422
- this.state = {status: BatchStatus.AwaitingDownload, blocks: []};
526
+ this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
423
527
  }
424
528
 
425
529
  /** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
426
530
  private errorType(type: BatchErrorType): BatchErrorType & BatchErrorMetadata {
427
- return {...type, ...this.getMetadata()};
531
+ return {...type, startEpoch: this.startEpoch, status: this.state.status};
428
532
  }
429
533
 
430
534
  private wrongStatusErrorType(expectedStatus: BatchStatus): BatchErrorType & BatchErrorMetadata {