@lodestar/beacon-node 1.44.0-dev.7418ddd9f7 → 1.44.0-dev.762dd5f186

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 (205) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +36 -33
  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 +1 -26
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/config/constants.d.ts +5 -0
  8. package/lib/api/impl/config/constants.d.ts.map +1 -1
  9. package/lib/api/impl/config/constants.js +6 -1
  10. package/lib/api/impl/config/constants.js.map +1 -1
  11. package/lib/api/impl/debug/index.d.ts.map +1 -1
  12. package/lib/api/impl/debug/index.js +69 -12
  13. package/lib/api/impl/debug/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  15. package/lib/api/impl/lodestar/index.js +28 -0
  16. package/lib/api/impl/lodestar/index.js.map +1 -1
  17. package/lib/api/impl/validator/index.d.ts.map +1 -1
  18. package/lib/api/impl/validator/index.js +41 -36
  19. package/lib/api/impl/validator/index.js.map +1 -1
  20. package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
  21. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  22. package/lib/chain/archiveStore/archiveStore.js +0 -4
  23. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  24. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  25. package/lib/chain/blocks/importBlock.js +15 -3
  26. package/lib/chain/blocks/importBlock.js.map +1 -1
  27. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  28. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +3 -3
  29. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  30. package/lib/chain/chain.d.ts +2 -2
  31. package/lib/chain/chain.d.ts.map +1 -1
  32. package/lib/chain/chain.js +9 -2
  33. package/lib/chain/chain.js.map +1 -1
  34. package/lib/chain/emitter.d.ts +2 -1
  35. package/lib/chain/emitter.d.ts.map +1 -1
  36. package/lib/chain/emitter.js.map +1 -1
  37. package/lib/chain/errors/blockError.d.ts +0 -7
  38. package/lib/chain/errors/blockError.d.ts.map +1 -1
  39. package/lib/chain/errors/blockError.js +0 -3
  40. package/lib/chain/errors/blockError.js.map +1 -1
  41. package/lib/chain/errors/executionPayloadBid.d.ts +12 -0
  42. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  43. package/lib/chain/errors/executionPayloadBid.js +2 -0
  44. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  45. package/lib/chain/errors/payloadAttestation.d.ts +6 -0
  46. package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
  47. package/lib/chain/errors/payloadAttestation.js +1 -0
  48. package/lib/chain/errors/payloadAttestation.js.map +1 -1
  49. package/lib/chain/forkChoice/index.d.ts +4 -4
  50. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  51. package/lib/chain/forkChoice/index.js +10 -7
  52. package/lib/chain/forkChoice/index.js.map +1 -1
  53. package/lib/chain/interface.d.ts +2 -2
  54. package/lib/chain/interface.d.ts.map +1 -1
  55. package/lib/chain/interface.js.map +1 -1
  56. package/lib/chain/options.d.ts.map +1 -1
  57. package/lib/chain/options.js +1 -0
  58. package/lib/chain/options.js.map +1 -1
  59. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  60. package/lib/chain/prepareNextSlot.js +1 -10
  61. package/lib/chain/prepareNextSlot.js.map +1 -1
  62. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -2
  63. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  64. package/lib/chain/produceBlock/produceBlockBody.js +7 -7
  65. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  66. package/lib/chain/regen/interface.d.ts +1 -1
  67. package/lib/chain/regen/interface.d.ts.map +1 -1
  68. package/lib/chain/regen/interface.js +1 -0
  69. package/lib/chain/regen/interface.js.map +1 -1
  70. package/lib/chain/regen/queued.d.ts +0 -1
  71. package/lib/chain/regen/queued.d.ts.map +1 -1
  72. package/lib/chain/regen/queued.js +0 -4
  73. package/lib/chain/regen/queued.js.map +1 -1
  74. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  75. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +8 -2
  76. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  77. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +0 -5
  78. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  79. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -5
  80. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  81. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +1 -4
  82. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  83. package/lib/chain/stateCache/persistentCheckpointsCache.js +5 -2
  84. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  85. package/lib/chain/stateCache/types.d.ts +0 -2
  86. package/lib/chain/stateCache/types.d.ts.map +1 -1
  87. package/lib/chain/stateCache/types.js.map +1 -1
  88. package/lib/chain/validation/block.d.ts +5 -1
  89. package/lib/chain/validation/block.d.ts.map +1 -1
  90. package/lib/chain/validation/block.js +4 -14
  91. package/lib/chain/validation/block.js.map +1 -1
  92. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  93. package/lib/chain/validation/executionPayloadBid.js +45 -6
  94. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  95. package/lib/chain/validation/executionPayloadEnvelope.js +1 -3
  96. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  97. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  98. package/lib/chain/validation/payloadAttestationMessage.js +24 -4
  99. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  100. package/lib/execution/engine/http.js +1 -1
  101. package/lib/execution/engine/http.js.map +1 -1
  102. package/lib/execution/engine/interface.d.ts +2 -2
  103. package/lib/execution/engine/interface.d.ts.map +1 -1
  104. package/lib/execution/engine/interface.js +6 -2
  105. package/lib/execution/engine/interface.js.map +1 -1
  106. package/lib/execution/engine/mock.d.ts.map +1 -1
  107. package/lib/execution/engine/mock.js +3 -5
  108. package/lib/execution/engine/mock.js.map +1 -1
  109. package/lib/execution/engine/types.d.ts +15 -7
  110. package/lib/execution/engine/types.d.ts.map +1 -1
  111. package/lib/execution/engine/types.js +47 -9
  112. package/lib/execution/engine/types.js.map +1 -1
  113. package/lib/metrics/metrics/lodestar.d.ts +4 -0
  114. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  115. package/lib/metrics/metrics/lodestar.js +10 -0
  116. package/lib/metrics/metrics/lodestar.js.map +1 -1
  117. package/lib/network/gossip/topic.d.ts +770 -3
  118. package/lib/network/gossip/topic.d.ts.map +1 -1
  119. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  120. package/lib/network/processor/gossipHandlers.js +10 -3
  121. package/lib/network/processor/gossipHandlers.js.map +1 -1
  122. package/lib/network/processor/index.d.ts +2 -2
  123. package/lib/network/processor/index.d.ts.map +1 -1
  124. package/lib/network/processor/index.js +25 -23
  125. package/lib/network/processor/index.js.map +1 -1
  126. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  127. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +9 -5
  128. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  129. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  130. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +13 -3
  131. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  132. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -1
  133. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  134. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts +2 -1
  135. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  136. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +16 -6
  137. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  138. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts +2 -1
  139. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  140. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +15 -1
  141. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  142. package/lib/network/reqresp/handlers/index.js +4 -4
  143. package/lib/network/reqresp/handlers/index.js.map +1 -1
  144. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  145. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +23 -5
  146. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  147. package/lib/sync/types.d.ts +9 -1
  148. package/lib/sync/types.d.ts.map +1 -1
  149. package/lib/sync/types.js +9 -2
  150. package/lib/sync/types.js.map +1 -1
  151. package/lib/sync/unknownBlock.d.ts.map +1 -1
  152. package/lib/sync/unknownBlock.js +64 -30
  153. package/lib/sync/unknownBlock.js.map +1 -1
  154. package/lib/util/dataColumns.d.ts.map +1 -1
  155. package/lib/util/dataColumns.js +16 -11
  156. package/lib/util/dataColumns.js.map +1 -1
  157. package/lib/util/sszBytes.js +1 -1
  158. package/package.json +15 -17
  159. package/src/api/impl/beacon/blocks/index.ts +40 -42
  160. package/src/api/impl/beacon/pool/index.ts +2 -41
  161. package/src/api/impl/config/constants.ts +10 -0
  162. package/src/api/impl/debug/index.ts +73 -12
  163. package/src/api/impl/lodestar/index.ts +30 -0
  164. package/src/api/impl/validator/index.ts +52 -53
  165. package/src/chain/archiveStore/archiveStore.ts +0 -5
  166. package/src/chain/blocks/importBlock.ts +20 -5
  167. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +3 -5
  168. package/src/chain/chain.ts +12 -4
  169. package/src/chain/emitter.ts +3 -2
  170. package/src/chain/errors/blockError.ts +0 -4
  171. package/src/chain/errors/executionPayloadBid.ts +14 -0
  172. package/src/chain/errors/payloadAttestation.ts +2 -0
  173. package/src/chain/forkChoice/index.ts +13 -0
  174. package/src/chain/interface.ts +1 -2
  175. package/src/chain/options.ts +1 -0
  176. package/src/chain/prepareNextSlot.ts +1 -11
  177. package/src/chain/produceBlock/produceBlockBody.ts +10 -10
  178. package/src/chain/regen/interface.ts +1 -1
  179. package/src/chain/regen/queued.ts +0 -5
  180. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +8 -3
  181. package/src/chain/stateCache/fifoBlockStateCache.ts +0 -6
  182. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  183. package/src/chain/stateCache/types.ts +0 -2
  184. package/src/chain/validation/block.ts +12 -16
  185. package/src/chain/validation/executionPayloadBid.ts +48 -6
  186. package/src/chain/validation/executionPayloadEnvelope.ts +1 -3
  187. package/src/chain/validation/payloadAttestationMessage.ts +26 -4
  188. package/src/execution/engine/http.ts +1 -1
  189. package/src/execution/engine/interface.ts +12 -2
  190. package/src/execution/engine/mock.ts +6 -5
  191. package/src/execution/engine/types.ts +75 -14
  192. package/src/metrics/metrics/lodestar.ts +11 -0
  193. package/src/network/processor/gossipHandlers.ts +10 -2
  194. package/src/network/processor/index.ts +27 -27
  195. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +12 -5
  196. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -3
  197. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -1
  198. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +22 -6
  199. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +20 -1
  200. package/src/network/reqresp/handlers/index.ts +4 -4
  201. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +22 -5
  202. package/src/sync/types.ts +11 -2
  203. package/src/sync/unknownBlock.ts +70 -31
  204. package/src/util/dataColumns.ts +17 -12
  205. package/src/util/sszBytes.ts +1 -1
