@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.
- package/.git-data.json +1 -1
- package/lib/cmds/beacon/handler.d.ts +1 -0
- package/lib/cmds/beacon/handler.d.ts.map +1 -1
- package/lib/cmds/beacon/handler.js +12 -21
- package/lib/cmds/beacon/handler.js.map +1 -1
- package/lib/cmds/beacon/initBeaconState.d.ts +12 -7
- package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -1
- package/lib/cmds/beacon/initBeaconState.js +42 -15
- package/lib/cmds/beacon/initBeaconState.js.map +1 -1
- package/lib/cmds/beacon/options.d.ts +1 -1
- package/lib/cmds/beacon/options.d.ts.map +1 -1
- package/lib/cmds/beacon/options.js +6 -5
- package/lib/cmds/beacon/options.js.map +1 -1
- package/lib/cmds/beacon/paths.d.ts +4 -0
- package/lib/cmds/beacon/paths.d.ts.map +1 -1
- package/lib/cmds/beacon/paths.js +3 -0
- package/lib/cmds/beacon/paths.js.map +1 -1
- package/lib/cmds/beacon/pubkeysFile.d.ts +10 -0
- package/lib/cmds/beacon/pubkeysFile.d.ts.map +1 -0
- package/lib/cmds/beacon/pubkeysFile.js +39 -0
- package/lib/cmds/beacon/pubkeysFile.js.map +1 -0
- package/lib/cmds/beacon/stateInitialization/checkpointState.d.ts.map +1 -1
- package/lib/cmds/beacon/stateInitialization/checkpointState.js +13 -12
- package/lib/cmds/beacon/stateInitialization/checkpointState.js.map +1 -1
- package/lib/cmds/beacon/stateInitialization/genesisState.d.ts +1 -1
- package/lib/cmds/beacon/stateInitialization/genesisState.d.ts.map +1 -1
- package/lib/cmds/beacon/stateInitialization/genesisState.js +4 -3
- package/lib/cmds/beacon/stateInitialization/genesisState.js.map +1 -1
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts +1 -1
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts.map +1 -1
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js +5 -7
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js.map +1 -1
- package/lib/cmds/beacon/stateInitialization/types.d.ts +5 -5
- package/lib/cmds/beacon/stateInitialization/types.d.ts.map +1 -1
- package/lib/cmds/beacon/stateInitialization/validation.d.ts +2 -2
- package/lib/cmds/beacon/stateInitialization/validation.d.ts.map +1 -1
- package/lib/cmds/beacon/stateInitialization/validation.js.map +1 -1
- package/lib/cmds/dev/handler.d.ts.map +1 -1
- package/lib/cmds/dev/handler.js +3 -1
- package/lib/cmds/dev/handler.js.map +1 -1
- package/lib/cmds/dev/options.d.ts +2 -1
- package/lib/cmds/dev/options.d.ts.map +1 -1
- package/lib/networks/index.d.ts.map +1 -1
- package/lib/networks/index.js +5 -0
- package/lib/networks/index.js.map +1 -1
- package/lib/options/beaconNodeOptions/chain.d.ts +2 -1
- package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -1
- package/lib/options/beaconNodeOptions/chain.js +11 -4
- package/lib/options/beaconNodeOptions/chain.js.map +1 -1
- package/lib/options/beaconNodeOptions/index.d.ts +2 -1
- package/lib/options/beaconNodeOptions/index.d.ts.map +1 -1
- package/package.json +15 -15
- package/src/cmds/beacon/handler.ts +17 -22
- package/src/cmds/beacon/initBeaconState.ts +61 -17
- package/src/cmds/beacon/options.ts +8 -7
- package/src/cmds/beacon/paths.ts +5 -0
- package/src/cmds/beacon/pubkeysFile.ts +50 -0
- package/src/cmds/beacon/stateInitialization/checkpointState.ts +18 -20
- package/src/cmds/beacon/stateInitialization/genesisState.ts +5 -4
- package/src/cmds/beacon/stateInitialization/prepareStateInitialization.ts +6 -7
- package/src/cmds/beacon/stateInitialization/types.ts +5 -5
- package/src/cmds/beacon/stateInitialization/validation.ts +2 -2
- package/src/cmds/dev/handler.ts +3 -1
- package/src/networks/index.ts +9 -0
- 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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
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(
|
|
71
|
+
return executeStateInitialization(
|
|
72
|
+
prepareArchivedStateInitialization(archived, context),
|
|
73
|
+
nativeStateTransition,
|
|
74
|
+
pubkeysFile,
|
|
75
|
+
logger
|
|
76
|
+
);
|
|
59
77
|
}
|
|
60
78
|
}
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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,
|
|
112
|
-
stateInit.log(anchorState);
|
|
113
|
-
return {anchorState,
|
|
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.",
|
package/src/cmds/beacon/paths.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
246
|
-
const stateEpoch =
|
|
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"
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
63
|
-
if (stateInit !== null) return stateInit;
|
|
62
|
+
return prepareUnfinalizedCheckpointInitialization(options, archived, context);
|
|
64
63
|
}
|
|
65
|
-
return
|
|
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 {
|
|
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:
|
|
56
|
-
persist: ((state:
|
|
57
|
-
log: (state:
|
|
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 {
|
|
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:
|
|
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);
|
package/src/cmds/dev/handler.ts
CHANGED
|
@@ -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)
|
|
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);
|
package/src/networks/index.ts
CHANGED
|
@@ -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,
|