@lodestar/beacon-node 1.36.0-dev.c7f3e8d129 → 1.36.0-dev.d4044b6621

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 (177) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +41 -22
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/lodestar/index.d.ts +5 -0
  5. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  6. package/lib/api/impl/lodestar/index.js +35 -10
  7. package/lib/api/impl/lodestar/index.js.map +1 -1
  8. package/lib/api/impl/node/utils.js +1 -1
  9. package/lib/api/impl/node/utils.js.map +1 -1
  10. package/lib/chain/archiveStore/archiveStore.d.ts +9 -0
  11. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/archiveStore.js +24 -0
  13. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  14. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +7 -0
  15. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  16. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +31 -5
  17. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  18. package/lib/chain/chain.d.ts +5 -2
  19. package/lib/chain/chain.d.ts.map +1 -1
  20. package/lib/chain/chain.js +32 -16
  21. package/lib/chain/chain.js.map +1 -1
  22. package/lib/chain/errors/dataColumnSidecarError.d.ts +23 -13
  23. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  24. package/lib/chain/errors/dataColumnSidecarError.js +5 -0
  25. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  26. package/lib/chain/forkChoice/index.d.ts +9 -1
  27. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  28. package/lib/chain/forkChoice/index.js +109 -4
  29. package/lib/chain/forkChoice/index.js.map +1 -1
  30. package/lib/chain/interface.d.ts +2 -0
  31. package/lib/chain/interface.d.ts.map +1 -1
  32. package/lib/chain/options.d.ts +0 -2
  33. package/lib/chain/options.d.ts.map +1 -1
  34. package/lib/chain/options.js +2 -2
  35. package/lib/chain/options.js.map +1 -1
  36. package/lib/chain/stateCache/datastore/db.d.ts +12 -0
  37. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  38. package/lib/chain/stateCache/datastore/db.js +70 -0
  39. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  40. package/lib/chain/stateCache/datastore/file.d.ts +1 -0
  41. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  42. package/lib/chain/stateCache/datastore/file.js +7 -0
  43. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  44. package/lib/chain/stateCache/datastore/types.d.ts +1 -0
  45. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  46. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +16 -1
  47. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  48. package/lib/chain/stateCache/persistentCheckpointsCache.js +31 -1
  49. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  50. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  51. package/lib/chain/validation/dataColumnSidecar.js +45 -17
  52. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  53. package/lib/index.d.ts +2 -0
  54. package/lib/index.d.ts.map +1 -1
  55. package/lib/index.js +2 -0
  56. package/lib/index.js.map +1 -1
  57. package/lib/metrics/metrics/lodestar.d.ts +10 -0
  58. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  59. package/lib/metrics/metrics/lodestar.js +15 -1
  60. package/lib/metrics/metrics/lodestar.js.map +1 -1
  61. package/lib/network/core/networkCore.d.ts.map +1 -1
  62. package/lib/network/core/networkCore.js +5 -1
  63. package/lib/network/core/networkCore.js.map +1 -1
  64. package/lib/network/core/networkCoreWorker.js +8 -8
  65. package/lib/network/core/networkCoreWorker.js.map +1 -1
  66. package/lib/network/core/networkCoreWorkerHandler.js +1 -1
  67. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  68. package/lib/network/discv5/worker.js +2 -7
  69. package/lib/network/discv5/worker.js.map +1 -1
  70. package/lib/network/events.d.ts +1 -0
  71. package/lib/network/events.d.ts.map +1 -1
  72. package/lib/network/gossip/encoding.js +1 -1
  73. package/lib/network/gossip/encoding.js.map +1 -1
  74. package/lib/network/gossip/snappy_bun.d.ts +3 -0
  75. package/lib/network/gossip/snappy_bun.d.ts.map +1 -0
  76. package/lib/network/gossip/snappy_bun.js +3 -0
  77. package/lib/network/gossip/snappy_bun.js.map +1 -0
  78. package/lib/network/metadata.d.ts +1 -1
  79. package/lib/network/metadata.d.ts.map +1 -1
  80. package/lib/network/metadata.js +1 -0
  81. package/lib/network/metadata.js.map +1 -1
  82. package/lib/network/options.d.ts +0 -1
  83. package/lib/network/options.d.ts.map +1 -1
  84. package/lib/network/options.js.map +1 -1
  85. package/lib/network/peers/discover.js +2 -2
  86. package/lib/network/peers/discover.js.map +1 -1
  87. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  88. package/lib/network/processor/gossipHandlers.js +14 -8
  89. package/lib/network/processor/gossipHandlers.js.map +1 -1
  90. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  91. package/lib/network/reqresp/ReqRespBeaconNode.js +3 -1
  92. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  93. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts +2 -1
  94. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  95. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -3
  96. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  97. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts +2 -1
  98. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  99. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +9 -1
  100. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  101. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts +2 -1
  102. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  103. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +9 -1
  104. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  105. package/lib/network/reqresp/handlers/index.js +6 -6
  106. package/lib/network/reqresp/handlers/index.js.map +1 -1
  107. package/lib/network/reqresp/types.d.ts +1 -0
  108. package/lib/network/reqresp/types.d.ts.map +1 -1
  109. package/lib/node/nodejs.d.ts +2 -1
  110. package/lib/node/nodejs.d.ts.map +1 -1
  111. package/lib/node/nodejs.js +2 -1
  112. package/lib/node/nodejs.js.map +1 -1
  113. package/lib/sync/range/range.d.ts.map +1 -1
  114. package/lib/sync/range/range.js +2 -1
  115. package/lib/sync/range/range.js.map +1 -1
  116. package/lib/sync/utils/downloadByRange.d.ts +58 -13
  117. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  118. package/lib/sync/utils/downloadByRange.js +201 -82
  119. package/lib/sync/utils/downloadByRange.js.map +1 -1
  120. package/lib/sync/utils/remoteSyncType.d.ts +2 -1
  121. package/lib/sync/utils/remoteSyncType.d.ts.map +1 -1
  122. package/lib/sync/utils/remoteSyncType.js +19 -4
  123. package/lib/sync/utils/remoteSyncType.js.map +1 -1
  124. package/lib/util/blobs.d.ts +1 -1
  125. package/lib/util/blobs.d.ts.map +1 -1
  126. package/lib/util/blobs.js +53 -20
  127. package/lib/util/blobs.js.map +1 -1
  128. package/lib/util/profile.d.ts +6 -4
  129. package/lib/util/profile.d.ts.map +1 -1
  130. package/lib/util/profile.js +40 -3
  131. package/lib/util/profile.js.map +1 -1
  132. package/lib/util/sszBytes.d.ts +2 -0
  133. package/lib/util/sszBytes.d.ts.map +1 -1
  134. package/lib/util/sszBytes.js +25 -0
  135. package/lib/util/sszBytes.js.map +1 -1
  136. package/package.json +31 -24
  137. package/src/api/impl/beacon/blocks/index.ts +47 -25
  138. package/src/api/impl/lodestar/index.ts +42 -10
  139. package/src/api/impl/node/utils.ts +1 -1
  140. package/src/chain/archiveStore/archiveStore.ts +27 -0
  141. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +32 -5
  142. package/src/chain/chain.ts +48 -23
  143. package/src/chain/errors/dataColumnSidecarError.ts +27 -13
  144. package/src/chain/forkChoice/index.ts +178 -2
  145. package/src/chain/interface.ts +2 -0
  146. package/src/chain/options.ts +2 -3
  147. package/src/chain/stateCache/datastore/db.ts +89 -1
  148. package/src/chain/stateCache/datastore/file.ts +8 -0
  149. package/src/chain/stateCache/datastore/types.ts +1 -0
  150. package/src/chain/stateCache/persistentCheckpointsCache.ts +45 -2
  151. package/src/chain/validation/dataColumnSidecar.ts +54 -19
  152. package/src/index.ts +2 -0
  153. package/src/metrics/metrics/lodestar.ts +18 -1
  154. package/src/network/core/networkCore.ts +5 -1
  155. package/src/network/core/networkCoreWorker.ts +9 -9
  156. package/src/network/core/networkCoreWorkerHandler.ts +1 -1
  157. package/src/network/discv5/worker.ts +2 -7
  158. package/src/network/events.ts +1 -1
  159. package/src/network/gossip/encoding.ts +1 -1
  160. package/src/network/gossip/snappy_bun.ts +2 -0
  161. package/src/network/metadata.ts +3 -1
  162. package/src/network/options.ts +0 -1
  163. package/src/network/peers/discover.ts +2 -2
  164. package/src/network/processor/gossipHandlers.ts +16 -7
  165. package/src/network/reqresp/ReqRespBeaconNode.ts +3 -1
  166. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +18 -3
  167. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +13 -1
  168. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +13 -1
  169. package/src/network/reqresp/handlers/index.ts +6 -6
  170. package/src/network/reqresp/types.ts +1 -0
  171. package/src/node/nodejs.ts +3 -0
  172. package/src/sync/range/range.ts +2 -1
  173. package/src/sync/utils/downloadByRange.ts +259 -103
  174. package/src/sync/utils/remoteSyncType.ts +23 -4
  175. package/src/util/blobs.ts +64 -20
  176. package/src/util/profile.ts +45 -3
  177. package/src/util/sszBytes.ts +30 -0
