@lodestar/beacon-node 1.44.0-dev.c874e6b4ce → 1.44.0-dev.d01471ec84

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 (131) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +13 -5
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/config/constants.d.ts +1 -0
  5. package/lib/api/impl/config/constants.d.ts.map +1 -1
  6. package/lib/api/impl/config/constants.js +2 -1
  7. package/lib/api/impl/config/constants.js.map +1 -1
  8. package/lib/api/impl/debug/index.d.ts.map +1 -1
  9. package/lib/api/impl/debug/index.js +69 -12
  10. package/lib/api/impl/debug/index.js.map +1 -1
  11. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  12. package/lib/api/impl/lodestar/index.js +28 -0
  13. package/lib/api/impl/lodestar/index.js.map +1 -1
  14. package/lib/api/impl/validator/index.d.ts.map +1 -1
  15. package/lib/api/impl/validator/index.js +3 -1
  16. package/lib/api/impl/validator/index.js.map +1 -1
  17. package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
  18. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  19. package/lib/chain/archiveStore/archiveStore.js +0 -4
  20. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  21. package/lib/chain/chain.d.ts.map +1 -1
  22. package/lib/chain/chain.js +8 -1
  23. package/lib/chain/chain.js.map +1 -1
  24. package/lib/chain/emitter.d.ts +2 -1
  25. package/lib/chain/emitter.d.ts.map +1 -1
  26. package/lib/chain/emitter.js.map +1 -1
  27. package/lib/chain/errors/blockError.d.ts +0 -7
  28. package/lib/chain/errors/blockError.d.ts.map +1 -1
  29. package/lib/chain/errors/blockError.js +0 -3
  30. package/lib/chain/errors/blockError.js.map +1 -1
  31. package/lib/chain/forkChoice/index.d.ts +4 -4
  32. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  33. package/lib/chain/forkChoice/index.js +10 -7
  34. package/lib/chain/forkChoice/index.js.map +1 -1
  35. package/lib/chain/options.d.ts.map +1 -1
  36. package/lib/chain/options.js +1 -0
  37. package/lib/chain/options.js.map +1 -1
  38. package/lib/chain/regen/interface.d.ts +0 -1
  39. package/lib/chain/regen/interface.d.ts.map +1 -1
  40. package/lib/chain/regen/queued.d.ts +0 -1
  41. package/lib/chain/regen/queued.d.ts.map +1 -1
  42. package/lib/chain/regen/queued.js +0 -4
  43. package/lib/chain/regen/queued.js.map +1 -1
  44. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +0 -5
  45. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  46. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -5
  47. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  48. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +1 -4
  49. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  50. package/lib/chain/stateCache/persistentCheckpointsCache.js +5 -2
  51. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  52. package/lib/chain/stateCache/types.d.ts +0 -2
  53. package/lib/chain/stateCache/types.d.ts.map +1 -1
  54. package/lib/chain/stateCache/types.js.map +1 -1
  55. package/lib/chain/validation/block.d.ts +5 -1
  56. package/lib/chain/validation/block.d.ts.map +1 -1
  57. package/lib/chain/validation/block.js +4 -14
  58. package/lib/chain/validation/block.js.map +1 -1
  59. package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
  60. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  61. package/lib/metrics/metrics/lodestar.d.ts +4 -0
  62. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  63. package/lib/metrics/metrics/lodestar.js +10 -0
  64. package/lib/metrics/metrics/lodestar.js.map +1 -1
  65. package/lib/network/gossip/topic.d.ts +19 -766
  66. package/lib/network/gossip/topic.d.ts.map +1 -1
  67. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  68. package/lib/network/processor/gossipHandlers.js +9 -2
  69. package/lib/network/processor/gossipHandlers.js.map +1 -1
  70. package/lib/network/processor/index.d.ts +2 -2
  71. package/lib/network/processor/index.d.ts.map +1 -1
  72. package/lib/network/processor/index.js +25 -23
  73. package/lib/network/processor/index.js.map +1 -1
  74. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  75. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +9 -5
  76. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  77. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  78. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +8 -3
  79. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  80. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -1
  81. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  82. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts +2 -1
  83. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  84. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +16 -6
  85. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  86. package/lib/network/reqresp/handlers/index.js +2 -2
  87. package/lib/network/reqresp/handlers/index.js.map +1 -1
  88. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  89. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -3
  90. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  91. package/lib/sync/types.d.ts +9 -1
  92. package/lib/sync/types.d.ts.map +1 -1
  93. package/lib/sync/types.js +9 -2
  94. package/lib/sync/types.js.map +1 -1
  95. package/lib/sync/unknownBlock.d.ts.map +1 -1
  96. package/lib/sync/unknownBlock.js +64 -30
  97. package/lib/sync/unknownBlock.js.map +1 -1
  98. package/lib/util/dataColumns.d.ts.map +1 -1
  99. package/lib/util/dataColumns.js +16 -11
  100. package/lib/util/dataColumns.js.map +1 -1
  101. package/package.json +15 -17
  102. package/src/api/impl/beacon/blocks/index.ts +13 -5
  103. package/src/api/impl/config/constants.ts +2 -0
  104. package/src/api/impl/debug/index.ts +73 -12
  105. package/src/api/impl/lodestar/index.ts +30 -0
  106. package/src/api/impl/validator/index.ts +3 -5
  107. package/src/chain/archiveStore/archiveStore.ts +0 -5
  108. package/src/chain/chain.ts +10 -1
  109. package/src/chain/emitter.ts +3 -2
  110. package/src/chain/errors/blockError.ts +0 -4
  111. package/src/chain/forkChoice/index.ts +13 -0
  112. package/src/chain/options.ts +1 -0
  113. package/src/chain/regen/interface.ts +0 -1
  114. package/src/chain/regen/queued.ts +0 -5
  115. package/src/chain/stateCache/fifoBlockStateCache.ts +0 -6
  116. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  117. package/src/chain/stateCache/types.ts +0 -2
  118. package/src/chain/validation/block.ts +12 -16
  119. package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
  120. package/src/metrics/metrics/lodestar.ts +11 -0
  121. package/src/network/processor/gossipHandlers.ts +9 -2
  122. package/src/network/processor/index.ts +27 -27
  123. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +12 -5
  124. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +11 -3
  125. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -1
  126. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +22 -6
  127. package/src/network/reqresp/handlers/index.ts +2 -2
  128. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +13 -3
  129. package/src/sync/types.ts +11 -2
  130. package/src/sync/unknownBlock.ts +70 -31
  131. package/src/util/dataColumns.ts +17 -12
