@lodestar/beacon-node 1.43.0-dev.aef3645690 → 1.43.0-dev.e5b13221e5

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 (175) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +1 -4
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/validator/index.d.ts.map +1 -1
  5. package/lib/api/impl/validator/index.js +1 -4
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/GetBlobsTracker.d.ts +1 -1
  8. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  9. package/lib/chain/GetBlobsTracker.js +1 -2
  10. package/lib/chain/GetBlobsTracker.js.map +1 -1
  11. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
  13. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  15. package/lib/chain/blocks/importBlock.js +27 -35
  16. package/lib/chain/blocks/importBlock.js.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.d.ts +1 -1
  18. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  19. package/lib/chain/blocks/importExecutionPayload.js +19 -26
  20. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  21. package/lib/chain/blocks/index.js +1 -1
  22. package/lib/chain/blocks/index.js.map +1 -1
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +3 -0
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +20 -0
  26. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
  28. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeProcessor.js +6 -4
  30. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  31. package/lib/chain/blocks/types.d.ts +1 -1
  32. package/lib/chain/blocks/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
  34. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
  35. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +25 -0
  36. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
  37. package/lib/chain/chain.d.ts.map +1 -1
  38. package/lib/chain/chain.js +17 -37
  39. package/lib/chain/chain.js.map +1 -1
  40. package/lib/chain/emitter.d.ts +13 -1
  41. package/lib/chain/emitter.d.ts.map +1 -1
  42. package/lib/chain/emitter.js +5 -0
  43. package/lib/chain/emitter.js.map +1 -1
  44. package/lib/chain/errors/attestationError.d.ts +8 -1
  45. package/lib/chain/errors/attestationError.d.ts.map +1 -1
  46. package/lib/chain/errors/attestationError.js +4 -0
  47. package/lib/chain/errors/attestationError.js.map +1 -1
  48. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  49. package/lib/chain/forkChoice/index.js +12 -4
  50. package/lib/chain/forkChoice/index.js.map +1 -1
  51. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  52. package/lib/chain/prepareNextSlot.js +22 -16
  53. package/lib/chain/prepareNextSlot.js.map +1 -1
  54. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
  55. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  56. package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
  57. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  58. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -8
  59. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  60. package/lib/chain/produceBlock/produceBlockBody.js +29 -19
  61. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  62. package/lib/chain/regen/errors.d.ts +1 -11
  63. package/lib/chain/regen/errors.d.ts.map +1 -1
  64. package/lib/chain/regen/errors.js +0 -2
  65. package/lib/chain/regen/errors.js.map +1 -1
  66. package/lib/chain/regen/interface.d.ts +6 -12
  67. package/lib/chain/regen/interface.d.ts.map +1 -1
  68. package/lib/chain/regen/queued.d.ts +6 -11
  69. package/lib/chain/regen/queued.d.ts.map +1 -1
  70. package/lib/chain/regen/queued.js +8 -40
  71. package/lib/chain/regen/queued.js.map +1 -1
  72. package/lib/chain/regen/regen.d.ts +0 -5
  73. package/lib/chain/regen/regen.d.ts.map +1 -1
  74. package/lib/chain/regen/regen.js +7 -34
  75. package/lib/chain/regen/regen.js.map +1 -1
  76. package/lib/chain/stateCache/datastore/db.d.ts +5 -4
  77. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  78. package/lib/chain/stateCache/datastore/db.js +10 -32
  79. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  80. package/lib/chain/stateCache/datastore/file.d.ts +1 -1
  81. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  82. package/lib/chain/stateCache/datastore/file.js +5 -5
  83. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  84. package/lib/chain/stateCache/datastore/types.d.ts +1 -1
  85. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  86. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
  87. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  88. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
  89. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  90. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
  91. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  92. package/lib/chain/stateCache/persistentCheckpointsCache.js +120 -216
  93. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  94. package/lib/chain/stateCache/types.d.ts +8 -15
  95. package/lib/chain/stateCache/types.d.ts.map +1 -1
  96. package/lib/chain/stateCache/types.js.map +1 -1
  97. package/lib/chain/validation/aggregateAndProof.js +12 -0
  98. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  99. package/lib/chain/validation/attestation.d.ts.map +1 -1
  100. package/lib/chain/validation/attestation.js +12 -0
  101. package/lib/chain/validation/attestation.js.map +1 -1
  102. package/lib/chain/validation/executionPayloadEnvelope.js +10 -10
  103. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  104. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  105. package/lib/chain/validation/payloadAttestationMessage.js +4 -3
  106. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  107. package/lib/db/repositories/executionPayloadEnvelopeArchive.js +1 -1
  108. package/lib/db/repositories/executionPayloadEnvelopeArchive.js.map +1 -1
  109. package/lib/execution/engine/http.d.ts.map +1 -1
  110. package/lib/execution/engine/http.js +21 -14
  111. package/lib/execution/engine/http.js.map +1 -1
  112. package/lib/execution/engine/interface.d.ts +1 -0
  113. package/lib/execution/engine/interface.d.ts.map +1 -1
  114. package/lib/execution/engine/mock.d.ts.map +1 -1
  115. package/lib/execution/engine/mock.js +6 -0
  116. package/lib/execution/engine/mock.js.map +1 -1
  117. package/lib/execution/engine/types.d.ts +20 -0
  118. package/lib/execution/engine/types.d.ts.map +1 -1
  119. package/lib/execution/engine/types.js +18 -0
  120. package/lib/execution/engine/types.js.map +1 -1
  121. package/lib/network/gossip/topic.d.ts +23 -2
  122. package/lib/network/gossip/topic.d.ts.map +1 -1
  123. package/lib/network/network.js +1 -1
  124. package/lib/network/network.js.map +1 -1
  125. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  126. package/lib/network/processor/gossipHandlers.js +22 -6
  127. package/lib/network/processor/gossipHandlers.js.map +1 -1
  128. package/lib/node/nodejs.d.ts.map +1 -1
  129. package/lib/node/nodejs.js +4 -2
  130. package/lib/node/nodejs.js.map +1 -1
  131. package/lib/util/sszBytes.d.ts.map +1 -1
  132. package/lib/util/sszBytes.js +16 -3
  133. package/lib/util/sszBytes.js.map +1 -1
  134. package/package.json +16 -16
  135. package/src/api/impl/beacon/blocks/index.ts +1 -4
  136. package/src/api/impl/validator/index.ts +3 -6
  137. package/src/chain/GetBlobsTracker.ts +1 -2
  138. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +2 -4
  139. package/src/chain/blocks/importBlock.ts +26 -39
  140. package/src/chain/blocks/importExecutionPayload.ts +20 -26
  141. package/src/chain/blocks/index.ts +1 -1
  142. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -0
  143. package/src/chain/blocks/payloadEnvelopeProcessor.ts +6 -5
  144. package/src/chain/blocks/types.ts +1 -1
  145. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +38 -0
  146. package/src/chain/chain.ts +16 -47
  147. package/src/chain/emitter.ts +12 -0
  148. package/src/chain/errors/attestationError.ts +6 -1
  149. package/src/chain/forkChoice/index.ts +12 -4
  150. package/src/chain/prepareNextSlot.ts +25 -16
  151. package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
  152. package/src/chain/produceBlock/produceBlockBody.ts +40 -20
  153. package/src/chain/regen/errors.ts +1 -6
  154. package/src/chain/regen/interface.ts +6 -12
  155. package/src/chain/regen/queued.ts +12 -48
  156. package/src/chain/regen/regen.ts +8 -36
  157. package/src/chain/stateCache/datastore/db.ts +10 -33
  158. package/src/chain/stateCache/datastore/file.ts +5 -6
  159. package/src/chain/stateCache/datastore/types.ts +2 -3
  160. package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
  161. package/src/chain/stateCache/persistentCheckpointsCache.ts +139 -247
  162. package/src/chain/stateCache/types.ts +8 -14
  163. package/src/chain/validation/aggregateAndProof.ts +13 -0
  164. package/src/chain/validation/attestation.ts +13 -0
  165. package/src/chain/validation/executionPayloadEnvelope.ts +10 -10
  166. package/src/chain/validation/payloadAttestationMessage.ts +5 -3
  167. package/src/db/repositories/executionPayloadEnvelopeArchive.ts +1 -1
  168. package/src/execution/engine/http.ts +21 -14
  169. package/src/execution/engine/interface.ts +1 -0
  170. package/src/execution/engine/mock.ts +8 -1
  171. package/src/execution/engine/types.ts +41 -0
  172. package/src/network/network.ts +1 -1
  173. package/src/network/processor/gossipHandlers.ts +26 -10
  174. package/src/node/nodejs.ts +4 -2
  175. package/src/util/sszBytes.ts +21 -3
