@lodestar/beacon-node 1.40.0-dev.4acd3ce568 → 1.40.0-dev.63c5c3e7f7

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 (136) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +8 -18
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/debug/index.d.ts +1 -1
  5. package/lib/api/impl/debug/index.d.ts.map +1 -1
  6. package/lib/api/impl/debug/index.js +3 -6
  7. package/lib/api/impl/debug/index.js.map +1 -1
  8. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  9. package/lib/api/impl/lodestar/index.js +5 -2
  10. package/lib/api/impl/lodestar/index.js.map +1 -1
  11. package/lib/api/impl/validator/index.d.ts.map +1 -1
  12. package/lib/api/impl/validator/index.js +9 -8
  13. package/lib/api/impl/validator/index.js.map +1 -1
  14. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/utils/archiveBlocks.js +4 -0
  16. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  17. package/lib/chain/blocks/blockInput/blockInput.d.ts +2 -0
  18. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  19. package/lib/chain/blocks/blockInput/blockInput.js +6 -0
  20. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  21. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  22. package/lib/chain/blocks/importBlock.js +2 -6
  23. package/lib/chain/blocks/importBlock.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts.map +1 -1
  25. package/lib/chain/blocks/index.js +0 -14
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/types.d.ts +0 -2
  28. package/lib/chain/blocks/types.d.ts.map +1 -1
  29. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlock.js +0 -7
  31. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  32. package/lib/chain/blocks/writeBlockInputToDb.d.ts +1 -4
  33. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  34. package/lib/chain/blocks/writeBlockInputToDb.js +20 -28
  35. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  36. package/lib/chain/chain.d.ts +14 -2
  37. package/lib/chain/chain.d.ts.map +1 -1
  38. package/lib/chain/chain.js +158 -10
  39. package/lib/chain/chain.js.map +1 -1
  40. package/lib/chain/interface.d.ts +14 -1
  41. package/lib/chain/interface.d.ts.map +1 -1
  42. package/lib/chain/interface.js.map +1 -1
  43. package/lib/chain/prepareNextSlot.js +6 -4
  44. package/lib/chain/prepareNextSlot.js.map +1 -1
  45. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  46. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  47. package/lib/chain/produceBlock/produceBlockBody.js +5 -3
  48. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  49. package/lib/chain/regen/interface.d.ts +2 -8
  50. package/lib/chain/regen/interface.d.ts.map +1 -1
  51. package/lib/chain/regen/interface.js +0 -1
  52. package/lib/chain/regen/interface.js.map +1 -1
  53. package/lib/chain/regen/queued.d.ts +1 -2
  54. package/lib/chain/regen/queued.d.ts.map +1 -1
  55. package/lib/chain/regen/queued.js +2 -16
  56. package/lib/chain/regen/queued.js.map +1 -1
  57. package/lib/chain/regen/regen.d.ts +3 -7
  58. package/lib/chain/regen/regen.d.ts.map +1 -1
  59. package/lib/chain/regen/regen.js +3 -16
  60. package/lib/chain/regen/regen.js.map +1 -1
  61. package/lib/chain/validation/blobSidecar.js +1 -1
  62. package/lib/chain/validation/blobSidecar.js.map +1 -1
  63. package/lib/chain/validation/dataColumnSidecar.js +1 -1
  64. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  65. package/lib/chain/validatorMonitor.d.ts +2 -0
  66. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  67. package/lib/chain/validatorMonitor.js +42 -3
  68. package/lib/chain/validatorMonitor.js.map +1 -1
  69. package/lib/metrics/metrics/lodestar.d.ts +7 -0
  70. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  71. package/lib/metrics/metrics/lodestar.js +24 -0
  72. package/lib/metrics/metrics/lodestar.js.map +1 -1
  73. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  74. package/lib/network/processor/gossipHandlers.js +0 -3
  75. package/lib/network/processor/gossipHandlers.js.map +1 -1
  76. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  77. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +2 -4
  78. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  79. package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts +1 -2
  80. package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts.map +1 -1
  81. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js +5 -26
  82. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js.map +1 -1
  83. package/lib/network/reqresp/handlers/blobSidecarsByRoot.d.ts +1 -2
  84. package/lib/network/reqresp/handlers/blobSidecarsByRoot.d.ts.map +1 -1
  85. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js +5 -7
  86. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js.map +1 -1
  87. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  88. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +1 -2
  89. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  90. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  91. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -5
  92. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  93. package/lib/network/reqresp/handlers/index.js +2 -2
  94. package/lib/network/reqresp/handlers/index.js.map +1 -1
  95. package/lib/sync/range/chain.d.ts.map +1 -1
  96. package/lib/sync/range/chain.js +0 -1
  97. package/lib/sync/range/chain.js.map +1 -1
  98. package/lib/sync/range/range.d.ts.map +1 -1
  99. package/lib/sync/range/range.js +0 -3
  100. package/lib/sync/range/range.js.map +1 -1
  101. package/lib/sync/unknownBlock.d.ts.map +1 -1
  102. package/lib/sync/unknownBlock.js +0 -3
  103. package/lib/sync/unknownBlock.js.map +1 -1
  104. package/package.json +15 -15
  105. package/src/api/impl/beacon/blocks/index.ts +8 -18
  106. package/src/api/impl/debug/index.ts +2 -6
  107. package/src/api/impl/lodestar/index.ts +6 -3
  108. package/src/api/impl/validator/index.ts +12 -11
  109. package/src/chain/archiveStore/utils/archiveBlocks.ts +4 -0
  110. package/src/chain/blocks/blockInput/blockInput.ts +8 -0
  111. package/src/chain/blocks/importBlock.ts +2 -6
  112. package/src/chain/blocks/index.ts +0 -19
  113. package/src/chain/blocks/types.ts +0 -2
  114. package/src/chain/blocks/verifyBlock.ts +0 -8
  115. package/src/chain/blocks/writeBlockInputToDb.ts +24 -30
  116. package/src/chain/chain.ts +180 -20
  117. package/src/chain/interface.ts +16 -0
  118. package/src/chain/prepareNextSlot.ts +6 -6
  119. package/src/chain/produceBlock/produceBlockBody.ts +7 -5
  120. package/src/chain/regen/interface.ts +1 -12
  121. package/src/chain/regen/queued.ts +3 -23
  122. package/src/chain/regen/regen.ts +4 -24
  123. package/src/chain/validation/blobSidecar.ts +1 -1
  124. package/src/chain/validation/dataColumnSidecar.ts +1 -1
  125. package/src/chain/validatorMonitor.ts +52 -3
  126. package/src/metrics/metrics/lodestar.ts +25 -0
  127. package/src/network/processor/gossipHandlers.ts +0 -3
  128. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +2 -4
  129. package/src/network/reqresp/handlers/beaconBlocksByRoot.ts +5 -32
  130. package/src/network/reqresp/handlers/blobSidecarsByRoot.ts +5 -9
  131. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +5 -2
  132. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -5
  133. package/src/network/reqresp/handlers/index.ts +2 -2
  134. package/src/sync/range/chain.ts +0 -1
  135. package/src/sync/range/range.ts +0 -3
  136. package/src/sync/unknownBlock.ts +0 -3
