@chainsafe/lodestar 1.50.0-dev.9c3f7bebe9 → 1.50.0-dev.9c7f3c203f

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 (65) hide show
  1. package/.git-data.json +1 -1
  2. package/lib/cmds/beacon/handler.d.ts +1 -0
  3. package/lib/cmds/beacon/handler.d.ts.map +1 -1
  4. package/lib/cmds/beacon/handler.js +12 -21
  5. package/lib/cmds/beacon/handler.js.map +1 -1
  6. package/lib/cmds/beacon/initBeaconState.d.ts +12 -7
  7. package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -1
  8. package/lib/cmds/beacon/initBeaconState.js +42 -15
  9. package/lib/cmds/beacon/initBeaconState.js.map +1 -1
  10. package/lib/cmds/beacon/options.d.ts +1 -1
  11. package/lib/cmds/beacon/options.d.ts.map +1 -1
  12. package/lib/cmds/beacon/options.js +6 -5
  13. package/lib/cmds/beacon/options.js.map +1 -1
  14. package/lib/cmds/beacon/paths.d.ts +4 -0
  15. package/lib/cmds/beacon/paths.d.ts.map +1 -1
  16. package/lib/cmds/beacon/paths.js +3 -0
  17. package/lib/cmds/beacon/paths.js.map +1 -1
  18. package/lib/cmds/beacon/pubkeysFile.d.ts +10 -0
  19. package/lib/cmds/beacon/pubkeysFile.d.ts.map +1 -0
  20. package/lib/cmds/beacon/pubkeysFile.js +39 -0
  21. package/lib/cmds/beacon/pubkeysFile.js.map +1 -0
  22. package/lib/cmds/beacon/stateInitialization/checkpointState.d.ts.map +1 -1
  23. package/lib/cmds/beacon/stateInitialization/checkpointState.js +13 -12
  24. package/lib/cmds/beacon/stateInitialization/checkpointState.js.map +1 -1
  25. package/lib/cmds/beacon/stateInitialization/genesisState.d.ts +1 -1
  26. package/lib/cmds/beacon/stateInitialization/genesisState.d.ts.map +1 -1
  27. package/lib/cmds/beacon/stateInitialization/genesisState.js +4 -3
  28. package/lib/cmds/beacon/stateInitialization/genesisState.js.map +1 -1
  29. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts +1 -1
  30. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts.map +1 -1
  31. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js +5 -7
  32. package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js.map +1 -1
  33. package/lib/cmds/beacon/stateInitialization/types.d.ts +5 -5
  34. package/lib/cmds/beacon/stateInitialization/types.d.ts.map +1 -1
  35. package/lib/cmds/beacon/stateInitialization/validation.d.ts +2 -2
  36. package/lib/cmds/beacon/stateInitialization/validation.d.ts.map +1 -1
  37. package/lib/cmds/beacon/stateInitialization/validation.js.map +1 -1
  38. package/lib/cmds/dev/handler.d.ts.map +1 -1
  39. package/lib/cmds/dev/handler.js +3 -1
  40. package/lib/cmds/dev/handler.js.map +1 -1
  41. package/lib/cmds/dev/options.d.ts +2 -1
  42. package/lib/cmds/dev/options.d.ts.map +1 -1
  43. package/lib/networks/index.d.ts.map +1 -1
  44. package/lib/networks/index.js +5 -0
  45. package/lib/networks/index.js.map +1 -1
  46. package/lib/options/beaconNodeOptions/chain.d.ts +2 -1
  47. package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -1
  48. package/lib/options/beaconNodeOptions/chain.js +11 -4
  49. package/lib/options/beaconNodeOptions/chain.js.map +1 -1
  50. package/lib/options/beaconNodeOptions/index.d.ts +2 -1
  51. package/lib/options/beaconNodeOptions/index.d.ts.map +1 -1
  52. package/package.json +15 -15
  53. package/src/cmds/beacon/handler.ts +17 -22
  54. package/src/cmds/beacon/initBeaconState.ts +61 -17
  55. package/src/cmds/beacon/options.ts +8 -7
  56. package/src/cmds/beacon/paths.ts +5 -0
  57. package/src/cmds/beacon/pubkeysFile.ts +50 -0
  58. package/src/cmds/beacon/stateInitialization/checkpointState.ts +18 -20
  59. package/src/cmds/beacon/stateInitialization/genesisState.ts +5 -4
  60. package/src/cmds/beacon/stateInitialization/prepareStateInitialization.ts +6 -7
  61. package/src/cmds/beacon/stateInitialization/types.ts +5 -5
  62. package/src/cmds/beacon/stateInitialization/validation.ts +2 -2
  63. package/src/cmds/dev/handler.ts +3 -1
  64. package/src/networks/index.ts +9 -0
  65. package/src/options/beaconNodeOptions/chain.ts +14 -5
@@ -4,11 +4,10 @@ import {SignableENR} from "@chainsafe/enr";
4
4
  import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