@@ -30,7 +30,6 @@ export enum ArchiveStoreTask {
30
30
  PruneHistory = "prune_history",
31
31
  OnFinalizedCheckpoint = "on_finalized_checkpoint",
32
32
  MaybeArchiveState = "maybe_archive_state",
33
- RegenPruneOnFinalized = "regen_prune_on_finalized",
34
33
  ForkchoicePrune = "forkchoice_prune",
35
34
  UpdateBackfillRange = "update_backfill_range",
36
35
  }
@@ -229,10 +228,6 @@ export class ArchiveStore {
229
228
  await this.statesArchiverStrategy.maybeArchiveState(finalized, this.metrics);
230
229
  timer?.({source: ArchiveStoreTask.MaybeArchiveState});
231
230
 
232
- timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
233
- this.chain.regen.pruneOnFinalized(finalizedEpoch);
234
- timer?.({source: ArchiveStoreTask.RegenPruneOnFinalized});
235
-
236
231
  // tasks rely on extended fork choice
237
232
  timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
238
233
  const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex);
@@ -22,7 +22,7 @@ import {
22
22
  } from "@lodestar/state-transition";
23
23
  import {Attestation, BeaconBlock, altair, capella, electra, isGloasBeaconBlock, phase0, ssz} from "@lodestar/types";
