@chainsafe/lodestar 1.50.0-dev.f2d839a25c → 1.50.0-dev.fef46df5ba

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 (49) hide show
  1. package/.git-data.json +1 -1
  2. package/lib/cmds/beacon/handler.d.ts.map +1 -1
  3. package/lib/cmds/beacon/handler.js +3 -16
  4. package/lib/cmds/beacon/handler.js.map +1 -1
  5. package/lib/cmds/beacon/initBeaconState.d.ts +11 -17
  6. package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -1
  7. package/lib/cmds/beacon/initBeaconState.js +71 -275
  8. package/lib/cmds/beacon/initBeaconState.js.map +1 -1
  9. package/lib/cmds/beacon/stateInitialization/checkpointState.d.ts +7 -0
  10. package/lib/cmds/beacon/stateInitialization/checkpointState.d.ts.map +1 -0
  11. package/lib/cmds/beacon/stateInitialization/checkpointState.js +168 -0
  12. package/lib/cmds/beacon/stateInitialization/checkpointState.js.map +1 -0
  13. package/lib/cmds/beacon/stateInitialization/errors.d.ts +17 -0
  14. package/lib/cmds/beacon/stateInitialization/errors.d.ts.map +1 -0
  15. package/lib/cmds/beacon/stateInitialization/errors.js +16 -0
  16. package/lib/cmds/beacon/stateInitialization/errors.js.map +1 -0
  17. package/lib/cmds/beacon/stateInitialization/genesisState.d.ts +3 -0
  18. package/lib/cmds/beacon/stateInitialization/genesisState.d.ts.map +1 -0
  19. package/lib/cmds/beacon/stateInitialization/genesisState.js +65 -0
  20. package/lib/cmds/beacon/stateInitialization/genesisState.js.map +1 -0
  21. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts +4 -0
  22. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts.map +1 -0
  23. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js +55 -0
  24. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js.map +1 -0
  25. package/lib/cmds/beacon/stateInitialization/types.d.ts +40 -0
  26. package/lib/cmds/beacon/stateInitialization/types.d.ts.map +1 -0
  27. package/lib/cmds/beacon/stateInitialization/types.js +2 -0
  28. package/lib/cmds/beacon/stateInitialization/types.js.map +1 -0
  29. package/lib/cmds/beacon/stateInitialization/validation.d.ts +4 -0
  30. package/lib/cmds/beacon/stateInitialization/validation.d.ts.map +1 -0
  31. package/lib/cmds/beacon/stateInitialization/validation.js +12 -0
  32. package/lib/cmds/beacon/stateInitialization/validation.js.map +1 -0
  33. package/lib/networks/index.d.ts +3 -9
  34. package/lib/networks/index.d.ts.map +1 -1
  35. package/lib/networks/index.js +11 -29
  36. package/lib/networks/index.js.map +1 -1
  37. package/lib/options/beaconNodeOptions/chain.js +1 -1
  38. package/lib/options/beaconNodeOptions/chain.js.map +1 -1
  39. package/package.json +14 -14
  40. package/src/cmds/beacon/handler.ts +4 -16
  41. package/src/cmds/beacon/initBeaconState.ts +112 -390
  42. package/src/cmds/beacon/stateInitialization/checkpointState.ts +256 -0
  43. package/src/cmds/beacon/stateInitialization/errors.ts +15 -0
  44. package/src/cmds/beacon/stateInitialization/genesisState.ts +85 -0
  45. package/src/cmds/beacon/stateInitialization/prepareStateInitialization.ts +66 -0
  46. package/src/cmds/beacon/stateInitialization/types.ts +59 -0
  47. package/src/cmds/beacon/stateInitialization/validation.ts +16 -0
  48. package/src/networks/index.ts +18 -42
  49. package/src/options/beaconNodeOptions/chain.ts +1 -1
