@lodestar/beacon-node 1.43.0-dev.07875b3e0c → 1.43.0-dev.1540a78cd0

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 (137) 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 +19 -8
  10. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  11. package/lib/chain/blocks/importExecutionPayload.js +31 -20
  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 -9
  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 +2 -2
  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/chain.d.ts +3 -2
  33. package/lib/chain/chain.d.ts.map +1 -1
  34. package/lib/chain/chain.js +14 -3
  35. package/lib/chain/chain.js.map +1 -1
  36. package/lib/chain/errors/blockError.d.ts +8 -1
  37. package/lib/chain/errors/blockError.d.ts.map +1 -1
  38. package/lib/chain/errors/blockError.js +2 -0
  39. package/lib/chain/errors/blockError.js.map +1 -1
  40. package/lib/chain/interface.d.ts +3 -2
  41. package/lib/chain/interface.d.ts.map +1 -1
  42. package/lib/chain/interface.js.map +1 -1
  43. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  44. package/lib/chain/prepareNextSlot.js +19 -8
  45. package/lib/chain/prepareNextSlot.js.map +1 -1
  46. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  47. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  48. package/lib/chain/produceBlock/produceBlockBody.js +27 -13
  49. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  50. package/lib/chain/validation/block.d.ts.map +1 -1
  51. package/lib/chain/validation/block.js +1 -0
  52. package/lib/chain/validation/block.js.map +1 -1
  53. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  54. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  55. package/lib/metrics/metrics/lodestar.js +4 -0
  56. package/lib/metrics/metrics/lodestar.js.map +1 -1
  57. package/lib/network/gossip/topic.d.ts +749 -2
  58. package/lib/network/gossip/topic.d.ts.map +1 -1
  59. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  60. package/lib/network/processor/gossipHandlers.js +2 -1
  61. package/lib/network/processor/gossipHandlers.js.map +1 -1
  62. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  63. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  64. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  65. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  66. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  67. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  68. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  69. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  70. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  71. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  72. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  73. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  74. package/lib/node/notifier.js +7 -1
  75. package/lib/node/notifier.js.map +1 -1
  76. package/lib/sync/range/batch.d.ts +12 -2
  77. package/lib/sync/range/batch.d.ts.map +1 -1
  78. package/lib/sync/range/batch.js +56 -30
  79. package/lib/sync/range/batch.js.map +1 -1
  80. package/lib/sync/range/chain.d.ts +6 -2
  81. package/lib/sync/range/chain.d.ts.map +1 -1
  82. package/lib/sync/range/chain.js +4 -3
  83. package/lib/sync/range/chain.js.map +1 -1
  84. package/lib/sync/range/range.d.ts.map +1 -1
  85. package/lib/sync/range/range.js +17 -6
  86. package/lib/sync/range/range.js.map +1 -1
  87. package/lib/sync/types.d.ts +34 -0
  88. package/lib/sync/types.d.ts.map +1 -1
  89. package/lib/sync/types.js +34 -0
  90. package/lib/sync/types.js.map +1 -1
  91. package/lib/sync/unknownBlock.d.ts +24 -1
  92. package/lib/sync/unknownBlock.d.ts.map +1 -1
  93. package/lib/sync/unknownBlock.js +649 -53
  94. package/lib/sync/unknownBlock.js.map +1 -1
  95. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  96. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  97. package/lib/sync/utils/downloadByRange.js +147 -24
  98. package/lib/sync/utils/downloadByRange.js.map +1 -1
  99. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  100. package/lib/sync/utils/downloadByRoot.js +6 -2
  101. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  102. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  103. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  104. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  105. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  106. package/package.json +16 -15
  107. package/src/api/impl/beacon/blocks/index.ts +5 -2
  108. package/src/api/impl/lodestar/index.ts +1 -1
  109. package/src/chain/blocks/importBlock.ts +4 -2
  110. package/src/chain/blocks/importExecutionPayload.ts +36 -21
  111. package/src/chain/blocks/index.ts +44 -12
  112. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  113. package/src/chain/blocks/types.ts +2 -2
  114. package/src/chain/blocks/utils/chainSegment.ts +106 -17
  115. package/src/chain/blocks/verifyBlock.ts +35 -6
  116. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
  117. package/src/chain/chain.ts +23 -3
  118. package/src/chain/errors/blockError.ts +4 -1
  119. package/src/chain/interface.ts +7 -1
  120. package/src/chain/prepareNextSlot.ts +29 -9
  121. package/src/chain/produceBlock/produceBlockBody.ts +30 -11
  122. package/src/chain/validation/block.ts +1 -0
  123. package/src/metrics/metrics/lodestar.ts +4 -0
  124. package/src/network/processor/gossipHandlers.ts +4 -1
  125. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  126. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  127. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  128. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  129. package/src/node/notifier.ts +8 -1
  130. package/src/sync/range/batch.ts +90 -35
  131. package/src/sync/range/chain.ts +13 -5
  132. package/src/sync/range/range.ts +18 -6
  133. package/src/sync/types.ts +72 -0
  134. package/src/sync/unknownBlock.ts +810 -57
  135. package/src/sync/utils/downloadByRange.ts +256 -39
  136. package/src/sync/utils/downloadByRoot.ts +12 -2
  137. package/src/sync/utils/pendingBlocksTree.ts +0 -15