@@ -1,5 +1,3 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import {Tree} from "@chainsafe/persistent-merkle-tree";
4
2
  import {routes} from "@lodestar/api";
5
3
  import {ApplicationMethods} from "@lodestar/api/server";
@@ -8,13 +6,16 @@ import {Repository} from "@lodestar/db";
8
6
  import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
9
7
  import {BeaconStateCapella, getLatestWeakSubjectivityCheckpointEpoch, loadState} from "@lodestar/state-transition";
10
8
  import {ssz} from "@lodestar/types";
11
- import {toHex, toRootHex} from "@lodestar/utils";
9
+ import {Checkpoint} from "@lodestar/types/phase0";
10
+ import {fromHex, toHex, toRootHex} from "@lodestar/utils";
12
11
  import {BeaconChain} from "../../../chain/index.js";
13
12
  import {QueuedStateRegenerator, RegenRequest} from "../../../chain/regen/index.js";
14
13
  import {IBeaconDb} from "../../../db/interface.js";
15
14
  import {GossipType} from "../../../network/index.js";
16
- import {profileNodeJS, writeHeapSnapshot} from "../../../util/profile.js";
15
+ import {getStateSlotFromBytes} from "../../../util/multifork.js";
16
+ import {ProfileThread, profileThread, writeHeapSnapshot} from "../../../util/profile.js";
17
17
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
18
+ import {ApiError} from "../errors.js";
18
19
  import {ApiModules} from "../types.js";
