@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
149 lines • 7.12 kB
JavaScript
import { ZERO_HASH } from "@lodestar/params";
import { computeEpochAtSlot, computeStartSlotAtEpoch, } from "@lodestar/state-transition";
import { ssz } from "@lodestar/types";
import { toHex, toRootHex } from "@lodestar/utils";
import { GENESIS_SLOT } from "../constants/index.js";
import { Eth1Provider } from "../eth1/index.js";
import { GenesisBuilder } from "./genesis/genesis.js";
export async function persistGenesisResult(db, genesisResult, genesisBlock) {
await Promise.all([
db.stateArchive.add(genesisResult.state),
db.blockArchive.add(genesisBlock),
db.depositDataRoot.putList(genesisResult.depositTree.getAllReadonlyValues()),
db.eth1Data.put(genesisResult.block.timestamp, {
...genesisResult.block,
depositCount: genesisResult.depositTree.length,
depositRoot: genesisResult.depositTree.hashTreeRoot(),
}),
]);
}
export async function persistAnchorState(config, db, anchorState, anchorStateBytes) {
if (anchorState.slot === GENESIS_SLOT) {
const genesisBlock = createGenesisBlock(config, anchorState);
const blockRoot = config.getForkTypes(GENESIS_SLOT).BeaconBlock.hashTreeRoot(genesisBlock.message);
const latestBlockHeader = ssz.phase0.BeaconBlockHeader.clone(anchorState.latestBlockHeader);
if (ssz.Root.equals(latestBlockHeader.stateRoot, ZERO_HASH)) {
latestBlockHeader.stateRoot = anchorState.hashTreeRoot();
}
const latestBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(latestBlockHeader);
if (Buffer.compare(blockRoot, latestBlockRoot) !== 0) {
throw Error(`Genesis block root ${toRootHex(blockRoot)} does not match genesis state latest block root ${toRootHex(latestBlockRoot)}`);
}
await Promise.all([
db.blockArchive.add(genesisBlock),
db.block.add(genesisBlock),
db.stateArchive.putBinary(anchorState.slot, anchorStateBytes),
]);
}
else {
await db.stateArchive.putBinary(anchorState.slot, anchorStateBytes);
}
}
export function createGenesisBlock(config, genesisState) {
const types = config.getForkTypes(GENESIS_SLOT);
const genesisBlock = types.SignedBeaconBlock.defaultValue();
const stateRoot = genesisState.hashTreeRoot();
genesisBlock.message.stateRoot = stateRoot;
return genesisBlock;
}
/**
* Initialize and persist a genesis state and related data
*/
export async function initStateFromEth1({ config, db, logger, opts, signal, }) {
logger.info("Listening to eth1 for genesis state");
const statePreGenesis = await db.preGenesisState.get();
const depositTree = await db.depositDataRoot.getDepositRootTree();
const lastProcessedBlockNumber = await db.preGenesisStateLastProcessedBlock.get();
const builder = new GenesisBuilder({
config,
eth1Provider: new Eth1Provider(config, { ...opts, logger }, signal),
logger,
signal,
pendingStatus: statePreGenesis && depositTree !== undefined && lastProcessedBlockNumber != null
? { state: statePreGenesis, depositTree, lastProcessedBlockNumber }
: undefined,
});
try {
const genesisResult = await builder.waitForGenesis();
// Note: .hashTreeRoot() automatically commits()
const genesisBlock = createGenesisBlock(config, genesisResult.state);
const types = config.getForkTypes(GENESIS_SLOT);
const stateRoot = genesisResult.state.hashTreeRoot();
const blockRoot = types.BeaconBlock.hashTreeRoot(genesisBlock.message);
logger.info("Initializing genesis state", {
stateRoot: toRootHex(stateRoot),
blockRoot: toRootHex(blockRoot),
validatorCount: genesisResult.state.validators.length,
});
await persistGenesisResult(db, genesisResult, genesisBlock);
logger.verbose("Clearing pending genesis state if any");
await db.preGenesisState.delete();
await db.preGenesisStateLastProcessedBlock.delete();
return genesisResult.state;
}
catch (e) {
if (builder.lastProcessedBlockNumber != null) {
logger.info("Persisting genesis state", { block: builder.lastProcessedBlockNumber });
// Commit changed before serializing
builder.state.commit();
await db.preGenesisState.put(builder.state);
await db.depositDataRoot.putList(builder.depositTree.getAllReadonlyValues());
await db.preGenesisStateLastProcessedBlock.put(builder.lastProcessedBlockNumber);
}
throw e;
}
}
/**
* Restore the latest beacon state from db
*/
export async function initStateFromDb(_config, db, logger) {
const state = await db.stateArchive.lastValue();
if (!state) {
throw new Error("No state exists in database");
}
logger.info("Initializing beacon state from db", {
slot: state.slot,
epoch: computeEpochAtSlot(state.slot),
stateRoot: toRootHex(state.hashTreeRoot()),
});
return state;
}
/**
* Initialize and persist an anchor state (either weak subjectivity or genesis)
*/
export async function checkAndPersistAnchorState(config, db, logger, anchorState, anchorStateBytes, { isWithinWeakSubjectivityPeriod, isCheckpointState, }) {
const expectedFork = config.getForkInfo(computeStartSlotAtEpoch(anchorState.fork.epoch));
const expectedForkVersion = toHex(expectedFork.version);
const stateFork = toHex(anchorState.fork.currentVersion);
if (stateFork !== expectedForkVersion) {
throw Error(`State current fork version ${stateFork} not equal to current config ${expectedForkVersion}. Maybe caused by importing a state from a different network`);
}
const stateInfo = isCheckpointState ? "checkpoint" : "db";
if (isWithinWeakSubjectivityPeriod) {
logger.info(`Initializing beacon from a valid ${stateInfo} state`, {
slot: anchorState.slot,
epoch: computeEpochAtSlot(anchorState.slot),
stateRoot: toRootHex(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: toRootHex(anchorState.hashTreeRoot()),
isWithinWeakSubjectivityPeriod,
});
logger.warn("Checkpoint sync recommended, please use --help to see checkpoint sync options");
}
if (isCheckpointState || anchorState.slot === GENESIS_SLOT) {
await persistAnchorState(config, db, anchorState, anchorStateBytes);
}
}
export function initBeaconMetrics(metrics, state) {
metrics.headSlot.set(state.slot);
metrics.previousJustifiedEpoch.set(state.previousJustifiedCheckpoint.epoch);
metrics.currentJustifiedEpoch.set(state.currentJustifiedCheckpoint.epoch);
metrics.finalizedEpoch.set(state.finalizedCheckpoint.epoch);
}
//# sourceMappingURL=initState.js.map