@@ -18,6 +18,7 @@ import {
18
18
  altair,
19
19
  capella,
20
20
  deneb,
21
+ electra,
21
22
  gloas,
22
23
  phase0,
23
24
  rewards,
@@ -231,6 +232,7 @@ export interface IBeaconChain {
231
232
  blockSlot: Slot,
232
233
  blockRootHex: string
233
234
  ): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
235
+ getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
234
236
 
235
237
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
236
238
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
@@ -248,7 +250,11 @@ export interface IBeaconChain {
248
250
  /** Process a block until complete */
249
251
  processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void>;
250
252
  /** Process a chain of blocks until complete */
251
- processChainSegment(blocks: IBlockInput[], opts?: ImportBlockOpts): Promise<void>;
253
+ processChainSegment(
254
+ blocks: IBlockInput[],
255
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
256
+ opts?: ImportBlockOpts
257
+ ): Promise<void>;
252
258
 
253
259
  /** Process execution payload envelope: verify, import to fork choice, and persist to DB */
254
260
  processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void>;
@@ -10,7 +10,7 @@ import {
10
10
  isStatePostBellatrix,
11
11
  isStatePostGloas,
12
12
  } from "@lodestar/state-transition";
13
- import {Bytes32, Slot} from "@lodestar/types";
13
+ import {Bytes32, Slot, electra} from "@lodestar/types";
14
14
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
15
15
  import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
16
16
  import {BuilderStatus} from "../execution/builder/http.js";
@@ -165,14 +165,19 @@ export class PrepareNextSlotScheduler {
165
165
  }
166
166
 
167
167
  let parentBlockHash: Bytes32;
168
+ let isExtendingPayload = false;
168
169
  if (isStatePostGloas(updatedPrepareState)) {
169
- parentBlockHash = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)
170
+ isExtendingPayload = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot);
171
+ parentBlockHash = isExtendingPayload
170
172
  ? updatedPrepareState.latestExecutionPayloadBid.blockHash
171
173
  : updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
172
174
  } else {
173
175
  parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
174
176
  }
175
177
 
