@lodestar/beacon-node 1.41.0 → 1.42.0-dev.5f2fffc2ce

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 (99) hide show
  1. package/lib/api/impl/beacon/state/utils.d.ts +2 -2
  2. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  3. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  4. package/lib/api/impl/validator/index.d.ts.map +1 -1
  5. package/lib/api/impl/validator/index.js +5 -1
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
  8. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/archiveStore.js +0 -9
  10. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  11. package/lib/chain/archiveStore/interface.d.ts +4 -4
  12. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  13. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +4 -4
  14. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +4 -1
  16. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  17. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  18. package/lib/chain/archiveStore/utils/archiveBlocks.js +38 -0
  19. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  20. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  21. package/lib/chain/blocks/importBlock.js +11 -7
  22. package/lib/chain/blocks/importBlock.js.map +1 -1
  23. package/lib/chain/blocks/verifyBlocksSignatures.js +1 -1
  24. package/lib/chain/blocks/verifyBlocksSignatures.js.map +1 -1
  25. package/lib/chain/chain.d.ts +3 -3
  26. package/lib/chain/chain.d.ts.map +1 -1
  27. package/lib/chain/chain.js +16 -7
  28. package/lib/chain/chain.js.map +1 -1
  29. package/lib/chain/interface.d.ts +2 -2
  30. package/lib/chain/interface.d.ts.map +1 -1
  31. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  32. package/lib/chain/prepareNextSlot.js +6 -2
  33. package/lib/chain/prepareNextSlot.js.map +1 -1
  34. package/lib/chain/regen/errors.d.ts +11 -1
  35. package/lib/chain/regen/errors.d.ts.map +1 -1
  36. package/lib/chain/regen/errors.js +2 -0
  37. package/lib/chain/regen/errors.js.map +1 -1
  38. package/lib/chain/regen/interface.d.ts +12 -6
  39. package/lib/chain/regen/interface.d.ts.map +1 -1
  40. package/lib/chain/regen/queued.d.ts +11 -6
  41. package/lib/chain/regen/queued.d.ts.map +1 -1
  42. package/lib/chain/regen/queued.js +40 -8
  43. package/lib/chain/regen/queued.js.map +1 -1
  44. package/lib/chain/regen/regen.d.ts +5 -0
  45. package/lib/chain/regen/regen.d.ts.map +1 -1
  46. package/lib/chain/regen/regen.js +33 -6
  47. package/lib/chain/regen/regen.js.map +1 -1
  48. package/lib/chain/stateCache/datastore/db.d.ts +4 -5
  49. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  50. package/lib/chain/stateCache/datastore/db.js +32 -10
  51. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  52. package/lib/chain/stateCache/datastore/file.d.ts +1 -1
  53. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  54. package/lib/chain/stateCache/datastore/file.js +5 -5
  55. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  56. package/lib/chain/stateCache/datastore/types.d.ts +1 -1
  57. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  58. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +7 -4
  59. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  60. package/lib/chain/stateCache/fifoBlockStateCache.js +8 -3
  61. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  62. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +33 -14
  63. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  64. package/lib/chain/stateCache/persistentCheckpointsCache.js +217 -119
  65. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  66. package/lib/chain/stateCache/types.d.ts +15 -8
  67. package/lib/chain/stateCache/types.d.ts.map +1 -1
  68. package/lib/chain/stateCache/types.js.map +1 -1
  69. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  70. package/lib/chain/validation/voluntaryExit.js +2 -2
  71. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  72. package/package.json +15 -15
  73. package/src/api/impl/beacon/state/utils.ts +2 -2
  74. package/src/api/impl/validator/index.ts +7 -3
  75. package/src/chain/archiveStore/archiveStore.ts +0 -10
  76. package/src/chain/archiveStore/interface.ts +4 -4
  77. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +8 -5
  78. package/src/chain/archiveStore/utils/archiveBlocks.ts +59 -1
  79. package/src/chain/blocks/importBlock.ts +11 -6
  80. package/src/chain/blocks/verifyBlocksSignatures.ts +1 -1
  81. package/src/chain/chain.ts +23 -12
  82. package/src/chain/interface.ts +2 -2
  83. package/src/chain/prepareNextSlot.ts +6 -2
  84. package/src/chain/regen/errors.ts +6 -1
  85. package/src/chain/regen/interface.ts +12 -6
  86. package/src/chain/regen/queued.ts +48 -12
  87. package/src/chain/regen/regen.ts +37 -7
  88. package/src/chain/stateCache/datastore/db.ts +33 -10
  89. package/src/chain/stateCache/datastore/file.ts +6 -5
  90. package/src/chain/stateCache/datastore/types.ts +3 -2
  91. package/src/chain/stateCache/fifoBlockStateCache.ts +10 -4
  92. package/src/chain/stateCache/persistentCheckpointsCache.ts +248 -139
  93. package/src/chain/stateCache/types.ts +18 -8
  94. package/src/chain/validation/voluntaryExit.ts +2 -1
  95. package/lib/chain/archiveStore/utils/archivePayloads.d.ts +0 -7
  96. package/lib/chain/archiveStore/utils/archivePayloads.d.ts.map +0 -1
  97. package/lib/chain/archiveStore/utils/archivePayloads.js +0 -10
  98. package/lib/chain/archiveStore/utils/archivePayloads.js.map +0 -1
  99. package/src/chain/archiveStore/utils/archivePayloads.ts +0 -15