@@ -280,6 +280,36 @@ export function getLodestarApi({
280
280
  };
281
281
  },
282
282
 
283
+ async getFastConfirmationInfo() {
284
+ const confirmedRoot = chain.forkChoice.getConfirmedRoot();
285
+ const confirmedBlock = chain.forkChoice.getConfirmedBlock();
286
+ const justifiedCheckpoint = chain.forkChoice.getJustifiedCheckpoint();
287
+ const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
288
+ const headRoot = chain.forkChoice.getHeadRoot();
289
+ const head = chain.forkChoice.getHead();
290
+
291
+ return {
292
+ data: {
293
+ confirmed: {
294
+ rootHex: confirmedRoot,
295
+ slot: confirmedBlock?.slot ?? null,
296
+ },
297
+ head: {
298
+ rootHex: headRoot,
299
+ slot: head.slot,
300
+ },
301
+ justifiedCheckpoint: {
302
+ rootHex: justifiedCheckpoint.rootHex,
303
+ epoch: justifiedCheckpoint.epoch,
304
+ },
305
+ finalizedCheckpoint: {
306
+ rootHex: finalizedCheckpoint.rootHex,
307
+ epoch: finalizedCheckpoint.epoch,
308
+ },
309
+ },
310
+ };
311
+ },
312
+
283
313
  async getAttesterSlashingsFromBlocks({signedBlocks}) {
284
314
  const attestations = new Map<Epoch, Attestation[]>();
285
315
 
@@ -927,11 +927,9 @@ export function getValidatorApi(
927
927
  // TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
928
928
  // the user control bid source preferences and value comparison. Also add external builder api
929
929
  // support when it is implemented.
930
- const builderBid = chain.executionPayloadBidPool.getBestBid(
931
- slot,
932
- parentBlock.executionPayloadBlockHash,
933
- parentBlockRootHex
934
- );
930
+ const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
931
+ const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
932
+ const builderBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
935
933
 
936
934
  const logCtx = {
937
935
  slot,
@@ -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);
@@ -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,
@@ -382,6 +382,14 @@ export class BeaconChain implements IBeaconChain {
382
382
  blockStateCache.setHeadState(anchorState);
383
383
  checkpointStateCache.add(checkpoint, anchorState);
384
384
 
385
+ const forkChoiceStateGetter: ForkChoiceStateGetter = ({stateRoot, checkpoint}) => {
386
+ if (stateRoot) return blockStateCache.get(stateRoot);
387
+
388
+ if (checkpoint) return checkpointStateCache.get({epoch: checkpoint.epoch, rootHex: checkpoint.rootHex});
389
+
390
+ return null;
391
+ };
392
+
385
393
  const forkChoice = initializeForkChoice(
386
394
  config,
387
395
  emitter,
@@ -390,6 +398,7 @@ export class BeaconChain implements IBeaconChain {
390
398
  isAnchorStateFinalized,
391
399
  opts,
392
400
  this.justifiedBalancesGetter.bind(this),
401
+ forkChoiceStateGetter,
393
402
  metrics,
394
403
  logger
395
404
  );
@@ -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}
@@ -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
 
@@ -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,
@@ -47,7 +47,6 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
47
47
  getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null;
48
48
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null;
49
49
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
50
- pruneOnFinalized(finalizedEpoch: Epoch): void;
51
50
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
52
51
  addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
53
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) => {
@@ -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
  }
@@ -35,8 +35,6 @@ async function validateExecutionPayloadEnvelope(
35
35
  // [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via
36
36
  // gossip or non-gossip sources) (a client MAY queue payload for processing once
37
37
  // the block is retrieved).
38
- // TODO GLOAS: Need to review this, we should queue the envelope for later
39
- // processing if the block is not yet known, otherwise we would ignore it here
40
38
  const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot);
41
39
  if (block === null) {
42
40
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
@@ -610,6 +610,11 @@ export function createLodestarMetrics(
610
610
  help: "The origination source of one of the BlockInputSync triggers",
611
611
  labelNames: ["source"],
612
612
  }),
613
+ payloadSource: register.counter<{source: BlockInputSource}>({
614
+ name: "lodestar_payload_input_sync_source_total",
615
+ help: "Count of payload (execution payload envelope) sync triggers, labeled by their source",
616
+ labelNames: ["source"],
617
+ }),
613
618
  pendingBlocks: register.gauge({
614
619
  name: "lodestar_sync_unknown_block_pending_blocks_size",
615
620
  help: "Current size of UnknownBlockSync pending blocks cache",
@@ -861,6 +866,12 @@ export function createLodestarMetrics(
861
866
  labelNames: ["numBlobs"],
862
867
  }),
863
868
 
869
+ skippedSlots: register.histogram({
870
+ name: "lodestar_gossip_block_skipped_slots",
871
+ help: "Number of skipped slots between a gossip block and its parent (blockSlot - parentSlot - 1)",
872
+ buckets: [0, 1, 2, 4, 8, 16, 32],
873
+ }),
874
+
864
875
  processBlockErrors: register.gauge<{error: BlockErrorCode | "NOT_BLOCK_ERROR"}>({
865
876
  name: "lodestar_gossip_block_process_block_errors",
866
877
  help: "Count of errors, by error type, while processing blocks",
@@ -185,7 +185,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
185
185
  peerIdStr,
186
186
  });
187
187
  try {
188
- await validateGossipBlock(config, chain, signedBlock, fork);
188
+ const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
189
189
 
190
190
  if (isForkPostGloas(fork)) {
191
191
  chain.seenPayloadEnvelopeInputCache.add({
@@ -205,8 +205,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
205
205
 
206
206
  metrics?.gossipBlock.gossipValidation.recvToValidation.observe(recvToValidation);
207
207
  metrics?.gossipBlock.gossipValidation.validationTime.observe(validationTime);
208
+ metrics?.gossipBlock.skippedSlots.observe(skippedSlots);
208
209
 
209
- logger.debug("Validated gossip block", {...blockInputMeta, ...logCtx, recvToValidation, validationTime});
210
+ logger.debug("Validated gossip block", {
211
+ ...blockInputMeta,
212
+ ...logCtx,
213
+ recvToValidation,
214
+ validationTime,
215
+ skippedSlots,
216
+ });
210
217
 
211
218
  chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex});
212
219
 
@@ -186,9 +186,11 @@ export class NetworkProcessor {
186
186
  // we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
187
187
  // to be stored in this Map and reprocessed once the block comes
188
188
  private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
189
+ private awaitingBlockMessageCount = 0;
189
190
  // we may not receive the payload for messages that require the FULL payload variant to be processed,
190
191
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
191
192
  private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
193
+ private awaitingPayloadMessageCount = 0;
192
194
  private unknownBlocksBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
193
195
  private unknownEnvelopesBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
194
196
 
@@ -228,8 +230,8 @@ export class NetworkProcessor {
228
230
  metrics.gossipValidationQueue.keySize.set({topic}, this.gossipQueues[topic].keySize);
229
231
  metrics.gossipValidationQueue.concurrency.set({topic}, this.gossipTopicConcurrency[topic]);
230
232
  }
231
- metrics.awaitingBlockGossipMessages.countPerSlot.set(this.unknownBlockGossipsubMessagesCount);
232
- metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.unknownPayloadGossipsubMessagesCount);
233
+ metrics.awaitingBlockGossipMessages.countPerSlot.set(this.awaitingBlockMessageCount);
234
+ metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.awaitingPayloadMessageCount);
233
235
  // specific metric for beacon_attestation topic
234
236
  metrics.gossipValidationQueue.keyAge.reset();
235
237
  for (const ageMs of this.gossipQueues.beacon_attestation.getDataAgeMs()) {
@@ -297,7 +299,7 @@ export class NetworkProcessor {
297
299
  return;
298
300
  }
299
301
  this.unknownEnvelopesBySlot.getOrDefault(slot).add(root);
300
- this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, peer, source});
302
+ this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
301
303
  }
302
304
 
303
305
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
@@ -497,7 +499,7 @@ export class NetworkProcessor {
497
499
  this.pushPendingGossipsubMessageToQueue(message);
498
500
  break;
499
501
  case PreprocessAction.AwaitBlock: {
500
- if (this.unknownBlockGossipsubMessagesCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
502
+ if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
501
503
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
502
504
  this.metrics?.awaitingBlockGossipMessages.reject.inc({
503
505
  reason: ReprocessRejectReason.reached_limit,
@@ -509,10 +511,11 @@ export class NetworkProcessor {
509
511
  this.metrics?.awaitingBlockGossipMessages.queue.inc({topic: topicType});
510
512
  const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
511
513
  awaitingGossipsubMessages.add(message);
514
+ this.awaitingBlockMessageCount++;
512
515
  break;
513
516
  }
514
517
  case PreprocessAction.AwaitEnvelope: {
515
- if (this.unknownPayloadGossipsubMessagesCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
518
+ if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
516
519
  this.metrics?.awaitingPayloadGossipMessages.reject.inc({
517
520
  reason: ReprocessRejectReason.reached_limit,
518
521
  topic: topicType,
@@ -525,6 +528,7 @@ export class NetworkProcessor {
525
528
  preprocessResult.root
526
529
  );
527
530
  awaitingPayloadGossipsubMessages.add(message);
531
+ this.awaitingPayloadMessageCount++;
528
532
  break;
529
533
  }
530
534
  }
@@ -548,6 +552,12 @@ export class NetworkProcessor {
548
552
  return;
549
553
  }
550
554
 
555
+ // Atomically remove from map and update counter before async iteration to
556
+ // prevent double-decrement race with onClockSlot during yield points below
557
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
558
+ this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
559
+ }
560
+
551
561
  const nowSec = Date.now() / 1000;
552
562
  let count = 0;
553
563
  // TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
@@ -567,8 +577,6 @@ export class NetworkProcessor {
567
577
  await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
568
578
  }
569
579
  }
570
-
571
- this.awaitingMessagesByBlockRoot.delete(rootHex);
572
580
  };
573
581
 
574
582
  private onPayloadEnvelopeProcessed = async ({blockRoot: rootHex}: {blockRoot: RootHex}): Promise<void> => {
@@ -577,6 +585,12 @@ export class NetworkProcessor {
577
585
  return;
578
586
  }
579
587
 
588
+ // Atomically remove from map and update counter before async iteration to
589
+ // prevent double-decrement race with onClockSlot during yield points below
590
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
591
+ this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
592
+ }
593
+
580
594
  const nowSec = Date.now() / 1000;
581
595
  let count = 0;
582
596
  for (const message of waitingGossipsubMessages) {
@@ -593,8 +607,6 @@ export class NetworkProcessor {
593
607
  await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
594
608
  }
595
609
  }
596
-
597
- this.awaitingMessagesByPayloadBlockRoot.delete(rootHex);
598
610
  };
599
611
 
600
612
  private onClockSlot = (clockSlot: Slot): void => {
@@ -618,7 +630,9 @@ export class NetworkProcessor {
618
630
  );
619
631
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
620
632
  }
621
- this.awaitingMessagesByBlockRoot.delete(rootHex);
633
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
634
+ this.awaitingBlockMessageCount -= gossipMessages.size;
635
+ }
622
636
  }
623
637
  }
624
638
  this.unknownBlocksBySlot.delete(slot);
@@ -641,7 +655,9 @@ export class NetworkProcessor {
641
655
  );
642
656
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
643
657
  }
644
- this.awaitingMessagesByPayloadBlockRoot.delete(rootHex);
658
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
659
+ this.awaitingPayloadMessageCount -= gossipMessages.size;
660
+ }
645
661
  }