178
+ // Reused by the SSE emit below to avoid a second DB lookup on cache miss
179
+ let parentExecutionRequests: electra.ExecutionRequests | undefined;
180
+
176
181
  if (feeRecipient) {
177
182
  const preparationTime =
178
183
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
@@ -182,6 +187,13 @@ export class PrepareNextSlotScheduler {
182
187
  const finalizedBlockHash =
183
188
  this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
184
189
 
190
+ if (isExtendingPayload) {
191
+ parentExecutionRequests = await this.chain.getParentExecutionRequests(
192
+ updatedHead.slot,
193
+ updatedHead.blockRoot
194
+ );
195
+ }
196
+
185
197
  // awaiting here instead of throwing an async call because there is no other task
186
198
  // left for scheduler and this gives nice semantics to catch and log errors in the
187
199
  // try/catch wrapper here.
@@ -194,7 +206,8 @@ export class PrepareNextSlotScheduler {
194
206
  safeBlockHash,
195
207
  finalizedBlockHash,
196
208
  updatedPrepareState,
197
- feeRecipient
209
+ feeRecipient,
210
+ parentExecutionRequests
198
211
  );
199
212
  this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
200
213
  prepareSlot,
@@ -215,19 +228,26 @@ export class PrepareNextSlotScheduler {
215
228
 
216
229
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
217
230
 
218
- // If emitPayloadAttributes is true emit a SSE payloadAttributes event
231
+ // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
232
+ // every slot. Without the flag, only emit the event if we are proposing in the next slot.
219
233
  if (
220
- this.chain.opts.emitPayloadAttributes === true &&
234
+ (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
221
235
  this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
222
236
  ) {
237
+ // if we didn't fetch above (not proposing), SSE still needs it here
238
+ if (!parentExecutionRequests && isExtendingPayload) {
239
+ parentExecutionRequests = await this.chain.getParentExecutionRequests(
240
+ updatedHead.slot,
241
+ updatedHead.blockRoot
242
+ );
243
+ }
223
244
  const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
224
245
  prepareState: updatedPrepareState,
225
246
  prepareSlot,
226
- parentBlockRoot: fromHex(headRoot),
247
+ parentBlockRoot: fromHex(updatedHead.blockRoot),
227
248
  parentBlockHash,
228
- // The likely consumers of this API are builders and will anyway ignore the
229
- // feeRecipient, so just pass zero hash for now till a real use case arises
230
- feeRecipient: "0x0000000000000000000000000000000000000000000000000000000000000000",
249
+ feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
250
+ parentExecutionRequests,
231
251
  });
232
252
  this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
233
253
  }
@@ -214,9 +214,13 @@ export async function produceBlockBody<T extends BlockType>(
214
214
  });
215
215
 
216
216
  // Get execution payload from EL
217
- const parentBlockHash = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot))
217
+ const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot));
218
+ const parentBlockHash = isExtendingPayload
218
219
  ? currentState.latestExecutionPayloadBid.blockHash
219
220
  : currentState.latestExecutionPayloadBid.parentBlockHash;
