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

Make dev command handler transparent #4314

Merged
merged 2 commits into from
Jul 18, 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
4 changes: 2 additions & 2 deletions packages/beacon-node/src/node/utils/interop/deposits.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {digest} from "@chainsafe/as-sha256";
import {phase0, ssz} from "@lodestar/types";
import {toGindex, Tree} from "@chainsafe/persistent-merkle-tree";
import {IChainForkConfig} from "@lodestar/config";
import {IChainConfig} from "@lodestar/config";
import {computeDomain, computeSigningRoot, interopSecretKeys, ZERO_HASH} from "@lodestar/state-transition";
import {BLS_WITHDRAWAL_PREFIX, DOMAIN_DEPOSIT, MAX_EFFECTIVE_BALANCE} from "@lodestar/params";
import {DepositTree} from "../../../db/repositories/depositDataRoot.js";
Expand All @@ -10,7 +10,7 @@ import {DepositTree} from "../../../db/repositories/depositDataRoot.js";
* Compute and return deposit data from other validators.
*/
export function interopDeposits(
config: IChainForkConfig,
config: IChainConfig,
depositDataRootList: DepositTree,
validatorCount: number
): phase0.Deposit[] {
Expand Down
21 changes: 5 additions & 16 deletions packages/beacon-node/src/node/utils/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,20 @@ import {IChainForkConfig} from "@lodestar/config";
import {BeaconStateAllForks} from "@lodestar/state-transition";
import {phase0, ssz} from "@lodestar/types";
import {IBeaconDb} from "../../db/index.js";
import {GENESIS_SLOT} from "../../constants/index.js";
import {interopDeposits} from "./interop/deposits.js";
import {getInteropState, InteropStateOpts} from "./interop/state.js";

export async function initDevState(
export function initDevState(
config: IChainForkConfig,
db: IBeaconDb,
validatorCount: number,
interopStateOpts: InteropStateOpts
): Promise<BeaconStateAllForks> {
): {deposits: phase0.Deposit[]; state: BeaconStateAllForks} {
const deposits = interopDeposits(config, ssz.phase0.DepositDataRootList.defaultViewDU(), validatorCount);
await storeDeposits(db, deposits);
const state = getInteropState(
config,
interopStateOpts,
deposits,
await db.depositDataRoot.getDepositRootTreeAtIndex(validatorCount - 1)
);
const block = config.getForkTypes(GENESIS_SLOT).SignedBeaconBlock.defaultValue();
block.message.stateRoot = state.hashTreeRoot();
await db.blockArchive.add(block);
return state;
const state = getInteropState(config, interopStateOpts, deposits);
return {deposits, state};
}

async function storeDeposits(db: IBeaconDb, deposits: phase0.Deposit[]): Promise<void> {
export async function writeDeposits(db: IBeaconDb, deposits: phase0.Deposit[]): Promise<void> {
for (let i = 0; i < deposits.length; i++) {
await Promise.all([
db.depositEvent.put(i, {
Expand Down
18 changes: 2 additions & 16 deletions packages/beacon-node/test/e2e/interop/genesisState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,10 @@ import {expect} from "chai";
import {toHexString} from "@chainsafe/ssz";
import {config} from "@lodestar/config/default";
import {ssz} from "@lodestar/types";
import {BeaconDb} from "../../../src/index.js";
import {initDevState} from "../../../src/node/utils/state.js";
import {testLogger} from "../../utils/logger.js";
import {interopDeposits} from "../../../src/node/utils/interop/deposits.js";
import {startTmpBeaconDb} from "../../utils/db.js";

describe("interop / initDevState", () => {
let db: BeaconDb;
const logger = testLogger();

before(async () => {
db = await startTmpBeaconDb(config, logger);
});

after(async () => {
await db.stop();
});

it("Create interop deposits", () => {
const deposits = interopDeposits(config, ssz.phase0.DepositDataRootList.defaultViewDU(), 1);

Expand Down Expand Up @@ -72,9 +58,9 @@ describe("interop / initDevState", () => {
]);
});

it("Create correct genesisState", async () => {
it("Create correct genesisState", () => {
const validatorCount = 8;
const state = await initDevState(config, db, validatorCount, {
const {state} = initDevState(config, validatorCount, {
genesisTime: 1644000000,
eth1BlockHash: Buffer.alloc(32, 0xaa),
eth1Timestamp: 1644000000,
Expand Down
20 changes: 16 additions & 4 deletions packages/beacon-node/test/utils/node/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {createIBeaconConfig, createIChainForkConfig, IChainConfig} from "@lodest
import {ILogger, RecursivePartial} from "@lodestar/utils";
import {LevelDbController} from "@lodestar/db";
import {phase0} from "@lodestar/types";
import {GENESIS_SLOT} from "@lodestar/params";
import {BeaconStateAllForks} from "@lodestar/state-transition";
import {isPlainObject} from "@lodestar/utils";
import {BeaconNode} from "../../../src/index.js";
import {createEnr} from "../../../../cli/src/config/enr.js";
import {createNodeJsLibp2p} from "../../../src/network/nodejs/index.js";
import {createPeerId} from "../../../src/network/index.js";
import {defaultNetworkOptions} from "../../../src/network/options.js";
import {initDevState} from "../../../src/node/utils/state.js";
import {initDevState, writeDeposits} from "../../../src/node/utils/state.js";
import {IBeaconNodeOptions} from "../../../src/node/options.js";
import {defaultOptions} from "../../../src/node/options.js";
import {BeaconDb} from "../../../src/db/index.js";
Expand Down Expand Up @@ -80,15 +81,26 @@ export async function getDevBeaconNode(
}
);

const state = opts.anchorState || (await initDevState(config, db, validatorCount, opts));
const beaconConfig = createIBeaconConfig(config, state.genesisValidatorsRoot);
let anchorState = opts.anchorState;
if (!anchorState) {
const {state, deposits} = initDevState(config, validatorCount, opts);
anchorState = state;

// Is it necessary to persist deposits and genesis block?
await writeDeposits(db, deposits);
const block = config.getForkTypes(GENESIS_SLOT).SignedBeaconBlock.defaultValue();
block.message.stateRoot = state.hashTreeRoot();
await db.blockArchive.add(block);
}

const beaconConfig = createIBeaconConfig(config, anchorState.genesisValidatorsRoot);
return await BeaconNode.init({
opts: options as IBeaconNodeOptions,
config: beaconConfig,
db,
logger,
libp2p,
anchorState: state,
anchorState,
wsCheckpoint: opts.wsCheckpoint,
});
}
Expand Down
155 changes: 43 additions & 112 deletions packages/cli/src/cmds/dev/handler.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
import fs from "node:fs";
import {promisify} from "node:util";
import path from "node:path";
import rimraf from "rimraf";
import {fromHexString} from "@chainsafe/ssz";
import {GENESIS_SLOT} from "@lodestar/params";
import {BeaconNode, BeaconDb, initStateFromAnchorState, createNodeJsLibp2p, nodeUtils} from "@lodestar/beacon-node";
import {SlashingProtection, Validator, SignerType} from "@lodestar/validator";
import {LevelDbController} from "@lodestar/db";
import {interopSecretKey} from "@lodestar/state-transition";
import {createIBeaconConfig} from "@lodestar/config";
import {ACTIVE_PRESET, PresetName} from "@lodestar/params";
import {onGracefulShutdown} from "../../util/process.js";
import {createEnr, createPeerId, overwriteEnrWithCliArgs} from "../../config/index.js";
import {IGlobalArgs, parseEnrArgs} from "../../options/index.js";
import {initializeOptionsAndConfig} from "../init/handler.js";
import {mkdir, getCliLogger, parseRange} from "../../util/index.js";
import {toHex, fromHex} from "@lodestar/utils";
import {nodeUtils} from "@lodestar/beacon-node";
import {IGlobalArgs} from "../../options/index.js";
import {mkdir, onGracefulShutdown} from "../../util/index.js";
import {getBeaconConfigFromArgs} from "../../config/beaconParams.js";
import {getBeaconPaths} from "../beacon/paths.js";
import {getValidatorPaths} from "../validator/paths.js";
import {getVersionData} from "../../util/version.js";
import {beaconHandler} from "../beacon/handler.js";
import {validatorHandler} from "../validator/handler.js";
import {IDevArgs} from "./options.js";

/**
* Run a beacon node with validator
*/
export async function devHandler(args: IDevArgs & IGlobalArgs): Promise<void> {
const {beaconNodeOptions, config} = await initializeOptionsAndConfig(args);

// ENR setup
const peerId = await createPeerId();
const enr = createEnr(peerId);
beaconNodeOptions.set({network: {discv5: {enr}}});
const enrArgs = parseEnrArgs(args);
overwriteEnrWithCliArgs(enr, enrArgs, beaconNodeOptions.getWithDefaults());

// Note: defaults to network "dev", to all paths are custom and don't conflict with networks.
// Flag --reset cleans up the custom dirs on dev stop
const beaconPaths = getBeaconPaths(args);
const validatorPaths = getValidatorPaths(args);
const beaconDbDir = beaconPaths.dbDir;
const validatorsDbDir = validatorPaths.validatorsDbDir;
const beaconDbDir = getBeaconPaths(args).dbDir;
const validatorsDbDir = getValidatorPaths(args).validatorsDbDir;

// Remove slashing protection db. Otherwise the validators won't be able to propose nor attest
// until the clock reach a higher slot than the previous run of the dev command
Expand All @@ -50,98 +31,48 @@ export async function devHandler(args: IDevArgs & IGlobalArgs): Promise<void> {
mkdir(beaconDbDir);
mkdir(validatorsDbDir);

// TODO: Rename db.name to db.path or db.location
beaconNodeOptions.set({db: {name: beaconPaths.dbDir}});
const options = beaconNodeOptions.getWithDefaults();

// Genesis params
const validatorCount = args.genesisValidators ?? 8;
const genesisTime = args.genesisTime ?? Math.floor(Date.now() / 1000) + 5;
// Set logger format to Eph with provided genesisTime
if (args.logFormatGenesisTime === undefined) args.logFormatGenesisTime = genesisTime;

// BeaconNode setup
const libp2p = await createNodeJsLibp2p(peerId, options.network, {peerStoreDir: beaconPaths.peerStoreDir});
const logger = getCliLogger(args, beaconPaths, config);
logger.info("Lodestar", {network: args.network, ...getVersionData()});
if (ACTIVE_PRESET === PresetName.minimal) logger.info("ACTIVE_PRESET == minimal preset");
if (args.reset) {
onGracefulShutdown(async () => {
await promisify(rimraf)(beaconDbDir);
await promisify(rimraf)(validatorsDbDir);
});
}

const db = new BeaconDb({config, controller: new LevelDbController(options.db, {logger})});
await db.start();
// TODO: Is this necessary?
if (args.network !== "dev") {
throw Error(`Must not run dev command with network '${args.network}', only 'dev' network`);
}

let anchorState;
// To be able to recycle beacon handler pass the genesis state via file
if (args.genesisStateFile) {
const state = config
.getForkTypes(GENESIS_SLOT)
.BeaconState.deserializeToViewDU(await fs.promises.readFile(args.genesisStateFile));
anchorState = await initStateFromAnchorState(config, db, logger, state);
// Already set, skip
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const eth1BlockHash = args.genesisEth1Hash ? fromHexString(args.genesisEth1Hash!) : undefined;
anchorState = await initStateFromAnchorState(
config,
db,
logger,
await nodeUtils.initDevState(config, db, validatorCount, {genesisTime, eth1BlockHash})
);
}
const beaconConfig = createIBeaconConfig(config, anchorState.genesisValidatorsRoot);
// Generate and write state to disk
const validatorCount = args.genesisValidators ?? 8;
const genesisTime = args.genesisTime ?? Math.floor(Date.now() / 1000) + 5;
const eth1BlockHash = fromHex(args.genesisEth1Hash ?? toHex(Buffer.alloc(32, 0x0b)));

const validators: Validator[] = [];
const config = getBeaconConfigFromArgs(args);
const {state} = nodeUtils.initDevState(config, validatorCount, {genesisTime, eth1BlockHash});

const node = await BeaconNode.init({
opts: options,
config: beaconConfig,
db,
logger,
libp2p,
anchorState,
});
args.genesisStateFile = "genesis.ssz";
fs.writeFileSync(args.genesisStateFile, state.serialize());

const onGracefulShutdownCbs: (() => Promise<void>)[] = [];
onGracefulShutdown(async () => {
for (const cb of onGracefulShutdownCbs) await cb();
await Promise.all([Promise.all(validators.map((v) => v.close())), node.close()]);
if (args.reset) {
logger.info("Cleaning db directories");
await promisify(rimraf)(beaconDbDir);
await promisify(rimraf)(validatorsDbDir);
}
}, logger.info.bind(logger));

if (args.startValidators) {
const indexes = parseRange(args.startValidators);
const secretKeys = indexes.map((i) => interopSecretKey(i));

const dbPath = path.join(validatorsDbDir, "validators");
fs.mkdirSync(dbPath, {recursive: true});

const api = args.server === "memory" ? node.api : args.server;
const dbOps = {
config: config,
controller: new LevelDbController({name: dbPath}, {logger}),
};
const slashingProtection = new SlashingProtection(dbOps);
// Set logger format to Eph with provided genesisTime
if (args.logFormatGenesisTime === undefined) args.logFormatGenesisTime = genesisTime;
}

const controller = new AbortController();
onGracefulShutdownCbs.push(async () => controller.abort());
// Note: recycle entire beacon handler
await beaconHandler(args);

// Initailize genesis once for all validators
const validator = await Validator.initializeFromBeaconNode({
dbOps,
slashingProtection,
api,
logger: logger.child({module: "vali"}),
// TODO: De-duplicate from validator cmd handler
processShutdownCallback: () => process.kill(process.pid, "SIGINT"),
signers: secretKeys.map((secretKey) => ({
type: SignerType.Local,
secretKey,
})),
doppelgangerProtectionEnabled: args.doppelgangerProtectionEnabled,
builder: {},
});

onGracefulShutdownCbs.push(() => validator.close());
if (args.startValidators) {
// TODO: Map dev option to validator's option
args.interopIndexes = args.startValidators;

// Note: recycle entire validator handler:
// - keystore handling
// - metrics
// - keymanager server
await validatorHandler(args);
}
}