5
5
  import {hasher} from "@chainsafe/persistent-merkle-tree";
6
6
  import {BeaconDb, BeaconNode} from "@lodestar/beacon-node";
7
- import {ChainForkConfig, createBeaconConfig} from "@lodestar/config";
7
+ import {ChainForkConfig} from "@lodestar/config";
8
8
  import {LevelDbController} from "@lodestar/db/controller/level";
9
9
  import {LoggerNode, getNodeLogger} from "@lodestar/logger/node";
10
- import {ACTIVE_PRESET, MAX_PENDING_DEPOSITS_PER_EPOCH, PresetName, SLOTS_PER_EPOCH} from "@lodestar/params";
11
- import {createBeaconStateView} from "@lodestar/state-transition";
10
+ import {ACTIVE_PRESET, PresetName} from "@lodestar/params";
12
11
  import {ErrorAborted, bytesToInt, formatBytes} from "@lodestar/utils";
13
12
  import {ProcessShutdownCallback} from "@lodestar/validator";
14
13
  import {BeaconNodeOptions, getBeaconConfigFromArgs} from "../../config/index.js";
@@ -28,6 +27,7 @@ import {initBeaconState} from "./initBeaconState.js";
28
27
  import {initPrivateKeyAndEnr} from "./initPeerIdAndEnr.js";
29
28
  import {BeaconArgs} from "./options.js";
30
29
  import {getBeaconPaths} from "./paths.js";
30
+ import {savePubkeysFile} from "./pubkeysFile.js";
31
31
 
32
32
  const DEFAULT_RETENTION_SSZ_OBJECTS_HOURS = 15 * 24;
33
33
  const HOURS_TO_MS = 3600 * 1000;
@@ -77,20 +77,9 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise<void
77
77
  try {
78
78
  const {
79
79
  anchorState,
80
- stateBytes: anchorStateBytes,
80
+ config: beaconConfig,
81
81
  isFinalized,
82
- } = await initBeaconState(args, beaconPaths.dataDir, config, db, logger);
83
- const beaconConfig = createBeaconConfig(config, anchorState.genesisValidatorsRoot);
84
- // Reserve 3 months of worst-case registry growth (MAX_PENDING_DEPOSITS_PER_EPOCH per epoch),
85
- // over a year at organic rates, to avoid routine cache reallocations. Cache growth is protected
86
- // by its native lock; if this headroom is exceeded, it grows by the same fixed step.
87
- const headroomEpochs = (90 * 24 * 60 * 60) / (config.SECONDS_PER_SLOT * SLOTS_PER_EPOCH);
88
- const pubkeyCacheHeadroom = MAX_PENDING_DEPOSITS_PER_EPOCH * Math.ceil(headroomEpochs);
89
- pubkeyCache.ensureCapacity(anchorState.validators.length + pubkeyCacheHeadroom);
90
- pubkeyCache.syncPubkeys(anchorState.validators.getAllReadonlyValues());
91
- const anchorStateView = args["chain.nativeStateView"]
92
- ? createBeaconStateView({useNative: true, stateBytes: anchorStateBytes})
93
- : createBeaconStateView({useNative: false, anchorState, config: beaconConfig, pubkeyCache});
82
+ } = await initBeaconState(args, beaconPaths.dataDir, beaconPaths.pubkeysFile, config, db, logger);
94
83
 
95
84
  const node = await BeaconNode.init({
96
85
  opts: options,
@@ -103,7 +92,7 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise<void
103
92
  dataDir: beaconPaths.dataDir,
104
93
  dataColumnDir: beaconPaths.dataColumnDir,
105
94
  peerStoreDir: beaconPaths.peerStoreDir,
106
- anchorState: anchorStateView,
95
+ anchorState,
107
96
  isAnchorStateFinalized: isFinalized,
108
97
  });
109
98
 
@@ -151,21 +140,21 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise<void
151
140
  abortController.signal.addEventListener(
152
141
  "abort",
153
142
  async () => {
143
+ let exitCode = 0;
154
144
  try {
155
145
  await node.close();
156
146
  logger.debug("Beacon node closed");
157
- // Explicitly exit until active handles issue is resolved
158
- // See https://github.com/ChainSafe/lodestar/issues/5642
159
- process.exit(0);
160
147
  } catch (e) {
161
148
  // If we start from unfinalized state, we don't have checkpoint state so there is this error
162
149
  // "No state in cache for finalized checkpoint state epoch"
163
150
  logger.warn("Error closing beacon node", {}, e as Error);
164
151
  // Make sure db is always closed gracefully
165
152
  await db.close();
166
- // Must explicitly exit process due to potential active handles
167
- process.exit(1);
153
+ exitCode = 1;
168
154
  }
155
+ savePubkeysFile(pubkeyCache, beaconPaths.pubkeysFile, logger);
156
+ // Explicitly exit process due to potential active handles
157
+ process.exit(exitCode);
169
158
  },
170
159
  {once: true}
171
160
  );
