Skip to content

Commit

Permalink
fix: avoid redundant anchor state serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Aug 28, 2024
1 parent a05c94f commit ba3cd96
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 37 deletions.
14 changes: 7 additions & 7 deletions packages/beacon-node/src/chain/initState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@ export async function persistGenesisResult(
export async function persistAnchorState(
config: ChainForkConfig,
db: IBeaconDb,
anchorState: BeaconStateAllForks
anchorState: BeaconStateAllForks,
anchorStateBytes: Uint8Array
): Promise<void> {
if (anchorState.slot === GENESIS_SLOT) {
const genesisBlock = createGenesisBlock(config, anchorState);
await Promise.all([
db.blockArchive.add(genesisBlock),
db.block.add(genesisBlock),
db.stateArchive.add(anchorState),
db.stateArchive.putBinary(anchorState.slot, anchorStateBytes),
]);
} else {
await db.stateArchive.add(anchorState);
await db.stateArchive.putBinary(anchorState.slot, anchorStateBytes);
}
}

Expand Down Expand Up @@ -159,11 +160,12 @@ export async function checkAndPersistAnchorState(
db: IBeaconDb,
logger: Logger,
anchorState: BeaconStateAllForks,
anchorStateBytes: Uint8Array,
{
isWithinWeakSubjectivityPeriod,
isCheckpointState,
}: {isWithinWeakSubjectivityPeriod: boolean; isCheckpointState: boolean}
): Promise<BeaconStateAllForks> {
): Promise<void> {
const expectedFork = config.getForkInfo(computeStartSlotAtEpoch(anchorState.fork.epoch));
const expectedForkVersion = toHex(expectedFork.version);
const stateFork = toHex(anchorState.fork.currentVersion);
Expand Down Expand Up @@ -192,10 +194,8 @@ export async function checkAndPersistAnchorState(
}

if (isCheckpointState || anchorState.slot === GENESIS_SLOT) {
await persistAnchorState(config, db, anchorState);
await persistAnchorState(config, db, anchorState, anchorStateBytes);
}

return anchorState;
}

export function initBeaconMetrics(metrics: Metrics, state: BeaconStateAllForks): void {
Expand Down
72 changes: 43 additions & 29 deletions packages/cli/src/cmds/beacon/initBeaconState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,36 @@ import {
} from "../../networks/index.js";
import {BeaconArgs} from "./options.js";

type StateWithBytes = {state: BeaconStateAllForks; stateBytes: Uint8Array};

async function initAndVerifyWeakSubjectivityState(
config: BeaconConfig,
db: IBeaconDb,
logger: Logger,
store: BeaconStateAllForks,
wsState: BeaconStateAllForks,
dbStateBytes: StateWithBytes,
wsStateBytes: StateWithBytes,
wsCheckpoint: Checkpoint,
opts: {ignoreWeakSubjectivityCheck?: boolean} = {}
): Promise<{anchorState: BeaconStateAllForks; wsCheckpoint: Checkpoint}> {
const dbState = dbStateBytes.state;
const wsState = wsStateBytes.state;
// Check if the store's state and wsState are compatible
if (
store.genesisTime !== wsState.genesisTime ||
!ssz.Root.equals(store.genesisValidatorsRoot, wsState.genesisValidatorsRoot)
dbState.genesisTime !== wsState.genesisTime ||
!ssz.Root.equals(dbState.genesisValidatorsRoot, wsState.genesisValidatorsRoot)
) {
throw new Error(
"Db state and checkpoint state are not compatible, either clear the db or verify your checkpoint source"
);
}

// Pick the state which is ahead as an anchor to initialize the beacon chain
let anchorState = wsState;
let anchorState = wsStateBytes;
let anchorCheckpoint = wsCheckpoint;
let isCheckpointState = true;
if (store.slot > wsState.slot) {
anchorState = store;
anchorCheckpoint = getCheckpointFromState(store);
if (dbState.slot > wsState.slot) {
anchorState = dbStateBytes;
anchorCheckpoint = getCheckpointFromState(dbState);
isCheckpointState = false;
logger.verbose(
"Db state is ahead of the provided checkpoint state, using the db state to initialize the beacon chain"
Expand All @@ -61,19 +65,19 @@ async function initAndVerifyWeakSubjectivityState(

// Throw error unless user explicitly asked not to, in testnets can happen that wss period is too small
// that even some epochs of non finalization can cause finalized checkpoint to be out of valid range
const wssCheck = wrapFnError(() => ensureWithinWeakSubjectivityPeriod(config, anchorState, anchorCheckpoint));
const wssCheck = wrapFnError(() => ensureWithinWeakSubjectivityPeriod(config, anchorState.state, anchorCheckpoint));
const isWithinWeakSubjectivityPeriod = wssCheck.err === null;
if (!isWithinWeakSubjectivityPeriod && !opts.ignoreWeakSubjectivityCheck) {
throw wssCheck.err;
}

anchorState = await checkAndPersistAnchorState(config, db, logger, anchorState, {
await checkAndPersistAnchorState(config, db, logger, anchorState.state, anchorState.stateBytes, {
isWithinWeakSubjectivityPeriod,
isCheckpointState,
});

// Return the latest anchorState but still return original wsCheckpoint to validate in backfill
return {anchorState, wsCheckpoint};
return {anchorState: anchorState.state, wsCheckpoint};
}

/**
Expand All @@ -100,14 +104,16 @@ export async function initBeaconState(
// i) used directly as the anchor state
// ii) used to load and verify a weak subjectivity state,
const lastDbSlot = await db.stateArchive.lastKey();
const lastDbStateBytes = lastDbSlot !== null ? await db.stateArchive.getBinary(lastDbSlot) : null;
const stateBytes = lastDbSlot !== null ? await db.stateArchive.getBinary(lastDbSlot) : null;
let lastDbState: BeaconStateAllForks | null = null;
let lastDbValidatorsBytes: Uint8Array | null = null;
if (lastDbStateBytes) {
logger.verbose("Found the last archived state", {slot: lastDbSlot, size: formatBytes(lastDbStateBytes.length)});
const {state, validatorsBytes} = loadStateAndValidators(chainForkConfig, lastDbStateBytes);
let lastDbStateWithBytes: StateWithBytes | null = null;
if (stateBytes) {
logger.verbose("Found the last archived state", {slot: lastDbSlot, size: formatBytes(stateBytes.length)});
const {state, validatorsBytes} = loadStateAndValidators(chainForkConfig, stateBytes);
lastDbState = state;
lastDbValidatorsBytes = validatorsBytes;
lastDbStateWithBytes = {state, stateBytes: stateBytes};
}

if (lastDbState) {
Expand All @@ -129,19 +135,23 @@ export async function initBeaconState(
// - if no checkpoint sync args provided, or
// - the lastDbState is within weak subjectivity period:
if ((!args.checkpointState && !args.checkpointSyncUrl) || wssCheck) {
const anchorState = await checkAndPersistAnchorState(config, db, logger, lastDbState, {
if (stateBytes === null) {
// this never happens
throw Error(`There is no stateBytes for the lastDbState at slot ${lastDbState.slot}`);
}
await checkAndPersistAnchorState(config, db, logger, lastDbState, stateBytes, {
isWithinWeakSubjectivityPeriod: wssCheck,
isCheckpointState: false,
});
return {anchorState};
return {anchorState: lastDbState};
}
}
}

// See if we can sync state using checkpoint sync args or else start from genesis
if (args.checkpointState) {
return readWSState(
lastDbState,
lastDbStateWithBytes,
lastDbValidatorsBytes,
{
checkpointState: args.checkpointState,
Expand All @@ -154,7 +164,7 @@ export async function initBeaconState(
);
} else if (args.checkpointSyncUrl) {
return fetchWSStateFromBeaconApi(
lastDbState,
lastDbStateWithBytes,
lastDbValidatorsBytes,
{
checkpointSyncUrl: args.checkpointSyncUrl,
Expand All @@ -169,10 +179,10 @@ export async function initBeaconState(
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 anchorState = getStateTypeFromBytes(chainForkConfig, stateBytes).deserializeToViewDU(stateBytes);
const config = createBeaconConfig(chainForkConfig, anchorState.genesisValidatorsRoot);
const wssCheck = isWithinWeakSubjectivityPeriod(config, anchorState, getCheckpointFromState(anchorState));
anchorState = await checkAndPersistAnchorState(config, db, logger, anchorState, {
await checkAndPersistAnchorState(config, db, logger, anchorState, stateBytes, {
isWithinWeakSubjectivityPeriod: wssCheck,
isCheckpointState: true,
});
Expand All @@ -186,7 +196,7 @@ export async function initBeaconState(
}

async function readWSState(
lastDbState: BeaconStateAllForks | null,
lastDbStateBytes: StateWithBytes | null,
lastDbValidatorsBytes: Uint8Array | null,
wssOpts: {checkpointState: string; wssCheckpoint?: string; ignoreWeakSubjectivityCheck?: boolean},
chainForkConfig: ChainForkConfig,
Expand All @@ -197,6 +207,7 @@ async function readWSState(
// if a weak subjectivity checkpoint has been provided, it is used for additional verification
// otherwise, the state itself is used for verification (not bad, because the trusted state has been explicitly provided)
const {checkpointState, wssCheckpoint, ignoreWeakSubjectivityCheck} = wssOpts;
const lastDbState = lastDbStateBytes?.state ?? null;

const stateBytes = await downloadOrLoadFile(checkpointState);
let wsState: BeaconStateAllForks;
Expand All @@ -207,15 +218,16 @@ async function readWSState(
wsState = getStateTypeFromBytes(chainForkConfig, stateBytes).deserializeToViewDU(stateBytes);
}
const config = createBeaconConfig(chainForkConfig, wsState.genesisValidatorsRoot);
const store = lastDbState ?? wsState;
const wsStateBytes = {state: wsState, stateBytes};
const store = lastDbStateBytes ?? wsStateBytes;
const checkpoint = wssCheckpoint ? getCheckpointFromArg(wssCheckpoint) : getCheckpointFromState(wsState);
return initAndVerifyWeakSubjectivityState(config, db, logger, store, wsState, checkpoint, {
return initAndVerifyWeakSubjectivityState(config, db, logger, store, wsStateBytes, checkpoint, {
ignoreWeakSubjectivityCheck,
});
}

async function fetchWSStateFromBeaconApi(
lastDbState: BeaconStateAllForks | null,
lastDbStateBytes: StateWithBytes | null,
lastDbValidatorsBytes: Uint8Array | null,
wssOpts: {checkpointSyncUrl: string; wssCheckpoint?: string; ignoreWeakSubjectivityCheck?: boolean},
chainForkConfig: ChainForkConfig,
Expand All @@ -237,13 +249,15 @@ async function fetchWSStateFromBeaconApi(
throw e;
}

const {wsState, wsCheckpoint} = await fetchWeakSubjectivityState(chainForkConfig, logger, wssOpts, {
lastDbState,
const {wsState, wsStateBytes, wsCheckpoint} = await fetchWeakSubjectivityState(chainForkConfig, logger, wssOpts, {
lastDbState: lastDbStateBytes?.state ?? null,
lastDbValidatorsBytes,
});

const config = createBeaconConfig(chainForkConfig, wsState.genesisValidatorsRoot);
const store = lastDbState ?? wsState;
return initAndVerifyWeakSubjectivityState(config, db, logger, store, wsState, wsCheckpoint, {
const wsStateWithBytes = {state: wsState, stateBytes: wsStateBytes};
const store = lastDbStateBytes ?? wsStateWithBytes;
return initAndVerifyWeakSubjectivityState(config, db, logger, store, wsStateWithBytes, wsCheckpoint, {
ignoreWeakSubjectivityCheck: wssOpts.ignoreWeakSubjectivityCheck,
});
}
3 changes: 2 additions & 1 deletion packages/cli/src/networks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export async function fetchWeakSubjectivityState(
lastDbState,
lastDbValidatorsBytes,
}: {lastDbState: BeaconStateAllForks | null; lastDbValidatorsBytes: Uint8Array | null}
): Promise<{wsState: BeaconStateAllForks; wsCheckpoint: Checkpoint}> {
): Promise<{wsState: BeaconStateAllForks; wsStateBytes: Uint8Array; wsCheckpoint: Checkpoint}> {
try {
let wsCheckpoint: Checkpoint | null;
let stateId: Slot | "finalized";
Expand Down Expand Up @@ -202,6 +202,7 @@ export async function fetchWeakSubjectivityState(

return {
wsState,
wsStateBytes,
wsCheckpoint: wsCheckpoint ?? getCheckpointFromState(wsState),
};
} catch (e) {
Expand Down

0 comments on commit ba3cd96

Please sign in to comment.