646
662
  }
647
663
  this.unknownEnvelopesBySlot.delete(slot);
@@ -784,20 +800,4 @@ export class NetworkProcessor {
784
800
 
785
801
  return null;
786
802
  }
787
-
788
- private get unknownBlockGossipsubMessagesCount(): number {
789
- let count = 0;
790
- for (const messages of this.awaitingMessagesByBlockRoot.values()) {
791
- count += messages.size;
792
- }
793
- return count;
794
- }
795
-
796
- private get unknownPayloadGossipsubMessagesCount(): number {
797
- let count = 0;
798
- for (const messages of this.awaitingMessagesByPayloadBlockRoot.values()) {
799
- count += messages.size;
800
- }
801
- return count;
802
- }
803
803
  }
@@ -1,6 +1,6 @@
1
1
  import {PeerId} from "@libp2p/interface";
2
2
  import {BeaconConfig} from "@lodestar/config";
3
- import {GENESIS_SLOT, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
3
+ import {GENESIS_SLOT, isForkPostDeneb} from "@lodestar/params";
4
4
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
5
5
  import {computeEpochAtSlot} from "@lodestar/state-transition";
6
6
  import {deneb, phase0} from "@lodestar/types";
@@ -29,13 +29,20 @@ export async function* onBeaconBlocksByRange(
29
29
  // starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
30
30
  const archiveMaxSlot = finalizedSlot;
31
31
 
32
- const forkName = chain.config.getForkName(startSlot);
33
- if (isForkPostFulu(forkName) && startSlot < chain.earliestAvailableSlot) {
34
- chain.logger.verbose("Peer did not respect earliestAvailableSlot for BeaconBlocksByRange", {
32
+ // endSlot is exclusive, so highest served slot is endSlot - 1.
33
+ // Throw only when the entire requested range is below earliestAvailableSlot.
34
+ if (endSlot - 1 < chain.earliestAvailableSlot) {
35
+ chain.logger.verbose("Peer requested range before earliestAvailableSlot for BeaconBlocksByRange", {
35
36
  peer: prettyPrintPeerId(peerId),
36
37
  client: peerClient,
38
+ startSlot,
39
+ count,
40
+ earliestAvailableSlot: chain.earliestAvailableSlot,
37
41
  });
38
- return;
42
+ throw new ResponseError(
43
+ RespStatus.RESOURCE_UNAVAILABLE,
44
+ `Requested range is before earliestAvailableSlot startSlot=${startSlot} count=${count} earliestAvailableSlot=${chain.earliestAvailableSlot}`
45
+ );
39
46
  }
40
47
 
41
48
  // Finalized range of blocks
@@ -34,12 +34,20 @@ export async function* onDataColumnSidecarsByRange(
34
34
  return;
35
35
  }
36
36
 
37
- if (startSlot < chain.earliestAvailableSlot) {
38
- chain.logger.verbose("Peer did not respect earliestAvailableSlot for DataColumnSidecarsByRange", {
37
+ // endSlot is exclusive, so highest served slot is endSlot - 1.
38
+ // Throw only when the entire requested range is below earliestAvailableSlot.
39
+ if (endSlot - 1 < chain.earliestAvailableSlot) {
40
+ chain.logger.verbose("Peer requested range before earliestAvailableSlot for DataColumnSidecarsByRange", {
39
41
  peer: prettyPrintPeerId(peerId),
40
42
  client: peerClient,
43
+ startSlot,
44
+ count,
45
+ earliestAvailableSlot: chain.earliestAvailableSlot,
41
46
  });
42
- return;
47
+ throw new ResponseError(
48
+ RespStatus.RESOURCE_UNAVAILABLE,
49
+ `Requested range is before earliestAvailableSlot startSlot=${startSlot} count=${count} earliestAvailableSlot=${chain.earliestAvailableSlot}`
50
+ );
43
51
  }
44
52
 
45
53
  const finalized = db.dataColumnSidecarArchive;
@@ -30,7 +30,7 @@ export async function* onDataColumnSidecarsByRoot(
30
30
  const {blockRoot, columns: requestedColumns} = dataColumnsByRootIdentifier;
31
31
  const availableColumns = validateRequestedDataColumns(chain, requestedColumns);
32
32
  if (availableColumns.length === 0) {
33
- return;
33
+ continue;
34
34
  }
35
35
 
36
36
  const blockRootHex = toRootHex(blockRoot);