@@ -1,3 +1,4 @@
1
+ import {PayloadStatus} from "@lodestar/fork-choice";
1
2
  import {Root, RootHex, Slot} from "@lodestar/types";
2
3
 
3
4
  export enum RegenErrorCode {
@@ -9,6 +10,8 @@ export enum RegenErrorCode {
9
10
  BLOCK_NOT_IN_DB = "REGEN_ERROR_BLOCK_NOT_IN_DB",
10
11
  STATE_TRANSITION_ERROR = "REGEN_ERROR_STATE_TRANSITION_ERROR",
11
12
  INVALID_STATE_ROOT = "REGEN_ERROR_INVALID_STATE_ROOT",
13
+ UNEXPECTED_PAYLOAD_STATUS = "REGEN_ERROR_UNEXPECTED_PAYLOAD_STATUS",
14
+ INTERNAL_ERROR = "REGEN_ERROR_INTERNAL_ERROR",
12
15
  }
13
16
 
14
17
  export type RegenErrorType =
@@ -19,7 +22,9 @@ export type RegenErrorType =
19
22
  | {code: RegenErrorCode.TOO_MANY_BLOCK_PROCESSED; stateRoot: RootHex | Root}
20
23
  | {code: RegenErrorCode.BLOCK_NOT_IN_DB; blockRoot: RootHex | Root}
21
24
  | {code: RegenErrorCode.STATE_TRANSITION_ERROR; error: Error}
22
- | {code: RegenErrorCode.INVALID_STATE_ROOT; slot: Slot; expected: RootHex; actual: RootHex};
25
+ | {code: RegenErrorCode.INVALID_STATE_ROOT; slot: Slot; expected: RootHex; actual: RootHex}
26
+ | {code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS; blockRoot: RootHex | Root; payloadStatus: PayloadStatus}
27
+ | {code: RegenErrorCode.INTERNAL_ERROR; message: string};
23
28
 
24
29
  export class RegenError extends Error {
25
30
  type: RegenErrorType;
@@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
4
4
  import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
- import {CheckpointHex} from "../stateCache/types.js";
5
+ import {CheckpointHexPayload} from "../stateCache/types.js";
6
6
 
7
7
  export enum RegenCaller {
8
8
  getDuties = "getDuties",
@@ -38,15 +38,21 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
38
38
  dumpCacheSummary(): routes.lodestar.StateCacheItem[];
39
39
  getStateSync(stateRoot: RootHex): CachedBeaconStateAllForks | null;
40
40
  getPreStateSync(block: BeaconBlock): CachedBeaconStateAllForks | null;
41
- getCheckpointStateOrBytes(cp: CheckpointHex): Promise<CachedBeaconStateAllForks | Uint8Array | null>;
42
- getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null;
41
+ getCheckpointStateOrBytes(cp: CheckpointHexPayload): Promise<CachedBeaconStateAllForks | Uint8Array | null>;
42
+ getCheckpointStateSync(cp: CheckpointHexPayload): CachedBeaconStateAllForks | null;
43
43
  getClosestHeadState(head: ProtoBlock): CachedBeaconStateAllForks | null;
44
44
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
45
45
  pruneOnFinalized(finalizedEpoch: Epoch): void;
46
- processState(blockRootHex: RootHex, postState: CachedBeaconStateAllForks): void;
47
- addCheckpointState(cp: phase0.Checkpoint, item: CachedBeaconStateAllForks): void;
46
+ processBlockState(blockRootHex: RootHex, postState: CachedBeaconStateAllForks): void;
47
+ processPayloadState(payloadState: CachedBeaconStateAllForks): void;
48
+ /**
49
+ * payloadPresent is true if this is payload state, false if block state.
50
+ * payloadPresent is always true for pre-gloas.
51
+ */
52
+ addCheckpointState(cp: phase0.Checkpoint, item: CachedBeaconStateAllForks, payloadPresent: boolean): void;
48
53
  updateHeadState(newHead: ProtoBlock, maybeHeadState: CachedBeaconStateAllForks): void;
49
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
54
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null;
55
+ upgradeForGloas(epoch: Epoch): void;
50
56
  }
51
57
 
52
58
  /**
@@ -1,11 +1,11 @@
1
1
  import {routes} from "@lodestar/api";
2
- import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition";
4
4
  import {BeaconBlock, Epoch, RootHex, Slot, isGloasBeaconBlock, phase0} from "@lodestar/types";
5
- import {Logger, toRootHex} from "@lodestar/utils";
5
+ import {Logger, fromHex, toRootHex} from "@lodestar/utils";
6
6
  import {Metrics} from "../../metrics/index.js";
7
7
  import {JobItemQueue} from "../../util/queue/index.js";
8
- import {BlockStateCache, CheckpointHex, CheckpointStateCache} from "../stateCache/types.js";
8
+ import {BlockStateCache, CheckpointHexPayload, CheckpointStateCache} from "../stateCache/types.js";
9
9
  import {RegenError, RegenErrorCode} from "./errors.js";
10
10
  import {
11
11
  IStateRegenerator,
@@ -104,9 +104,19 @@ export class QueuedStateRegenerator implements IStateRegenerator {
104
104
  const parentEpoch = computeEpochAtSlot(parentBlock.slot);
105
105
  const blockEpoch = computeEpochAtSlot(block.slot);
106
106
 
107
+ // Convert PayloadStatus to payloadPresent boolean
108
+ if (parentBlock.payloadStatus === PayloadStatus.PENDING) {
109
+ throw new RegenError({
110
+ code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS,
111
+ blockRoot: block.parentRoot,
112
+ payloadStatus: parentBlock.payloadStatus,
113
+ });
114
+ }
115
+ const payloadPresent = parentBlock.payloadStatus === PayloadStatus.FULL;
116
+
107
117
  // Check the checkpoint cache (if the pre-state is a checkpoint state)
108
118
  if (parentEpoch < blockEpoch) {
109
- const checkpointState = this.checkpointStateCache.getLatest(parentRoot, blockEpoch);
119
+ const checkpointState = this.checkpointStateCache.getLatest(parentRoot, blockEpoch, payloadPresent);
110
120
  if (checkpointState && computeEpochAtSlot(checkpointState.slot) === blockEpoch) {
111
121
  return checkpointState;
112
122
  }
@@ -125,14 +135,14 @@ export class QueuedStateRegenerator implements IStateRegenerator {
125
135
  return null;
126
136
  }
127
137
 
128
- async getCheckpointStateOrBytes(cp: CheckpointHex): Promise<CachedBeaconStateAllForks | Uint8Array | null> {
138
+ async getCheckpointStateOrBytes(cp: CheckpointHexPayload): Promise<CachedBeaconStateAllForks | Uint8Array | null> {
129
139
  return this.checkpointStateCache.getStateOrBytes(cp);
130
140
  }
131
141
 
132
142
  /**
133
143
  * Get checkpoint state from cache
134
144
  */
135
- getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null {
145
+ getCheckpointStateSync(cp: CheckpointHexPayload): CachedBeaconStateAllForks | null {
136
146
  return this.checkpointStateCache.get(cp);
137
147
  }
138
148
 
@@ -140,7 +150,19 @@ export class QueuedStateRegenerator implements IStateRegenerator {
140
150
  * Get state closest to head
141
151
  */
142
152
  getClosestHeadState(head: ProtoBlock): CachedBeaconStateAllForks | null {
143
- return this.checkpointStateCache.getLatest(head.blockRoot, Infinity) || this.blockStateCache.get(head.stateRoot);
153
+ // Convert PayloadStatus to payloadPresent boolean
154
+ if (head.payloadStatus === PayloadStatus.PENDING) {
155
+ throw new RegenError({
156
+ code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS,
157
+ blockRoot: fromHex(head.blockRoot),
158
+ payloadStatus: head.payloadStatus,
159
+ });
160
+ }
161
+ const payloadPresent = head.payloadStatus === PayloadStatus.FULL;
162
+ return (
163
+ this.checkpointStateCache.getLatest(head.blockRoot, Infinity, payloadPresent) ||
164
+ this.blockStateCache.get(head.stateRoot)
165
+ );
144
166
  }
145
167
 
146
168
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void {
@@ -153,15 +175,24 @@ export class QueuedStateRegenerator implements IStateRegenerator {
153
175
  this.blockStateCache.deleteAllBeforeEpoch(finalizedEpoch);
154
176
  }
155
177
 
156
- processState(blockRootHex: RootHex, postState: CachedBeaconStateAllForks): void {
178
+ processBlockState(blockRootHex: RootHex, postState: CachedBeaconStateAllForks): void {
157
179
  this.blockStateCache.add(postState);
158
180
  this.checkpointStateCache.processState(blockRootHex, postState).catch((e) => {
159
181
  this.logger.debug("Error processing block state", {blockRootHex, slot: postState.slot}, e);
160
182
  });
161
183
  }
162
184
 
163
- addCheckpointState(cp: phase0.Checkpoint, item: CachedBeaconStateAllForks): void {
164
- this.checkpointStateCache.add(cp, item);
185
+ /**
186
+ * Process payload state for caching after importing execution payload.
187
+ */
188
+ processPayloadState(payloadState: CachedBeaconStateAllForks): void {
189
+ // Add payload state to block state cache (keyed by payload state root)
190
+ this.blockStateCache.add(payloadState);
191
+ }
192
+
193
+ // TODO GLOAS: This should also be called when importing execution payload after we implement it
194
+ addCheckpointState(cp: phase0.Checkpoint, item: CachedBeaconStateAllForks, payloadPresent: boolean): void {
195
+ this.checkpointStateCache.add(cp, item, payloadPresent);
165
196
  }
166
197
 
167
198
  updateHeadState(newHead: ProtoBlock, maybeHeadState: CachedBeaconStateAllForks): void {
@@ -197,8 +228,13 @@ export class QueuedStateRegenerator implements IStateRegenerator {
197
228
  }
198
229
  }
199
230
 
200
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null {
201
- return this.checkpointStateCache.updatePreComputedCheckpoint(rootHex, epoch);
231
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null {
232
+ return this.checkpointStateCache.updatePreComputedCheckpoint(rootHex, epoch, payloadPresent);
233
+ }
234
+
235
+ upgradeForGloas(epoch: Epoch): void {
236
+ this.logger.verbose("Upgrading block state cache for Gloas fork", {epoch});
237
+ this.blockStateCache.upgradeToGloas();
202
238
  }
203
239
 
204
240
  /**
@@ -1,6 +1,6 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
3
- import {SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
3
+ import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
4
4
  import {
5
5
  CachedBeaconStateAllForks,
6
6
  DataAvailabilityStatus,
@@ -111,9 +111,20 @@ export class StateRegenerator implements IStateRegeneratorInternal {
111
111
  const {blockRoot} = block;
112
112
  const {checkpointStateCache} = this.modules;
113
113
  const epoch = computeEpochAtSlot(slot);
114
+
115
+ // Convert PayloadStatus to payloadPresent boolean
116
+ if (block.payloadStatus === PayloadStatus.PENDING) {
117
+ throw new RegenError({
118
+ code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS,
119
+ blockRoot: fromHex(blockRoot),
120
+ payloadStatus: block.payloadStatus,
121
+ });
122
+ }
123
+ const payloadPresent = block.payloadStatus === PayloadStatus.FULL;
124
+
114
125
  const latestCheckpointStateCtx = allowDiskReload
115
- ? await checkpointStateCache.getOrReloadLatest(blockRoot, epoch)
116
- : checkpointStateCache.getLatest(blockRoot, epoch);
126
+ ? await checkpointStateCache.getOrReloadLatest(blockRoot, epoch, payloadPresent)
127
+ : checkpointStateCache.getLatest(blockRoot, epoch, payloadPresent);
117
128
 
118
129
  // If a checkpoint state exists with the given checkpoint root, it either is in requested epoch
119
130
  // or needs to have empty slots processed until the requested epoch
@@ -166,9 +177,19 @@ export class StateRegenerator implements IStateRegeneratorInternal {
166
177
  const lastBlockToReplay = blocksToReplay.at(-1);
167
178
  if (!lastBlockToReplay) continue;
168
179
  const epoch = computeEpochAtSlot(lastBlockToReplay.slot - 1);
180
+
181
+ // Convert PayloadStatus to payloadPresent boolean
182
+ if (b.payloadStatus === PayloadStatus.PENDING) {
183
+ throw new RegenError({
184
+ code: RegenErrorCode.INTERNAL_ERROR,
185
+ message: `Unexpected PENDING payloadStatus for ancestor block ${b.blockRoot} at slot ${b.slot}`,
186
+ });
187
+ }
188
+ const payloadPresent = b.payloadStatus === PayloadStatus.FULL;
189
+
169
190
  state = allowDiskReload
170
- ? await checkpointStateCache.getOrReloadLatest(b.blockRoot, epoch)
171
- : checkpointStateCache.getLatest(b.blockRoot, epoch);
191
+ ? await checkpointStateCache.getOrReloadLatest(b.blockRoot, epoch, payloadPresent)
192
+ : checkpointStateCache.getLatest(b.blockRoot, epoch, payloadPresent);
172
193
  if (state) {
173
194
  break;
174
195
  }
@@ -332,6 +353,11 @@ async function processSlotsByCheckpoint(
332
353
  * emitting "checkpoint" events after every epoch processed.
333
354
  *
334
355
  * Stops processing after no more full epochs can be processed.
356
+ *
357
+ * Output state variant:
358
+ * - Post-Gloas: If slots are processed, returns block state (payloadPresent=false).
359
+ * If no slots processed, returns preState as-is (preserves variant).
360
+ * - Pre-Gloas: Always payloadPresent=true (no block/payload distinction).
335
361
  */
336
362
  export async function processSlotsToNearestCheckpoint(
337
363
  modules: {
@@ -374,7 +400,11 @@ export async function processSlotsToNearestCheckpoint(
374
400
  // This may becomes the "official" checkpoint state if the 1st block of epoch is skipped
375
401
  const checkpointState = postState;
376
402
  const cp = getCheckpointFromState(checkpointState);
377
- checkpointStateCache.add(cp, checkpointState);
403
+ // processSlots() only does epoch transitions, never processes payloads
404
+ // Pre-Gloas: payloadPresent is always true (execution payload embedded in block)
405
+ // Post-Gloas: result is a block state (payloadPresent=false)
406
+ const isPayloadPresent = checkpointState.config.getForkSeq(checkpointState.slot) < ForkSeq.gloas;
407
+ checkpointStateCache.add(cp, checkpointState, isPayloadPresent);
378
408
  // consumers should not mutate state ever
379
409
  emitter?.emit(ChainEvent.checkpoint, cp, checkpointState);
380
410
 
@@ -1,6 +1,6 @@
1
1
  import {SLOTS_PER_EPOCH} from "@lodestar/params";
2
2
  import {Epoch, phase0, ssz} from "@lodestar/types";
3
- import {MapDef} from "@lodestar/utils";
3
+ import {MapDef, byteArrayEquals} from "@lodestar/utils";
4
4
  import {IBeaconDb} from "../../../db/interface.js";
5
5
  import {
6
6
  getLastProcessedSlotFromBeaconStateSerialized,
@@ -14,8 +14,8 @@ import {CPStateDatastore, DatastoreKey} from "./types.js";
14
14
  export class DbCPStateDatastore implements CPStateDatastore {
15
15
  constructor(private readonly db: IBeaconDb) {}
16
16
 
17
- async write(cpKey: phase0.Checkpoint, stateBytes: Uint8Array): Promise<DatastoreKey> {
18
- const serializedCheckpoint = checkpointToDatastoreKey(cpKey);
17
+ async write(cpKey: phase0.Checkpoint, stateBytes: Uint8Array, payloadPresent: boolean): Promise<DatastoreKey> {
18
+ const serializedCheckpoint = checkpointToDatastoreKey(cpKey, payloadPresent);
19
19
  await this.db.checkpointState.putBinary(serializedCheckpoint, stateBytes);
20
20
  return serializedCheckpoint;
21
21
  }
@@ -40,18 +40,30 @@ export class DbCPStateDatastore implements CPStateDatastore {
40
40
  }
41
41
  }
42
42
 
43
+ function extractCheckpointBytes(key: DatastoreKey): Uint8Array {
44
+ const fixedSize = ssz.phase0.Checkpoint.minSize;
45
+ return key.subarray(0, fixedSize);
46
+ }
47
+
43
48
  export function datastoreKeyToCheckpoint(key: DatastoreKey): phase0.Checkpoint {
44
- return ssz.phase0.Checkpoint.deserialize(key);
49
+ return ssz.phase0.Checkpoint.deserialize(extractCheckpointBytes(key));
50
+ }
51
+
52
+ export function checkpointToDatastoreKey(cp: phase0.Checkpoint, payloadPresent: boolean): DatastoreKey {
53
+ const cpBytes = ssz.phase0.Checkpoint.serialize(cp);
54
+ const key = new Uint8Array(cpBytes.length + 1);
55
+ key.set(cpBytes);
56
+ key[cpBytes.length] = payloadPresent ? 1 : 0;
57
+ return key;
45
58
  }
46
59
 
47
- export function checkpointToDatastoreKey(cp: phase0.Checkpoint): DatastoreKey {
48
- return ssz.phase0.Checkpoint.serialize(cp);
60
+ function isPayloadCheckpointState(key: DatastoreKey): boolean {
61
+ return key.at(-1) === 1;
49
62
  }
50
63
 
51
64
  /**
52
- * Get the latest safe checkpoint state the node can use to boot from
53
- * - it should be the checkpoint state that's unique in its epoch
54
- * - its last processed block slot should be at epoch boundary or last slot of previous epoch
65
+ * Get the latest "safe" checkpoint state the node can use to boot from
66
+ * - its last processed block slot should be at epoch boundary (CRCS) or last slot of previous epoch (PRCS)
55
67
  * - state slot should be at epoch boundary
56
68
  * - state slot should be equal to epoch * SLOTS_PER_EPOCH
57
69
  *
@@ -70,9 +82,20 @@ export async function getLatestSafeDatastoreKey(
70
82
 
71
83
  const dataStoreKeyByEpoch: Map<Epoch, DatastoreKey> = new Map();
72
84
  for (const [epoch, keys] of checkpointsByEpoch.entries()) {
73
- // only consider epochs with a single checkpoint to avoid ambiguity from forks
74
85
  if (keys.length === 1) {
86
+ // PRCS (skipped slot) or CRCS and no payloadPresent
87
+ // Pre-gloas always fall into this case
75
88
  dataStoreKeyByEpoch.set(epoch, keys[0]);
89
+ } else if (keys.length === 2) {
90
+ // CRCS without payload and CRCS with payload
91
+ // ie Two keys for the same checkpoint with different payloadPresent suffix (FULL/EMPTY)
92
+ // TODO GLOAS: Here we pick FULL key, there is a chance that payload is orphaned hence we not be able to sync
93
+ const cp0 = extractCheckpointBytes(keys[0]);
94
+ const cp1 = extractCheckpointBytes(keys[1]);
95
+ if (byteArrayEquals(cp0, cp1)) {
96
+ const fullKey = isPayloadCheckpointState(keys[0]) ? keys[0] : keys[1];
97
+ dataStoreKeyByEpoch.set(epoch, fullKey);
98
+ }
76
99
  }
77
100
  }
78
101
 
@@ -1,12 +1,13 @@
1
1
  import path from "node:path";
2
- import {phase0, ssz} from "@lodestar/types";
2
+ import {phase0} from "@lodestar/types";
3
3
  import {fromHex, toHex} from "@lodestar/utils";
4
4
  import {ensureDir, readFile, readFileNames, removeFile, writeIfNotExist} from "../../../util/file.js";
5
- import {getLatestSafeDatastoreKey} from "./db.js";
5
+ import {checkpointToDatastoreKey, getLatestSafeDatastoreKey} from "./db.js";
6
6
  import {CPStateDatastore, DatastoreKey} from "./types.js";
7
7
 
8
8
  const CHECKPOINT_STATES_FOLDER = "checkpoint_states";
9
- const CHECKPOINT_FILE_NAME_LENGTH = 82;
9
+ /** 41 bytes (40 checkpoint + 1 payloadPresent) = 82 hex chars + "0x" prefix = 84 */
10
+ const CHECKPOINT_FILE_NAME_LENGTH = 84;
10
11
 
11
12
  /**
12
13
  * Implementation of CPStateDatastore using file system, this is beneficial for debugging.
@@ -28,8 +29,8 @@ export class FileCPStateDatastore implements CPStateDatastore {
28
29
  }
29
30
  }
30
31
 
31
- async write(cpKey: phase0.Checkpoint, stateBytes: Uint8Array): Promise<DatastoreKey> {
32
- const serializedCheckpoint = ssz.phase0.Checkpoint.serialize(cpKey);
32
+ async write(cpKey: phase0.Checkpoint, stateBytes: Uint8Array, payloadPresent: boolean): Promise<DatastoreKey> {
33
+ const serializedCheckpoint = checkpointToDatastoreKey(cpKey, payloadPresent);
33
34
  const filePath = path.join(this.folderPath, toHex(serializedCheckpoint));
34
35
  await writeIfNotExist(filePath, stateBytes);
35
36
  return serializedCheckpoint;
@@ -1,11 +1,12 @@
1
1
  import {phase0} from "@lodestar/types";
2
2
 
3
- // With db implementation, persistedKey is serialized data of a checkpoint
3
+ // With db implementation, persistedKey is serialized data of a checkpoint + 1
4
+ // ie a fixed size of `ssz.phase0.Checkpoint.minSize + 1`
4
5
  export type DatastoreKey = Uint8Array;
5
6
 
6
7
  // Make this generic to support testing
7
8
  export interface CPStateDatastore {
8
- write: (cpKey: phase0.Checkpoint, stateBytes: Uint8Array) => Promise<DatastoreKey>;
9
+ write: (cpKey: phase0.Checkpoint, stateBytes: Uint8Array, payloadPresent: boolean) => Promise<DatastoreKey>;
9
10
  remove: (key: DatastoreKey) => Promise<void>;
10
11
  read: (key: DatastoreKey) => Promise<Uint8Array | null>;
11
12
  readLatestSafe: () => Promise<Uint8Array | null>;
@@ -20,6 +20,11 @@ export type FIFOBlockStateCacheOpts = {
20
20
  * clock slot
21
21
  */
22
22
  export const DEFAULT_MAX_BLOCK_STATES = 64;
23
+ /**
24
+ * For Gloas (ePBS), each block can have two states: block state and payload state.
25
+ * Double the cache size to maintain the same effective block depth.
26
+ */
27
+ export const DEFAULT_MAX_BLOCK_STATES_GLOAS = 128;
23
28
 
24
29
  /**
25
30
  * New implementation of BlockStateCache that keeps the most recent n states consistently
@@ -41,10 +46,7 @@ export const DEFAULT_MAX_BLOCK_STATES = 64;
41
46
  * The maintained key order would be: 11 -> 13 -> 12 -> 10, and state 10 will be pruned first.
42
47
  */
43
48
  export class FIFOBlockStateCache implements BlockStateCache {
44
- /**
45
- * Max number of states allowed in the cache
46
- */
47
- readonly maxStates: number;
49
+ private maxStates: number;
48
50
 
49
51
  private readonly cache: MapTracker<string, CachedBeaconStateAllForks>;
50
52
  /**
@@ -170,6 +172,10 @@ export class FIFOBlockStateCache implements BlockStateCache {
170
172
  }
171
173
  }
172
174
 
175
+ upgradeToGloas(): void {
176
+ this.maxStates = DEFAULT_MAX_BLOCK_STATES_GLOAS;
177
+ }
178
+
173
179
  /**
174
180
  * No need for this implementation
175
181
  * This is only to conform to the old api