@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.
- package/lib/api/impl/beacon/state/utils.d.ts +2 -2
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +5 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +0 -9
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +4 -4
- package/lib/chain/archiveStore/interface.d.ts.map +1 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +4 -4
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +4 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +38 -0
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +11 -7
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSignatures.js +1 -1
- package/lib/chain/blocks/verifyBlocksSignatures.js.map +1 -1
- package/lib/chain/chain.d.ts +3 -3
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +16 -7
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +6 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/regen/errors.d.ts +11 -1
- package/lib/chain/regen/errors.d.ts.map +1 -1
- package/lib/chain/regen/errors.js +2 -0
- package/lib/chain/regen/errors.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +12 -6
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/queued.d.ts +11 -6
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +40 -8
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +5 -0
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +33 -6
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/stateCache/datastore/db.d.ts +4 -5
- package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/db.js +32 -10
- package/lib/chain/stateCache/datastore/db.js.map +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/file.js +5 -5
- package/lib/chain/stateCache/datastore/file.js.map +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts +7 -4
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.js +8 -3
- package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +33 -14
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +217 -119
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/stateCache/types.d.ts +15 -8
- package/lib/chain/stateCache/types.d.ts.map +1 -1
- package/lib/chain/stateCache/types.js.map +1 -1
- package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
- package/lib/chain/validation/voluntaryExit.js +2 -2
- package/lib/chain/validation/voluntaryExit.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/state/utils.ts +2 -2
- package/src/api/impl/validator/index.ts +7 -3
- package/src/chain/archiveStore/archiveStore.ts +0 -10
- package/src/chain/archiveStore/interface.ts +4 -4
- package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +8 -5
- package/src/chain/archiveStore/utils/archiveBlocks.ts +59 -1
- package/src/chain/blocks/importBlock.ts +11 -6
- package/src/chain/blocks/verifyBlocksSignatures.ts +1 -1
- package/src/chain/chain.ts +23 -12
- package/src/chain/interface.ts +2 -2
- package/src/chain/prepareNextSlot.ts +6 -2
- package/src/chain/regen/errors.ts +6 -1
- package/src/chain/regen/interface.ts +12 -6
- package/src/chain/regen/queued.ts +48 -12
- package/src/chain/regen/regen.ts +37 -7
- package/src/chain/stateCache/datastore/db.ts +33 -10
- package/src/chain/stateCache/datastore/file.ts +6 -5
- package/src/chain/stateCache/datastore/types.ts +3 -2
- package/src/chain/stateCache/fifoBlockStateCache.ts +10 -4
- package/src/chain/stateCache/persistentCheckpointsCache.ts +248 -139
- package/src/chain/stateCache/types.ts +18 -8
- package/src/chain/validation/voluntaryExit.ts +2 -1
- package/lib/chain/archiveStore/utils/archivePayloads.d.ts +0 -7
- package/lib/chain/archiveStore/utils/archivePayloads.d.ts.map +0 -1
- package/lib/chain/archiveStore/utils/archivePayloads.js +0 -10
- package/lib/chain/archiveStore/utils/archivePayloads.js.map +0 -1
- 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 {
|
|
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:
|
|
42
|
-
getCheckpointStateSync(cp:
|
|
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
|
-
|
|
47
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
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
|
/**
|
package/src/chain/regen/regen.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
48
|
-
return
|
|
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
|
-
* -
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|