@@ -241,6 +230,12 @@ export async function beaconHandlerInit(args: BeaconArgs & GlobalArgs) {
241
230
  // Render final options
242
231
  const options = beaconNodeOptions.getWithDefaults();
243
232
 
233
+ if (options.chain.nativeStateTransition && config.GLOAS_FORK_EPOCH !== Infinity) {
234
+ throw Error(
235
+ `--chain.nativeStateTransition does not support Gloas, which is scheduled at epoch ${config.GLOAS_FORK_EPOCH}`
236
+ );
237
+ }
238
+
244
239
  return {config, options, beaconPaths, network, version, commit, privateKey, logger};
245
240
  }
246
241
 
@@ -1,18 +1,24 @@
1
- import {IBeaconDb, getStateTypeFromBytes} from "@lodestar/beacon-node";
2
- import {ChainForkConfig} from "@lodestar/config";
1
+ import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
2
+ import {IBeaconDb} from "@lodestar/beacon-node";
3
+ import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
4
+ import {MAX_PENDING_DEPOSITS_PER_EPOCH, SLOTS_PER_EPOCH} from "@lodestar/params";
3
5
  import {
4
- BeaconStateAllForks,
6
+ IBeaconStateView,
5
7
  computeWeakSubjectivitySummaryFromStateBytes,
8
+ createBeaconStateView,
9
+ getValidatorCountFromStateBytes,
6
10
  isWithinWeakSubjectivityPeriodFromSummary,
7
11
  readBeaconStateBytesMetadata,
8
12
  } from "@lodestar/state-transition";
9
13
  import {Logger, formatBytes} from "@lodestar/utils";
10
14
  import {GlobalArgs} from "../../options/globalOptions.js";
11
15
  import {BeaconArgs} from "./options.js";
16
+ import {loadPubkeysFile} from "./pubkeysFile.js";
12
17
  import {StateInitializationError, StateInitializationErrorCode} from "./stateInitialization/errors.js";
18
+ import {prepareGenesisInitialization} from "./stateInitialization/genesisState.js";
13
19
  import {
14
20
  prepareArchivedStateInitialization,
15
- prepareCheckpointOrGenesisInitialization,
21
+ prepareCheckpointSourceInitialization,
16
22
  } from "./stateInitialization/prepareStateInitialization.js";
