Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkpoint sync only if db state is stale #4563

Merged
merged 1 commit into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 22 additions & 6 deletions packages/beacon-node/src/chain/initState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,29 @@ export async function initStateFromAnchorState(
config: IChainForkConfig,
db: IBeaconDb,
logger: ILogger,
anchorState: BeaconStateAllForks
anchorState: BeaconStateAllForks,
{
isWithinWeakSubjectivityPeriod,
isCheckpointState,
}: {isWithinWeakSubjectivityPeriod: boolean; isCheckpointState: boolean}
): Promise<BeaconStateAllForks> {
logger.info("Initializing beacon state from anchor state", {
slot: anchorState.slot,
epoch: computeEpochAtSlot(anchorState.slot),
stateRoot: toHexString(anchorState.hashTreeRoot()),
});
const stateInfo = isCheckpointState ? "checkpoint" : "db";
if (isWithinWeakSubjectivityPeriod) {
logger.info(`Initializing beacon from a valid ${stateInfo} state`, {
slot: anchorState.slot,
epoch: computeEpochAtSlot(anchorState.slot),
stateRoot: toHexString(anchorState.hashTreeRoot()),
isWithinWeakSubjectivityPeriod,
});
} else {
logger.warn(`Initializing from a stale ${stateInfo} state vulnerable to long range attacks`, {
slot: anchorState.slot,
epoch: computeEpochAtSlot(anchorState.slot),
stateRoot: toHexString(anchorState.hashTreeRoot()),
isWithinWeakSubjectivityPeriod,
});
logger.warn("Checkpoint sync recommended, please use --help to see checkpoint sync options");
}

await persistAnchorState(config, db, anchorState);

Expand Down
49 changes: 36 additions & 13 deletions packages/cli/src/cmds/beacon/initBeaconState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {defaultNetwork, IGlobalArgs} from "../../options/globalOptions.js";
import {fetchWeakSubjectivityState, getCheckpointFromArg, getGenesisFileUrl} from "../../networks/index.js";
import {IBeaconArgs} from "./options.js";

function getCheckpointFromState(state: BeaconStateAllForks): Checkpoint {
export function getCheckpointFromState(state: BeaconStateAllForks): Checkpoint {
return {
epoch: computeEpochAtSlot(state.latestBlockHeader.slot),
root: getLatestBlockRoot(state),
Expand Down Expand Up @@ -49,19 +49,26 @@ async function initAndVerifyWeakSubjectivityState(
// Pick the state which is ahead as an anchor to initialize the beacon chain
let anchorState = wsState;
let anchorCheckpoint = wsCheckpoint;
let isCheckpointState = true;
if (store.slot > wsState.slot) {
anchorState = store;
anchorCheckpoint = getCheckpointFromState(store);
isCheckpointState = false;
logger.verbose(
"Db state is ahead of the provided checkpoint state, using the db state to initialize the beacon chain"
);
}

// Instead of warning user of wss check failure, we throw because user explicity wants to use
// the checkpoint sync
if (!isWithinWeakSubjectivityPeriod(config, anchorState, anchorCheckpoint)) {
throw new Error("Fetched weak subjectivity checkpoint not within weak subjectivity period.");
}

anchorState = await initStateFromAnchorState(config, db, logger, anchorState);
anchorState = await initStateFromAnchorState(config, db, logger, anchorState, {
isWithinWeakSubjectivityPeriod: true,
isCheckpointState,
});

// Return the latest anchorState but still return original wsCheckpoint to validate in backfill
return {anchorState, wsCheckpoint};
Expand All @@ -84,10 +91,26 @@ export async function initBeaconState(
logger: ILogger,
signal: AbortSignal
): Promise<{anchorState: BeaconStateAllForks; wsCheckpoint?: Checkpoint}> {
// fetch the latest state stored in the db
// this will be used in all cases, if it exists, either used during verification of a weak subjectivity state, or used directly as the anchor state
// fetch the latest state stored in the db which will be used in all cases, if it exists, either
// i) used directly as the anchor state
// ii) used during verification of a weak subjectivity state,
const lastDbState = await db.stateArchive.lastValue();
if (lastDbState) {
const config = createIBeaconConfig(chainForkConfig, lastDbState.genesisValidatorsRoot);
const wssCheck = isWithinWeakSubjectivityPeriod(config, lastDbState, getCheckpointFromState(lastDbState));
// All cases when we want to directly use lastDbState as the anchor state:
// - if no checkpoint sync args provided, or
// - the lastDbState is within weak subjectivity period:
if ((!args.checkpointState && !args.checkpointSyncUrl) || wssCheck) {
const anchorState = await initStateFromAnchorState(config, db, logger, lastDbState, {
isWithinWeakSubjectivityPeriod: wssCheck,
isCheckpointState: false,
});
return {anchorState};
}
}

// See if we can sync state using checkpoint sync args or else start from genesis
if (args.checkpointState) {
return await readWSState(
lastDbState,
Expand All @@ -104,20 +127,20 @@ export async function initBeaconState(
db,
logger
);
} else if (lastDbState) {
// start the chain from the latest stored state in the db
const config = createIBeaconConfig(chainForkConfig, lastDbState.genesisValidatorsRoot);
const anchorState = await initStateFromAnchorState(config, db, logger, lastDbState);
return {anchorState};
} else {
const genesisStateFile = args.genesisStateFile || getGenesisFileUrl(args.network || defaultNetwork);
if (genesisStateFile && !args.forceGenesis) {
const stateBytes = await downloadOrLoadFile(genesisStateFile);
let anchorState = getStateTypeFromBytes(chainForkConfig, stateBytes).deserializeToViewDU(stateBytes);
const config = createIBeaconConfig(chainForkConfig, anchorState.genesisValidatorsRoot);
anchorState = await initStateFromAnchorState(config, db, logger, anchorState);
const wssCheck = isWithinWeakSubjectivityPeriod(config, anchorState, getCheckpointFromState(anchorState));
anchorState = await initStateFromAnchorState(config, db, logger, anchorState, {
isWithinWeakSubjectivityPeriod: wssCheck,
isCheckpointState: true,
});
return {anchorState};
} else {
// Only place we will not bother checking isWithinWeakSubjectivityPeriod as forceGenesis passed by user
const anchorState = await initStateFromEth1({config: chainForkConfig, db, logger, opts: options.eth1, signal});
return {anchorState};
}
Expand Down Expand Up @@ -157,9 +180,9 @@ async function fetchWSStateFromBeaconApi(
try {
// Validate the weakSubjectivityServerUrl and only log the origin to mask the
// username password credentials
const wssUrl = new URL(wssOpts.checkpointSyncUrl);
logger.info("Fetching weak subjectivity state", {
weakSubjectivityServerUrl: wssUrl.origin,
const checkpointSyncUrl = new URL(wssOpts.checkpointSyncUrl);
logger.info("Fetching checkpoint state", {
checkpointSyncUrl: checkpointSyncUrl.origin,
});
} catch (e) {
logger.error("Invalid", {checkpointSyncUrl: wssOpts.checkpointSyncUrl}, e as Error);
Expand Down