@@ -1,6 +1,5 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {BeaconConfig} from "@lodestar/config";
3
- import {CheckpointWithPayloadStatus} from "@lodestar/fork-choice";
4
3
  import {IBeaconStateView, computeStartSlotAtEpoch} from "@lodestar/state-transition";
5
4
  import {Epoch, RootHex, phase0} from "@lodestar/types";
6
5
  import {Logger, MapDef, fromHex, sleep, toHex, toRootHex} from "@lodestar/utils";
@@ -10,7 +9,7 @@ import {IClock} from "../../util/clock.js";
10
9
  import {serializeState} from "../serializeState.js";
11
10
  import {CPStateDatastore, DatastoreKey} from "./datastore/index.js";
12
11
  import {MapTracker} from "./mapMetrics.js";
13
- import {BlockStateCache, CacheItemType, CheckpointHexPayload, CheckpointStateCache} from "./types.js";
12
+ import {BlockStateCache, CacheItemType, CheckpointHex, CheckpointStateCache} from "./types.js";
14
13
 
15
14
  export type PersistentCheckpointStateCacheOpts = {
16
15
  /** Keep max n state epochs in memory, persist the rest to disk */
@@ -50,22 +49,6 @@ type CacheItem = InMemoryCacheItem | PersistedCacheItem;
50
49
 
51
50
  type LoadedStateBytesData = {persistedKey: DatastoreKey; stateBytes: Uint8Array};
52
51
 
53
- /** Bitmask for tracking which payload variants exist per root in the epochIndex */
54
- enum PayloadAvailability {
55
- NOT_PRESENT = 1,
56
- PRESENT = 2,
57
- }
58
-
59
- const PAYLOAD_AVAILABILITY_ALL = [PayloadAvailability.NOT_PRESENT, PayloadAvailability.PRESENT] as const;
60
-
61
- function toPayloadAvailability(payloadPresent: boolean): PayloadAvailability {
62
- return payloadPresent ? PayloadAvailability.PRESENT : PayloadAvailability.NOT_PRESENT;
63
- }
64
-
65
- function fromPayloadAvailability(flag: PayloadAvailability): boolean {
66
- return flag === PayloadAvailability.PRESENT;
67
- }
68
-
69
52
  /**
70
53
  * Before n-historical states, lodestar keeps all checkpoint states since finalized
71
54
  * Since Sep 2024, lodestar stores 3 most recent checkpoint states in memory and the rest on disk. The finalized state
@@ -118,8 +101,8 @@ const PROCESS_CHECKPOINT_STATES_BPS = 6667;
118
101
  */
119
102
  export class PersistentCheckpointStateCache implements CheckpointStateCache {
120
103
  private readonly cache: MapTracker<CacheKey, CacheItem>;
121
- /** Epoch -> Map<blockRoot, PayloadAvailability bitmask> */
122
- private readonly epochIndex = new MapDef<Epoch, Map<RootHex, number>>(() => new Map());
104
+ /** Epoch -> Set<blockRoot> */
105
+ private readonly epochIndex = new MapDef<Epoch, Set<RootHex>>(() => new Set<string>());
123
106
  private readonly config: BeaconConfig;
124
107
  private readonly metrics: Metrics | null | undefined;
125
108
  private readonly logger: Logger;
@@ -215,18 +198,13 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
215
198
  * - Get block for processing
216
199
  * - Regen head state
217
200
  */
218
- async getOrReload(cp: CheckpointHexPayload): Promise<IBeaconStateView | null> {
201
+ async getOrReload(cp: CheckpointHex): Promise<IBeaconStateView | null> {
219
202
  const stateOrStateBytesData = await this.getStateOrLoadDb(cp);
220
203
  if (stateOrStateBytesData === null || isBeaconStateView(stateOrStateBytesData)) {
221
204
  return stateOrStateBytesData ?? null;
222
205
  }
223
206
  const {persistedKey, stateBytes} = stateOrStateBytesData;
224
- const logMeta = {
225
- epoch: cp.epoch,
226
- rootHex: cp.rootHex,
227
- payloadPresent: cp.payloadPresent,
228
- persistedKey: toHex(persistedKey),
229
- };
207
+ const logMeta = {persistedKey: toHex(persistedKey)};
230
208
  this.logger.debug("Reload: read state successful", logMeta);
231
209
  this.metrics?.cpStateCache.stateReloadSecFromSlot.observe(
232
210
  this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0
@@ -248,7 +226,10 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
248
226
  }
249
227
  sszTimer?.();
250
228
  const timer = this.metrics?.cpStateCache.stateReloadDuration.startTimer();
251
- const newCachedState = seedState.loadOtherState(stateBytes, validatorsBytes);
229
+ // preload validators and balances for faster state transition
230
+ const newCachedState = seedState.loadOtherState(stateBytes, validatorsBytes, {
231
+ preloadValidatorsAndBalances: true,
232
+ });
252
233
  // hashTreeRoot() calls the commit() inside
253
234
  // there is no modification inside the state, it's just that we want to compute and cache all roots
254
235
  const stateRoot = toRootHex(newCachedState.hashTreeRoot());
@@ -264,7 +245,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
264
245
  // only remove persisted state once we reload successfully
265
246
  const cpKey = toCacheKey(cp);
266
247
  this.cache.set(cpKey, {type: CacheItemType.inMemory, state: newCachedState, persistedKey});
267
- this.addToEpochIndex(cp.epoch, cp.rootHex, cp.payloadPresent);
248
+ this.epochIndex.getOrDefault(cp.epoch).add(cp.rootHex);
268
249
  // don't prune from memory here, call it at the last 1/3 of slot 0 of an epoch
269
250
  return newCachedState;
270
251
  } catch (e) {
@@ -276,7 +257,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
276
257
  /**
277
258
  * Return either state or state bytes loaded from db.
278
259
  */
279
- async getStateOrBytes(cp: CheckpointHexPayload): Promise<IBeaconStateView | Uint8Array | null> {
260
+ async getStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null> {
280
261
  const stateOrLoadedState = await this.getStateOrLoadDb(cp);
281
262
  if (stateOrLoadedState === null || isBeaconStateView(stateOrLoadedState)) {
282
263
  return stateOrLoadedState;
@@ -287,7 +268,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
287
268
  /**
288
269
  * Return either state or state bytes with persisted key loaded from db.
289
270
  */
290
- async getStateOrLoadDb(cp: CheckpointHexPayload): Promise<IBeaconStateView | LoadedStateBytesData | null> {
271
+ async getStateOrLoadDb(cp: CheckpointHex): Promise<IBeaconStateView | LoadedStateBytesData | null> {
291
272
  const cpKey = toCacheKey(cp);
292
273
  const inMemoryState = this.get(cpKey);
293
274
  if (inMemoryState) {
@@ -318,7 +299,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
318
299
  /**
319
300
  * Similar to get() api without reloading from disk
320
301
  */
321
- get(cpOrKey: CheckpointHexPayload | CacheKey): IBeaconStateView | null {
302
+ get(cpOrKey: CheckpointHex | CacheKey): IBeaconStateView | null {
322
303
  this.metrics?.cpStateCache.lookups.inc();
323
304
  const cpKey = typeof cpOrKey === "string" ? cpOrKey : toCacheKey(cpOrKey);
324
305
  const cacheItem = this.cache.get(cpKey);
@@ -344,11 +325,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
344
325
 
345
326
  /**
346
327
  * Add a state of a checkpoint to this cache, prune from memory if necessary.
347
- * @param payloadPresent - For Gloas: true if this is payload state, false if block state.
348
- * Always true for pre-Gloas.
349
328
  */
350
- add(cp: phase0.Checkpoint, state: IBeaconStateView, payloadPresent: boolean): void {
351
- const cpHex = toCheckpointHexPayload(cp, payloadPresent);
329
+ add(cp: phase0.Checkpoint, state: IBeaconStateView): void {
330
+ const cpHex = toCheckpointHex(cp);
352
331
  const key = toCacheKey(cpHex);
353
332
  const cacheItem = this.cache.get(key);
354
333
  this.metrics?.cpStateCache.adds.inc();
@@ -359,32 +338,27 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
359
338
  this.logger.verbose("Added checkpoint state to memory but a persisted key existed", {
360
339
  epoch: cp.epoch,
361
340
  rootHex: cpHex.rootHex,
362
- payloadPresent,
363
341
  persistedKey: toHex(persistedKey),
364
342
  });
365
343
  } else {
366
344
  this.cache.set(key, {type: CacheItemType.inMemory, state});
367
- this.logger.verbose("Added checkpoint state to memory", {
368
- epoch: cp.epoch,
369
- rootHex: cpHex.rootHex,
370
- payloadPresent,
371
- });
345
+ this.logger.verbose("Added checkpoint state to memory", {epoch: cp.epoch, rootHex: cpHex.rootHex});
372
346
  }
373
- this.addToEpochIndex(cp.epoch, cpHex.rootHex, cpHex.payloadPresent);
347
+ this.epochIndex.getOrDefault(cp.epoch).add(cpHex.rootHex);
374
348
  this.prunePersistedStates();
375
349
  }
376
350
 
377
351
  /**
378
352
  * Searches in-memory state for the latest cached state with a `root` without reload, starting with `epoch` and descending
379
353
  */
380
- getLatest(rootHex: RootHex, maxEpoch: Epoch, payloadPresent: boolean): IBeaconStateView | null {
354
+ getLatest(rootHex: RootHex, maxEpoch: Epoch): IBeaconStateView | null {
381
355
  // sort epochs in descending order, only consider epochs lte `epoch`
382
356
  const epochs = Array.from(this.epochIndex.keys())
383
357
  .sort((a, b) => b - a)
384
358
  .filter((e) => e <= maxEpoch);
385
359
  for (const epoch of epochs) {
386
- if (this.hasPayloadVariant(epoch, rootHex, payloadPresent)) {
387
- const inMemoryClonedState = this.get({rootHex, epoch, payloadPresent});
360
+ if (this.epochIndex.get(epoch)?.has(rootHex)) {
361
+ const inMemoryClonedState = this.get({rootHex, epoch});
388
362
  if (inMemoryClonedState) {
389
363
  return inMemoryClonedState;
390
364
  }
@@ -400,24 +374,20 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
400
374
  * - Get block for processing
401
375
  * - Regen head state
402
376
  */
403
- async getOrReloadLatest(
404
- rootHex: RootHex,
405
- maxEpoch: Epoch,
406
- payloadPresent: boolean
407
- ): Promise<IBeaconStateView | null> {
377
+ async getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null> {
408
378
  // sort epochs in descending order, only consider epochs lte `epoch`
409
379
  const epochs = Array.from(this.epochIndex.keys())
410
380
  .sort((a, b) => b - a)
411
381
  .filter((e) => e <= maxEpoch);
412
382
  for (const epoch of epochs) {
413
- if (this.hasPayloadVariant(epoch, rootHex, payloadPresent)) {
383
+ if (this.epochIndex.get(epoch)?.has(rootHex)) {
414
384
  try {
415
- const state = await this.getOrReload({rootHex, epoch, payloadPresent});
385
+ const state = await this.getOrReload({rootHex, epoch});
416
386
  if (state) {
417
387
  return state;
418
388
  }
419
389
  } catch (e) {
420
- this.logger.debug("Error get or reload state", {epoch, rootHex, payloadPresent}, e as Error);
390
+ this.logger.debug("Error get or reload state", {epoch, rootHex}, e as Error);
421
391
  }
422
392
  }
423
393
  }
@@ -427,12 +397,10 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
427
397
  /**
428
398
  * Update the precomputed checkpoint and return the number of hits for the
429
399
  * previous one (if any).
430
- * @param payloadPresent - For Gloas: true if head block has FULL payload, false if EMPTY.
431
- * Always true for pre-Gloas.
432
400
  */
433
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null {
401
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null {
434
402
  const previousHits = this.preComputedCheckpointHits;
435
- this.preComputedCheckpoint = toCacheKey({rootHex, epoch, payloadPresent});
403
+ this.preComputedCheckpoint = toCacheKey({rootHex, epoch});
436
404
  this.preComputedCheckpointHits = 0;
437
405
  return previousHits;
438
406
  }
@@ -506,9 +474,6 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
506
474
  * - 2 then we'll persist {root: b2, epoch n-2} checkpoint state to disk, there are also 2 checkpoint states in memory at epoch n, same to the above (maxEpochsInMemory=1)
507
475
  *
508
476
  * As of Mar 2024, it takes <=350ms to persist a holesky state on fast server
509
- *
510
- * For Gloas: Processes both block state and payload state variants together. The decision of which roots to persist/prune
511
- * is based on root canonicality (from state's view), not payload presence. Both variants are managed as a unit.
512
477
  */
513
478
  async processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number> {
514
479
  let persistCount = 0;
@@ -579,7 +544,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
579
544
  *
580
545
  * Use seed state from the block cache if cannot find any seed states within this cache.
581
546
  */
582
- findSeedStateToReload(reloadedCp: CheckpointHexPayload): IBeaconStateView {
547
+ findSeedStateToReload(reloadedCp: CheckpointHex): IBeaconStateView {
583
548
  const maxEpoch = Math.max(...Array.from(this.epochIndex.keys()));
584
549
  const reloadedCpSlot = computeStartSlotAtEpoch(reloadedCp.epoch);
585
550
  let firstState: IBeaconStateView | null = null;
@@ -592,35 +557,31 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
592
557
  return firstState;
593
558
  }
594
559
 
595
- for (const [rootHex, bitmask] of this.epochIndex.get(epoch) || []) {
596
- for (const flag of PAYLOAD_AVAILABILITY_ALL) {
597
- if (!(bitmask & flag)) continue;
598
- const payloadPresent = fromPayloadAvailability(flag);
599
- const cpKey = toCacheKey({rootHex, epoch, payloadPresent});
600
- const cacheItem = this.cache.get(cpKey);
601
- if (cacheItem === undefined) {
602
- continue;
560
+ for (const rootHex of this.epochIndex.get(epoch) || []) {
561
+ const cpKey = toCacheKey({rootHex, epoch});
562
+ const cacheItem = this.cache.get(cpKey);
563
+ if (cacheItem === undefined) {
564
+ continue;
565
+ }
566
+ if (isInMemoryCacheItem(cacheItem)) {
567
+ const {state} = cacheItem;
568
+ if (firstState === null) {
569
+ firstState = state;
603
570
  }
604
- if (isInMemoryCacheItem(cacheItem)) {
605
- const {state} = cacheItem;
606
- if (firstState === null) {
607
- firstState = state;
608
- }
609
- const cpLog = {cpEpoch: epoch, cpRoot: rootHex, payloadPresent};
610
-
611
- try {
612
- // amongst states of the same epoch, choose the one with the same view of reloadedCp
613
- if (
614
- reloadedCpSlot < state.slot &&
615
- toRootHex(state.getBlockRootAtSlot(reloadedCpSlot)) === reloadedCp.rootHex
616
- ) {
617
- this.logger.verbose("Reload: use checkpoint state as seed state", {...cpLog, ...logCtx});
618
- return state;
619
- }
620
- } catch (e) {
621
- // getBlockRootAtSlot may throw error
622
- this.logger.debug("Error finding checkpoint state to reload", {...cpLog, ...logCtx}, e as Error);
571
+ const cpLog = {cpEpoch: epoch, cpRoot: rootHex};
572
+
573
+ try {
574
+ // amongst states of the same epoch, choose the one with the same view of reloadedCp
575
+ if (
576
+ reloadedCpSlot < state.slot &&
577
+ toRootHex(state.getBlockRootAtSlot(reloadedCpSlot)) === reloadedCp.rootHex
578
+ ) {
579
+ this.logger.verbose("Reload: use checkpoint state as seed state", {...cpLog, ...logCtx});
580
+ return state;
623
581
  }
582
+ } catch (e) {
583
+ // getBlockRootAtSlot may throw error
584
+ this.logger.debug("Error finding checkpoint state to reload", {...cpLog, ...logCtx}, e as Error);
624
585
  }
625
586
  }
626
587
  }
@@ -637,31 +598,6 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
637
598
  this.epochIndex.clear();
638
599
  }
639
600
 
640
- private addToEpochIndex(epoch: Epoch, rootHex: RootHex, payloadPresent: boolean): void {
641
- const rootMap = this.epochIndex.getOrDefault(epoch);
642
- rootMap.set(rootHex, (rootMap.get(rootHex) ?? 0) | toPayloadAvailability(payloadPresent));
643
- }
644
-
645
- private removeFromEpochIndex(epoch: Epoch, rootHex: RootHex, payloadPresent: boolean): void {
646
- const rootMap = this.epochIndex.get(epoch);
647
- if (rootMap === undefined) return;
648
- const existing = rootMap.get(rootHex);
649
- if (existing === undefined) return;
650
- const updated = existing & ~toPayloadAvailability(payloadPresent);
651
- if (updated === 0) {
652
- rootMap.delete(rootHex);
653
- if (rootMap.size === 0) {
654
- this.epochIndex.delete(epoch);
655
- }
656
- } else {
657
- rootMap.set(rootHex, updated);
658
- }
659
- }
660
-
661
- private hasPayloadVariant(epoch: Epoch, rootHex: RootHex, payloadPresent: boolean): boolean {
662
- return Boolean((this.epochIndex.get(epoch)?.get(rootHex) ?? 0) & toPayloadAvailability(payloadPresent));
663
- }
664
-
665
601
  /** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
666
602
  dumpSummary(): routes.lodestar.StateCacheItem[] {
667
603
  return Array.from(this.cache.keys()).map((key) => {
@@ -736,7 +672,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
736
672
  const prevEpochRoot = toRootHex(state.getBlockRootAtSlot(epochBoundarySlot - 1));
737
673
 
738
674
  // for each epoch, usually there are 2 rootHexes respective to the 2 checkpoint states: Previous Root Checkpoint State and Current Root Checkpoint State
739
- const cpRootHexMap = this.epochIndex.get(epoch) ?? new Map<RootHex, number>();
675
+ const cpRootHexes = this.epochIndex.get(epoch) ?? [];
740
676
  const persistedRootHexes = new Set<RootHex>();
741
677
 
742
678
  // 1) if there is no CRCS, persist PRCS (block 0 of epoch is skipped). In this case prevEpochRoot === epochBoundaryHex
@@ -745,81 +681,82 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
745
681
  persistedRootHexes.add(epochBoundaryHex);
746
682
 
747
683
  // 3) persist any states with unknown roots to this state
748
- for (const rootHex of cpRootHexMap.keys()) {
684
+ for (const rootHex of cpRootHexes) {
749
685
  if (rootHex !== epochBoundaryHex && rootHex !== prevEpochRoot) {
750
686
  persistedRootHexes.add(rootHex);
751
687
  }
752
688
  }
753
689
 
754
- for (const [rootHex, bitmask] of cpRootHexMap) {
755
- for (const flag of PAYLOAD_AVAILABILITY_ALL) {
756
- if (!(bitmask & flag)) continue;
757
- const payloadPresent = fromPayloadAvailability(flag);
758
- const cpKey = toCacheKey({epoch: epoch, rootHex, payloadPresent});
759
- const cacheItem = this.cache.get(cpKey);
690
+ for (const rootHex of cpRootHexes) {
691
+ const cpKey = toCacheKey({epoch: epoch, rootHex});
692
+ const cacheItem = this.cache.get(cpKey);
760
693
 
761
- if (cacheItem !== undefined && isInMemoryCacheItem(cacheItem)) {
762
- let {persistedKey} = cacheItem;
763
- const {state} = cacheItem;
764
- const logMeta = {
765
- stateSlot: state.slot,
766
- rootHex,
767
- payloadPresent,
768
- epochBoundaryHex,
769
- persistedKey: persistedKey ? toHex(persistedKey) : "",
770
- };
771
-
772
- if (persistedRootHexes.has(rootHex)) {
773
- if (persistedKey) {
774
- // we don't care if the checkpoint state is already persisted
775
- this.logger.verbose("Pruned checkpoint state from memory but no need to persist", logMeta);
776
- } else {
777
- // persist and do not update epochIndex
778
- this.metrics?.cpStateCache.statePersistSecFromSlot.observe(
779
- this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0
780
- );
781
- const cpPersist = {epoch: epoch, root: fromHex(rootHex)};
782
- // It's not sustainable to allocate ~240MB for each state every epoch, so we use buffer pool to reuse the memory.
783
- // As monitored on holesky as of Jan 2024:
784
- // - This does not increase heap allocation while gc time is the same
785
- // - It helps stabilize persist time and save ~300ms in average (1.5s vs 1.2s)
786
- // - It also helps the state reload to save ~500ms in average (4.3s vs 3.8s)
787
- // - Also `serializeState.test.ts` perf test shows a lot of differences allocating ~240MB once vs per state serialization
788
- const timer = this.metrics?.stateSerializeDuration.startTimer({
789
- source: AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE,
790
- });
791
- persistedKey = await serializeState(
792
- state,
793
- AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE,
794
- (stateBytes) => {
795
- timer?.();
796
- return this.datastore.write(cpPersist, stateBytes, payloadPresent);
797
- },
798
- this.bufferPool
799
- );
800
-
801
- persistCount++;
802
- this.logger.verbose("Pruned checkpoint state from memory and persisted to disk", {
803
- ...logMeta,
804
- persistedKey: toHex(persistedKey),
805
- });
806
- }
807
- // overwrite cpKey, this means the state is deleted from memory
694
+ if (cacheItem !== undefined && isInMemoryCacheItem(cacheItem)) {
695
+ let {persistedKey} = cacheItem;
696
+ const {state} = cacheItem;
697
+ const logMeta = {
698
+ stateSlot: state.slot,
699
+ rootHex,
700
+ epochBoundaryHex,
701
+ persistedKey: persistedKey ? toHex(persistedKey) : "",
702
+ };
703
+
704
+ if (persistedRootHexes.has(rootHex)) {
705
+ if (persistedKey) {
706
+ // we don't care if the checkpoint state is already persisted
707
+ this.logger.verbose("Pruned checkpoint state from memory but no need to persist", logMeta);
708
+ } else {
709
+ // persist and do not update epochIndex
710
+ this.metrics?.cpStateCache.statePersistSecFromSlot.observe(
711
+ this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0
712
+ );
713
+ const cpPersist = {epoch: epoch, root: fromHex(rootHex)};
714
+ // It's not sustainable to allocate ~240MB for each state every epoch, so we use buffer pool to reuse the memory.
715
+ // As monitored on holesky as of Jan 2024:
716
+ // - This does not increase heap allocation while gc time is the same
717
+ // - It helps stabilize persist time and save ~300ms in average (1.5s vs 1.2s)
718
+ // - It also helps the state reload to save ~500ms in average (4.3s vs 3.8s)
719
+ // - Also `serializeState.test.ts` perf test shows a lot of differences allocating ~240MB once vs per state serialization
720
+ const timer = this.metrics?.stateSerializeDuration.startTimer({
721
+ source: AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE,
722
+ });
723
+ persistedKey = await serializeState(
724
+ state,
725
+ AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE,
726
+ (stateBytes) => {
727
+ timer?.();
728
+ return this.datastore.write(cpPersist, stateBytes);
729
+ },
730
+ this.bufferPool
731
+ );
732
+
733
+ persistCount++;
734
+ this.logger.verbose("Pruned checkpoint state from memory and persisted to disk", {
735
+ ...logMeta,
736
+ persistedKey: toHex(persistedKey),
737
+ });
738
+ }
739
+ // overwrite cpKey, this means the state is deleted from memory
740
+ this.cache.set(cpKey, {type: CacheItemType.persisted, value: persistedKey});
741
+ } else {
742
+ if (persistedKey) {
743
+ // persisted file will be eventually deleted by the archive task
744
+ // this also means the state is deleted from memory
808
745
  this.cache.set(cpKey, {type: CacheItemType.persisted, value: persistedKey});
746
+ // do not update epochIndex
809
747
  } else {
810
- if (persistedKey) {
811
- // persisted file will be eventually deleted by the archive task
812
- // this also means the state is deleted from memory
813
- this.cache.set(cpKey, {type: CacheItemType.persisted, value: persistedKey});
814
- // do not update epochIndex
815
- } else {
816
- // delete the state from memory
817
- this.cache.delete(cpKey);
818
- this.removeFromEpochIndex(epoch, rootHex, payloadPresent);
748
+ // delete the state from memory
749
+ this.cache.delete(cpKey);
750
+ const rootSet = this.epochIndex.get(epoch);
751
+ if (rootSet) {
752
+ rootSet.delete(rootHex);
753
+ if (rootSet.size === 0) {
754
+ this.epochIndex.delete(epoch);
755
+ }
819
756
  }
820
- this.metrics?.cpStateCache.statePruneFromMemoryCount.inc();
821
- this.logger.verbose("Pruned checkpoint state from memory", logMeta);
822
757
  }
758
+ this.metrics?.cpStateCache.statePruneFromMemoryCount.inc();
759
+ this.logger.verbose("Pruned checkpoint state from memory", logMeta);
823
760
  }
824
761
  }
825
762
  }
@@ -832,40 +769,26 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
832
769
  */
833
770
  private async deleteAllEpochItems(epoch: Epoch): Promise<void> {
834
771
  let persistCount = 0;
835
- const rootHexMap = this.epochIndex.get(epoch) || new Map<RootHex, number>();
836
- for (const [rootHex, bitmask] of rootHexMap) {
837
- for (const flag of PAYLOAD_AVAILABILITY_ALL) {
838
- if (!(bitmask & flag)) continue;
839
- const payloadPresent = fromPayloadAvailability(flag);
840
- const key = toCacheKey({rootHex, epoch, payloadPresent});
841
- const cacheItem = this.cache.get(key);
842
-
843
- if (cacheItem) {
844
- const persistedKey = isPersistedCacheItem(cacheItem) ? cacheItem.value : cacheItem.persistedKey;
845
- if (persistedKey) {
846
- await this.datastore.remove(persistedKey);
847
- persistCount++;
848
- this.metrics?.cpStateCache.persistedStateRemoveCount.inc();
849
- }
772
+ const rootHexes = this.epochIndex.get(epoch) || [];
773
+ for (const rootHex of rootHexes) {
774
+ const key = toCacheKey({rootHex, epoch});
775
+ const cacheItem = this.cache.get(key);
776
+
777
+ if (cacheItem) {
778
+ const persistedKey = isPersistedCacheItem(cacheItem) ? cacheItem.value : cacheItem.persistedKey;
779
+ if (persistedKey) {
780
+ await this.datastore.remove(persistedKey);
781
+ persistCount++;
782
+ this.metrics?.cpStateCache.persistedStateRemoveCount.inc();
850
783
  }
851
- this.cache.delete(key);
852
- this.logger.verbose("Pruned checkpoint state", {
853
- epoch,
854
- rootHex,
855
- payloadPresent,
856
- type: cacheItem ? (isPersistedCacheItem(cacheItem) ? "persisted" : "in-memory") : "missing",
857
- });
858
784
  }
785
+ this.cache.delete(key);
859
786
  }
860
787
  this.epochIndex.delete(epoch);
861
- this.logger.verbose("Pruned all checkpoint states for epoch", {
788
+ this.logger.verbose("Pruned checkpoint states for epoch", {
862
789
  epoch,
863
790
  persistCount,
864
- items: Array.from(rootHexMap.entries())
865
- .flatMap(([rootHex, bitmask]) =>
866
- PAYLOAD_AVAILABILITY_ALL.filter((f) => bitmask & f).map((f) => `${rootHex}:${fromPayloadAvailability(f)}`)
867
- )
868
- .join(","),
791
+ rootHexes: Array.from(rootHexes).join(","),
869
792
  });
870
793
  }
871
794
 
@@ -916,57 +839,26 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
916
839
  }
917
840
  }
918
841
 
919
- export function toCheckpointHexPayload(checkpoint: phase0.Checkpoint, payloadPresent: boolean): CheckpointHexPayload {
842
+ export function toCheckpointHex(checkpoint: phase0.Checkpoint): CheckpointHex {
920
843
  return {
921
844
  epoch: checkpoint.epoch,
922
845
  rootHex: toRootHex(checkpoint.root),
923
- payloadPresent,
924
- };
925
- }
926
-
927
- /**
928
- * Convert fork-choice CheckpointWithPayloadStatus to beacon-node CheckpointHexPayload.
929
- * Maps PayloadStatus enum to boolean payloadPresent.
930
- * @throws Error if checkpoint has PENDING payload status (ambiguous which variant to use)
931
- */
932
- export function fcCheckpointToHexPayload(checkpoint: CheckpointWithPayloadStatus): CheckpointHexPayload {
933
- const PayloadStatus = {PENDING: 0, EMPTY: 1, FULL: 2} as const;
934
-
935
- if (checkpoint.payloadStatus === PayloadStatus.PENDING) {
936
- throw Error(
937
- `Cannot convert checkpoint with PENDING payload status at epoch ${checkpoint.epoch} root ${checkpoint.rootHex}`
938
- );
939
- }
940
-
941
- return {
942
- epoch: checkpoint.epoch,
943
- rootHex: checkpoint.rootHex,
944
- payloadPresent: checkpoint.payloadStatus === PayloadStatus.FULL,
945
846
  };
946
847
  }
947
848
 
948
- export function toCheckpointKey(cp: CheckpointHexPayload): string {
949
- return `${cp.rootHex}:${cp.epoch}:${cp.payloadPresent}`;
849
+ export function toCheckpointKey(cp: CheckpointHex): string {
850
+ return `${cp.rootHex}:${cp.epoch}`;
950
851
  }
951
852
 
952
- /**
953
- * Convert checkpoint to cache key string.
954
- * Format: `{rootHex}_{epoch}_{payloadPresent}`
955
- */
956
- function toCacheKey(cp: CheckpointHexPayload): CacheKey {
957
- return `${cp.rootHex}_${cp.epoch}_${cp.payloadPresent}`;
853
+ function toCacheKey(cp: CheckpointHex): CacheKey {
854
+ return `${cp.rootHex}_${cp.epoch}`;
958
855
  }
959
856
 
960
- function fromCacheKey(key: CacheKey): CheckpointHexPayload {
961
- const parts = key.split("_");
962
- const rootHex = parts[0];
963
- const epoch = Number(parts[1]);
964
- // For backward compatibility with old format (rootHex_epoch), default to true
965
- const payloadPresent = parts.length > 2 ? parts[2] === "true" : true;
857
+ function fromCacheKey(key: CacheKey): CheckpointHex {
858
+ const [rootHex, epoch] = key.split("_");
966
859
  return {
967
860
  rootHex,
968
- epoch,
969
- payloadPresent,
861
+ epoch: Number(epoch),
970
862
  };
971
863
  }
972
864
 
@@ -2,11 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {IBeaconStateView} from "@lodestar/state-transition";
3
3
  import {Epoch, RootHex, phase0} from "@lodestar/types";
4
4
 
5
- /**
6
- * Checkpoint hex representation for state cache keys.
7
- * Extends CheckpointWithHex (from fork-choice) with payloadPresent.
8
- */
9
- export type CheckpointHexPayload = {epoch: Epoch; rootHex: RootHex; payloadPresent: boolean};
5
+ export type CheckpointHex = {epoch: Epoch; rootHex: RootHex};
10
6
 
11
7
  /**
12
8
  * Lodestar currently keeps two state caches around.
@@ -35,8 +31,6 @@ export interface BlockStateCache {
35
31
  size: number;
36
32
  prune(headStateRootHex: RootHex): void;
37
33
  deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
38
- /** Upgrade cache capacity for Gloas fork (2x states for block + payload states) */
39
- upgradeToGloas(): void;
40
34
  dumpSummary(): routes.lodestar.StateCacheItem[];
41
35
  /** Expose beacon states stored in cache. Use with caution */
42
36
  getStates(): IterableIterator<IBeaconStateView>;
@@ -65,13 +59,13 @@ export interface BlockStateCache {
65
59
  */
66
60
  export interface CheckpointStateCache {
67
61
  init?: () => Promise<void>;
68
- getOrReload(cp: CheckpointHexPayload): Promise<IBeaconStateView | null>;
69
- getStateOrBytes(cp: CheckpointHexPayload): Promise<IBeaconStateView | Uint8Array | null>;
70
- get(cpOrKey: CheckpointHexPayload | string): IBeaconStateView | null;
71
- add(cp: phase0.Checkpoint, state: IBeaconStateView, payloadPresent: boolean): void;
72
- getLatest(rootHex: RootHex, maxEpoch: Epoch, payloadPresent: boolean): IBeaconStateView | null;
73
- getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch, payloadPresent: boolean): Promise<IBeaconStateView | null>;
74
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null;
62
+ getOrReload(cp: CheckpointHex): Promise<IBeaconStateView | null>;
63
+ getStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null>;
64
+ get(cpOrKey: CheckpointHex | string): IBeaconStateView | null;
65
+ add(cp: phase0.Checkpoint, state: IBeaconStateView): void;
66
+ getLatest(rootHex: RootHex, maxEpoch: Epoch): IBeaconStateView | null;
67
+ getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null>;
68
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
75
69
  prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void;
76
70
  pruneFinalized(finalizedEpoch: Epoch): void;
77
71
  processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number>;