17
23
  import {
18
24
  ArchivedStateBytes,
@@ -21,15 +27,22 @@ import {
21
27
  StatePreparationContext,
22
28
  } from "./stateInitialization/types.js";
23
29
 
24
- /** Select serialized anchor bytes before constructing the state used for validation, persistence, and return. */
30
+ type InitBeaconStateResult = {anchorState: IBeaconStateView; config: BeaconConfig; isFinalized: boolean};
31
+
32
+ /**
33
+ * Select serialized anchor bytes before constructing the state used for validation, persistence, and return.
34
+ * Populates the global `pubkeyCache` with the anchor state's validators, reusing `pubkeysFile` if it matches.
35
+ */
25
36
  export async function initBeaconState(
26
37
  args: BeaconArgs & GlobalArgs,
27
38
  dataDir: string,
39
+ pubkeysFile: string,
28
40
  chainForkConfig: ChainForkConfig,
29
41
  db: IBeaconDb,
30
42
  logger: Logger
31
- ): Promise<{anchorState: BeaconStateAllForks; stateBytes: Uint8Array; isFinalized: boolean}> {
43
+ ): Promise<InitBeaconStateResult> {
32
44
  const options: StateInitializationOptions = args;
45
+ const nativeStateTransition = options["chain.nativeStateTransition"] ?? false;
33
46
  if (
34
47
  options.forceCheckpointSync &&
35
48
  !(options.checkpointState || options.checkpointSyncUrl || options.unsafeCheckpointState)
@@ -55,11 +68,23 @@ export async function initBeaconState(
55
68
  options.lastPersistedCheckpointState
56
69
  );
57
70
  if (!options.forceCheckpointSync && (!hasCheckpointSource || archived.isWithinWeakSubjectivityPeriod)) {
58
- return executeStateInitialization(prepareArchivedStateInitialization(archived, context));
71
+ return executeStateInitialization(
72
+ prepareArchivedStateInitialization(archived, context),
73
+ nativeStateTransition,
74
+ pubkeysFile,
75
+ logger
76
+ );
59
77
  }
60
78
  }
61
- const stateInit = await prepareCheckpointOrGenesisInitialization(options, archived, context);
62
- return executeStateInitialization(stateInit);
79
+ let stateInit = await prepareCheckpointSourceInitialization(options, archived, context);
80
+ if (stateInit === null) {
81
+ // Without a usable checkpoint state, resume from the db as if no checkpoint source was set instead of genesis
82
+ stateInit =
83
+ archived !== null
84
+ ? prepareArchivedStateInitialization(archived, context)
85
+ : await prepareGenesisInitialization(options, context);
86
+ }
87
+ return executeStateInitialization(stateInit, nativeStateTransition, pubkeysFile, logger);
63
88
  }
64
89
 
65
90
  /**
@@ -87,7 +112,7 @@ async function readLatestArchivedStateBytes({
87
112
  if (weakSubjectivity === null) {
88
113
  throw new StateInitializationError(
89
114
  {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
90
- "Cannot extract weak subjectivity summary from archived state bytes"
115
+ `Cannot extract weak subjectivity summary from archived state bytes, expected ${chainForkConfig.getForkName(metadata.slot)} state at slot ${metadata.slot}, possible fork or network mismatch`
91
116
  );
92
117
  }
93
118
  return {
@@ -103,12 +128,31 @@ async function readLatestArchivedStateBytes({
103
128
  * Validate before persistence, and log success only after any required writes succeed.
104
129
  */
105
130
  async function executeStateInitialization(
106
- stateInit: StateInitialization
107
- ): Promise<{anchorState: BeaconStateAllForks; stateBytes: Uint8Array; isFinalized: boolean}> {
108
- const stateType = getStateTypeFromBytes(stateInit.config, stateInit.stateBytes);
109
- const anchorState = stateType.deserializeToViewDU(stateInit.stateBytes);
131
+ stateInit: StateInitialization,
132
+ nativeStateTransition: boolean,
133
+ pubkeysFile: string,
134
+ logger: Logger
135
+ ): Promise<InitBeaconStateResult> {
136
+ const {config, stateBytes} = stateInit;
137
+ const validatorCount = getValidatorCountFromStateBytes(config, stateBytes);
138
+ if (validatorCount === null) {
139
+ throw new StateInitializationError(
140
+ {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
141
+ "Cannot read validator count from selected state bytes"
142
+ );
143
+ }
144
+ // Reserve 3 months of worst-case registry growth (MAX_PENDING_DEPOSITS_PER_EPOCH per epoch),
145
+ // over a year at organic rates, to avoid routine cache reallocations. Cache growth is protected
146
+ // by its native lock; if this headroom is exceeded, it grows by the same fixed step.
147
+ // The view syncs pubkeys during construction, so capacity must be reserved first.
148
+ const headroomEpochs = (90 * 24 * 60 * 60) / (config.SECONDS_PER_SLOT * SLOTS_PER_EPOCH);
149
+ const pubkeyCacheCapacity = validatorCount + MAX_PENDING_DEPOSITS_PER_EPOCH * Math.ceil(headroomEpochs);
150
+ loadPubkeysFile(pubkeyCache, pubkeysFile, pubkeyCacheCapacity, config, stateBytes, validatorCount, logger);
151
+ // unilaterally expand capacity after best-effort pubkey file loading
152
+ pubkeyCache.ensureCapacity(pubkeyCacheCapacity);
153
+ const anchorState = createBeaconStateView({nativeStateTransition, config, stateBytes});
110
154
  stateInit.validate(anchorState);
111
- await stateInit.persist?.(anchorState, stateInit.stateBytes);
112
- stateInit.log(anchorState);
113
- return {anchorState, stateBytes: stateInit.stateBytes, isFinalized: stateInit.isFinalized};
155
+ await stateInit.persist?.(anchorState, stateBytes);
156
+ stateInit.log(anchorState, nativeStateTransition);
157
+ return {anchorState, config, isFinalized: stateInit.isFinalized};
114
158
  }
@@ -4,7 +4,6 @@ import {LogArgs, logOptions} from "../../options/logOptions.js";
4
4
  import {BeaconPaths, defaultBeaconPaths} from "./paths.js";
5
5
 
6
6
  type BeaconExtraArgs = {
7
- forceGenesis?: boolean;
8
7
  genesisStateFile?: string;
9
8
  configFile?: string;
10
9
  bootnodesFile?: string;
@@ -22,6 +21,7 @@ type BeaconExtraArgs = {
22
21
  persistInvalidSszObjectsRetentionHours?: number;
23
22
  persistOrphanedBlocksDir?: string;
24
23
  peerStoreDir?: string;
24
+ pubkeysFile?: string;
25
25
  persistNetworkIdentity?: boolean;
26
26
  private?: boolean;
27
27
  validatorMonitorLogs?: boolean;
@@ -30,12 +30,6 @@ type BeaconExtraArgs = {
30
30
  };
31
31
 
32
32
  export const beaconExtraOptions: CliCommandOptions<BeaconExtraArgs> = {
33
- forceGenesis: {
34
- description: "Force beacon to create genesis without file",
35
- type: "boolean",
36
- hidden: true,
37
- },
38
-
39
33
  genesisStateFile: {
40
34
  description: "Path or URL to download a genesis state file in ssz-encoded format",
41
35
  type: "string",
@@ -148,6 +142,13 @@ export const beaconExtraOptions: CliCommandOptions<BeaconExtraArgs> = {
148
142
  type: "string",
149
143
  },
150
144
 
145
+ pubkeysFile: {
146
+ hidden: true,
147
+ description: "Pubkey cache file, loaded on startup and saved on graceful shutdown",
148
+ defaultDescription: defaultBeaconPaths.pubkeysFile,
149
+ type: "string",
150
+ },
151
+
151
152
  persistNetworkIdentity: {
152
153
  description:
153
154
  "Whether to reuse the same peer-id across restarts. Validator custody requires custody group count to persist relative to a given ENR. Setting to false will reset ENR and validator custody requirements on restarts.",
@@ -9,6 +9,7 @@ export type BeaconPathsPartial = Partial<{
9
9
  dataColumnDir: string;
10
10
  persistInvalidSszObjectsDir: string;
11
11
  persistOrphanedBlocksDir?: string;
12
+ pubkeysFile: string;
12
13
  }>;
13
14
 
14
15
  export type BeaconPaths = {
@@ -18,6 +19,7 @@ export type BeaconPaths = {
18
19
  dataColumnDir: string;
19
20
  persistInvalidSszObjectsDir: string;
20
21
  persistOrphanedBlocksDir: string;
22
+ pubkeysFile: string;
21
23
  };
22
24
 
23
25
  /**
@@ -29,6 +31,7 @@ export type BeaconPaths = {
29
31
  * ├── beacon.config.json
30
32
  * ├── peer-id.json
31
33
  * ├── enr
34
+ * ├── pubkeys
32
35
  * ├── chain-db
33
36
  * └── beacon.log
34
37
  * ```
@@ -48,6 +51,7 @@ export function getBeaconPaths(
48
51
  const persistInvalidSszObjectsDir = args.persistInvalidSszObjectsDir ?? path.join(beaconDir, "invalidSszObjects");
49
52
  const peerStoreDir = args.peerStoreDir ?? path.join(beaconDir, "peerstore");
50
53
  const persistOrphanedBlocksDir = args.persistOrphanedBlocksDir ?? path.join(beaconDir, "orphaned_blocks");
54
+ const pubkeysFile = args.pubkeysFile ?? path.join(beaconDir, "pubkeys");
51
55
 
52
56
  return {
53
57
  ...globalPaths,
@@ -57,6 +61,7 @@ export function getBeaconPaths(
57
61
  persistInvalidSszObjectsDir,
58
62
  peerStoreDir,
59
63
  persistOrphanedBlocksDir,
64
+ pubkeysFile,
60
65
  };
61
66
  }
62
67
 
@@ -0,0 +1,50 @@
1
+ import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
2
+ import {ChainForkConfig} from "@lodestar/config";
3
+ import {getValidatorPubkeyFromStateBytes} from "@lodestar/state-transition";
4
+ import {Logger, byteArrayEquals} from "@lodestar/utils";
5
+
6
+ /**
7
+ * Best effort load of a trusted pubkey cache saved by a previous run, to skip decompressing every validator
8
+ * pubkey on boot. Must be called before any worker uses the cache.
9
+ */
10
+ export function loadPubkeysFile(
11
+ pubkeyCache: PubkeyCache,
12
+ filepath: string,
13
+ maxCapacity: number,
14
+ config: ChainForkConfig,
15
+ anchorStateBytes: Uint8Array,
16
+ anchorValidatorCount: number,
17
+ logger: Logger
18
+ ): void {
19
+ const start = Date.now();
20
+ try {
21
+ pubkeyCache.load(filepath, maxCapacity);
22
+ } catch (e) {
23
+ logger.debug("Unable to load pubkeys file", {filepath}, e as Error);
24
+ return;
25
+ }
26
+
27
+ // load() only checks framing and checksum, so reject a file from another chain. Checking the endpoints
28
+ // suffices since the registry is append-only.
29
+ const overlap = Math.min(pubkeyCache.size, anchorValidatorCount);
30
+ for (const index of overlap > 0 ? [0, overlap - 1] : []) {
31
+ const anchorPubkey = getValidatorPubkeyFromStateBytes(config, anchorStateBytes, index);
32
+ if (anchorPubkey === null || !byteArrayEquals(pubkeyCache.getPubkeyBytesOrThrow(index), anchorPubkey)) {
33
+ pubkeyCache.reset();
34
+ logger.debug("Discarded pubkeys file not matching anchor state", {filepath, index});
35
+ return;
36
+ }
37
+ }
38
+
39
+ logger.debug("Loaded pubkeys file", {filepath, count: pubkeyCache.size, durationMs: Date.now() - start});
40
+ }
41
+
42
+ export function savePubkeysFile(pubkeyCache: PubkeyCache, filepath: string, logger: Logger): void {
43
+ const start = Date.now();
44
+ try {
45
+ pubkeyCache.save(filepath);
46
+ logger.debug("Saved pubkeys file", {filepath, count: pubkeyCache.size, durationMs: Date.now() - start});
47
+ } catch (e) {
48
+ logger.debug("Unable to save pubkeys file", {filepath}, e as Error);
49
+ }
50
+ }
@@ -1,13 +1,10 @@
1
1
  import {DbCPStateDatastore, FileCPStateDatastore, persistAnchorState} from "@lodestar/beacon-node";
2
2
  import {createBeaconConfig} from "@lodestar/config";
3
3
  import {
4
- BeaconStateAllForks,
5
- computeAnchorCheckpoint,
6
- computeCheckpointEpochAtStateSlot,
4
+ IBeaconStateView,
7
5
  computeEpochAtSlot,
8
6
  computeWeakSubjectivitySummaryFromStateBytes,
9
7
  getCurrentSlot,
10
- getLatestBlockRoot,
11
8
  isWithinWeakSubjectivityPeriodFromSummary,
12
9
  readBeaconStateBytesMetadata,
13
10
  } from "@lodestar/state-transition";
@@ -181,7 +178,7 @@ function prepareCheckpointInitialization(
181
178
  if (weakSubjectivity === null) {
182
179
  throw new StateInitializationError(
183
180
  {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
184
- `Cannot extract weak subjectivity summary from ${candidate.source}`
181
+ `Cannot extract weak subjectivity summary from ${candidate.source}, expected ${chainForkConfig.getForkName(metadata.slot)} state at slot ${metadata.slot}, possible fork or network mismatch`
185
182
  );
186
183
  }
187
184
  const expectedCheckpoint = useArchived ? null : candidate.expectedCheckpoint;
@@ -189,6 +186,16 @@ function prepareCheckpointInitialization(
189
186
  const {isFinalized, ignoreWeakSubjectivityCheck} = policy;
190
187
  const config = createBeaconConfig(chainForkConfig, metadata.genesisValidatorsRoot);
191
188
  const archivedWithinWeakSubjectivityPeriod = useArchived ? archived.isWithinWeakSubjectivityPeriod : null;
189
+ const passedValidation =
190
+ archivedWithinWeakSubjectivityPeriod ?? isWithinWeakSubjectivityPeriodFromSummary(config, weakSubjectivity);
191
+ // Fast-fail stale checkpoints before deserializing the state or reserving pubkey-cache capacity.
192
+ if (!passedValidation && !ignoreWeakSubjectivityCheck) {
193
+ const clockEpoch = computeEpochAtSlot(getCurrentSlot(config, weakSubjectivity.genesisTime));
194
+ throw new StateInitializationError(
195
+ {code: StateInitializationErrorCode.STALE_CHECKPOINT},
196
+ `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`
197
+ );
198
+ }
192
199
  // DB-selected anchors need no persistence, including at slot zero.
193
200
  const shouldPersist = isFinalized && !useArchived;
194
201
  return {
@@ -198,17 +205,8 @@ function prepareCheckpointInitialization(
198
205
  validate(state) {
199
206
  // A supplied checkpoint must match even when the period check is ignored.
200
207
  if (expectedCheckpoint !== null) assertStateMatchesCheckpoint(state, expectedCheckpoint);
201
- const passedValidation =
202
- archivedWithinWeakSubjectivityPeriod ?? isWithinWeakSubjectivityPeriodFromSummary(config, weakSubjectivity);
203
- if (!passedValidation && !ignoreWeakSubjectivityCheck) {
204
- const clockEpoch = computeEpochAtSlot(getCurrentSlot(config, weakSubjectivity.genesisTime));
205
- throw new StateInitializationError(
206
- {code: StateInitializationErrorCode.STALE_CHECKPOINT},
207
- `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`
208
- );
209
- }
208
+ assertAnchorStateForkMatchesConfig(config, state);
210
209
  if (isFinalized) {
211
- assertAnchorStateForkMatchesConfig(config, state);
212
210
  const source = useArchived ? "db" : "checkpoint";
213
211
  const logData = {
214
212
  slot: state.slot,
@@ -226,8 +224,8 @@ function prepareCheckpointInitialization(
226
224
  }
227
225
  },
228
226
  persist: shouldPersist ? (state, bytes) => persistAnchorState(config, db, state, bytes) : null,
229
- log(state) {
230
- const {checkpoint} = computeAnchorCheckpoint(config, state);
227
+ log(state, nativeStateTransition) {
228
+ const {checkpoint} = state.computeAnchorCheckpoint();
231
229
  logger.info("Initialized checkpoint state", {
232
230
  source,
233
231
  slot: state.slot,
@@ -236,15 +234,15 @@ function prepareCheckpointInitialization(
236
234
  stateRoot: toRootHex(state.hashTreeRoot()),
237
235
  checkpointRoot: toRootHex(checkpoint.root),
238
236
  isFinalized,
237
+ nativeStateTransition,
239
238
  ...(isFinalized ? {} : {lastProcessedSlot: state.latestBlockHeader.slot}),
240
239
  });
241
240
  },
242
241
  };
243
242
  }
244
243
 
245
- function assertStateMatchesCheckpoint(state: BeaconStateAllForks, checkpoint: Checkpoint): void {
246
- const stateEpoch = computeCheckpointEpochAtStateSlot(state.slot);
247
- const blockRoot = getLatestBlockRoot(state);
244
+ function assertStateMatchesCheckpoint(state: IBeaconStateView, checkpoint: Checkpoint): void {
245
+ const {root: blockRoot, epoch: stateEpoch} = state.computeAnchorCheckpoint().checkpoint;
248
246
  if (!ssz.Root.equals(blockRoot, checkpoint.root)) {
249
247
  throw new StateInitializationError(
250
248
  {code: StateInitializationErrorCode.CHECKPOINT_ROOT_MISMATCH},
@@ -15,11 +15,11 @@ import {StateInitialization, StateInitializationOptions, StatePreparationContext
15
15
  import {assertAnchorStateForkMatchesConfig} from "./validation.js";
16
16
 
17
17
  export async function prepareGenesisInitialization(
18
- options: Pick<StateInitializationOptions, "genesisStateFile" | "network" | "forceGenesis">,
18
+ options: Pick<StateInitializationOptions, "genesisStateFile" | "network">,
19
19
  {chainForkConfig, db, logger}: StatePreparationContext
20
20
  ): Promise<StateInitialization> {
21
21
  const source = options.genesisStateFile || getGenesisFileUrl(options.network || defaultNetwork);
22
- if (!source || options.forceGenesis) {
22
+ if (!source) {
23
23
  throw new StateInitializationError(
24
24
  {code: StateInitializationErrorCode.MISSING_INITIALIZATION_SOURCE},
25
25
  "Failed to initialize beacon state, please provide a genesis state file or use checkpoint sync"
@@ -40,7 +40,7 @@ export async function prepareGenesisInitialization(
40
40
  if (weakSubjectivity === null) {
41
41
  throw new StateInitializationError(
42
42
  {code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
43
- "Cannot extract weak subjectivity summary from genesis state bytes"
43
+ `Cannot extract weak subjectivity summary from genesis state bytes, expected ${chainForkConfig.getForkName(metadata.slot)} state at slot ${metadata.slot}, possible fork or network mismatch`
44
44
  );
45
45
  }
46
46
  const config = createBeaconConfig(chainForkConfig, metadata.genesisValidatorsRoot);
@@ -74,11 +74,12 @@ export async function prepareGenesisInitialization(
74
74
  }
75
75
  },
76
76
  persist: (state, bytes) => persistAnchorState(config, db, state, bytes),
77
- log(state) {
77
+ log(state, nativeStateTransition) {
78
78
  logger.info("Initialized genesis state", {
79
79
  slot: state.slot,
80
80
  stateRoot: toRootHex(state.hashTreeRoot()),
81
81
  isFinalized: true,
82
+ nativeStateTransition,
82
83
  });
83
84
  },
84
85
  };
@@ -6,7 +6,6 @@ import {
6
6
  prepareCheckpointFileInitialization,
7
7
  prepareUnfinalizedCheckpointInitialization,
8
8
  } from "./checkpointState.js";
9
- import {prepareGenesisInitialization} from "./genesisState.js";
10
9
  import {ArchivedStateBytes, StateInitialization, StateInitializationOptions, StatePreparationContext} from "./types.js";
11
10
  import {assertAnchorStateForkMatchesConfig} from "./validation.js";
12
11
 
@@ -36,22 +35,23 @@ export function prepareArchivedStateInitialization(
36
35
  }
37
36
  },
38
37
  persist: null,
39
- log(state) {
38
+ log(state, nativeStateTransition) {
40
39
  logger.info("Initialized state from db", {
41
40
  slot: state.slot,
42
41
  epoch: computeEpochAtSlot(state.slot),
43
42
  stateRoot: toRootHex(state.hashTreeRoot()),
44
43
  isFinalized: true,
44
+ nativeStateTransition,
45
45
  });
46
46
  },
47
47
  };
48
48
  }
49
49
 
50
- export async function prepareCheckpointOrGenesisInitialization(
50
+ export async function prepareCheckpointSourceInitialization(
51
51
  options: StateInitializationOptions,
52
52
  archived: ArchivedStateBytes | null,
53
53
  context: StatePreparationContext
54
- ): Promise<StateInitialization> {
54
+ ): Promise<StateInitialization | null> {
55
55
  if (options.checkpointState) {
56
56
  return prepareCheckpointFileInitialization(options.checkpointState, options, archived, context);
57
57
  }
@@ -59,8 +59,7 @@ export async function prepareCheckpointOrGenesisInitialization(
59
59
  return prepareCheckpointApiInitialization(options.checkpointSyncUrl, options, archived, context);
60
60
  }
61
61
  if (options.lastPersistedCheckpointState || options.unsafeCheckpointState) {
62
- const stateInit = await prepareUnfinalizedCheckpointInitialization(options, archived, context);
63
- if (stateInit !== null) return stateInit;
62
+ return prepareUnfinalizedCheckpointInitialization(options, archived, context);
64
63
  }
65
- return prepareGenesisInitialization(options, context);
64
+ return null;
66
65
  }
@@ -1,6 +1,6 @@
1
1
  import {IBeaconDb} from "@lodestar/beacon-node";
2
2
  import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
3
- import {BeaconStateAllForks, StateBytesMetadata, WeakSubjectivitySummary} from "@lodestar/state-transition";
3
+ import {IBeaconStateView, StateBytesMetadata, WeakSubjectivitySummary} from "@lodestar/state-transition";
4
4
  import {Checkpoint} from "@lodestar/types/phase0";
5
5
  import {Logger} from "@lodestar/utils";
6
6
  import {GlobalArgs} from "../../../options/globalOptions.js";
@@ -16,9 +16,9 @@ export type StateInitializationOptions = Pick<
16
16
  | "wssCheckpoint"
17
17
  | "forceCheckpointSync"
18
18
  | "ignoreWeakSubjectivityCheck"
19
- | "forceGenesis"
20
19
  | "network"
21
20
  | "chain.nHistoricalStatesFileDataStore"
21
+ | "chain.nativeStateTransition"
22
22
  >;
23
23
 
24
24
  export type StatePreparationContext = {
@@ -52,7 +52,7 @@ export type StateInitialization = {
52
52
  stateBytes: Uint8Array;
53
53
  config: BeaconConfig;
54
54
  isFinalized: boolean;
55
- validate: (state: BeaconStateAllForks) => void;
56
- persist: ((state: BeaconStateAllForks, stateBytes: Uint8Array) => Promise<void>) | null;
57
- log: (state: BeaconStateAllForks) => void;
55
+ validate: (state: IBeaconStateView) => void;
56
+ persist: ((state: IBeaconStateView, stateBytes: Uint8Array) => Promise<void>) | null;
57
+ log: (state: IBeaconStateView, nativeStateTransition: boolean) => void;
58
58
  };
@@ -1,9 +1,9 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {BeaconStateAllForks, computeStartSlotAtEpoch} from "@lodestar/state-transition";
2
+ import {IBeaconStateView, computeStartSlotAtEpoch} from "@lodestar/state-transition";
3
3
  import {toHex} from "@lodestar/utils";
4
4
  import {StateInitializationError, StateInitializationErrorCode} from "./errors.js";
5
5
 
6
- export function assertAnchorStateForkMatchesConfig(config: ChainForkConfig, anchorState: BeaconStateAllForks): void {
6
+ export function assertAnchorStateForkMatchesConfig(config: ChainForkConfig, anchorState: IBeaconStateView): void {
7
7
  const expectedFork = config.getForkInfo(computeStartSlotAtEpoch(anchorState.fork.epoch));
8
8
  const expectedForkVersion = toHex(expectedFork.version);
9
9
  const stateFork = toHex(anchorState.fork.currentVersion);
@@ -31,7 +31,7 @@ export async function devHandler(args: IDevArgs & GlobalArgs): Promise<void> {
31
31
 
32
32
  // Note: defaults to network "dev", to all paths are custom and don't conflict with networks.
33
33
  // Flag --reset cleans up the custom dirs on dev stop
34
- const beaconDbDir = getBeaconPaths(args, network).dbDir;
34
+ const {dbDir: beaconDbDir, pubkeysFile} = getBeaconPaths(args, network);
35
35
  const validatorsDbDir = getValidatorPaths(args, network).validatorsDbDir;
36
36
 
37
37
  // Remove slashing protection db. Otherwise the validators won't be able to propose nor attest
@@ -39,6 +39,8 @@ export async function devHandler(args: IDevArgs & GlobalArgs): Promise<void> {
39
39
  if (args.genesisTime === undefined) {
40
40
  await rimraf(beaconDbDir);
41
41
  await rimraf(validatorsDbDir);
42
+ // Validators deposited in the previous run do not exist on the new chain
43
+ await rimraf(pubkeysFile);
42
44
  }
43
45
 
44
46
  mkdir(beaconDbDir);
@@ -182,11 +182,20 @@ export async function fetchWeakSubjectivityStateBytes(
182
182
  GET_STATE_LOG_INTERVAL
183
183
  );
184
184
  const wsStateBytes = response.ssz();
185
+ const {version} = response.meta();
185
186
 
186
187
  const wsSlot = getStateSlotFromBytes(wsStateBytes);
187
188
  const logData = {stateId, size: formatBytes(wsStateBytes.length)};
188
189
  logger.info("Download completed", typeof stateId === "number" ? logData : {...logData, slot: wsSlot});
189
190
 
191
+ const expectedFork = config.getForkName(wsSlot);
192
+ if (version !== expectedFork) {
193
+ throw new LodestarError(
194
+ {code: "CHECKPOINT_FORK_MISMATCH"},
195
+ `Checkpoint sync server returned ${version} state at slot ${wsSlot} but local config expects ${expectedFork}, verify --network or the chain config`
196
+ );
197
+ }
198
+
190
199
  return {
191
200
  stateBytes: new Uint8Array(wsStateBytes.buffer, wsStateBytes.byteOffset, wsStateBytes.byteLength),
192
201
  expectedCheckpoint: wsCheckpoint,