24
24
  import {isErrorAborted, toRootHex} from "@lodestar/utils";
25
- import {ZERO_HASH_HEX} from "../../constants/index.js";
25
+ import {GENESIS_SLOT, ZERO_HASH_HEX} from "../../constants/index.js";
26
26
  import {callInNextEventLoop} from "../../util/eventLoop.js";
27
27
  import {isOptimisticBlock} from "../../util/forkChoice.js";
28
28
  import {isQueueErrorAborted} from "../../util/queue/index.js";
@@ -189,10 +189,12 @@ export async function importBlock(
189
189
  this.seenBlockAttesters.addIndices(blockEpoch, indexedAttestation.attestingIndices);
190
190
 
191
191
  const correctHead = ssz.Root.equals(rootCache.getBlockRootAtSlot(attestation.data.slot), beaconBlockRoot);
192
- const missedSlotVote = ssz.Root.equals(
193
- rootCache.getBlockRootAtSlot(attestation.data.slot - 1),
194
- rootCache.getBlockRootAtSlot(attestation.data.slot)
195
- );
192
+ const missedSlotVote =
193
+ attestation.data.slot > GENESIS_SLOT &&
194
+ ssz.Root.equals(
195
+ rootCache.getBlockRootAtSlot(attestation.data.slot - 1),
196
+ rootCache.getBlockRootAtSlot(attestation.data.slot)
197
+ );
196
198
  this.validatorMonitor?.registerAttestationInBlock(
197
199
  indexedAttestation,
198
200
  parentBlockSlot,
@@ -263,6 +265,7 @@ export async function importBlock(
263
265
  if (ptcIndices.length > 0) {
264
266
  this.forkChoice.notifyPtcMessages(
265
267
  toRootHex(payloadAttestation.data.beaconBlockRoot),
268
+ payloadAttestation.data.slot,
266
269
  ptcIndices,
267
270
  payloadAttestation.data.payloadPresent,
268
271
  payloadAttestation.data.blobDataAvailable
@@ -280,6 +283,18 @@ export async function importBlock(
280
283
  const newHead = this.recomputeForkChoiceHead(ForkchoiceCaller.importBlock);
281
284
  const currFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
282
285
 
286
+ // Prune the gloas payload-envelope cache below the new head's parent so it stays bounded during
287
+ // syncing. On a synced node, cache holds just 2 entries — head (parent for
288
+ // next-slot production) and head.parent (proposer-boost-reorg fallback)
289
+ if (fork >= ForkSeq.gloas) {
290
+ callInNextEventLoop(() => {
291
+ const newHeadParent = this.forkChoice.getBlockHexDefaultStatus(newHead.parentRoot);
292
+ if (newHeadParent) {
293
+ this.seenPayloadEnvelopeInputCache.pruneBelowParent(newHeadParent);
294
+ }
295
+ });
296
+ }
297
+
283
298
  if (newHead.blockRoot !== oldHead.blockRoot) {
284
299
  // Set head state as strong reference
285
300
  this.regen.updateHeadState(newHead, postState);
@@ -61,10 +61,8 @@ export function verifyExecutionPayloadEnvelope(
61
61
  `Prev randao mismatch between bid and payload bid=${toHex(bid.prevRandao)} payload=${toHex(payload.prevRandao)}`
62
62
  );
63
63
  }
64
- if (Number(bid.gasLimit) !== payload.gasLimit) {
65
- throw new Error(
66
- `Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${Number(bid.gasLimit)}`
67
- );
64
+ if (bid.gasLimit !== payload.gasLimit) {
65
+ throw new Error(`Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${bid.gasLimit}`);
68
66
  }
69
67
  if (!byteArrayEquals(bid.blockHash, payload.blockHash)) {
70
68
  throw new Error(
@@ -74,7 +72,7 @@ export function verifyExecutionPayloadEnvelope(
74
72
  // Verify execution_requests_root matches bid commitment.
75
73
  // Can be skipped if already verified during gossip validation.
76
74
  if (verifyExecutionRequestsRoot) {
77
- const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
75
+ const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
78
76
  if (!byteArrayEquals(requestsRoot, bid.executionRequestsRoot)) {
79
77
  throw new Error(
80
78
  `Execution requests root mismatch envelope=${toRootHex(requestsRoot)} bid=${toRootHex(bid.executionRequestsRoot)}`
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Type} from "@chainsafe/ssz";
4
4
  import {BeaconConfig} from "@lodestar/config";
5
- import {CheckpointWithHex, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
5
+ import {CheckpointWithHex, ForkChoiceStateGetter, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
6
6
  import {LoggerNode} from "@lodestar/logger/node";
7
7
  import {
8
8
  EFFECTIVE_BALANCE_INCREMENT,
@@ -40,7 +40,6 @@ import {
40
40
  ValidatorIndex,
41
41
  Wei,
42
42
  deneb,
43
- electra,
44
43
  gloas,
45
44
  isBlindedBeaconBlock,
46
45
  phase0,
@@ -382,6 +381,14 @@ export class BeaconChain implements IBeaconChain {
382
381
  blockStateCache.setHeadState(anchorState);
383
382
  checkpointStateCache.add(checkpoint, anchorState);
384
383
 
384
+ const forkChoiceStateGetter: ForkChoiceStateGetter = ({stateRoot, checkpoint}) => {
385
+ if (stateRoot) return blockStateCache.get(stateRoot);
386
+
387
+ if (checkpoint) return checkpointStateCache.get({epoch: checkpoint.epoch, rootHex: checkpoint.rootHex});
388
+
389
+ return null;
390
+ };
391
+
385
392
  const forkChoice = initializeForkChoice(
386
393
  config,
387
394
  emitter,
@@ -390,6 +397,7 @@ export class BeaconChain implements IBeaconChain {
390
397
  isAnchorStateFinalized,
391
398
  opts,
392
399
  this.justifiedBalancesGetter.bind(this),
400
+ forkChoiceStateGetter,
393
401
  metrics,
394
402
  logger
395
403
  );
@@ -916,10 +924,10 @@ export class BeaconChain implements IBeaconChain {
916
924
  async getParentExecutionRequests(
917
925
  parentBlockSlot: Slot,
918
926
  parentBlockRootHex: RootHex
919
- ): Promise<electra.ExecutionRequests> {
927
+ ): Promise<gloas.ExecutionRequests> {
920
928
  // at the fork boundary, parent is pre-gloas
921
929
  if (!isForkPostGloas(this.config.getForkName(parentBlockSlot))) {
922
- return ssz.electra.ExecutionRequests.defaultValue();
930
+ return ssz.gloas.ExecutionRequests.defaultValue();
923
931
  }
924
932
  const envelope = await this.getExecutionPayloadEnvelope(parentBlockSlot, parentBlockRootHex);
925
933
  if (envelope === null) {
@@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {routes} from "@lodestar/api";
4
4
  import {CheckpointWithHex} from "@lodestar/fork-choice";
5
5
  import {IBeaconStateView} from "@lodestar/state-transition";
6
- import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
6
+ import {DataColumnSidecar, RootHex, Slot, deneb, phase0} from "@lodestar/types";
7
7
  import {PeerIdStr} from "../util/peerId.js";
8
8
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
9
9
  import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
@@ -94,7 +94,8 @@ export type ChainEventData = {
94
94
  peer: PeerIdStr;
95
95
  source: BlockInputSource;
96
96
  };
97
- [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
97
+ // slot is the message slot, not necessarily the envelope's slot, but useful as a logging/prune hint
98
+ [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; slot: Slot; peer?: PeerIdStr; source: BlockInputSource};
98
99
  };
99
100
 
100
101
  export type IChainEvents = ApiEvents & {
@@ -61,9 +61,6 @@ export enum BlockErrorCode {
61
61
  TRANSACTIONS_TOO_BIG = "BLOCK_ERROR_TRANSACTIONS_TOO_BIG",
62
62
  /** Execution engine is unavailable, syncing, or api call errored. Peers must not be downscored on this code */
63
63
  EXECUTION_ENGINE_ERROR = "BLOCK_ERROR_EXECUTION_ERROR",
64
- /** The attestation head block is too far behind the attestation slot, causing many skip slots.
65
- This is deemed a DoS risk */
66
- TOO_MANY_SKIPPED_SLOTS = "TOO_MANY_SKIPPED_SLOTS",
67
64
  /** The blobs are unavailable */
68
65
  DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
69
66
  /** Block contains too many kzg commitments */
@@ -89,7 +86,6 @@ export type BlockErrorType =
89
86
  | {code: BlockErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
90
87
  | {code: BlockErrorCode.STATE_ROOT_MISMATCH}
91
88
  | {code: BlockErrorCode.GENESIS_BLOCK}
92
- | {code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS; parentSlot: Slot; blockSlot: Slot}
93
89
  | {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT; blockSlot: Slot; finalizedSlot: Slot}
94
90
  | {code: BlockErrorCode.ALREADY_KNOWN; root: RootHex}
95
91
  | {code: BlockErrorCode.REPEAT_PROPOSAL; proposerIndex: ValidatorIndex}
@@ -3,6 +3,7 @@ import {GossipActionError} from "./gossipValidation.js";
3
3
 
4
4
  export enum ExecutionPayloadBidErrorCode {
5
5
  BUILDER_NOT_ELIGIBLE = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_NOT_ELIGIBLE",
6
+ INVALID_BUILDER_VERSION = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_BUILDER_VERSION",
6
7
  NON_ZERO_EXECUTION_PAYMENT = "EXECUTION_PAYLOAD_BID_ERROR_NON_ZERO_EXECUTION_PAYMENT",
7
8
  BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
8
9
  BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
@@ -12,6 +13,7 @@ export enum ExecutionPayloadBidErrorCode {
12
13
  UNKNOWN_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_PARENT_BLOCK_HASH",
13
14
  INVALID_SLOT = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SLOT",
14
15
  NOT_LATER_THAN_PARENT = "EXECUTION_PAYLOAD_BID_ERROR_NOT_LATER_THAN_PARENT",
16
+ INVALID_PREV_RANDAO = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_PREV_RANDAO",
15
17
  INVALID_SIGNATURE = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SIGNATURE",
16
18
  NO_MATCHING_PROPOSER_PREFERENCES = "EXECUTION_PAYLOAD_BID_ERROR_NO_MATCHING_PROPOSER_PREFERENCES",
17
19
  PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH = "EXECUTION_PAYLOAD_BID_ERROR_PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH",
@@ -20,6 +22,12 @@ export enum ExecutionPayloadBidErrorCode {
20
22
 
21
23
  export type ExecutionPayloadBidErrorType =
22
24
  | {code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE; builderIndex: BuilderIndex}
25
+ | {
26
+ code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION;
27
+ builderIndex: BuilderIndex;
28
+ version: number;
29
+ expectedVersion: number;
30
+ }
23
31
  | {
24
32
  code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT;
25
33
  builderIndex: BuilderIndex;
@@ -43,6 +51,12 @@ export type ExecutionPayloadBidErrorType =
43
51
  | {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
44
52
  | {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
45
53
  | {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
54
+ | {
55
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO;
56
+ builderIndex: BuilderIndex;
57
+ bidPrevRandao: string;
58
+ expectedPrevRandao: string;
59
+ }
46
60
  | {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot}
47
61
  | {
48
62
  code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES;
@@ -5,6 +5,7 @@ export enum PayloadAttestationErrorCode {
5
5
  NOT_CURRENT_SLOT = "PAYLOAD_ATTESTATION_ERROR_NOT_CURRENT_SLOT",
6
6
  PAYLOAD_ATTESTATION_ALREADY_KNOWN = "PAYLOAD_ATTESTATION_ERROR_PAYLOAD_ATTESTATION_ALREADY_KNOWN",
7
7
  UNKNOWN_BLOCK_ROOT = "PAYLOAD_ATTESTATION_ERROR_UNKNOWN_BLOCK_ROOT",
8
+ INVALID_BLOCK_SLOT = "PAYLOAD_ATTESTATION_ERROR_INVALID_BLOCK_SLOT",
8
9
  INVALID_BLOCK = "PAYLOAD_ATTESTATION_ERROR_INVALID_BLOCK",
9
10
  INVALID_ATTESTER = "PAYLOAD_ATTESTATION_ERROR_INVALID_ATTESTER",
10
11
  INVALID_SIGNATURE = "PAYLOAD_ATTESTATION_ERROR_INVALID_SIGNATURE",
@@ -18,6 +19,7 @@ export type PayloadAttestationErrorType =
18
19
  blockRoot: RootHex;
19
20
  }
20
21
  | {code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT; blockRoot: RootHex}
22
+ | {code: PayloadAttestationErrorCode.INVALID_BLOCK_SLOT; blockRoot: RootHex; blockSlot: Slot; slot: Slot}
21
23
  | {code: PayloadAttestationErrorCode.INVALID_BLOCK; blockRoot: RootHex}
22
24
  | {code: PayloadAttestationErrorCode.INVALID_ATTESTER; attesterIndex: ValidatorIndex}
23
25
  | {code: PayloadAttestationErrorCode.INVALID_SIGNATURE};
@@ -1,7 +1,9 @@
1
+ import {routes} from "@lodestar/api";
1
2
  import {ChainForkConfig} from "@lodestar/config";
2
3
  import {
3
4
  ExecutionStatus,
4
5
  ForkChoice,
6
+ ForkChoiceStateGetter,
5
7
  ForkChoiceStore,
6
8
  JustifiedBalancesGetter,
7
9
  PayloadStatus,
@@ -45,6 +47,7 @@ export function initializeForkChoice(
45
47
  isFinalizedState: boolean,
46
48
  opts: ForkChoiceOpts,
47
49
  justifiedBalancesGetter: JustifiedBalancesGetter,
50
+ stateGetter: ForkChoiceStateGetter,
48
51
  metrics: Metrics | null,
49
52
  logger?: Logger
50
53
  ): ForkChoice {
@@ -56,6 +59,7 @@ export function initializeForkChoice(
56
59
  state,
57
60
  opts,
58
61
  justifiedBalancesGetter,
62
+ stateGetter,
59
63
  metrics,
60
64
  logger
61
65
  )
@@ -66,6 +70,7 @@ export function initializeForkChoice(
66
70
  state,
67
71
  opts,
68
72
  justifiedBalancesGetter,
73
+ stateGetter,
69
74
  metrics,
70
75
  logger
71
76
  );
@@ -81,6 +86,7 @@ export function initializeForkChoiceFromFinalizedState(
81
86
  state: IBeaconStateView,
82
87
  opts: ForkChoiceOpts,
83
88
  justifiedBalancesGetter: JustifiedBalancesGetter,
89
+ stateGetter: ForkChoiceStateGetter,
84
90
  metrics: Metrics | null,
85
91
  logger?: Logger
86
92
  ): ForkChoice {
@@ -112,9 +118,12 @@ export function initializeForkChoiceFromFinalizedState(
112
118
  finalizedCheckpoint,
113
119
  justifiedBalances,
114
120
  justifiedBalancesGetter,
121
+ stateGetter,
115
122
  {
116
123
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
117
124
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
125
+ onFastConfirmation: ({block, slot, currentSlot}) =>
126
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
118
127
  }
119
128
  ),
120
129
 
@@ -172,6 +181,7 @@ export function initializeForkChoiceFromUnfinalizedState(
172
181
  unfinalizedState: IBeaconStateView,
173
182
  opts: ForkChoiceOpts,
174
183
  justifiedBalancesGetter: JustifiedBalancesGetter,
184
+ stateGetter: ForkChoiceStateGetter,
175
185
  metrics: Metrics | null,
176
186
  logger?: Logger
177
187
  ): ForkChoice {
@@ -203,9 +213,12 @@ export function initializeForkChoiceFromUnfinalizedState(
203
213
  finalizedCheckpoint,
204
214
  justifiedBalances,
205
215
  justifiedBalancesGetter,
216
+ stateGetter,
206
217
  {
207
218
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
208
219
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
220
+ onFastConfirmation: ({block, slot, currentSlot}) =>
221
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
209
222
  }
210
223
  );
211
224
 
@@ -18,7 +18,6 @@ import {
18
18
  altair,
19
19
  capella,
20
20
  deneb,
21
- electra,
22
21
  gloas,
23
22
  phase0,
24
23
  rewards,
@@ -236,7 +235,7 @@ export interface IBeaconChain {
236
235
  blockSlot: Slot,
237
236
  blockRootHex: string
238
237
  ): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
239
- getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
238
+ getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<gloas.ExecutionRequests>;
240
239
 
241
240
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
242
241
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
@@ -105,6 +105,7 @@ export const defaultChainOptions: IChainOptions = {
105
105
  proposerBoost: true,
106
106
  proposerBoostReorg: true,
107
107
  computeUnrealized: true,
108
+ fastConfirmation: false,
108
109
  suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
109
110
  serveHistoricalState: false,
110
111
  assertCorrectProgressiveBalances: false,
@@ -170,7 +170,7 @@ export class PrepareNextSlotScheduler {
170
170
  let stateAfterParentPayload: IBeaconStateViewBellatrix = updatedPrepareState;
171
171
  if (isStatePostGloas(updatedPrepareState)) {
172
172
  // Spec: should_build_on_full(store, head) — see produceBlockBody.ts for context.
173
- if (this.chain.forkChoice.shouldBuildOnFull(updatedHead)) {
173
+ if (this.chain.forkChoice.shouldBuildOnFull(updatedHead, prepareSlot)) {
174
174
  parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.blockHash;
175
175
  // Skip applying parent payload unless we're proposing the next slot or have to emit payload_attributes events
176
176
  if (feeRecipient !== undefined || this.chain.opts.emitPayloadAttributes === true) {
@@ -217,16 +217,6 @@ export class PrepareNextSlotScheduler {
217
217
  });
218
218
  }
219
219
 
220
- if (ForkSeq[fork] >= ForkSeq.gloas) {
221
- // Cutoff = slot of the parent of the block we'll actually build on (post-reorg).
222
- // Steady state: cache holds just 2 entries — head (parent for next-slot production)
223
- // and head.parent (proposer-boost-reorg fallback). Anything older is evicted.
224
- const updatedHeadParent = this.chain.forkChoice.getBlockHexDefaultStatus(updatedHead.parentRoot);
225
- if (updatedHeadParent) {
226
- this.chain.seenPayloadEnvelopeInputCache.pruneBelowParent(updatedHeadParent);
227
- }
228
- }
229
-
230
220
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
231
221
 
232
222
  // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
@@ -108,7 +108,7 @@ export type ProduceFullGloas = {
108
108
  type: BlockType.Full;
109
109
  fork: ForkPostGloas;
110
110
  executionPayload: ExecutionPayload<ForkPostGloas>;
111
- executionRequests: electra.ExecutionRequests;
111
+ executionRequests: gloas.ExecutionRequests;
112
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
113
113
  cells: fulu.Cell[][];
114
114
  parentBlockRoot: Root;
@@ -228,7 +228,7 @@ export async function produceBlockBody<T extends BlockType>(
228
228
  );
229
229
  const parentExecutionRequests = isExtendingPayload
230
230
  ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
231
- : ssz.electra.ExecutionRequests.defaultValue();
231
+ : ssz.gloas.ExecutionRequests.defaultValue();
232
232
  executionPayloadValue = BigInt(builderBid.message.value) * GWEI_TO_WEI;
233
233
 
234
234
  const commonBlockBody = await commonBlockBodyPromise;
@@ -272,20 +272,20 @@ export async function produceBlockBody<T extends BlockType>(
272
272
 
273
273
  // Get execution payload from EL
274
274
  let parentBlockHash: Bytes32;
275
- let parentExecutionRequests: electra.ExecutionRequests;
275
+ let parentExecutionRequests: gloas.ExecutionRequests;
276
276
  // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
277
277
  let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
278
278
  // Spec: should_build_on_full(store, head). `parentBlock` is the proposer's head
279
- // (set by chain.getProposerHead(slot)). Returns false when the PTC majority
280
- // signalled the blob data is not available, forcing a build on EMPTY (reorg).
281
- const isBuildingOnFull = this.forkChoice.shouldBuildOnFull(parentBlock);
279
+ // (set by chain.getProposerHead(slot)). Returns false when the PTC majority signalled
280
+ // the blob data is not available or the payload was not timely, forcing a build on EMPTY (reorg).
281
+ const isBuildingOnFull = this.forkChoice.shouldBuildOnFull(parentBlock, blockSlot);
282
282
  if (isBuildingOnFull) {
283
283
  parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
284
284
  parentExecutionRequests = await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot);
285
285
  stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
286
286
  } else {
287
287
  parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash;
288
- parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue();
288
+ parentExecutionRequests = ssz.gloas.ExecutionRequests.defaultValue();
289
289
  }
290
290
  const prepareRes = await prepareExecutionPayload(
291
291
  this,
@@ -344,13 +344,13 @@ export async function produceBlockBody<T extends BlockType>(
344
344
  blockHash: executionPayload.blockHash,
345
345
  prevRandao: currentState.getRandaoMix(currentState.epoch),
346
346
  feeRecipient: executionPayload.feeRecipient,
347
- gasLimit: BigInt(executionPayload.gasLimit),
347
+ gasLimit: executionPayload.gasLimit,
348
348
  builderIndex: BUILDER_INDEX_SELF_BUILD,
349
349
  slot: blockSlot,
350
350
  value: 0,
351
351
  executionPayment: 0,
352
352
  blobKzgCommitments: blobsBundle.commitments,
353
- executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests),
353
+ executionRequestsRoot: ssz.gloas.ExecutionRequests.hashTreeRoot(executionRequests as gloas.ExecutionRequests),
354
354
  };
355
355
  const signedBid: gloas.SignedExecutionPayloadBid = {
356
356
  message: bid,
@@ -375,7 +375,7 @@ export async function produceBlockBody<T extends BlockType>(
375
375
  // Store execution payload data required to construct execution payload envelope later
376
376
  const gloasResult = produceResult as ProduceFullGloas;
377
377
  gloasResult.executionPayload = executionPayload as ExecutionPayload<ForkPostGloas>;
378
- gloasResult.executionRequests = executionRequests;
378
+ gloasResult.executionRequests = executionRequests as gloas.ExecutionRequests;
379
379
  gloasResult.blobsBundle = blobsBundle;
380
380
  gloasResult.cells = cells;
381
381
  gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot);
@@ -22,6 +22,7 @@ export enum RegenCaller {
22
22
  validateGossipAttestation = "validateGossipAttestation",
23
23
  validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
24
24
  validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
25
+ validateGossipPayloadAttestationMessage = "validateGossipPayloadAttestationMessage",
25
26
  validateGossipProposerPreferences = "validateGossipProposerPreferences",
26
27
  onForkChoiceFinalized = "onForkChoiceFinalized",
27
28
  restApi = "restApi",
@@ -46,7 +47,6 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
46
47
  getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null;
47
48
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null;
48
49
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
49
- pruneOnFinalized(finalizedEpoch: Epoch): void;
50
50
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
51
51
  addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
52
52
  updateHeadState(newHead: ProtoBlock, maybeHeadState: IBeaconStateView): void;
@@ -143,11 +143,6 @@ export class QueuedStateRegenerator implements IStateRegenerator {
143
143
  this.blockStateCache.prune(headStateRoot);
144
144
  }
145
145
 
146
- pruneOnFinalized(finalizedEpoch: number): void {
147
- this.checkpointStateCache.pruneFinalized(finalizedEpoch);
148
- this.blockStateCache.deleteAllBeforeEpoch(finalizedEpoch);
149
- }
150
-
151
146
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void {
152
147
  this.blockStateCache.add(postState);
153
148
  this.checkpointStateCache.processState(blockRootHex, postState).catch((e) => {
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {CheckpointWithHex, IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
4
  import {RootHex} from "@lodestar/types";
5
5
  import {Logger} from "@lodestar/utils";
@@ -154,9 +154,14 @@ export class SeenPayloadEnvelopeInput {
154
154
 
155
155
  pruneBelowParent(parentBlock: ProtoBlock): void {
156
156
  for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
157
- if (block.slot < parentBlock.slot) {
157
+ // Only evict once the payload is FULL (revealed/imported) — on an EMPTY/PENDING branch we may
158
+ // still need to download the FULL envelope (see #9475), and evicting would make payload-by-root
159
+ // sync throw "Missing PayloadEnvelopeInput for known block".
160
+ if (block.slot < parentBlock.slot && block.payloadStatus === PayloadStatus.FULL) {
158
161
  const input = this.payloadInputs.get(block.blockRoot);
159
- if (input) {
162
+ // ...and don't evict while columns are still being gathered: writeDataColumnsToDb awaits the
163
+ // same hasComputedAllData() before persisting. Such entries are pruned by a later call.
164
+ if (input?.hasComputedAllData()) {
160
165
  this.evictPayloadInput(input);
161
166
  this.logger?.verbose("SeenPayloadEnvelopeInput.pruneBelowParent deleted", {
162
167
  slot: block.slot,
@@ -167,12 +167,6 @@ export class FIFOBlockStateCache implements BlockStateCache {
167
167
  }
168
168
  }
169
169
 
170
- /**
171
- * No need for this implementation
172
- * This is only to conform to the old api
173
- */
174
- deleteAllBeforeEpoch(): void {}
175
-
176
170
  /**
177
171
  * ONLY FOR DEBUGGING PURPOSES. For lodestar debug API.
178
172
  */
@@ -414,11 +414,12 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
414
414
 
415
415
  /**
416
416
  * Prune all checkpoint states before the provided finalized epoch.
417
+ * Driven sequentially from processState() so it never interleaves with persist.
417
418
  */
418
- pruneFinalized(finalizedEpoch: Epoch): void {
419
+ private async pruneFinalized(finalizedEpoch: Epoch): Promise<void> {
419
420
  for (const epoch of this.epochIndex.keys()) {
420
421
  if (epoch < finalizedEpoch) {
421
- this.deleteAllEpochItems(epoch).catch((e) =>
422
+ await this.deleteAllEpochItems(epoch).catch((e) =>
422
423
  this.logger.debug("Error delete all epoch items", {epoch, finalizedEpoch}, e as Error)
423
424
  );
424
425
  }
@@ -476,6 +477,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
476
477
  * As of Mar 2024, it takes <=350ms to persist a holesky state on fast server
477
478
  */
478
479
  async processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number> {
480
+ // prune finalized in the same flow so a finalized cp state is pruned, never persisted
481
+ await this.pruneFinalized(state.finalizedCheckpoint.epoch);
482
+
479
483
  let persistCount = 0;
480
484
  // it's important to sort the epochs in ascending order, in case of big reorg we always want to keep the most recent checkpoint states
481
485
  const sortedEpochs = Array.from(this.epochIndex.keys()).sort((a, b) => a - b);
@@ -30,7 +30,6 @@ export interface BlockStateCache {
30
30
  clear(): void;
31
31
  size: number;
32
32
  prune(headStateRootHex: RootHex): void;
33
- deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
34
33
  dumpSummary(): routes.lodestar.StateCacheItem[];
35
34
  /** Expose beacon states stored in cache. Use with caution */
36
35
  getStates(): IterableIterator<IBeaconStateView>;
@@ -67,7 +66,6 @@ export interface CheckpointStateCache {
67
66
  getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null>;
68
67
  updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
69
68
  prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void;
70
- pruneFinalized(finalizedEpoch: Epoch): void;
71
69
  processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number>;
72
70
  clear(): void;
73
71
  dumpSummary(): routes.lodestar.StateCacheItem[];
@@ -15,12 +15,17 @@ import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js
15
15
  import {IBeaconChain} from "../interface.js";
16
16
  import {RegenCaller} from "../regen/index.js";
17
17
 
18
+ export type GossipBlockValidationResult = {
19
+ /** Number of skipped slots between the block and its parent (blockSlot - parentSlot - 1) */
20
+ skippedSlots: number;
21
+ };
22
+
18
23
  export async function validateGossipBlock(
19
24
  config: ChainForkConfig,
20
25
  chain: IBeaconChain,
21
26
  signedBlock: SignedBeaconBlock,
22
27
  fork: ForkName
23
- ): Promise<void> {
28
+ ): Promise<GossipBlockValidationResult> {
24
29
  const block = signedBlock.message;
25
30
  const blockSlot = block.slot;
26
31
  const blockEpoch = computeEpochAtSlot(blockSlot);
@@ -109,21 +114,6 @@ export async function validateGossipBlock(
109
114
  }
110
115
  }
111
116
 
112
- // [IGNORE] The attestation head block is too far behind the attestation slot, causing many skip slots.
113
- // This is deemed a DoS risk because we need to get the proposerShuffling. To get the shuffling we have
114
- // to do a bunch of epoch transitions, the longer the distance between the parent and block,
115
- // the more we have to do. epochTransitions are expensive ~750ms, so we must limit how many a
116
- // single bad block can trigger
117
- // Note: Ensure this check is done before calling chain.regen.getBlockSlotStat as this is the function that does various epoch transitions.
118
- // Note: This validation check is not part of the spec.
119
- if (chain.opts.maxSkipSlots != null && parentBlock.slot + chain.opts.maxSkipSlots < blockSlot) {
120
- throw new BlockGossipError(GossipAction.IGNORE, {
121
- code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS,
122
- parentSlot: parentBlock.slot,
123
- blockSlot,
124
- });
125
- }
126
-
127
117
  // [REJECT] The block is from a higher slot than its parent.
128
118
  if (parentBlock.slot >= blockSlot) {
129
119
  throw new BlockGossipError(GossipAction.REJECT, {
@@ -133,6 +123,10 @@ export async function validateGossipBlock(
133
123
  });
134
124
  }
135
125
 
126
+ // Number of skipped slots between block and parent (non-spec). Previously this gated blocks via
127
+ // maxSkipSlots; now the caller only observes it so legitimate post-skip blocks are no longer ignored.
128
+ const skippedSlots = blockSlot - parentBlock.slot - 1;
129
+
136
130
  // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
137
131
  if (isForkPostDeneb(fork) && !isForkPostGloas(fork)) {
138
132
  const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
@@ -247,4 +241,6 @@ export async function validateGossipBlock(
247
241
  }
248
242
 
249
243
  chain.seenBlockProposers.add(blockSlot, proposerIndex);
244
+
245
+ return {skippedSlots};
250
246
  }