@@ -0,0 +1,256 @@
1
+ import {DbCPStateDatastore, FileCPStateDatastore, persistAnchorState} from "@lodestar/beacon-node";
2
+ import {createBeaconConfig} from "@lodestar/config";
3
+ import {
4
+ IBeaconStateView,
5
+ computeEpochAtSlot,
6
+ computeWeakSubjectivitySummaryFromStateBytes,
7
+ getCurrentSlot,
8
+ isWithinWeakSubjectivityPeriodFromSummary,
9
+ readBeaconStateBytesMetadata,
10
+ } from "@lodestar/state-transition";
11
+ import {ssz} from "@lodestar/types";
12
+ import {Checkpoint} from "@lodestar/types/phase0";
13
+ import {formatBytes, toRootHex} from "@lodestar/utils";
14
+ import {fetchWeakSubjectivityStateBytes, getCheckpointFromArg} from "../../../networks/index.js";
15
+ import {downloadOrLoadFile} from "../../../util/index.js";
16
+ import {StateInitializationError, StateInitializationErrorCode} from "./errors.js";
17
+ import {
18
+ ArchivedStateBytes,
19
+ CheckpointStartupPolicy,
20
+ CheckpointStateBytesCandidate,
21
+ StateInitialization,
22
+ StateInitializationOptions,
23
+ StatePreparationContext,
24
+ } from "./types.js";
25
+ import {assertAnchorStateForkMatchesConfig} from "./validation.js";
26
+
27
+ type CheckpointOptions = Pick<
28
+ StateInitializationOptions,
29
+ "wssCheckpoint" | "forceCheckpointSync" | "ignoreWeakSubjectivityCheck"
30
+ >;
31
+
32
+ export async function prepareCheckpointFileInitialization(
33
+ checkpointState: string,
34
+ options: CheckpointOptions,
35
+ archived: ArchivedStateBytes | null,
36
+ context: StatePreparationContext
37
+ ): Promise<StateInitialization> {
38
+ context.logger.info("Loading checkpoint state", {checkpointState});
39
+ const bytes = await downloadOrLoadFile(checkpointState);
40
+ const stateBytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
41
+ context.logger.info("Loaded checkpoint state", {checkpointState, size: formatBytes(stateBytes.length)});
42
+ return prepareCheckpointInitialization(
43
+ {
44
+ stateBytes,
45
+ expectedCheckpoint: options.wssCheckpoint ? getCheckpointFromArg(options.wssCheckpoint) : null,
46
+ source: "checkpoint file",
47
+ },
48
+ archived,
49
+ {
50
+ isFinalized: true,
51
+ forceCheckpointSync: Boolean(options.forceCheckpointSync),
52
+ ignoreWeakSubjectivityCheck: Boolean(options.ignoreWeakSubjectivityCheck),
53
+ },
54
+ context
55
+ );
56
+ }
57
+
58
+ export async function prepareCheckpointApiInitialization(
59
+ checkpointSyncUrl: string,
60
+ options: CheckpointOptions,
61
+ archived: ArchivedStateBytes | null,
62
+ context: StatePreparationContext
63
+ ): Promise<StateInitialization> {
64
+ const {chainForkConfig, logger} = context;
65
+ try {
66
+ const url = new URL(checkpointSyncUrl);
67
+ logger.info("Fetching checkpoint state", {checkpointSyncUrl: url.origin});
68
+ } catch (error) {
69
+ logger.error("Invalid checkpoint sync URL", {checkpointSyncUrl}, error as Error);
70
+ throw error;
71
+ }
72
+ const {stateBytes, expectedCheckpoint} = await fetchWeakSubjectivityStateBytes(chainForkConfig, logger, {
73
+ checkpointSyncUrl,
74
+ wssCheckpoint: options.wssCheckpoint,
75
+ });
76
+ return prepareCheckpointInitialization(
77
+ {stateBytes, expectedCheckpoint, source: "checkpointSyncUrl"},
78
+ archived,
79
+ {
80
+ isFinalized: true,
81
+ forceCheckpointSync: Boolean(options.forceCheckpointSync),
82
+ ignoreWeakSubjectivityCheck: Boolean(options.ignoreWeakSubjectivityCheck),
83
+ },
84
+ context
85
+ );
86
+ }
87
+
88
+ export async function prepareUnfinalizedCheckpointInitialization(
89
+ options: CheckpointOptions &
90
+ Pick<
91
+ StateInitializationOptions,
92
+ "lastPersistedCheckpointState" | "unsafeCheckpointState" | "chain.nHistoricalStatesFileDataStore"
93
+ >,
94
+ archived: ArchivedStateBytes | null,
95
+ context: StatePreparationContext
96
+ ): Promise<StateInitialization | null> {
97
+ const {db, dataDir, logger} = context;
98
+ let bytes: Uint8Array | null = null;
99
+ if (options.lastPersistedCheckpointState && !options.forceCheckpointSync) {
100
+ const store = options["chain.nHistoricalStatesFileDataStore"]
101
+ ? new FileCPStateDatastore(dataDir)
102
+ : new DbCPStateDatastore(db);
103
+ logger.verbose(`Finding last persisted checkpoint state from ${store.constructor.name}`);
104
+ bytes = await store.readLatestSafe();
105
+ if (bytes === null) {
106
+ logger.warn("Last persisted checkpoint state not found");
107
+ } else {
108
+ logger.info("Found last persisted checkpoint state", {size: formatBytes(bytes.length)});
109
+ }
110
+ }
111
+ if (bytes === null && options.unsafeCheckpointState) {
112
+ logger.info("Loading checkpoint state", {unsafeCheckpointState: options.unsafeCheckpointState});
113
+ bytes = await downloadOrLoadFile(options.unsafeCheckpointState);
114
+ logger.info("Loaded checkpoint state", {size: formatBytes(bytes.length)});
115
+ }
116
+ if (bytes === null) return null;
117
+ logger.warn(
118
+ "Initializing from unfinalized checkpoint state is unsafe and may cause the node to follow a minority chain"
119
+ );
120
+ const stateBytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
121
+ return prepareCheckpointInitialization(
122
+ {
123
+ stateBytes,
124
+ expectedCheckpoint: options.wssCheckpoint ? getCheckpointFromArg(options.wssCheckpoint) : null,
125
+ source: "unfinalized source",
126
+ },
127
+ archived,
128
+ {
129
+ isFinalized: false,
130
+ forceCheckpointSync: Boolean(options.forceCheckpointSync),
131
+ ignoreWeakSubjectivityCheck: Boolean(options.ignoreWeakSubjectivityCheck),
132
+ },
133
+ context
134
+ );
135
+ }
136
+
137
+ function prepareCheckpointInitialization(
138
+ candidate: CheckpointStateBytesCandidate,
139
+ archived: ArchivedStateBytes | null,
140
+ policy: CheckpointStartupPolicy,
141
+ {chainForkConfig, db, logger}: StatePreparationContext
142
+ ): StateInitialization {
143
+ const candidateMetadata = readBeaconStateBytesMetadata(candidate.stateBytes);
144
+ if (candidateMetadata === null) {
145
+ throw new StateInitializationError(
146
+ {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
147
+ `Cannot read state metadata from ${candidate.source}`
148
+ );
149
+ }
150
+ if (
151
+ archived !== null &&
152
+ (archived.metadata.genesisTime !== candidateMetadata.genesisTime ||
153
+ !ssz.Root.equals(archived.metadata.genesisValidatorsRoot, candidateMetadata.genesisValidatorsRoot))
154
+ ) {
155
+ throw new StateInitializationError(
156
+ {code: StateInitializationErrorCode.INCOMPATIBLE_GENESIS},
157
+ "Db state and checkpoint state are not compatible, either clear the db or verify your checkpoint source"
158
+ );
159
+ }
160
+ const useArchived =
161
+ archived !== null && archived.metadata.slot > candidateMetadata.slot && !policy.forceCheckpointSync;
162
+ if (useArchived) {
163
+ logger.verbose(
164
+ "Db state is ahead of the provided checkpoint state, using the db state to initialize the beacon chain"
165
+ );
166
+ }
167
+ const {stateBytes, metadata, weakSubjectivity} = useArchived
168
+ ? archived
169
+ : {
170
+ stateBytes: candidate.stateBytes,
171
+ metadata: candidateMetadata,
172
+ weakSubjectivity: computeWeakSubjectivitySummaryFromStateBytes(
173
+ chainForkConfig,
174
+ candidate.stateBytes,
175
+ candidateMetadata
176
+ ),
177
+ };
178
+ if (weakSubjectivity === null) {
179
+ throw new StateInitializationError(
180
+ {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
181
+ `Cannot extract weak subjectivity summary from ${candidate.source}`
182
+ );
183
+ }
184
+ const expectedCheckpoint = useArchived ? null : candidate.expectedCheckpoint;
185
+ const source = useArchived ? "db" : candidate.source;
186
+ const {isFinalized, ignoreWeakSubjectivityCheck} = policy;
187
+ const config = createBeaconConfig(chainForkConfig, metadata.genesisValidatorsRoot);
188
+ const archivedWithinWeakSubjectivityPeriod = useArchived ? archived.isWithinWeakSubjectivityPeriod : null;
189
+ // DB-selected anchors need no persistence, including at slot zero.
190
+ const shouldPersist = isFinalized && !useArchived;
191
+ return {
192
+ stateBytes,
193
+ config,
194
+ isFinalized,
195
+ validate(state) {
196
+ // A supplied checkpoint must match even when the period check is ignored.
197
+ if (expectedCheckpoint !== null) assertStateMatchesCheckpoint(state, expectedCheckpoint);
198
+ const passedValidation =
199
+ archivedWithinWeakSubjectivityPeriod ?? isWithinWeakSubjectivityPeriodFromSummary(config, weakSubjectivity);
200
+ if (!passedValidation && !ignoreWeakSubjectivityCheck) {
201
+ const clockEpoch = computeEpochAtSlot(getCurrentSlot(config, weakSubjectivity.genesisTime));
202
+ throw new StateInitializationError(
203
+ {code: StateInitializationErrorCode.STALE_CHECKPOINT},
204
+ `The selected state with epoch ${weakSubjectivity.checkpointEpoch} is not within weak subjectivity period of ${weakSubjectivity.period} epochs from the current epoch ${clockEpoch}. Please verify your checkpoint source`
205
+ );
206
+ }
207
+ if (isFinalized) {
208
+ assertAnchorStateForkMatchesConfig(config, state);
209
+ const source = useArchived ? "db" : "checkpoint";
210
+ const logData = {
211
+ slot: state.slot,
212
+ epoch: computeEpochAtSlot(state.slot),
213
+ stateSize: formatBytes(stateBytes.length),
214
+ stateRoot: toRootHex(state.hashTreeRoot()),
215
+ isWithinWeakSubjectivityPeriod: passedValidation,
216
+ };
217
+ if (passedValidation) {
218
+ logger.info(`Initializing beacon from a valid ${source} state`, logData);
219
+ } else {
220
+ logger.warn(`Initializing from a stale ${source} state vulnerable to long range attacks`, logData);
221
+ logger.warn("Checkpoint sync recommended, please use --help to see checkpoint sync options");
222
+ }
223
+ }
224
+ },
225
+ persist: shouldPersist ? (state, bytes) => persistAnchorState(config, db, state, bytes) : null,
226
+ log(state) {
227
+ const {checkpoint} = state.computeAnchorCheckpoint();
228
+ logger.info("Initialized checkpoint state", {
229
+ source,
230
+ slot: state.slot,
231
+ epoch: checkpoint.epoch,
232
+ stateSize: formatBytes(stateBytes.length),
233
+ stateRoot: toRootHex(state.hashTreeRoot()),
234
+ checkpointRoot: toRootHex(checkpoint.root),
235
+ isFinalized,
236
+ ...(isFinalized ? {} : {lastProcessedSlot: state.latestBlockHeader.slot}),
237
+ });
238
+ },
239
+ };
240
+ }
241
+
242
+ function assertStateMatchesCheckpoint(state: IBeaconStateView, checkpoint: Checkpoint): void {
243
+ const {root: blockRoot, epoch: stateEpoch} = state.computeAnchorCheckpoint().checkpoint;
244
+ if (!ssz.Root.equals(blockRoot, checkpoint.root)) {
245
+ throw new StateInitializationError(
246
+ {code: StateInitializationErrorCode.CHECKPOINT_ROOT_MISMATCH},
247
+ `Roots do not match. expected=${toRootHex(checkpoint.root)}, actual=${toRootHex(blockRoot)}`
248
+ );
249
+ }
250
+ if (!ssz.Epoch.equals(stateEpoch, checkpoint.epoch)) {
251
+ throw new StateInitializationError(
252
+ {code: StateInitializationErrorCode.CHECKPOINT_EPOCH_MISMATCH},
253
+ `Epochs do not match. expected=${checkpoint.epoch}, actual=${stateEpoch}`
254
+ );
255
+ }
256
+ }
@@ -0,0 +1,15 @@
1
+ import {LodestarError} from "@lodestar/utils";
2
+
3
+ export enum StateInitializationErrorCode {
4
+ INVALID_CHECKPOINT_SOURCE = "INVALID_CHECKPOINT_SOURCE",
5
+ MALFORMED_STATE_BYTES = "MALFORMED_STATE_BYTES",
6
+ INCOMPATIBLE_GENESIS = "INCOMPATIBLE_GENESIS",
7
+ CHECKPOINT_ROOT_MISMATCH = "CHECKPOINT_ROOT_MISMATCH",
8
+ CHECKPOINT_EPOCH_MISMATCH = "CHECKPOINT_EPOCH_MISMATCH",
9
+ STALE_CHECKPOINT = "STALE_CHECKPOINT",
10
+ ANCHOR_STATE_FORK_MISMATCH = "ANCHOR_STATE_FORK_MISMATCH",
11
+ GENESIS_ROOT_MISMATCH = "GENESIS_ROOT_MISMATCH",
12
+ MISSING_INITIALIZATION_SOURCE = "MISSING_INITIALIZATION_SOURCE",
13
+ }
14
+
15
+ export class StateInitializationError extends LodestarError<{code: StateInitializationErrorCode}> {}
@@ -0,0 +1,85 @@
1
+ import {persistAnchorState} from "@lodestar/beacon-node";
2
+ import {createBeaconConfig} from "@lodestar/config";
3
+ import {
4
+ computeEpochAtSlot,
5
+ computeWeakSubjectivitySummaryFromStateBytes,
6
+ isWithinWeakSubjectivityPeriodFromSummary,
7
+ readBeaconStateBytesMetadata,
8
+ } from "@lodestar/state-transition";
9
+ import {formatBytes, toRootHex} from "@lodestar/utils";
10
+ import {getGenesisFileUrl, getGenesisStateRoot} from "../../../networks/index.js";
11
+ import {defaultNetwork} from "../../../options/globalOptions.js";
12
+ import {downloadOrLoadFile} from "../../../util/index.js";
13
+ import {StateInitializationError, StateInitializationErrorCode} from "./errors.js";
14
+ import {StateInitialization, StateInitializationOptions, StatePreparationContext} from "./types.js";
15
+ import {assertAnchorStateForkMatchesConfig} from "./validation.js";
16
+
17
+ export async function prepareGenesisInitialization(
18
+ options: Pick<StateInitializationOptions, "genesisStateFile" | "network" | "forceGenesis">,
19
+ {chainForkConfig, db, logger}: StatePreparationContext
20
+ ): Promise<StateInitialization> {
21
+ const source = options.genesisStateFile || getGenesisFileUrl(options.network || defaultNetwork);
22
+ if (!source || options.forceGenesis) {
23
+ throw new StateInitializationError(
24
+ {code: StateInitializationErrorCode.MISSING_INITIALIZATION_SOURCE},
25
+ "Failed to initialize beacon state, please provide a genesis state file or use checkpoint sync"
26
+ );
27
+ }
28
+ logger.info("Loading genesis state", {genesisStateFile: source});
29
+ const bytes = await downloadOrLoadFile(source);
30
+ const stateBytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
31
+ logger.info("Loaded genesis state", {size: formatBytes(stateBytes.length)});
32
+ const metadata = readBeaconStateBytesMetadata(stateBytes);
33
+ if (metadata === null) {
34
+ throw new StateInitializationError(
35
+ {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
36
+ "Cannot read metadata from genesis state bytes"
37
+ );
38
+ }
39
+ const weakSubjectivity = computeWeakSubjectivitySummaryFromStateBytes(chainForkConfig, stateBytes, metadata);
40
+ if (weakSubjectivity === null) {
41
+ throw new StateInitializationError(
42
+ {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
43
+ "Cannot extract weak subjectivity summary from genesis state bytes"
44
+ );
45
+ }
46
+ const config = createBeaconConfig(chainForkConfig, metadata.genesisValidatorsRoot);
47
+ const expectedRoot = getGenesisStateRoot(options.network);
48
+ return {
49
+ stateBytes,
50
+ config,
51
+ isFinalized: true,
52
+ validate(state) {
53
+ const stateRoot = toRootHex(state.hashTreeRoot());
54
+ if (expectedRoot !== null && stateRoot !== expectedRoot) {
55
+ throw new StateInitializationError(
56
+ {code: StateInitializationErrorCode.GENESIS_ROOT_MISMATCH},
57
+ `Genesis state root mismatch expected=${expectedRoot} received=${stateRoot}`
58
+ );
59
+ }
60
+ assertAnchorStateForkMatchesConfig(config, state);
61
+ const passedValidation = isWithinWeakSubjectivityPeriodFromSummary(config, weakSubjectivity);
62
+ const logData = {
63
+ slot: state.slot,
64
+ epoch: computeEpochAtSlot(state.slot),
65
+ stateSize: formatBytes(stateBytes.length),
66
+ stateRoot,
67
+ isWithinWeakSubjectivityPeriod: passedValidation,
68
+ };
69
+ if (passedValidation) {
70
+ logger.info("Initializing beacon from a valid checkpoint state", logData);
71
+ } else {
72
+ logger.warn("Initializing from a stale checkpoint state vulnerable to long range attacks", logData);
73
+ logger.warn("Checkpoint sync recommended, please use --help to see checkpoint sync options");
74
+ }
75
+ },
76
+ persist: (state, bytes) => persistAnchorState(config, db, state, bytes),
77
+ log(state) {
78
+ logger.info("Initialized genesis state", {
79
+ slot: state.slot,
80
+ stateRoot: toRootHex(state.hashTreeRoot()),
81
+ isFinalized: true,
82
+ });
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,66 @@
1
+ import {createBeaconConfig} from "@lodestar/config";
2
+ import {computeEpochAtSlot} from "@lodestar/state-transition";
3
+ import {formatBytes, toRootHex} from "@lodestar/utils";
4
+ import {
5
+ prepareCheckpointApiInitialization,
6
+ prepareCheckpointFileInitialization,
7
+ prepareUnfinalizedCheckpointInitialization,
8
+ } from "./checkpointState.js";
9
+ import {prepareGenesisInitialization} from "./genesisState.js";
10
+ import {ArchivedStateBytes, StateInitialization, StateInitializationOptions, StatePreparationContext} from "./types.js";
11
+ import {assertAnchorStateForkMatchesConfig} from "./validation.js";
12
+
13
+ export function prepareArchivedStateInitialization(
14
+ {stateBytes, metadata, isWithinWeakSubjectivityPeriod}: ArchivedStateBytes,
15
+ {chainForkConfig, logger}: StatePreparationContext
16
+ ): StateInitialization {
17
+ const config = createBeaconConfig(chainForkConfig, metadata.genesisValidatorsRoot);
18
+ return {
19
+ stateBytes,
20
+ config,
21
+ isFinalized: true,
22
+ validate(state) {
23
+ assertAnchorStateForkMatchesConfig(config, state);
24
+ const logData = {
25
+ slot: state.slot,
26
+ epoch: computeEpochAtSlot(state.slot),
27
+ stateSize: formatBytes(stateBytes.length),
28
+ stateRoot: toRootHex(state.hashTreeRoot()),
29
+ isWithinWeakSubjectivityPeriod,
30
+ };
31
+ if (isWithinWeakSubjectivityPeriod) {
32
+ logger.info("Initializing beacon from a valid db state", logData);
33
+ } else {
34
+ logger.warn("Initializing from a stale db state vulnerable to long range attacks", logData);
35
+ logger.warn("Checkpoint sync recommended, please use --help to see checkpoint sync options");
36
+ }
37
+ },
38
+ persist: null,
39
+ log(state) {
40
+ logger.info("Initialized state from db", {
41
+ slot: state.slot,
42
+ epoch: computeEpochAtSlot(state.slot),
43
+ stateRoot: toRootHex(state.hashTreeRoot()),
44
+ isFinalized: true,
45
+ });
46
+ },
47
+ };
48
+ }
49
+
50
+ export async function prepareCheckpointOrGenesisInitialization(
51
+ options: StateInitializationOptions,
52
+ archived: ArchivedStateBytes | null,
53
+ context: StatePreparationContext
54
+ ): Promise<StateInitialization> {
55
+ if (options.checkpointState) {
56
+ return prepareCheckpointFileInitialization(options.checkpointState, options, archived, context);
57
+ }
58
+ if (options.checkpointSyncUrl) {
59
+ return prepareCheckpointApiInitialization(options.checkpointSyncUrl, options, archived, context);
60
+ }
61
+ if (options.lastPersistedCheckpointState || options.unsafeCheckpointState) {
62
+ const stateInit = await prepareUnfinalizedCheckpointInitialization(options, archived, context);
63
+ if (stateInit !== null) return stateInit;
64
+ }
65
+ return prepareGenesisInitialization(options, context);
66
+ }
@@ -0,0 +1,59 @@
1
+ import {IBeaconDb} from "@lodestar/beacon-node";
2
+ import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
3
+ import {IBeaconStateView, StateBytesMetadata, WeakSubjectivitySummary} from "@lodestar/state-transition";
4
+ import {Checkpoint} from "@lodestar/types/phase0";
5
+ import {Logger} from "@lodestar/utils";
6
+ import {GlobalArgs} from "../../../options/globalOptions.js";
7
+ import {BeaconArgs} from "../options.js";
8
+
9
+ export type StateInitializationOptions = Pick<
10
+ BeaconArgs & GlobalArgs,
11
+ | "checkpointState"
12
+ | "checkpointSyncUrl"
13
+ | "unsafeCheckpointState"
14
+ | "lastPersistedCheckpointState"
15
+ | "genesisStateFile"
16
+ | "wssCheckpoint"
17
+ | "forceCheckpointSync"
18
+ | "ignoreWeakSubjectivityCheck"
19
+ | "forceGenesis"
20
+ | "network"
21
+ | "chain.nHistoricalStatesFileDataStore"
22
+ | "chain.nativeStateView"
23
+ >;
24
+
25
+ export type StatePreparationContext = {
26
+ chainForkConfig: ChainForkConfig;
27
+ db: IBeaconDb;
28
+ dataDir: string;
29
+ logger: Logger;
30
+ };
31
+
32
+ export type ArchivedStateBytes = {
33
+ stateBytes: Uint8Array;
34
+ metadata: StateBytesMetadata;
35
+ weakSubjectivity: WeakSubjectivitySummary;
36
+ isWithinWeakSubjectivityPeriod: boolean;
37
+ };
38
+
39
+ export type CheckpointStateBytesCandidate = {
40
+ stateBytes: Uint8Array;
41
+ expectedCheckpoint: Checkpoint | null;
42
+ source: "checkpoint file" | "checkpointSyncUrl" | "unfinalized source";
43
+ };
44
+
45
+ export type CheckpointStartupPolicy = {
46
+ isFinalized: boolean;
47
+ forceCheckpointSync: boolean;
48
+ ignoreWeakSubjectivityCheck: boolean;
49
+ };
50
+
51
+ /** Selected state bytes and source-specific initialization steps. */
52
+ export type StateInitialization = {
53
+ stateBytes: Uint8Array;
54
+ config: BeaconConfig;
55
+ isFinalized: boolean;
56
+ validate: (state: IBeaconStateView) => void;
57
+ persist: ((state: IBeaconStateView, stateBytes: Uint8Array) => Promise<void>) | null;
58
+ log: (state: IBeaconStateView) => void;
59
+ };
@@ -0,0 +1,16 @@
1
+ import {ChainForkConfig} from "@lodestar/config";
2
+ import {IBeaconStateView, computeStartSlotAtEpoch} from "@lodestar/state-transition";
3
+ import {toHex} from "@lodestar/utils";
4
+ import {StateInitializationError, StateInitializationErrorCode} from "./errors.js";
5
+
6
+ export function assertAnchorStateForkMatchesConfig(config: ChainForkConfig, anchorState: IBeaconStateView): void {
7
+ const expectedFork = config.getForkInfo(computeStartSlotAtEpoch(anchorState.fork.epoch));
8
+ const expectedForkVersion = toHex(expectedFork.version);
9
+ const stateFork = toHex(anchorState.fork.currentVersion);
10
+ if (stateFork !== expectedForkVersion) {
11
+ throw new StateInitializationError(
12
+ {code: StateInitializationErrorCode.ANCHOR_STATE_FORK_MISMATCH},
13
+ `State current fork version ${stateFork} not equal to current config ${expectedForkVersion}. Maybe caused by importing a state from a different network`
14
+ );
15
+ }
16
+ }
@@ -4,15 +4,9 @@ import {WireFormat, getClient} from "@lodestar/api";
4
4
  import {getStateSlotFromBytes} from "@lodestar/beacon-node";
5
5
  import {ChainConfig, ChainForkConfig} from "@lodestar/config";
6
6
  import {SLOTS_PER_EPOCH} from "@lodestar/params";
7
- import {
8
- BeaconStateAllForks,
9
- computeCheckpointEpochAtStateSlot,
10
- getLatestBlockRoot,
11
- loadState,
12
- } from "@lodestar/state-transition";
13
- import {Slot, sszTypesFor} from "@lodestar/types";
7
+ import {Slot} from "@lodestar/types";
14
8
  import {Checkpoint} from "@lodestar/types/phase0";
15
- import {Logger, callFnWhenAwait, fetch, formatBytes, fromHex} from "@lodestar/utils";
9
+ import {LodestarError, Logger, callFnWhenAwait, fetch, formatBytes, fromHex} from "@lodestar/utils";
16
10
  import {parseBootnodesFile} from "../util/format.js";
17
11
  import * as chiado from "./chiado.js";
18
12
  import * as dev from "./dev.js";
@@ -160,15 +154,11 @@ export function readBootnodes(bootnodesFilePath: string): string[] {
160
154
  /**
161
155
  * Fetch weak subjectivity state from a remote beacon node
162
156
  */
163
- export async function fetchWeakSubjectivityState(
157
+ export async function fetchWeakSubjectivityStateBytes(
164
158
  config: ChainForkConfig,
165
159
  logger: Logger,
166
- {checkpointSyncUrl, wssCheckpoint}: {checkpointSyncUrl: string; wssCheckpoint?: string},
167
- {
168
- lastDbState,
169
- lastDbValidatorsBytes,
170
- }: {lastDbState: BeaconStateAllForks | null; lastDbValidatorsBytes: Uint8Array | null}
171
- ): Promise<{wsState: BeaconStateAllForks; wsStateBytes: Uint8Array; wsCheckpoint: Checkpoint}> {
160
+ {checkpointSyncUrl, wssCheckpoint}: {checkpointSyncUrl: string; wssCheckpoint?: string}
161
+ ): Promise<{stateBytes: Uint8Array; expectedCheckpoint: Checkpoint | null}> {
172
162
  try {
173
163
  let wsCheckpoint: Checkpoint | null;
174
164
  let stateId: Slot | "finalized";
@@ -178,7 +168,7 @@ export async function fetchWeakSubjectivityState(
178
168
  wsCheckpoint = getCheckpointFromArg(wssCheckpoint);
179
169
  stateId = wsCheckpoint.epoch * SLOTS_PER_EPOCH;
180
170
  } else {
181
- // Fetch current finalized state and extract checkpoint from it
171
+ // Checkpoint identity can be derived after the selected state is constructed.
182
172
  stateId = "finalized";
183
173
  wsCheckpoint = null;
184
174
  }
@@ -186,33 +176,28 @@ export async function fetchWeakSubjectivityState(
186
176
  // getStateV2 should be available for all forks including phase0
187
177
  const getStatePromise = api.debug.getStateV2({stateId}, {responseWireFormat: WireFormat.ssz});
188
178
 
189
- const {wsStateBytes, fork} = await callFnWhenAwait(
179
+ const response = await callFnWhenAwait(
190
180
  getStatePromise,
191
181
  () => logger.info("Download in progress, please wait..."),
192
182
  GET_STATE_LOG_INTERVAL
193
- ).then((res) => {
194
- return {wsStateBytes: res.ssz(), fork: res.meta().version};
195
- });
183
+ );
184
+ const wsStateBytes = response.ssz();
196
185
 
197
186
  const wsSlot = getStateSlotFromBytes(wsStateBytes);
198
187
  const logData = {stateId, size: formatBytes(wsStateBytes.length)};
199
188
  logger.info("Download completed", typeof stateId === "number" ? logData : {...logData, slot: wsSlot});
200
189
 
201
- let wsState: BeaconStateAllForks;
202
- if (lastDbState && lastDbValidatorsBytes) {
203
- // use lastDbState to load wsState if possible to share the same state tree
204
- wsState = loadState(config, lastDbState, wsStateBytes, lastDbValidatorsBytes).state;
205
- } else {
206
- wsState = sszTypesFor(fork).BeaconState.deserializeToViewDU(wsStateBytes);
207
- }
208
-
209
190
  return {
210
- wsState,
211
- wsStateBytes,
212
- wsCheckpoint: wsCheckpoint ?? getCheckpointFromState(wsState),
191
+ stateBytes: new Uint8Array(wsStateBytes.buffer, wsStateBytes.byteOffset, wsStateBytes.byteLength),
192
+ expectedCheckpoint: wsCheckpoint,
213
193
  };
214
194
  } catch (e) {
215
- throw new Error("Unable to fetch weak subjectivity state: " + (e as Error).message);
195
+ const error = new LodestarError(
196
+ {code: "CHECKPOINT_DOWNLOAD_FAILED"},
197
+ "Unable to fetch weak subjectivity state: " + (e as Error).message
198
+ );
199
+ error.cause = e;
200
+ throw error;
216
201
  }
217
202
  }
218
203
 
@@ -220,16 +205,7 @@ export function getCheckpointFromArg(checkpointStr: string): Checkpoint {
220
205
  const checkpointRegex = /^(?:0x)?([0-9a-f]{64}):([0-9]+)$/;
221
206
  const match = checkpointRegex.exec(checkpointStr.toLowerCase());
222
207
  if (!match) {
223
- throw new Error(`Could not parse checkpoint string: ${checkpointStr}`);
208
+ throw new LodestarError({code: "INVALID_CHECKPOINT"}, `Could not parse checkpoint string: ${checkpointStr}`);
224
209
  }
225
210
  return {root: fromHex(match[1]), epoch: parseInt(match[2])};
226
211
  }
227
-
228
- export function getCheckpointFromState(state: BeaconStateAllForks): Checkpoint {
229
- return {
230
- // the correct checkpoint is based on state's slot, its latestBlockHeader's slot's epoch can be
231
- // behind the state
232
- epoch: computeCheckpointEpochAtStateSlot(state.slot),
233
- root: getLatestBlockRoot(state),
234
- };
235
- }
@@ -240,7 +240,7 @@ Will double processing times. Use only for debugging purposes.",
240
240
 
241
241
  "chain.fastConfirmation": {
242
242
  type: "boolean",
243
- description: "Enable Fast Confirmation Rule for faster block confirmation (experimental)",
243
+ description: "Enable Fast Confirmation Rule for faster block confirmation",
244
244
  defaultDescription: String(defaultOptions.chain.fastConfirmation),
245
245
  group: "chain",
246
246
  },