221
+ const parentExecutionRequests = isExtendingPayload
222
+ ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
223
+ : ssz.electra.ExecutionRequests.defaultValue();
220
224
  const prepareRes = await prepareExecutionPayload(
221
225
  this,
222
226
  this.logger,
@@ -226,7 +230,8 @@ export async function produceBlockBody<T extends BlockType>(
226
230
  safeBlockHash,
227
231
  finalizedBlockHash ?? ZERO_HASH_HEX,
228
232
  currentState,
229
- feeRecipient
233
+ feeRecipient,
234
+ parentExecutionRequests
230
235
  );
231
236
 
232
237
  const {prepType, payloadId} = prepareRes;
@@ -282,7 +287,7 @@ export async function produceBlockBody<T extends BlockType>(
282
287
  gloasBody.signedExecutionPayloadBid = signedBid;
283
288
  // TODO GLOAS: Get payload attestations from pool for previous slot
284
289
  gloasBody.payloadAttestations = [];
285
- // TODO GLOAS: set parentExecutionRequests in the block body
290
+ gloasBody.parentExecutionRequests = parentExecutionRequests;
286
291
  blockBody = gloasBody as AssembledBodyType<T>;
287
292
 
288
293
  // Store execution payload data required to construct execution payload envelope later
@@ -619,7 +624,8 @@ export async function prepareExecutionPayload(
619
624
  safeBlockHash: RootHex,
620
625
  finalizedBlockHash: RootHex,
621
626
  state: IBeaconStateViewBellatrix,
622
- suggestedFeeRecipient: string
627
+ suggestedFeeRecipient: string,
628
+ parentExecutionRequests?: electra.ExecutionRequests
623
629
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
624
630
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
625
631
  const prevRandao = state.getRandaoMix(state.epoch);
@@ -656,6 +662,7 @@ export async function prepareExecutionPayload(
656
662
  parentBlockRoot,
657
663
  parentBlockHash,
658
664
  feeRecipient: suggestedFeeRecipient,
665
+ parentExecutionRequests,
659
666
  });
660
667
 
661
668
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
@@ -714,12 +721,14 @@ export function getPayloadAttributesForSSE(
714
721
  parentBlockRoot,
715
722
  parentBlockHash,
716
723
  feeRecipient,
724
+ parentExecutionRequests,
717
725
  }: {
718
726
  prepareState: IBeaconStateViewBellatrix;
719
727
  prepareSlot: Slot;
720
728
  parentBlockRoot: Root;
721
729
  parentBlockHash: Bytes32;
722
730
  feeRecipient: string;
731
+ parentExecutionRequests?: electra.ExecutionRequests;
723
732
  }
724
733
  ): SSEPayloadAttributes {
725
734
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
@@ -728,6 +737,7 @@ export function getPayloadAttributesForSSE(
728
737
  parentBlockRoot,
729
738
  parentBlockHash,
730
739
  feeRecipient,
740
+ parentExecutionRequests,
731
741
  });
732
742
 
733
743
  let parentBlockNumber: number;
@@ -766,12 +776,14 @@ function preparePayloadAttributes(
766
776
  parentBlockRoot,
767
777
  parentBlockHash,
768
778
  feeRecipient,
779
+ parentExecutionRequests,
769
780
  }: {
770
781
  prepareState: IBeaconStateViewBellatrix;
771
782
  prepareSlot: Slot;
772
783
  parentBlockRoot: Root;
773
784
  parentBlockHash: Bytes32;
774
785
  feeRecipient: string;
786
+ parentExecutionRequests?: electra.ExecutionRequests;
775
787
  }
776
788
  ): SSEPayloadAttributes["payloadAttributes"] {
777
789
  const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
@@ -789,13 +801,20 @@ function preparePayloadAttributes(
789
801
 
790
802
  if (isStatePostGloas(prepareState)) {
791
803
  const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
792
- // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
793
- // already deducted from CL balances but never credited on the EL (the envelope
794
- // was not delivered). The next payload must carry those same withdrawals to
795
- // restore CL/EL consistency, otherwise validators permanently lose that balance.
796
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = isExtendingPayload
797
- ? prepareState.getExpectedWithdrawals().expectedWithdrawals
798
- : prepareState.payloadExpectedWithdrawals;
804
+ if (isExtendingPayload) {
805
+ if (parentExecutionRequests === undefined) {
806
+ throw new Error("parentExecutionRequests required when extending full parent");
807
+ }
808
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
809
+ prepareState.getExpectedWithdrawalsForFullParent(parentExecutionRequests);
810
+ } else {
811
+ // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
812
+ // already deducted from CL balances but never credited on the EL (the envelope
813
+ // was not delivered). The next payload must carry those same withdrawals to
814
+ // restore CL/EL consistency, otherwise validators permanently lose that balance.
815
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
816
+ prepareState.payloadExpectedWithdrawals;
817
+ }
799
818
  } else {
800
819
  // withdrawals logic is now fork aware as it changes on electra fork post capella
801
820
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -103,6 +103,7 @@ export async function validateGossipBlock(
103
103
  if (chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHashHex) === null) {
104
104
  throw new BlockGossipError(GossipAction.IGNORE, {
105
105
  code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
106
+ parentRoot,
106
107
  parentBlockHash: parentBlockHashHex,
107
108
  });
108
109
  }
@@ -613,6 +613,10 @@ export function createLodestarMetrics(
613
613
  name: "lodestar_sync_unknown_block_pending_blocks_size",
614
614
  help: "Current size of UnknownBlockSync pending blocks cache",
615
615
  }),
616
+ pendingPayloads: register.gauge({
617
+ name: "lodestar_sync_unknown_block_pending_payloads_size",
618
+ help: "Current size of UnknownBlockSync pending payloads cache",
619
+ }),
616
620
  knownBadBlocks: register.gauge({
617
621
  name: "lodestar_sync_unknown_block_known_bad_blocks_size",
618
622
  help: "Current size of UnknownBlockSync known bad blocks cache",
@@ -198,7 +198,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
198
198
  } catch (e) {
199
199
  if (e instanceof BlockGossipError) {
200
200
  logger.debug("Gossip block has error", {slot, root: blockShortHex, code: e.type.code});
201
- if (e.type.code === BlockErrorCode.PARENT_UNKNOWN && blockInput) {
201
+ if (
202
+ (e.type.code === BlockErrorCode.PARENT_UNKNOWN || e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN) &&
203
+ blockInput
204
+ ) {
202
205
  chain.emitter.emit(ChainEvent.blockUnknownParent, {
203
206
  blockInput,
204
207
  peer: peerIdStr,
@@ -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) {