@@ -80,9 +80,8 @@ export class PrepareNextSlotScheduler {
80
80
  await sleep(this.config.getSlotComponentDurationMs(PREPARE_NEXT_SLOT_BPS), this.signal);
81
81
 
82
82
  // calling updateHead() here before we produce a block to reduce reorg possibility
83
- const {slot: headSlot, blockRoot: headRoot} = this.chain.recomputeForkChoiceHead(
84
- ForkchoiceCaller.prepareNextSlot
85
- );
83
+ const headBlock = this.chain.recomputeForkChoiceHead(ForkchoiceCaller.prepareNextSlot);
84
+ const {slot: headSlot, blockRoot: headRoot} = headBlock;
86
85
 
87
86
  // PS: previously this was comparing slots, but that gave no leway on the skipped
88
87
  // slots on epoch bounday. Making it more fluid.
@@ -112,7 +111,7 @@ export class PrepareNextSlotScheduler {
112
111
  // Pre Bellatrix: we only do precompute state transition for the last slot of epoch
113
112
  // For Bellatrix, we always do the `processSlots()` to prepare payload for the next slot
114
113
  const prepareState = await this.chain.regen.getBlockSlotState(
115
- headRoot,
114
+ headBlock,
116
115
  prepareSlot,
117
116
  // the slot 0 of next epoch will likely use this Previous Root Checkpoint state for state transition so we transfer cache here
118
117
  // the resulting state with cache will be cached in Checkpoint State Cache which is used for the upcoming block processing
@@ -129,7 +128,8 @@ export class PrepareNextSlotScheduler {
129
128
 
130
129
  if (feeRecipient) {
131
130
  // If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not
132
- const {slot: proposerHeadSlot, blockRoot: proposerHeadRoot} = this.chain.predictProposerHead(clockSlot);
131
+ const proposerHead = this.chain.predictProposerHead(clockSlot);
132
+ const {slot: proposerHeadSlot, blockRoot: proposerHeadRoot} = proposerHead;
133
133
 
134
134
  // If we predict we can reorg, update prepareState with proposer head block
135
135
  if (proposerHeadRoot !== headRoot || proposerHeadSlot !== headSlot) {
@@ -141,7 +141,7 @@ export class PrepareNextSlotScheduler {
141
141
  });
142
142
  this.metrics?.weakHeadDetected.inc();
143
143
  updatedPrepareState = (await this.chain.regen.getBlockSlotState(
144
- proposerHeadRoot,
144
+ proposerHead,
145
145
  prepareSlot,
146
146
  // only transfer cache if epoch transition because that's the state we will use to stateTransition() the 1st block of epoch
147
147
  {dontTransferCache: !isEpochTransition},
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
2
+ import {ProtoBlock, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
3
3
  import {
4
4
  ForkName,
5
5
  ForkPostBellatrix,
@@ -43,7 +43,7 @@ import {
43
43
  electra,
44
44
  fulu,
45
45
  } from "@lodestar/types";
46
- import {Logger, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
46
+ import {Logger, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
47
47
  import {ZERO_HASH_HEX} from "../../constants/index.js";
48
48
  import {numToQuantity} from "../../execution/engine/utils.js";
49
49
  import {
@@ -86,7 +86,7 @@ export type BlockAttributes = {
86
86
  randaoReveal: BLSSignature;
87
87
  graffiti: Bytes32;
88
88
  slot: Slot;
89
- parentBlockRoot: Root;
89
+ parentBlock: ProtoBlock;
90
90
  feeRecipient?: string;
91
91
  };
92
92
 
@@ -155,13 +155,14 @@ export async function produceBlockBody<T extends BlockType>(
155
155
  const {
156
156
  slot: blockSlot,
157
157
  feeRecipient: requestedFeeRecipient,
158
- parentBlockRoot,
158
+ parentBlock,
159
159
  proposerIndex,
160
160
  proposerPubKey,
161
161
  commonBlockBodyPromise,
162
162
  } = blockAttr;
163
163
  let executionPayloadValue: Wei;
164
164
  let blockBody: AssembledBodyType<T>;
165
+ const parentBlockRoot = fromHex(parentBlock.blockRoot);
165
166
  // even though shouldOverrideBuilder is relevant for the engine response, for simplicity of typing
166
167
  // we just return it undefined for the builder which anyway doesn't get consumed downstream
167
168
  let shouldOverrideBuilder: boolean | undefined;
@@ -637,7 +638,7 @@ export async function produceCommonBlockBody<T extends BlockType>(
637
638
  this: BeaconChain,
638
639
  blockType: T,
639
640
  currentState: CachedBeaconStateAllForks,
640
- {randaoReveal, graffiti, slot, parentBlockRoot}: BlockAttributes
641
+ {randaoReveal, graffiti, slot, parentBlock}: BlockAttributes
641
642
  ): Promise<CommonBlockBody> {
642
643
  const stepsMetrics =
643
644
  blockType === BlockType.Full
@@ -691,6 +692,7 @@ export async function produceCommonBlockBody<T extends BlockType>(
691
692
 
692
693
  const endSyncAggregate = stepsMetrics?.startTimer();
693
694
  if (ForkSeq[fork] >= ForkSeq.altair) {
695
+ const parentBlockRoot = fromHex(parentBlock.blockRoot);
694
696
  const previousSlot = slot - 1;
695
697
  const syncAggregate = this.syncContributionAndProofPool.getAggregate(previousSlot, parentBlockRoot);
696
698
  this.metrics?.production.producedSyncAggregateParticipants.observe(
@@ -26,7 +26,6 @@ export enum RegenFnName {
26
26
  getBlockSlotState = "getBlockSlotState",
27
27
  getState = "getState",
28
28
  getPreState = "getPreState",
29
- getCheckpointState = "getCheckpointState",
30
29
  }
31
30
 
32
31
  export type StateRegenerationOpts = {
@@ -63,21 +62,11 @@ export interface IStateRegeneratorInternal {
63
62
  rCaller: RegenCaller
64
63
  ): Promise<CachedBeaconStateAllForks>;
65
64
 
66
- /**
67
- * Return a valid checkpoint state
68
- * This will always return a state with `state.slot % SLOTS_PER_EPOCH === 0`
69
- */
70
- getCheckpointState(
71
- cp: phase0.Checkpoint,
72
- opts: StateRegenerationOpts,
73
- rCaller: RegenCaller
74
- ): Promise<CachedBeaconStateAllForks>;
75
-
76
65
  /**
77
66
  * Return the state of `blockRoot` processed to slot `slot`
78
67
  */
79
68
  getBlockSlotState(
80
- blockRoot: RootHex,
69
+ block: ProtoBlock,
81
70
  slot: Slot,
82
71
  opts: StateRegenerationOpts,
83
72
  rCaller: RegenCaller
@@ -5,7 +5,7 @@ import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
5
  import {Logger, toRootHex} from "@lodestar/utils";
6
6
  import {Metrics} from "../../metrics/index.js";
7
7
  import {JobItemQueue} from "../../util/queue/index.js";
8
- import {CheckpointHex, toCheckpointHex} from "../stateCache/index.js";
8
+ import {CheckpointHex} from "../stateCache/index.js";
9
9
  import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js";
10
10
  import {RegenError, RegenErrorCode} from "./errors.js";
11
11
  import {
@@ -220,24 +220,6 @@ export class QueuedStateRegenerator implements IStateRegenerator {
220
220
  return this.jobQueue.push({key: "getPreState", args: [block, opts, rCaller]});
221
221
  }
222
222
 
223
- async getCheckpointState(
224
- cp: phase0.Checkpoint,
225
- opts: StateRegenerationOpts,
226
- rCaller: RegenCaller
227
- ): Promise<CachedBeaconStateAllForks> {
228
- this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getCheckpointState});
229
-
230
- // First attempt to fetch the state from cache before queueing
231
- const checkpointState = this.checkpointStateCache.get(toCheckpointHex(cp));
232
- if (checkpointState) {
233
- return checkpointState;
234
- }
235
-
236
- // The state is not immediately available in the caches, enqueue the job
237
- this.metrics?.regenFnQueuedTotal.inc({caller: rCaller, entrypoint: RegenFnName.getCheckpointState});
238
- return this.jobQueue.push({key: "getCheckpointState", args: [cp, opts, rCaller]});
239
- }
240
-
241
223
  /**
242
224
  * Get state of provided `blockRoot` and dial forward to `slot`
243
225
  * Use this api with care because we don't want the queue to be busy
@@ -245,7 +227,7 @@ export class QueuedStateRegenerator implements IStateRegenerator {
245
227
  * @returns
246
228
  */
247
229
  async getBlockSlotState(
248
- blockRoot: RootHex,
230
+ block: ProtoBlock,
249
231
  slot: Slot,
250
232
  opts: StateRegenerationOpts,
251
233
  rCaller: RegenCaller
@@ -253,7 +235,7 @@ export class QueuedStateRegenerator implements IStateRegenerator {
253
235
  this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getBlockSlotState});
254
236
 
255
237
  // The state is not immediately available in the caches, enqueue the job
256
- return this.jobQueue.push({key: "getBlockSlotState", args: [blockRoot, slot, opts, rCaller]});
238
+ return this.jobQueue.push({key: "getBlockSlotState", args: [block, slot, opts, rCaller]});
257
239
  }
258
240
 
259
241
  async getState(stateRoot: RootHex, rCaller: RegenCaller): Promise<CachedBeaconStateAllForks> {
@@ -281,8 +263,6 @@ export class QueuedStateRegenerator implements IStateRegenerator {
281
263
  switch (regenRequest.key) {
282
264
  case "getPreState":
283
265
  return await this.regen.getPreState(...regenRequest.args);
284
- case "getCheckpointState":
285
- return await this.regen.getCheckpointState(...regenRequest.args);
286
266
  case "getBlockSlotState":
287
267
  return await this.regen.getBlockSlotState(...regenRequest.args);
288
268
  case "getState":
@@ -11,7 +11,7 @@ import {
11
11
  processSlots,
12
12
  stateTransition,
13
13
  } from "@lodestar/state-transition";
14
- import {BeaconBlock, RootHex, SignedBeaconBlock, Slot, phase0} from "@lodestar/types";
14
+ import {BeaconBlock, RootHex, SignedBeaconBlock, Slot} from "@lodestar/types";
15
15
  import {Logger, fromHex, toRootHex} from "@lodestar/utils";
16
16
  import {IBeaconDb} from "../../db/index.js";
17
17
  import {Metrics} from "../../metrics/index.js";
@@ -74,45 +74,24 @@ export class StateRegenerator implements IStateRegeneratorInternal {
74
74
  // We may have the checkpoint state with parent root inside the checkpoint state cache
75
75
  // through gossip validation.
76
76
  if (parentEpoch < blockEpoch) {
77
- return this.getCheckpointState({root: block.parentRoot, epoch: blockEpoch}, opts, regenCaller, allowDiskReload);
77
+ return this.getBlockSlotState(parentBlock, block.slot, opts, regenCaller, allowDiskReload);
78
78
  }
79
79
 
80
80
  // Otherwise, get the state normally.
81
81
  return this.getState(parentBlock.stateRoot, regenCaller, allowDiskReload);
82
82
  }
83
83
 
84
- /**
85
- * Get state after block `cp.root` dialed forward to first slot of `cp.epoch`
86
- */
87
- async getCheckpointState(
88
- cp: phase0.Checkpoint,
89
- opts: StateRegenerationOpts,
90
- regenCaller: RegenCaller,
91
- allowDiskReload = false
92
- ): Promise<CachedBeaconStateAllForks> {
93
- const checkpointStartSlot = computeStartSlotAtEpoch(cp.epoch);
94
- return this.getBlockSlotState(toRootHex(cp.root), checkpointStartSlot, opts, regenCaller, allowDiskReload);
95
- }
96
-
97
84
  /**
98
85
  * Get state after block `blockRoot` dialed forward to `slot`
99
86
  * - allowDiskReload should be used with care, as it will cause the state to be reloaded from disk
100
87
  */
101
88
  async getBlockSlotState(
102
- blockRoot: RootHex,
89
+ block: ProtoBlock,
103
90
  slot: Slot,
104
91
  opts: StateRegenerationOpts,
105
92
  regenCaller: RegenCaller,
106
93
  allowDiskReload = false
107
94
  ): Promise<CachedBeaconStateAllForks> {
108
- const block = this.modules.forkChoice.getBlockHex(blockRoot);
109
- if (!block) {
110
- throw new RegenError({
111
- code: RegenErrorCode.BLOCK_NOT_IN_FORKCHOICE,
112
- blockRoot,
113
- });
114
- }
115
-
116
95
  if (slot < block.slot) {
117
96
  throw new RegenError({
118
97
  code: RegenErrorCode.SLOT_BEFORE_BLOCK_SLOT,
@@ -121,6 +100,7 @@ export class StateRegenerator implements IStateRegeneratorInternal {
121
100
  });
122
101
  }
123
102
 
103
+ const {blockRoot} = block;
124
104
  const {checkpointStateCache} = this.modules;
125
105
  const epoch = computeEpochAtSlot(slot);
126
106
  const latestCheckpointStateCtx = allowDiskReload
@@ -124,7 +124,7 @@ export async function validateGossipBlobSidecar(
124
124
  // [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved).
125
125
  // [REJECT] The block's parent (defined by block.parent_root) passes validation.
126
126
  const blockState = await chain.regen
127
- .getBlockSlotState(parentRoot, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
127
+ .getBlockSlotState(parentBlock, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
128
128
  .catch(() => {
129
129
  throw new BlobSidecarGossipError(GossipAction.IGNORE, {
130
130
  code: BlobSidecarErrorCode.PARENT_UNKNOWN,
@@ -106,7 +106,7 @@ export async function validateGossipDataColumnSidecar(
106
106
  // this is something we should change this in the future to make the code airtight to the spec.
107
107
  // 7) [REJECT] The sidecar's block's parent passes validation.
108
108
  const blockState = await chain.regen
109
- .getBlockSlotState(parentRoot, blockHeader.slot, {dontTransferCache: true}, RegenCaller.validateGossipDataColumn)
109
+ .getBlockSlotState(parentBlock, blockHeader.slot, {dontTransferCache: true}, RegenCaller.validateGossipDataColumn)
110
110
  .catch(() => {
111
111
  throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
112
112
  code: DataColumnSidecarErrorCode.PARENT_UNKNOWN,
@@ -24,7 +24,7 @@ import {
24
24
  altair,
25
25
  deneb,
26
26
  } from "@lodestar/types";
27
- import {LogData, LogHandler, LogLevel, Logger, MapDef, MapDefMax, toRootHex} from "@lodestar/utils";
27
+ import {LogData, LogHandler, LogLevel, Logger, MapDef, MapDefMax, prettyPrintIndices, toRootHex} from "@lodestar/utils";
28
28
  import {GENESIS_SLOT} from "../constants/constants.js";
29
29
  import {RegistryMetricCreator} from "../metrics/index.js";
30
30
 
@@ -39,7 +39,8 @@ const MAX_CACHED_DISTINCT_TARGETS = 4;
39
39
  const LATE_ATTESTATION_SUBMISSION_BPS = 5000;
40
40
  const LATE_BLOCK_SUBMISSION_BPS = 2500;
41
41
 
42
- const RETAIN_REGISTERED_VALIDATORS_MS = 1 * 3600 * 1000; // 1 hour
42
+ /** Number of epochs to retain registered validators after their last registration */
43
+ const RETAIN_REGISTERED_VALIDATORS_EPOCHS = 2;
43
44
 
44
45
  type Seconds = number;
45
46
  export enum OpSource {
@@ -97,6 +98,8 @@ export type ValidatorMonitor = {
97
98
  ): void;
98
99
  onceEveryEndOfEpoch(state: CachedBeaconStateAllForks): void;
99
100
  scrapeMetrics(slotClock: Slot): void;
101
+ /** Returns the list of validator indices currently being monitored */
102
+ getMonitoredValidatorIndices(): ValidatorIndex[];
100
103
  };
101
104
 
102
105
  export type ValidatorMonitorOpts = {
@@ -284,6 +287,9 @@ export function createValidatorMonitor(
284
287
  logger[logLevel](message, context);
285
288
  };
286
289
 
290
+ // Calculate retain time dynamically based on slot duration (2 epochs)
291
+ const retainRegisteredValidatorsMs = SLOTS_PER_EPOCH * config.SLOT_DURATION_MS * RETAIN_REGISTERED_VALIDATORS_EPOCHS;
292
+
287
293
  /** The validators that require additional monitoring. */
288
294
  const validators = new MapDef<ValidatorIndex, MonitoredValidator>(() => ({
289
295
  summaries: new Map<Epoch, EpochSummary>(),
@@ -306,11 +312,19 @@ export function createValidatorMonitor(
306
312
 
307
313
  let lastRegisteredStatusEpoch = -1;
308
314
 
315
+ // Track validator additions/removals per epoch for logging
316
+ const addedValidatorsInEpoch: Set<ValidatorIndex> = new Set();
317
+ const removedValidatorsInEpoch: Set<ValidatorIndex> = new Set();
318
+
309
319
  const validatorMonitorMetrics = metricsRegister ? createValidatorMonitorMetrics(metricsRegister) : null;
310
320
 
311
321
  const validatorMonitor: ValidatorMonitor = {
312
322
  registerLocalValidator(index) {
323
+ const isNewValidator = !validators.has(index);
313
324
  validators.getOrDefault(index).lastRegisteredTimeMs = Date.now();
325
+ if (isNewValidator) {
326
+ addedValidatorsInEpoch.add(index);
327
+ }
314
328
  },
315
329
 
316
330
  registerLocalValidatorInSyncCommittee(index, untilEpoch) {
@@ -673,11 +687,29 @@ export function createValidatorMonitor(
673
687
 
674
688
  // Prune validators not seen in a while
675
689
  for (const [index, validator] of validators.entries()) {
676
- if (Date.now() - validator.lastRegisteredTimeMs > RETAIN_REGISTERED_VALIDATORS_MS) {
690
+ if (Date.now() - validator.lastRegisteredTimeMs > retainRegisteredValidatorsMs) {
677
691
  validators.delete(index);
692
+ removedValidatorsInEpoch.add(index);
678
693
  }
679
694
  }
680
695
 
696
+ // Log validator monitor status every epoch
697
+ const allIndices = Array.from(validators.keys()).sort((a, b) => a - b);
698
+ const addedIndices = Array.from(addedValidatorsInEpoch).sort((a, b) => a - b);
699
+ const removedIndices = Array.from(removedValidatorsInEpoch).sort((a, b) => a - b);
700
+
701
+ log("Validator monitor status", {
702
+ epoch: computeEpochAtSlot(headState.slot),
703
+ added: addedIndices.length > 0 ? prettyPrintIndices(addedIndices) : "none",
704
+ removed: removedIndices.length > 0 ? prettyPrintIndices(removedIndices) : "none",
705
+ total: validators.size,
706
+ indices: prettyPrintIndices(allIndices),
707
+ });
708
+
709
+ // Clear tracking sets for next epoch
710
+ addedValidatorsInEpoch.clear();
711
+ removedValidatorsInEpoch.clear();
712
+
681
713
  // Compute summaries of previous epoch attestation performance
682
714
  const prevEpoch = computeEpochAtSlot(headState.slot) - 1;
683
715
 
@@ -736,6 +768,13 @@ export function createValidatorMonitor(
736
768
  scrapeMetrics(slotClock) {
737
769
  validatorMonitorMetrics?.validatorsConnected.set(validators.size);
738
770
 
771
+ // Update static metric with connected validator indices
772
+ if (validatorMonitorMetrics?.validatorsConnectedIndices) {
773
+ validatorMonitorMetrics.validatorsConnectedIndices.reset();
774
+ const allIndices = Array.from(validators.keys()).sort((a, b) => a - b);
775
+ validatorMonitorMetrics.validatorsConnectedIndices.set({indices: prettyPrintIndices(allIndices)}, 1);
776
+ }
777
+
739
778
  const epoch = computeEpochAtSlot(slotClock);
740
779
  const slotInEpoch = slotClock % SLOTS_PER_EPOCH;
741
780
 
@@ -815,6 +854,10 @@ export function createValidatorMonitor(
815
854
  validatorMonitorMetrics?.prevEpochSyncCommitteeHits.set(prevEpochSyncCommitteeHits);
816
855
  validatorMonitorMetrics?.prevEpochSyncCommitteeMisses.set(prevEpochSyncCommitteeMisses);
817
856
  },
857
+
858
+ getMonitoredValidatorIndices() {
859
+ return Array.from(validators.keys()).sort((a, b) => a - b);
860
+ },
818
861
  };
819
862
 
820
863
  // Register a single collect() function to run all validatorMonitor metrics
@@ -1098,6 +1141,12 @@ function createValidatorMonitorMetrics(register: RegistryMetricCreator) {
1098
1141
  help: "Count of validators that are specifically monitored by this beacon node",
1099
1142
  }),
1100
1143
 
1144
+ validatorsConnectedIndices: register.gauge<{indices: string}>({
1145
+ name: "validator_monitor_indices",
1146
+ help: "Static metric with connected validator indices as label, value is always 1",
1147
+ labelNames: ["indices"],
1148
+ }),
1149
+
1101
1150
  validatorsInSyncCommittee: register.gauge({
1102
1151
  name: "validator_monitor_validators_in_sync_committee",
1103
1152
  help: "Count of validators monitored by this beacon node that are part of sync committee",
@@ -212,6 +212,31 @@ export function createLodestarMetrics(
212
212
  }),
213
213
  },
214
214
 
215
+ unfinalizedBlockWritesQueue: {
216
+ length: register.gauge({
217
+ name: "lodestar_unfinalized_block_writes_queue_length",
218
+ help: "Count of total unfinalized block writes queue length",
219
+ }),
220
+ droppedJobs: register.gauge({
221
+ name: "lodestar_unfinalized_block_writes_queue_dropped_jobs_total",
222
+ help: "Count of total unfinalized block writes queue dropped jobs",
223
+ }),
224
+ jobTime: register.histogram({
225
+ name: "lodestar_unfinalized_block_writes_queue_job_time_seconds",
226
+ help: "Time to process unfinalized block writes queue job in seconds",
227
+ buckets: [0.01, 0.1, 1, 4, 12],
228
+ }),
229
+ jobWaitTime: register.histogram({
230
+ name: "lodestar_unfinalized_block_writes_queue_job_wait_time_seconds",
231
+ help: "Time from job added to the unfinalized block writes queue to starting in seconds",
232
+ buckets: [0.01, 0.1, 1, 4, 12],
233
+ }),
234
+ concurrency: register.gauge({
235
+ name: "lodestar_unfinalized_block_writes_queue_concurrency",
236
+ help: "Current concurrency of unfinalized block writes queue",
237
+ }),
238
+ },
239
+
215
240
  engineHttpProcessorQueue: {
216
241
  length: register.gauge({
217
242
  name: "lodestar_engine_http_processor_queue_length",
@@ -436,14 +436,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
436
436
  blsVerifyOnMainThread: true,
437
437
  // to track block process steps
438
438
  seenTimestampSec,
439
- // gossip block is validated, we want to process it asap
440
- eagerPersistBlock: true,
441
439
  })
442
440
  .then(() => {
443
441
  // Returns the delay between the start of `block.slot` and `current time`
444
442
  const delaySec = chain.clock.secFromSlot(slot);
445
443
  metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
446
- chain.seenBlockInputCache.prune(blockInput.blockRootHex);
447
444
  })
448
445
  .catch((e) => {
449
446
  // Adjust verbosity based on error type
@@ -4,7 +4,6 @@ import {GENESIS_SLOT, isForkPostDeneb, isForkPostFulu} 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";
7
- import {fromHex} from "@lodestar/utils";
8
7
  import {IBeaconChain} from "../../../chain/index.js";
9
8
  import {IBeaconDb} from "../../../db/index.js";
10
9
  import {prettyPrintPeerId} from "../../util.ts";
@@ -22,7 +21,6 @@ export async function* onBeaconBlocksByRange(
22
21
  const endSlot = startSlot + count;
23
22
 
24
23
  const finalized = db.blockArchive;
25
- const unfinalized = db.block;
26
24
  // in the case of initializing from a non-finalized state, we don't have the finalized block so this api does not work
27
25
  // chain.forkChoice.getFinalizeBlock().slot
28
26
  const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
@@ -65,7 +63,7 @@ export async function* onBeaconBlocksByRange(
65
63
  // re-org there's no need to abort the request
66
64
  // Spec: https://github.com/ethereum/consensus-specs/blob/a1e46d1ae47dd9d097725801575b46907c12a1f8/specs/eip4844/p2p-interface.md#blobssidecarsbyrange-v1
67
65
 
68
- const blockBytes = await unfinalized.getBinary(fromHex(block.blockRoot));
66
+ const blockBytes = await chain.getSerializedBlockByRoot(block.blockRoot);
69
67
  if (!blockBytes) {
70
68
  throw new ResponseError(
71
69
  RespStatus.SERVER_ERROR,
@@ -74,7 +72,7 @@ export async function* onBeaconBlocksByRange(
74
72
  }
75
73
 
76
74
  yield {
77
- data: blockBytes,
75
+ data: blockBytes.block,
78
76
  boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(block.slot)),
79
77
  };
80
78
  }
@@ -1,48 +1,21 @@
1
1
  import {ResponseOutgoing} from "@lodestar/reqresp";
2
2
  import {computeEpochAtSlot} from "@lodestar/state-transition";
3
- import {Slot} from "@lodestar/types";
4
3
  import {toRootHex} from "@lodestar/utils";
5
4
  import {IBeaconChain} from "../../../chain/index.js";
6
- import {IBeaconDb} from "../../../db/index.js";
7
- import {getSlotFromSignedBeaconBlockSerialized} from "../../../util/sszBytes.js";
8
5
  import {BeaconBlocksByRootRequest} from "../../../util/types.js";
9
6
 
10
7
  export async function* onBeaconBlocksByRoot(
11
8
  requestBody: BeaconBlocksByRootRequest,
12
- chain: IBeaconChain,
13
- db: IBeaconDb
9
+ chain: IBeaconChain
14
10
  ): AsyncIterable<ResponseOutgoing> {
15
11
  for (const blockRoot of requestBody) {
16
12
  const root = blockRoot;
17
- const summary = chain.forkChoice.getBlock(root);
18
- let blockBytes: Uint8Array | null = null;
19
-
20
- // finalized block has summary in forkchoice but it stays in blockArchive db
21
- if (summary) {
22
- blockBytes = await db.block.getBinary(root);
23
- }
24
-
25
- let slot: Slot | undefined = undefined;
26
- if (!blockBytes) {
27
- const blockEntry = await db.blockArchive.getBinaryEntryByRoot(root);
28
- if (blockEntry) {
29
- slot = blockEntry.key;
30
- blockBytes = blockEntry.value;
31
- }
32
- }
33
-
34
- if (blockBytes) {
35
- if (slot === undefined) {
36
- const slotFromBytes = getSlotFromSignedBeaconBlockSerialized(blockBytes);
37
- if (slotFromBytes === null) {
38
- throw Error(`Invalid block bytes for block root ${toRootHex(root)}`);
39
- }
40
- slot = slotFromBytes;
41
- }
13
+ const block = await chain.getSerializedBlockByRoot(toRootHex(root));
42
14
 
15
+ if (block) {
43
16
  yield {
44
- data: blockBytes,
45
- boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(slot)),
17
+ data: block.block,
18
+ boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(block.slot)),
46
19
  };
47
20
  }
48
21
  }
@@ -2,16 +2,13 @@ import {BLOB_SIDECAR_FIXED_SIZE} from "@lodestar/params";
2
2
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
3
3
  import {computeEpochAtSlot} from "@lodestar/state-transition";
4
4
  import {RootHex} from "@lodestar/types";
5
- import {fromHex, toRootHex} from "@lodestar/utils";
5
+ import {toRootHex} from "@lodestar/utils";
6
6
  import {IBeaconChain} from "../../../chain/index.js";
7
- import {IBeaconDb} from "../../../db/index.js";
8
- import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../../../db/repositories/blobSidecars.js";
9
7
  import {BlobSidecarsByRootRequest} from "../../../util/types.js";
10
8
 
11
9
  export async function* onBlobSidecarsByRoot(
12
10
  requestBody: BlobSidecarsByRootRequest,
13
- chain: IBeaconChain,
14
- db: IBeaconDb
11
+ chain: IBeaconChain
15
12
  ): AsyncIterable<ResponseOutgoing> {
16
13
  const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
17
14
 
@@ -34,14 +31,13 @@ export async function* onBlobSidecarsByRoot(
34
31
 
35
32
  // Check if we need to load sidecars for a new block root
36
33
  if (lastFetchedSideCars === null || lastFetchedSideCars.blockRoot !== blockRootHex) {
37
- const blobSideCarsBytesWrapped = await db.blobSidecars.getBinary(fromHex(block.blockRoot));
38
- if (!blobSideCarsBytesWrapped) {
34
+ const blobSidecarsBytes = await chain.getSerializedBlobSidecars(block.slot, blockRootHex);
35
+ if (!blobSidecarsBytes) {
39
36
  // Handle the same to onBeaconBlocksByRange
40
37
  throw new ResponseError(RespStatus.SERVER_ERROR, `No item for root ${block.blockRoot} slot ${block.slot}`);
41
38
  }
42
- const blobSideCarsBytes = blobSideCarsBytesWrapped.slice(BLOB_SIDECARS_IN_WRAPPER_INDEX);
43
39
 
44
- lastFetchedSideCars = {blockRoot: blockRootHex, bytes: blobSideCarsBytes};
40
+ lastFetchedSideCars = {blockRoot: blockRootHex, bytes: blobSidecarsBytes};
45
41
  }
46
42
 
47
43
  const blobSidecarBytes = lastFetchedSideCars.bytes.slice(
@@ -38,7 +38,6 @@ export async function* onDataColumnSidecarsByRange(
38
38
  }
39
39
 
40
40
  const finalized = db.dataColumnSidecarArchive;
41
- const unfinalized = db.dataColumnSidecar;
42
41
  const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
43
42
 
44
43
  // Finalized range of columns
@@ -92,7 +91,11 @@ export async function* onDataColumnSidecarsByRange(
92
91
  // at the time of the start of the request. Spec is clear the chain of columns must be consistent, but on
93
92
  // re-org there's no need to abort the request
94
93
  // Spec: https://github.com/ethereum/consensus-specs/blob/ad36024441cf910d428d03f87f331fbbd2b3e5f1/specs/fulu/p2p-interface.md#L425-L429
95
- const dataColumnSidecars = await unfinalized.getManyBinary(fromHex(block.blockRoot), availableColumns);
94
+ const dataColumnSidecars = await chain.getSerializedDataColumnSidecars(
95
+ block.slot,
96
+ block.blockRoot,
97
+ availableColumns
98
+ );
96
99
 
97
100
  const unavailableColumnIndices: ColumnIndex[] = [];
98
101
  for (let i = 0; i < dataColumnSidecars.length; i++) {
@@ -61,11 +61,7 @@ export async function* onDataColumnSidecarsByRoot(
61
61
  continue;
62
62
  }
63
63
 
64
- const dataColumns = block
65
- ? // Non-finalized sidecars are stored by block root
66
- await db.dataColumnSidecar.getManyBinary(blockRoot, availableColumns)
67
- : // Finalized sidecars are archived and stored by slot
68
- await db.dataColumnSidecarArchive.getManyBinary(slot, availableColumns);
64
+ const dataColumns = await chain.getSerializedDataColumnSidecars(slot, blockRootHex, availableColumns);
69
65
 
70
66
  const unavailableColumnIndices: ColumnIndex[] = [];
71
67
  for (let i = 0; i < dataColumns.length; i++) {
@@ -42,12 +42,12 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
42
42
  [ReqRespMethod.BeaconBlocksByRoot]: (req) => {
43
43
  const fork = chain.config.getForkName(chain.clock.currentSlot);
44
44
  const body = BeaconBlocksByRootRequestType(fork, chain.config).deserialize(req.data);
45
- return onBeaconBlocksByRoot(body, chain, db);
45
+ return onBeaconBlocksByRoot(body, chain);
46
46
  },
47
47
  [ReqRespMethod.BlobSidecarsByRoot]: (req) => {
48
48
  const fork = chain.config.getForkName(chain.clock.currentSlot);
49
49
  const body = BlobSidecarsByRootRequestType(fork, chain.config).deserialize(req.data);
50
- return onBlobSidecarsByRoot(body, chain, db);
50
+ return onBlobSidecarsByRoot(body, chain);
51
51
  },
52
52
  [ReqRespMethod.BlobSidecarsByRange]: (req) => {
53
53
  const body = ssz.deneb.BlobSidecarsByRangeRequest.deserialize(req.data);
@@ -585,7 +585,6 @@ export class SyncChain {
585
585
 
586
586
  if (!res.err) {
587
587
  batch.processingSuccess();
588
- this.pruneBlockInputs(batch.getBlocks());
589
588
 
590
589
  // If the processed batch is not empty, validate previous AwaitingValidation blocks.
591
590
  if (blocks.length > 0) {
@@ -188,9 +188,6 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
188
188
  // when this runs, syncing is the most important thing and gossip is not likely to run
189
189
  // so we can utilize worker threads to verify signatures
190
190
  blsVerifyOnMainThread: false,
191
- // we want to be safe to only persist blocks after verifying it to avoid any attacks that may cause our DB
192
- // to grow too much
193
- eagerPersistBlock: false,
194
191
  };
195
192
 
196
193
  if (this.opts?.disableProcessAsChainSegment) {