19
20
 
20
21
  export function getLodestarApi({
@@ -26,7 +27,9 @@ export function getLodestarApi({
26
27
  }: Pick<ApiModules, "chain" | "config" | "db" | "network" | "sync">): ApplicationMethods<routes.lodestar.Endpoints> {
27
28
  let writingHeapdump = false;
28
29
  let writingProfile = false;
29
- const defaultProfileMs = SLOTS_PER_EPOCH * config.SLOT_DURATION_MS;
30
+ // for NodeJS, profile the whole epoch
31
+ // for Bun, profile 1 slot. Otherwise it will either crash the app, and/or inspector cannot render the profile
32
+ const defaultProfileMs = globalThis.Bun ? config.SLOT_DURATION_MS : SLOTS_PER_EPOCH * config.SLOT_DURATION_MS;
30
33
 
31
34
  return {
32
35
  async writeHeapdump({thread = "main", dirpath = "."}) {
@@ -63,7 +66,6 @@ export function getLodestarApi({
63
66
 
64
67
  try {
65
68
  let filepath: string;
66
- let profile: string;
67
69
  switch (thread) {
68
70
  case "network":
69
71
  filepath = await network.writeNetworkThreadProfile(duration, dirpath);
@@ -73,12 +75,10 @@ export function getLodestarApi({
73
75
  break;
74
76
  default:
75
77
  // main thread
76
- profile = await profileNodeJS(duration);
77
- filepath = path.join(dirpath, `main_thread_${new Date().toISOString()}.cpuprofile`);
78
- fs.writeFileSync(filepath, profile);
78
+ filepath = await profileThread(ProfileThread.MAIN, duration, dirpath);
79
79
  break;
80
80
  }
81
- return {data: {filepath}};
81
+ return {data: {result: filepath}};
82
82
  } finally {
83
83
  writingProfile = false;
84
84
  }
@@ -218,6 +218,26 @@ export function getLodestarApi({
218
218
  meta: {executionOptimistic, finalized, version: fork},
219
219
  };
220
220
  },
221
+
222
+ // the optional checkpoint is in root:epoch format
223
+ async getPersistedCheckpointState({checkpointId}) {
224
+ const checkpoint = checkpointId ? getCheckpointFromArg(checkpointId) : undefined;
225
+ const stateBytes = await chain.getPersistedCheckpointState(checkpoint);
226
+ if (stateBytes === null) {
227
+ throw new ApiError(
228
+ 404,
229
+ checkpointId ? `Checkpoint state not found for id ${checkpointId}` : "Latest safe checkpoint state not found"
230
+ );
231
+ }
232
+
233
+ const slot = getStateSlotFromBytes(stateBytes);
234
+ return {
235
+ data: stateBytes,
236
+ meta: {
237
+ version: config.getForkName(slot),
238
+ },
239
+ };
240
+ },
221
241
  };
222
242
  }
223
243
 
@@ -247,6 +267,18 @@ function regenRequestToJson(config: ChainForkConfig, regenRequest: RegenRequest)
247
267
  }
248
268
  }
249
269
 
270
+ const CHECKPOINT_REGEX = /^(?:0x)?([0-9a-f]{64}):([0-9]+)$/;
271
+ /**
272
+ * Extract a checkpoint from a string in the format `rootHex:epoch`.
273
+ */
274
+ export function getCheckpointFromArg(checkpointStr: string): Checkpoint {
275
+ const match = CHECKPOINT_REGEX.exec(checkpointStr.toLowerCase());
276
+ if (!match) {
277
+ throw new ApiError(400, `Could not parse checkpoint string: ${checkpointStr}`);
278
+ }
279
+ return {root: fromHex(match[1]), epoch: parseInt(match[2])};
280
+ }
281
+
250
282
  function stringifyKeys(keys: (Uint8Array | number | string)[]): string[] {
251
283
  return keys.map((key) => {
252
284
  if (key instanceof Uint8Array) {
@@ -10,7 +10,7 @@ export function formatNodePeer(peerIdStr: string, connections: Connection[]): ro
10
10
  return {
11
11
  peerId: conn ? conn.remotePeer.toString() : peerIdStr,
12
12
  // TODO: figure out how to get enr of peer
13
- enr: "",
13
+ enr: null,
14
14
  lastSeenP2pAddress: conn ? conn.remoteAddr.toString() : "",
15
15
  direction: conn ? (conn.direction as routes.node.PeerDirection) : null,
16
16
  state: conn ? getPeerState(conn.status) : "disconnected",
@@ -25,6 +25,16 @@ type ArchiveStoreModules = {
25
25
 
26
26
  type ArchiveStoreInitOpts = ArchiveStoreOpts & {dbName: string; anchorState: {finalizedCheckpoint: Checkpoint}};
27
27
 
28
+ export enum ArchiveStoreTask {
29
+ ArchiveBlocks = "archive_blocks",
30
+ PruneHistory = "prune_history",
31
+ OnFinalizedCheckpoint = "on_finalized_checkpoint",
32
+ MaybeArchiveState = "maybe_archive_state",
33
+ RegenPruneOnFinalized = "regen_prune_on_finalized",
34
+ ForkchoicePrune = "forkchoice_prune",
35
+ UpdateBackfillRange = "update_backfill_range",
36
+ }
37
+
28
38
  /**
29
39
  * Used for running tasks that depends on some events or are executed
30
40
  * periodically.
@@ -176,6 +186,8 @@ export class ArchiveStore {
176
186
  try {
177
187
  const finalizedEpoch = finalized.epoch;
178
188
  this.logger.verbose("Start processing finalized checkpoint", {epoch: finalizedEpoch, rootHex: finalized.rootHex});
189
+
190
+ let timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
179
191
  await archiveBlocks(
180
192
  this.chain.config,
181
193
  this.db,
@@ -188,7 +200,10 @@ export class ArchiveStore {
188
200
  this.chain.opts.persistOrphanedBlocks,
189
201
  this.chain.opts.persistOrphanedBlocksDir
190
202
  );
203
+ timer?.({source: ArchiveStoreTask.ArchiveBlocks});
204
+
191
205
  if (this.opts.pruneHistory) {
206
+ timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
192
207
  await pruneHistory(
193
208
  this.chain.config,
194
209
  this.db,
@@ -197,18 +212,30 @@ export class ArchiveStore {
197
212
  finalizedEpoch,
198
213
  this.chain.clock.currentEpoch
199
214
  );
215
+ timer?.({source: ArchiveStoreTask.PruneHistory});
200
216
  }
201
217
 
218
+ timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
202
219
  await this.statesArchiverStrategy.onFinalizedCheckpoint(finalized, this.metrics);
220
+ timer?.({source: ArchiveStoreTask.OnFinalizedCheckpoint});
203
221
 
204
222
  // should be after ArchiveBlocksTask to handle restart cleanly
223
+ timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
205
224
  await this.statesArchiverStrategy.maybeArchiveState(finalized, this.metrics);
225
+ timer?.({source: ArchiveStoreTask.MaybeArchiveState});
206
226
 
227
+ timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
207
228
  this.chain.regen.pruneOnFinalized(finalizedEpoch);
229
+ timer?.({source: ArchiveStoreTask.RegenPruneOnFinalized});
208
230
 
209
231
  // tasks rely on extended fork choice
232
+ timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
210
233
  const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex);
234
+ timer?.({source: ArchiveStoreTask.ForkchoicePrune});
235
+
236
+ timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
211
237
  await updateBackfillRange({chain: this.chain, db: this.db, logger: this.logger}, finalized);
238
+ timer?.({source: ArchiveStoreTask.UpdateBackfillRange});
212
239
 
213
240
  this.logger.verbose("Finish processing finalized checkpoint", {
214
241
  epoch: finalizedEpoch,
@@ -17,6 +17,15 @@ import {StateArchiveStrategy, StatesArchiveOpts} from "../interface.js";
17
17
  */
18
18
  export const PERSIST_TEMP_STATE_EVERY_EPOCHS = 32;
19
19
 
20
+ export enum FrequencyStateArchiveStep {
21
+ LoadLastStoredSlot = "load_last_stored_slot",
22
+ GetFinalizedState = "get_finalized_state",
23
+ // SerializeState is tracked via stateSerializeDuration metric
24
+ PersistState = "persist_state",
25
+ LoadStoredSlotsToDelete = "load_stored_slots_to_delete",
26
+ DeleteOldStates = "delete_old_states",
27
+ }
28
+
20
29
  /**
21
30
  * Archives finalized states from active bucket to archive bucket.
22
31
  *
@@ -47,11 +56,16 @@ export class FrequencyStateArchiveStrategy implements StateArchiveStrategy {
47
56
  * ```
48
57
  */
49
58
  async maybeArchiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise<void> {
59
+ let timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
50
60
  const lastStoredSlot = await this.db.stateArchive.lastKey();
61
+ timer?.({step: FrequencyStateArchiveStep.LoadLastStoredSlot});
62
+
51
63
  const lastStoredEpoch = computeEpochAtSlot(lastStoredSlot ?? 0);
52
64
  const {archiveStateEpochFrequency} = this.opts;
53
65
 
66
+ const logCtx = {finalizedEpoch: finalized.epoch, lastStoredEpoch, archiveStateEpochFrequency};
54
67
  if (finalized.epoch - lastStoredEpoch >= Math.min(PERSIST_TEMP_STATE_EVERY_EPOCHS, archiveStateEpochFrequency)) {
68
+ this.logger.verbose("Start archiving state", logCtx);
55
69
  await this.archiveState(finalized, metrics);
56
70
 
57
71
  // Only check the current and previous intervals
@@ -60,23 +74,29 @@ export class FrequencyStateArchiveStrategy implements StateArchiveStrategy {
60
74
  (Math.floor(finalized.epoch / archiveStateEpochFrequency) - 1) * archiveStateEpochFrequency
61
75
  );
62
76
 
77
+ timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
63
78
  const storedStateSlots = await this.db.stateArchive.keys({
64
79
  lt: computeStartSlotAtEpoch(finalized.epoch),
65
80
  gte: computeStartSlotAtEpoch(minEpoch),
66
81
  });
82
+ timer?.({step: FrequencyStateArchiveStep.LoadStoredSlotsToDelete});
67
83
 
68
84
  const statesSlotsToDelete = computeStateSlotsToDelete(storedStateSlots, archiveStateEpochFrequency);
85
+ timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
69
86
  if (statesSlotsToDelete.length > 0) {
70
87
  await this.db.stateArchive.batchDelete(statesSlotsToDelete);
71
88
  }
89
+ timer?.({step: FrequencyStateArchiveStep.DeleteOldStates});
72
90
 
73
91
  // More logs to investigate the rss spike issue https://github.com/ChainSafe/lodestar/issues/5591
74
92
  this.logger.verbose("Archived state completed", {
75
- finalizedEpoch: finalized.epoch,
93
+ ...logCtx,
76
94
  minEpoch,
77
95
  storedStateSlots: storedStateSlots.join(","),
78
96
  statesSlotsToDelete: statesSlotsToDelete.join(","),
79
97
  });
98
+ } else {
99
+ this.logger.verbose("Skip archiving state", logCtx);
80
100
  }
81
101
  }
82
102
 
@@ -86,24 +106,31 @@ export class FrequencyStateArchiveStrategy implements StateArchiveStrategy {
86
106
  */
87
107
  async archiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise<void> {
88
108
  // starting from Mar 2024, the finalized state could be from disk or in memory
109
+ let timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
89
110
  const finalizedStateOrBytes = await this.regen.getCheckpointStateOrBytes(finalized);
111
+ timer?.({step: FrequencyStateArchiveStep.GetFinalizedState});
112
+
90
113
  const {rootHex} = finalized;
91
114
  if (!finalizedStateOrBytes) {
92
115
  throw Error(`No state in cache for finalized checkpoint state epoch #${finalized.epoch} root ${rootHex}`);
93
116
  }
94
117
  if (finalizedStateOrBytes instanceof Uint8Array) {
95
118
  const slot = getStateSlotFromBytes(finalizedStateOrBytes);
119
+ timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
96
120
  await this.db.stateArchive.putBinary(slot, finalizedStateOrBytes);
121
+ timer?.({step: FrequencyStateArchiveStep.PersistState});
97
122
  this.logger.verbose("Archived finalized state bytes", {epoch: finalized.epoch, slot, root: rootHex});
98
123
  } else {
99
124
  // serialize state using BufferPool if provided
100
- const timer = metrics?.stateSerializeDuration.startTimer({source: AllocSource.ARCHIVE_STATE});
125
+ const sszTimer = metrics?.stateSerializeDuration.startTimer({source: AllocSource.ARCHIVE_STATE});
101
126
  await serializeState(
102
127
  finalizedStateOrBytes,
103
128
  AllocSource.ARCHIVE_STATE,
104
- (stateBytes) => {
105
- timer?.();
106
- return this.db.stateArchive.putBinary(finalizedStateOrBytes.slot, stateBytes);
129
+ async (stateBytes) => {
130
+ sszTimer?.();
131
+ timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
132
+ await this.db.stateArchive.putBinary(finalizedStateOrBytes.slot, stateBytes);
133
+ timer?.({step: FrequencyStateArchiveStep.PersistState});
107
134
  },
108
135
  this.bufferPool
109
136
  );
@@ -37,6 +37,7 @@ import {
37
37
  ValidatorIndex,
38
38
  Wei,
39
39
  isBlindedBeaconBlock,
40
+ phase0,
40
41
  } from "@lodestar/types";
41
42
  import {Logger, fromHex, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
42
43
  import {ProcessShutdownCallback} from "@lodestar/validator";
@@ -95,11 +96,13 @@ import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
95
96
  import {SeenBlockInput} from "./seenCache/seenGossipBlockInput.js";
96
97
  import {ShufflingCache} from "./shufflingCache.js";
97
98
  import {BlockStateCacheImpl} from "./stateCache/blockStateCacheImpl.js";
98
- import {DbCPStateDatastore} from "./stateCache/datastore/db.js";
99
+ import {DbCPStateDatastore, checkpointToDatastoreKey} from "./stateCache/datastore/db.js";
99
100
  import {FileCPStateDatastore} from "./stateCache/datastore/file.js";
101
+ import {CPStateDatastore} from "./stateCache/datastore/types.js";
100
102
  import {FIFOBlockStateCache} from "./stateCache/fifoBlockStateCache.js";
101
103
  import {InMemoryCheckpointStateCache} from "./stateCache/inMemoryCheckpointsCache.js";
102
104
  import {PersistentCheckpointStateCache} from "./stateCache/persistentCheckpointsCache.js";
105
+ import {CheckpointStateCache} from "./stateCache/types.js";
103
106
  import {ValidatorMonitor} from "./validatorMonitor.js";
104
107
 
105
108
  /**
@@ -181,6 +184,8 @@ export class BeaconChain implements IBeaconChain {
181
184
 
182
185
  protected readonly blockProcessor: BlockProcessor;
183
186
  protected readonly db: IBeaconDb;
187
+ // this is only available if nHistoricalStates is enabled
188
+ private readonly cpStateDatastore?: CPStateDatastore;
184
189
  private abortController = new AbortController();
185
190
  private processShutdownCallback: ProcessShutdownCallback;
186
191
  private _earliestAvailableSlot: Slot;
@@ -210,6 +215,7 @@ export class BeaconChain implements IBeaconChain {
210
215
  metrics,
211
216
  validatorMonitor,
212
217
  anchorState,
218
+ isAnchorStateFinalized,
213
219
  eth1,
214
220
  executionEngine,
215
221
  executionBuilder,
@@ -226,6 +232,7 @@ export class BeaconChain implements IBeaconChain {
226
232
  metrics: Metrics | null;
227
233
  validatorMonitor: ValidatorMonitor | null;
228
234
  anchorState: BeaconStateAllForks;
235
+ isAnchorStateFinalized: boolean;
229
236
  eth1: IEth1ForBlockProduction;
230
237
  executionEngine: IExecutionEngine;
231
238
  executionBuilder?: IExecutionBuilder;
@@ -264,8 +271,7 @@ export class BeaconChain implements IBeaconChain {
264
271
  this.seenAttestationDatas = new SeenAttestationDatas(metrics, this.opts?.attDataCacheSlotDistance);
265
272
 
266
273
  const nodeId = computeNodeIdFromPrivateKey(privateKey);
267
- const initialCustodyGroupCount =
268
- opts.initialCustodyGroupCount ?? (opts.supernode ? config.NUMBER_OF_CUSTODY_GROUPS : config.CUSTODY_REQUIREMENT);
274
+ const initialCustodyGroupCount = opts.initialCustodyGroupCount ?? config.CUSTODY_REQUIREMENT;
269
275
  this.metrics?.peerDas.targetCustodyGroupCount.set(initialCustodyGroupCount);
270
276
  this.custodyConfig = new CustodyConfig({
271
277
  nodeId,
@@ -326,23 +332,26 @@ export class BeaconChain implements IBeaconChain {
326
332
  this.bufferPool = this.opts.nHistoricalStates
327
333
  ? new BufferPool(anchorState.type.tree_serializedSize(anchorState.node), metrics)
328
334
  : null;
329
- const checkpointStateCache = this.opts.nHistoricalStates
330
- ? new PersistentCheckpointStateCache(
331
- {
332
- metrics,
333
- logger,
334
- clock,
335
- blockStateCache,
336
- bufferPool: this.bufferPool,
337
- datastore: fileDataStore
338
- ? // debug option if we want to investigate any issues with the DB
339
- new FileCPStateDatastore(dataDir)
340
- : // production option
341
- new DbCPStateDatastore(this.db),
342
- },
343
- this.opts
344
- )
345
- : new InMemoryCheckpointStateCache({metrics});
335
+
336
+ let checkpointStateCache: CheckpointStateCache;
337
+ this.cpStateDatastore = undefined;
338
+ if (this.opts.nHistoricalStates) {
339
+ this.cpStateDatastore = fileDataStore ? new FileCPStateDatastore(dataDir) : new DbCPStateDatastore(this.db);
340
+ checkpointStateCache = new PersistentCheckpointStateCache(
341
+ {
342
+ metrics,
343
+ logger,
344
+ clock,
345
+ blockStateCache,
346
+ bufferPool: this.bufferPool,
347
+ datastore: this.cpStateDatastore,
348
+ },
349
+ this.opts
350
+ );
351
+ } else {
352
+ checkpointStateCache = new InMemoryCheckpointStateCache({metrics});
353
+ }
354
+
346
355
  const {checkpoint} = computeAnchorCheckpoint(config, anchorState);
347
356
  blockStateCache.add(cachedState);
348
357
  blockStateCache.setHeadState(cachedState);
@@ -353,6 +362,7 @@ export class BeaconChain implements IBeaconChain {
353
362
  emitter,
354
363
  clock.currentSlot,
355
364
  cachedState,
365
+ isAnchorStateFinalized,
356
366
  opts,
357
367
  this.justifiedBalancesGetter.bind(this),
358
368
  metrics,
@@ -604,6 +614,20 @@ export class BeaconChain implements IBeaconChain {
604
614
  return data && {state: data, executionOptimistic: false, finalized: true};
605
615
  }
606
616
 
617
+ async getPersistedCheckpointState(checkpoint?: phase0.Checkpoint): Promise<Uint8Array | null> {
618
+ if (!this.cpStateDatastore) {
619
+ throw new Error("n-historical-state flag is not enabled");
620
+ }
621
+
622
+ if (checkpoint == null) {
623
+ // return the last safe checkpoint state by default
624
+ return this.cpStateDatastore.readLatestSafe();
625
+ }
626
+
627
+ const persistedKey = checkpointToDatastoreKey(checkpoint);
628
+ return this.cpStateDatastore.read(persistedKey);
629
+ }
630
+
607
631
  getStateByCheckpoint(
608
632
  checkpoint: CheckpointWithHex
609
633
  ): {state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null {
@@ -1216,9 +1240,10 @@ export class BeaconChain implements IBeaconChain {
1216
1240
  }
1217
1241
 
1218
1242
  private async updateValidatorsCustodyRequirement(finalizedCheckpoint: CheckpointWithHex): Promise<void> {
1219
- if (this.opts.supernode) {
1220
- // Disable dynamic custody updates for supernodes since they must maintain custody
1221
- // of all custody groups regardless of validator effective balances
1243
+ if (this.custodyConfig.targetCustodyGroupCount === this.config.NUMBER_OF_CUSTODY_GROUPS) {
1244
+ // Custody requirements can only be increased, we can disable dynamic custody updates
1245
+ // if the node already maintains custody of all custody groups in case it is configured
1246
+ // as a supernode or has validators attached with a total effective balance of at least 4096 ETH.
1222
1247
  return;
1223
1248
  }
1224
1249
 
@@ -8,13 +8,18 @@ export enum DataColumnSidecarErrorCode {
8
8
  MISMATCHED_LENGTHS = "DATA_COLUMN_SIDECAR_ERROR_MISMATCHED_LENGTHS",
9
9
  INVALID_SUBNET = "DATA_COLUMN_SIDECAR_ERROR_INVALID_SUBNET",
10
10
  INVALID_KZG_PROOF = "DATA_COLUMN_SIDECAR_ERROR_INVALID_KZG_PROOF",
11
+ TOO_MANY_KZG_COMMITMENTS = "DATA_COLUMN_SIDECAR_ERROR_TOO_MANY_KZG_COMMITMENTS",
11
12
 
12
13
  // Validation errors when validating against an existing block
13
14
 
15
+ /** Block and sidecars header root mismatch */
16
+ INCORRECT_HEADER_ROOT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_HEADER_ROOT",
14
17
  /** Block and sidecars data column count mismatch */
15
18
  INCORRECT_SIDECAR_COUNT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_SIDECAR_COUNT",
16
19
  /** Sidecar doesn't match block */
17
20
  INCORRECT_BLOCK = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_BLOCK",
21
+ /** Sidecar cell count not as expected */
22
+ INCORRECT_CELL_COUNT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_CELL_COUNT",
18
23
  /** Sidecar kzg proof count not as expected */
19
24
  INCORRECT_KZG_COMMITMENTS_COUNT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_KZG_COMMITMENTS_COUNT",
20
25
  /** Sidecar kzg proof count not as expected */
@@ -34,42 +39,51 @@ export enum DataColumnSidecarErrorCode {
34
39
  }
35
40
 
36
41
  export type DataColumnSidecarErrorType =
37
- | {code: DataColumnSidecarErrorCode.INVALID_INDEX; slot: Slot; columnIdx: number}
38
- | {code: DataColumnSidecarErrorCode.NO_COMMITMENTS; slot: Slot; columnIdx: number}
42
+ | {code: DataColumnSidecarErrorCode.INVALID_INDEX; slot: Slot; columnIndex: number}
43
+ | {code: DataColumnSidecarErrorCode.NO_COMMITMENTS; slot: Slot; columnIndex: number}
39
44
  | {
40
45
  code: DataColumnSidecarErrorCode.MISMATCHED_LENGTHS;
41
46
  columnLength: number;
42
47
  commitmentsLength: number;
43
48
  proofsLength: number;
44
49
  }
45
- | {code: DataColumnSidecarErrorCode.INVALID_SUBNET; columnIdx: number; gossipSubnet: SubnetID}
46
- | {code: DataColumnSidecarErrorCode.ALREADY_KNOWN; columnIdx: number; slot: Slot}
50
+ | {code: DataColumnSidecarErrorCode.INVALID_SUBNET; columnIndex: number; gossipSubnet: SubnetID}
51
+ | {
52
+ code: DataColumnSidecarErrorCode.TOO_MANY_KZG_COMMITMENTS;
53
+ slot: number;
54
+ columnIndex: number;
55
+ count: number;
56
+ limit: number;
57
+ }
58
+ | {code: DataColumnSidecarErrorCode.ALREADY_KNOWN; columnIndex: number; slot: Slot}
47
59
  | {code: DataColumnSidecarErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
48
60
  | {code: DataColumnSidecarErrorCode.WOULD_REVERT_FINALIZED_SLOT; blockSlot: Slot; finalizedSlot: Slot}
49
61
  | {code: DataColumnSidecarErrorCode.PARENT_UNKNOWN; parentRoot: RootHex}
50
62
  | {code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID}
51
63
  | {code: DataColumnSidecarErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
52
- | {code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID; slot: Slot; columnIdx: number}
53
- | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF; slot: Slot; columnIdx: number}
64
+ | {code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID; slot: Slot; columnIndex: number}
65
+ | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF; slot: Slot; columnIndex: number}
54
66
  | {code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_COUNT; slot: number; expected: number; actual: number}
55
67
  | {
56
68
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK;
57
69
  slot: number;
58
- columnIdx: number;
70
+ columnIndex: number;
59
71
  expected: string;
60
72
  actual: string;
61
73
  }
62
74
  | {
63
- code: DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT;
75
+ code: DataColumnSidecarErrorCode.INCORRECT_HEADER_ROOT;
64
76
  slot: number;
65
- columnIdx: number;
66
- expected: number;
67
- actual: number;
77
+ expected: string;
78
+ actual: string;
68
79
  }
69
80
  | {
70
- code: DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT;
81
+ code:
82
+ | DataColumnSidecarErrorCode.INCORRECT_CELL_COUNT
83
+ | DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT
84
+ | DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT;
71
85
  slot: number;
72
- columnIdx: number;
86
+ columnIndex: number;
73
87
  expected: number;
74
88
  actual: number;
75
89
  }