@chainsafe/lodestar 1.50.0-dev.ebecebb9ba → 1.50.0-dev.f0a5c36612
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 +13 -21
- package/lib/cmds/beacon/handler.js.map +1 -1
- package/lib/cmds/beacon/initBeaconState.d.ts +13 -17
- package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -1
- package/lib/cmds/beacon/initBeaconState.js +84 -275
- 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 +7 -0
- package/lib/cmds/beacon/stateInitialization/checkpointState.d.ts.map +1 -0
- package/lib/cmds/beacon/stateInitialization/checkpointState.js +184 -0
- package/lib/cmds/beacon/stateInitialization/checkpointState.js.map +1 -0
- package/lib/cmds/beacon/stateInitialization/errors.d.ts +17 -0
- package/lib/cmds/beacon/stateInitialization/errors.d.ts.map +1 -0
- package/lib/cmds/beacon/stateInitialization/errors.js +16 -0
- package/lib/cmds/beacon/stateInitialization/errors.js.map +1 -0
- package/lib/cmds/beacon/stateInitialization/genesisState.d.ts +3 -0
- package/lib/cmds/beacon/stateInitialization/genesisState.d.ts.map +1 -0
- package/lib/cmds/beacon/stateInitialization/genesisState.js +79 -0
- package/lib/cmds/beacon/stateInitialization/genesisState.js.map +1 -0
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts +4 -0
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.d.ts.map +1 -0
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js +67 -0
- package/lib/cmds/beacon/stateInitialization/prepareStateInitialization.js.map +1 -0
- package/lib/cmds/beacon/stateInitialization/types.d.ts +42 -0
- package/lib/cmds/beacon/stateInitialization/types.d.ts.map +1 -0
- package/lib/cmds/beacon/stateInitialization/types.js +2 -0
- package/lib/cmds/beacon/stateInitialization/types.js.map +1 -0
- package/lib/cmds/beacon/stateInitialization/validation.d.ts +4 -0
- package/lib/cmds/beacon/stateInitialization/validation.d.ts.map +1 -0
- package/lib/cmds/beacon/stateInitialization/validation.js +12 -0
- package/lib/cmds/beacon/stateInitialization/validation.js.map +1 -0
- 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 +3 -9
- package/lib/networks/index.d.ts.map +1 -1
- package/lib/networks/index.js +15 -28
- 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 +12 -5
- 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 +19 -22
- package/src/cmds/beacon/initBeaconState.ts +136 -387
- 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 +272 -0
- package/src/cmds/beacon/stateInitialization/errors.ts +15 -0
- package/src/cmds/beacon/stateInitialization/genesisState.ts +99 -0
- package/src/cmds/beacon/stateInitialization/prepareStateInitialization.ts +79 -0
- package/src/cmds/beacon/stateInitialization/types.ts +60 -0
- package/src/cmds/beacon/stateInitialization/validation.ts +16 -0
- package/src/cmds/dev/handler.ts +3 -1
- package/src/networks/index.ts +25 -40
- package/src/options/beaconNodeOptions/chain.ts +15 -6
|
@@ -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,10 @@ 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
|
-
|
|
83
|
-
|
|
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
|
+
earliestAvailableSlot,
|
|
83
|
+
} = await initBeaconState(args, beaconPaths.dataDir, beaconPaths.pubkeysFile, config, db, logger);
|
|
94
84
|
|
|
95
85
|
const node = await BeaconNode.init({
|
|
96
86
|
opts: options,
|
|
@@ -103,8 +93,9 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise<void
|
|
|
103
93
|
dataDir: beaconPaths.dataDir,
|
|
104
94
|
dataColumnDir: beaconPaths.dataColumnDir,
|
|
105
95
|
peerStoreDir: beaconPaths.peerStoreDir,
|
|
106
|
-
anchorState
|
|
96
|
+
anchorState,
|
|
107
97
|
isAnchorStateFinalized: isFinalized,
|
|
98
|
+
earliestAvailableSlot,
|
|
108
99
|
});
|
|
109
100
|
|
|
110
101
|
// dev debug option to have access to the BN instance
|
|
@@ -151,21 +142,21 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise<void
|
|
|
151
142
|
abortController.signal.addEventListener(
|
|
152
143
|
"abort",
|
|
153
144
|
async () => {
|
|
145
|
+
let exitCode = 0;
|
|
154
146
|
try {
|
|
155
147
|
await node.close();
|
|
156
148
|
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
149
|
} catch (e) {
|
|
161
150
|
// If we start from unfinalized state, we don't have checkpoint state so there is this error
|
|
162
151
|
// "No state in cache for finalized checkpoint state epoch"
|
|
163
152
|
logger.warn("Error closing beacon node", {}, e as Error);
|
|
164
153
|
// Make sure db is always closed gracefully
|
|
165
154
|
await db.close();
|
|
166
|
-
|
|
167
|
-
process.exit(1);
|
|
155
|
+
exitCode = 1;
|
|
168
156
|
}
|
|
157
|
+
savePubkeysFile(pubkeyCache, beaconPaths.pubkeysFile, logger);
|
|
158
|
+
// Explicitly exit process due to potential active handles
|
|
159
|
+
process.exit(exitCode);
|
|
169
160
|
},
|
|
170
161
|
{once: true}
|
|
171
162
|
);
|
|
@@ -241,6 +232,12 @@ export async function beaconHandlerInit(args: BeaconArgs & GlobalArgs) {
|
|
|
241
232
|
// Render final options
|
|
242
233
|
const options = beaconNodeOptions.getWithDefaults();
|
|
243
234
|
|
|
235
|
+
if (options.chain.nativeStateTransition && config.GLOAS_FORK_EPOCH !== Infinity) {
|
|
236
|
+
throw Error(
|
|
237
|
+
`--chain.nativeStateTransition does not support Gloas, which is scheduled at epoch ${config.GLOAS_FORK_EPOCH}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
244
241
|
return {config, options, beaconPaths, network, version, commit, privateKey, logger};
|
|
245
242
|
}
|
|
246
243
|
|
|
@@ -1,416 +1,165 @@
|
|
|
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";
|
|
1
5
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {BeaconConfig, ChainForkConfig, createBeaconConfig} from "@lodestar/config";
|
|
9
|
-
import {
|
|
10
|
-
BeaconStateAllForks,
|
|
11
|
-
computeAnchorCheckpoint,
|
|
12
|
-
computeEpochAtSlot,
|
|
13
|
-
ensureWithinWeakSubjectivityPeriod,
|
|
14
|
-
isWithinWeakSubjectivityPeriod,
|
|
15
|
-
loadState,
|
|
16
|
-
loadStateAndValidators,
|
|
6
|
+
IBeaconStateView,
|
|
7
|
+
computeWeakSubjectivitySummaryFromStateBytes,
|
|
8
|
+
createBeaconStateView,
|
|
9
|
+
getValidatorCountFromStateBytes,
|
|
10
|
+
isWithinWeakSubjectivityPeriodFromSummary,
|
|
11
|
+
readBeaconStateBytesMetadata,
|
|
17
12
|
} from "@lodestar/state-transition";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
fetchWeakSubjectivityState,
|
|
23
|
-
getCheckpointFromArg,
|
|
24
|
-
getCheckpointFromState,
|
|
25
|
-
getGenesisFileUrl,
|
|
26
|
-
getGenesisStateRoot,
|
|
27
|
-
} from "../../networks/index.js";
|
|
28
|
-
import {GlobalArgs, defaultNetwork} from "../../options/globalOptions.js";
|
|
29
|
-
import {downloadOrLoadFile, wrapFnError} from "../../util/index.js";
|
|
13
|
+
import {Slot} from "@lodestar/types";
|
|
14
|
+
import {Logger, formatBytes} from "@lodestar/utils";
|
|
15
|
+
import {GlobalArgs} from "../../options/globalOptions.js";
|
|
30
16
|
import {BeaconArgs} from "./options.js";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
throw new Error(
|
|
52
|
-
"Db state and checkpoint state are not compatible, either clear the db or verify your checkpoint source"
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Pick the state which is ahead as an anchor to initialize the beacon chain
|
|
57
|
-
let anchorState = wsStateBytes;
|
|
58
|
-
let anchorCheckpoint = wsCheckpoint;
|
|
59
|
-
let isCheckpointState = true;
|
|
60
|
-
if (dbState.slot > wsState.slot && !opts.forceCheckpointSync) {
|
|
61
|
-
anchorState = dbStateBytes;
|
|
62
|
-
anchorCheckpoint = getCheckpointFromState(dbState);
|
|
63
|
-
isCheckpointState = false;
|
|
64
|
-
logger.verbose(
|
|
65
|
-
"Db state is ahead of the provided checkpoint state, using the db state to initialize the beacon chain"
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Throw error unless user explicitly asked not to, in testnets can happen that wss period is too small
|
|
70
|
-
// that even some epochs of non finalization can cause finalized checkpoint to be out of valid range
|
|
71
|
-
const wssCheck = wrapFnError(() => ensureWithinWeakSubjectivityPeriod(config, anchorState.state, anchorCheckpoint));
|
|
72
|
-
const isWithinWeakSubjectivityPeriod = wssCheck.err === null;
|
|
73
|
-
if (!isWithinWeakSubjectivityPeriod && !opts.ignoreWeakSubjectivityCheck) {
|
|
74
|
-
throw wssCheck.err;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (isWsStateFinalized) {
|
|
78
|
-
await checkAndPersistAnchorState(config, db, logger, anchorState.state, anchorState.stateBytes, {
|
|
79
|
-
isWithinWeakSubjectivityPeriod,
|
|
80
|
-
isCheckpointState,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {anchorState: anchorState.state, stateBytes: anchorState.stateBytes};
|
|
85
|
-
}
|
|
17
|
+
import {loadPubkeysFile} from "./pubkeysFile.js";
|
|
18
|
+
import {StateInitializationError, StateInitializationErrorCode} from "./stateInitialization/errors.js";
|
|
19
|
+
import {prepareGenesisInitialization} from "./stateInitialization/genesisState.js";
|
|
20
|
+
import {
|
|
21
|
+
prepareArchivedStateInitialization,
|
|
22
|
+
prepareCheckpointSourceInitialization,
|
|
23
|
+
} from "./stateInitialization/prepareStateInitialization.js";
|
|
24
|
+
import {
|
|
25
|
+
ArchivedStateBytes,
|
|
26
|
+
StateInitialization,
|
|
27
|
+
StateInitializationOptions,
|
|
28
|
+
StatePreparationContext,
|
|
29
|
+
} from "./stateInitialization/types.js";
|
|
30
|
+
|
|
31
|
+
type InitBeaconStateResult = {
|
|
32
|
+
anchorState: IBeaconStateView;
|
|
33
|
+
config: BeaconConfig;
|
|
34
|
+
isFinalized: boolean;
|
|
35
|
+
earliestAvailableSlot: Slot;
|
|
36
|
+
};
|
|
86
37
|
|
|
87
38
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* State is initialized in one of three ways:
|
|
91
|
-
* 1. restore from weak subjectivity state (possibly downloaded from a remote beacon node)
|
|
92
|
-
* 2. restore from db
|
|
93
|
-
* 3. restore from genesis state (possibly downloaded via URL)
|
|
94
|
-
*
|
|
95
|
-
* The returned anchorState could be finalized or not.
|
|
96
|
-
* - if we load from checkpointState, checkpointSyncUrl, genesisStateFile or archived db, it is finalized
|
|
97
|
-
* - it's not finalized if we load from unsafeCheckpointState or lastPersistedCheckpointState
|
|
39
|
+
* Select serialized anchor bytes before constructing the state used for validation, persistence, and return.
|
|
40
|
+
* Populates the global `pubkeyCache` with the anchor state's validators, reusing `pubkeysFile` if it matches.
|
|
98
41
|
*/
|
|
99
42
|
export async function initBeaconState(
|
|
100
43
|
args: BeaconArgs & GlobalArgs,
|
|
101
44
|
dataDir: string,
|
|
45
|
+
pubkeysFile: string,
|
|
102
46
|
chainForkConfig: ChainForkConfig,
|
|
103
47
|
db: IBeaconDb,
|
|
104
48
|
logger: Logger
|
|
105
|
-
): Promise<{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// ii) used to load and verify a weak subjectivity state,
|
|
116
|
-
const lastDbSlot = await db.stateArchive.lastKey();
|
|
117
|
-
let stateBytes = lastDbSlot !== null ? await db.stateArchive.getBinary(lastDbSlot) : null;
|
|
118
|
-
// Convert to `Uint8Array` to avoid unexpected behavior such as `Buffer.prototype.slice` not copying memory
|
|
119
|
-
stateBytes = stateBytes ? new Uint8Array(stateBytes.buffer, stateBytes.byteOffset, stateBytes.byteLength) : null;
|
|
120
|
-
let lastDbState: BeaconStateAllForks | null = null;
|
|
121
|
-
let lastDbValidatorsBytes: Uint8Array | null = null;
|
|
122
|
-
let lastDbStateWithBytes: StateWithBytes | null = null;
|
|
123
|
-
if (stateBytes) {
|
|
124
|
-
logger.verbose("Found the last archived state", {slot: lastDbSlot, size: formatBytes(stateBytes.length)});
|
|
125
|
-
const {state, validatorsBytes} = loadStateAndValidators(chainForkConfig, stateBytes);
|
|
126
|
-
lastDbState = state;
|
|
127
|
-
lastDbValidatorsBytes = validatorsBytes;
|
|
128
|
-
lastDbStateWithBytes = {state, stateBytes: stateBytes};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (lastDbState) {
|
|
132
|
-
// state from archive db is finalized
|
|
133
|
-
const isFinalized = true;
|
|
134
|
-
const config = createBeaconConfig(chainForkConfig, lastDbState.genesisValidatorsRoot);
|
|
135
|
-
const wssCheck = isWithinWeakSubjectivityPeriod(config, lastDbState, getCheckpointFromState(lastDbState));
|
|
136
|
-
|
|
137
|
-
// Explicitly force syncing from checkpoint state
|
|
138
|
-
if (args.forceCheckpointSync) {
|
|
139
|
-
// Forcing to sync from checkpoint is only recommended if node is taking too long to sync from last db state.
|
|
140
|
-
// It is important to remind the user to remove this flag again unless it is absolutely necessary.
|
|
141
|
-
if (wssCheck) {
|
|
142
|
-
logger.warn(
|
|
143
|
-
`Forced syncing from checkpoint even though db state at slot ${lastDbState.slot} is within weak subjectivity period`
|
|
144
|
-
);
|
|
145
|
-
logger.warn("Please consider removing --forceCheckpointSync flag unless absolutely necessary");
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
// All cases when we want to directly use lastDbState as the anchor state:
|
|
149
|
-
// - if no checkpoint sync args provided, or
|
|
150
|
-
// - the lastDbState is within weak subjectivity period:
|
|
151
|
-
if (
|
|
152
|
-
(!args.checkpointState &&
|
|
153
|
-
!args.checkpointSyncUrl &&
|
|
154
|
-
!args.unsafeCheckpointState &&
|
|
155
|
-
!args.lastPersistedCheckpointState) ||
|
|
156
|
-
wssCheck
|
|
157
|
-
) {
|
|
158
|
-
if (stateBytes === null) {
|
|
159
|
-
// this never happens
|
|
160
|
-
throw Error(`There is no stateBytes for the lastDbState at slot ${lastDbState.slot}`);
|
|
161
|
-
}
|
|
162
|
-
await checkAndPersistAnchorState(config, db, logger, lastDbState, stateBytes, {
|
|
163
|
-
isWithinWeakSubjectivityPeriod: wssCheck,
|
|
164
|
-
isCheckpointState: false,
|
|
165
|
-
});
|
|
166
|
-
logger.info("Initialized state from db", {
|
|
167
|
-
slot: lastDbState.slot,
|
|
168
|
-
epoch: computeEpochAtSlot(lastDbState.slot),
|
|
169
|
-
stateRoot: toRootHex(lastDbState.hashTreeRoot()),
|
|
170
|
-
isFinalized,
|
|
171
|
-
});
|
|
172
|
-
return {anchorState: lastDbState, stateBytes, isFinalized};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// See if we can sync state using checkpoint sync args or else start from genesis
|
|
178
|
-
if (args.checkpointState) {
|
|
179
|
-
// state is trusted to be finalized
|
|
180
|
-
const isFinalized = true;
|
|
181
|
-
logger.info("Loading checkpoint state", {checkpointState: args.checkpointState});
|
|
182
|
-
const stateBytes = await downloadOrLoadFile(args.checkpointState);
|
|
183
|
-
logger.info("Loaded checkpoint state", {
|
|
184
|
-
size: formatBytes(stateBytes.length),
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const stateAndCp = await readWSState(
|
|
188
|
-
lastDbStateWithBytes,
|
|
189
|
-
lastDbValidatorsBytes,
|
|
190
|
-
{
|
|
191
|
-
stateBytes,
|
|
192
|
-
isFinalized,
|
|
193
|
-
wssCheckpoint: args.wssCheckpoint,
|
|
194
|
-
forceCheckpointSync: args.forceCheckpointSync,
|
|
195
|
-
ignoreWeakSubjectivityCheck: args.ignoreWeakSubjectivityCheck,
|
|
196
|
-
},
|
|
197
|
-
chainForkConfig,
|
|
198
|
-
db,
|
|
199
|
-
logger
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const {checkpoint} = computeAnchorCheckpoint(chainForkConfig, stateAndCp.anchorState);
|
|
203
|
-
|
|
204
|
-
logger.info("Initialized checkpoint state", {
|
|
205
|
-
slot: stateAndCp.anchorState.slot,
|
|
206
|
-
epoch: checkpoint.epoch,
|
|
207
|
-
checkpointRoot: toRootHex(checkpoint.root),
|
|
208
|
-
isFinalized,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
return {...stateAndCp, isFinalized};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (args.checkpointSyncUrl) {
|
|
215
|
-
// state is trusted to be finalized
|
|
216
|
-
const isFinalized = true;
|
|
217
|
-
const stateAndCp = await fetchWSStateFromBeaconApi(
|
|
218
|
-
lastDbStateWithBytes,
|
|
219
|
-
lastDbValidatorsBytes,
|
|
220
|
-
{
|
|
221
|
-
checkpointSyncUrl: args.checkpointSyncUrl,
|
|
222
|
-
wssCheckpoint: args.wssCheckpoint,
|
|
223
|
-
forceCheckpointSync: args.forceCheckpointSync,
|
|
224
|
-
ignoreWeakSubjectivityCheck: args.ignoreWeakSubjectivityCheck,
|
|
225
|
-
},
|
|
226
|
-
chainForkConfig,
|
|
227
|
-
db,
|
|
228
|
-
logger
|
|
49
|
+
): Promise<InitBeaconStateResult> {
|
|
50
|
+
const options: StateInitializationOptions = args;
|
|
51
|
+
const nativeStateTransition = options["chain.nativeStateTransition"] ?? false;
|
|
52
|
+
if (
|
|
53
|
+
options.forceCheckpointSync &&
|
|
54
|
+
!(options.checkpointState || options.checkpointSyncUrl || options.unsafeCheckpointState)
|
|
55
|
+
) {
|
|
56
|
+
throw new StateInitializationError(
|
|
57
|
+
{code: StateInitializationErrorCode.INVALID_CHECKPOINT_SOURCE},
|
|
58
|
+
"Forced checkpoint sync without specifying a checkpointState, checkpointSyncUrl or unsafeCheckpointState"
|
|
229
59
|
);
|
|
230
|
-
|
|
231
|
-
const {checkpoint} = computeAnchorCheckpoint(chainForkConfig, stateAndCp.anchorState);
|
|
232
|
-
|
|
233
|
-
logger.info("Initialized checkpoint state", {
|
|
234
|
-
slot: stateAndCp.anchorState.slot,
|
|
235
|
-
epoch: checkpoint.epoch,
|
|
236
|
-
checkpointRoot: toRootHex(checkpoint.root),
|
|
237
|
-
isFinalized,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return {...stateAndCp, isFinalized};
|
|
241
60
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (args.lastPersistedCheckpointState && !args.forceCheckpointSync) {
|
|
249
|
-
// find the last persisted checkpoint state to load
|
|
250
|
-
const cpDataStore = args["chain.nHistoricalStatesFileDataStore"]
|
|
251
|
-
? new FileCPStateDatastore(dataDir)
|
|
252
|
-
: new DbCPStateDatastore(db);
|
|
253
|
-
logger.verbose(`Finding last persisted checkpoint state from ${cpDataStore.constructor.name}`);
|
|
254
|
-
stateBytes = await cpDataStore.readLatestSafe();
|
|
255
|
-
if (stateBytes === null) {
|
|
256
|
-
logger.warn("Last persisted checkpoint state not found");
|
|
257
|
-
} else {
|
|
258
|
-
logger.info("Found last persisted checkpoint state", {size: formatBytes(stateBytes.length)});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (stateBytes === null && args.unsafeCheckpointState) {
|
|
263
|
-
logger.info("Loading checkpoint state", {unsafeCheckpointState: args.unsafeCheckpointState});
|
|
264
|
-
stateBytes = await downloadOrLoadFile(args.unsafeCheckpointState);
|
|
265
|
-
logger.info("Loaded checkpoint state", {
|
|
266
|
-
size: formatBytes(stateBytes.length),
|
|
61
|
+
const context: StatePreparationContext = {chainForkConfig, db, dataDir, logger};
|
|
62
|
+
const archived = await readLatestArchivedStateBytes(context);
|
|
63
|
+
if (archived !== null) {
|
|
64
|
+
if (options.forceCheckpointSync && archived.isWithinWeakSubjectivityPeriod) {
|
|
65
|
+
logger.warn("Forced syncing from checkpoint even though db state is within weak subjectivity period", {
|
|
66
|
+
slot: archived.metadata.slot,
|
|
267
67
|
});
|
|
68
|
+
logger.warn("Please consider removing --forceCheckpointSync flag unless absolutely necessary");
|
|
268
69
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
wssCheckpoint: args.wssCheckpoint,
|
|
281
|
-
forceCheckpointSync: args.forceCheckpointSync,
|
|
282
|
-
ignoreWeakSubjectivityCheck: args.ignoreWeakSubjectivityCheck,
|
|
283
|
-
},
|
|
284
|
-
chainForkConfig,
|
|
285
|
-
db,
|
|
70
|
+
const hasCheckpointSource = Boolean(
|
|
71
|
+
options.checkpointState ||
|
|
72
|
+
options.checkpointSyncUrl ||
|
|
73
|
+
options.unsafeCheckpointState ||
|
|
74
|
+
options.lastPersistedCheckpointState
|
|
75
|
+
);
|
|
76
|
+
if (!options.forceCheckpointSync && (!hasCheckpointSource || archived.isWithinWeakSubjectivityPeriod)) {
|
|
77
|
+
return executeStateInitialization(
|
|
78
|
+
prepareArchivedStateInitialization(archived, context),
|
|
79
|
+
nativeStateTransition,
|
|
80
|
+
pubkeysFile,
|
|
286
81
|
logger
|
|
287
82
|
);
|
|
288
|
-
|
|
289
|
-
const lastProcessedSlot = stateAndCp.anchorState.latestBlockHeader.slot;
|
|
290
|
-
const {checkpoint} = computeAnchorCheckpoint(chainForkConfig, stateAndCp.anchorState);
|
|
291
|
-
|
|
292
|
-
logger.info("Initialized checkpoint state", {
|
|
293
|
-
slot: stateAndCp.anchorState.slot,
|
|
294
|
-
epoch: checkpoint.epoch,
|
|
295
|
-
checkpointRoot: toRootHex(checkpoint.root),
|
|
296
|
-
lastProcessedSlot,
|
|
297
|
-
isFinalized,
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
return {...stateAndCp, isFinalized};
|
|
301
83
|
}
|
|
302
84
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
logger.info("Loaded genesis state", {size: formatBytes(stateBytes.length)});
|
|
311
|
-
// Convert to `Uint8Array` to avoid unexpected behavior such as `Buffer.prototype.slice` not copying memory
|
|
312
|
-
stateBytes = new Uint8Array(stateBytes.buffer, stateBytes.byteOffset, stateBytes.byteLength);
|
|
313
|
-
const anchorState = getStateTypeFromBytes(chainForkConfig, stateBytes).deserializeToViewDU(stateBytes);
|
|
314
|
-
// Validate genesis state root
|
|
315
|
-
const stateRoot = toRootHex(anchorState.hashTreeRoot());
|
|
316
|
-
const expectedRoot = getGenesisStateRoot(args.network);
|
|
317
|
-
if (expectedRoot !== null && stateRoot !== expectedRoot) {
|
|
318
|
-
throw Error(`Genesis state root mismatch expected=${expectedRoot} received=${stateRoot}`);
|
|
319
|
-
}
|
|
320
|
-
const config = createBeaconConfig(chainForkConfig, anchorState.genesisValidatorsRoot);
|
|
321
|
-
const wssCheck = isWithinWeakSubjectivityPeriod(config, anchorState, getCheckpointFromState(anchorState));
|
|
322
|
-
await checkAndPersistAnchorState(config, db, logger, anchorState, stateBytes, {
|
|
323
|
-
isWithinWeakSubjectivityPeriod: wssCheck,
|
|
324
|
-
isCheckpointState: true,
|
|
325
|
-
});
|
|
326
|
-
logger.info("Initialized genesis state", {
|
|
327
|
-
slot: anchorState.slot,
|
|
328
|
-
stateRoot,
|
|
329
|
-
isFinalized,
|
|
330
|
-
});
|
|
331
|
-
return {anchorState, stateBytes, isFinalized};
|
|
85
|
+
let stateInit = await prepareCheckpointSourceInitialization(options, archived, context);
|
|
86
|
+
if (stateInit === null) {
|
|
87
|
+
// Without a usable checkpoint state, resume from the db as if no checkpoint source was set instead of genesis
|
|
88
|
+
stateInit =
|
|
89
|
+
archived !== null
|
|
90
|
+
? prepareArchivedStateInitialization(archived, context)
|
|
91
|
+
: await prepareGenesisInitialization(options, context);
|
|
332
92
|
}
|
|
333
|
-
|
|
334
|
-
throw Error("Failed to initialize beacon state, please provide a genesis state file or use checkpoint sync");
|
|
93
|
+
return executeStateInitialization(stateInit, nativeStateTransition, pubkeysFile, logger);
|
|
335
94
|
}
|
|
336
95
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
logger:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
let wsState: BeaconStateAllForks;
|
|
358
|
-
if (lastDbState && lastDbValidatorsBytes) {
|
|
359
|
-
// use lastDbState to load wsState if possible to share the same state tree
|
|
360
|
-
wsState = loadState(chainForkConfig, lastDbState, stateBytes, lastDbValidatorsBytes).state;
|
|
361
|
-
} else {
|
|
362
|
-
wsState = getStateTypeFromBytes(chainForkConfig, stateBytes).deserializeToViewDU(stateBytes);
|
|
96
|
+
/**
|
|
97
|
+
* Read the archived DB anchor without constructing a state. Retain its metadata, weak-subjectivity summary,
|
|
98
|
+
* and period verdict so selection and initialization use the same result without rescanning or checking time again.
|
|
99
|
+
*/
|
|
100
|
+
async function readLatestArchivedStateBytes({
|
|
101
|
+
chainForkConfig,
|
|
102
|
+
db,
|
|
103
|
+
logger,
|
|
104
|
+
}: StatePreparationContext): Promise<ArchivedStateBytes | null> {
|
|
105
|
+
const slot = await db.stateArchive.lastKey();
|
|
106
|
+
const bytes = slot === null ? null : await db.stateArchive.getBinary(slot);
|
|
107
|
+
if (bytes === null) return null;
|
|
108
|
+
logger.verbose("Found the last archived state", {slot, size: formatBytes(bytes.length)});
|
|
109
|
+
const stateBytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
110
|
+
const metadata = readBeaconStateBytesMetadata(stateBytes);
|
|
111
|
+
if (metadata === null) {
|
|
112
|
+
throw new StateInitializationError(
|
|
113
|
+
{code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
|
|
114
|
+
"Cannot read metadata from archived state bytes"
|
|
115
|
+
);
|
|
363
116
|
}
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
117
|
+
const weakSubjectivity = computeWeakSubjectivitySummaryFromStateBytes(chainForkConfig, stateBytes, metadata);
|
|
118
|
+
if (weakSubjectivity === null) {
|
|
119
|
+
throw new StateInitializationError(
|
|
120
|
+
{code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
|
|
121
|
+
`Cannot extract weak subjectivity summary from archived state bytes, expected ${chainForkConfig.getForkName(metadata.slot)} state at slot ${metadata.slot}, possible fork or network mismatch`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
stateBytes,
|
|
126
|
+
metadata,
|
|
127
|
+
weakSubjectivity,
|
|
128
|
+
isWithinWeakSubjectivityPeriod: isWithinWeakSubjectivityPeriodFromSummary(chainForkConfig, weakSubjectivity),
|
|
129
|
+
};
|
|
372
130
|
}
|
|
373
131
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
},
|
|
383
|
-
chainForkConfig: ChainForkConfig,
|
|
384
|
-
db: IBeaconDb,
|
|
132
|
+
/**
|
|
133
|
+
* Shared completion template for all sources: construct only the selected state, exactly once.
|
|
134
|
+
* Validate before persistence, and log success only after any required writes succeed.
|
|
135
|
+
*/
|
|
136
|
+
async function executeStateInitialization(
|
|
137
|
+
stateInit: StateInitialization,
|
|
138
|
+
nativeStateTransition: boolean,
|
|
139
|
+
pubkeysFile: string,
|
|
385
140
|
logger: Logger
|
|
386
|
-
): Promise<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
logger.info("Fetching checkpoint state", {
|
|
395
|
-
checkpointSyncUrl: checkpointSyncUrl.origin,
|
|
396
|
-
});
|
|
397
|
-
} catch (e) {
|
|
398
|
-
logger.error("Invalid", {checkpointSyncUrl: wssOpts.checkpointSyncUrl}, e as Error);
|
|
399
|
-
throw e;
|
|
141
|
+
): Promise<InitBeaconStateResult> {
|
|
142
|
+
const {config, stateBytes} = stateInit;
|
|
143
|
+
const validatorCount = getValidatorCountFromStateBytes(config, stateBytes);
|
|
144
|
+
if (validatorCount === null) {
|
|
145
|
+
throw new StateInitializationError(
|
|
146
|
+
{code: StateInitializationErrorCode.MALFORMED_STATE_BYTES},
|
|
147
|
+
"Cannot read validator count from selected state bytes"
|
|
148
|
+
);
|
|
400
149
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
150
|
+
// Reserve 3 months of worst-case registry growth (MAX_PENDING_DEPOSITS_PER_EPOCH per epoch),
|
|
151
|
+
// over a year at organic rates, to avoid routine cache reallocations. Cache growth is protected
|
|
152
|
+
// by its native lock; if this headroom is exceeded, it grows by the same fixed step.
|
|
153
|
+
// The view syncs pubkeys during construction, so capacity must be reserved first.
|
|
154
|
+
const headroomEpochs = (90 * 24 * 60 * 60) / (config.SECONDS_PER_SLOT * SLOTS_PER_EPOCH);
|
|
155
|
+
const pubkeyCacheCapacity = validatorCount + MAX_PENDING_DEPOSITS_PER_EPOCH * Math.ceil(headroomEpochs);
|
|
156
|
+
loadPubkeysFile(pubkeyCache, pubkeysFile, pubkeyCacheCapacity, config, stateBytes, validatorCount, logger);
|
|
157
|
+
// unilaterally expand capacity after best-effort pubkey file loading
|
|
158
|
+
pubkeyCache.ensureCapacity(pubkeyCacheCapacity);
|
|
159
|
+
const anchorState = createBeaconStateView({nativeStateTransition, config, stateBytes});
|
|
160
|
+
stateInit.validate(anchorState);
|
|
161
|
+
const earliestAvailableSlot = await stateInit.initializeEarliestAvailableSlot(anchorState);
|
|
162
|
+
await stateInit.persist?.(anchorState, stateBytes);
|
|
163
|
+
stateInit.log(anchorState, nativeStateTransition);
|
|
164
|
+
return {anchorState, config, isFinalized: stateInit.isFinalized, earliestAvailableSlot};
|
|
416
165
|
}
|