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 1 commit
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
2 changes: 1 addition & 1 deletion packages/beacon-node/test/utils/node/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export async function getDevBeaconNode(
}
);

const state = opts.anchorState || (await initDevState(config, db, validatorCount, opts));
const state = opts.anchorState || initDevState(config, validatorCount, opts).state;
const beaconConfig = createIBeaconConfig(config, state.genesisValidatorsRoot);
return await BeaconNode.init({
opts: options as IBeaconNodeOptions,
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);
}
}
24 changes: 7 additions & 17 deletions packages/cli/src/cmds/dev/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,17 @@ import {ICliCommandOptions} from "../../util/index.js";
import {beaconOptions, IBeaconArgs} from "../beacon/options.js";
import {NetworkName} from "../../networks/index.js";
import {beaconNodeOptions, globalOptions} from "../../options/index.js";
import {IValidatorCliArgs, validatorOptions, KeymanagerArgs, keymanagerOptions} from "../validator/options.js";
import {IValidatorCliArgs, validatorOptions} from "../validator/options.js";

type IDevOwnArgs = {
genesisEth1Hash?: string;
genesisValidators?: number;
startValidators?: string;
genesisTime?: number;
reset?: boolean;
doppelgangerProtectionEnabled?: boolean;
server: string;
} & KeymanagerArgs &
Pick<IValidatorCliArgs, "importKeystoresPath" | "importKeystoresPassword" | "doppelgangerProtectionEnabled">;
};

const devOwnOptions: ICliCommandOptions<IDevOwnArgs> = {
...keymanagerOptions,
importKeystoresPath: validatorOptions["importKeystoresPath"],
importKeystoresPassword: validatorOptions["importKeystoresPassword"],
doppelgangerProtectionEnabled: validatorOptions["doppelgangerProtectionEnabled"],
genesisEth1Hash: {
description: "If present it will create genesis with this eth1 hash.",
type: "string",
Expand All @@ -30,6 +23,7 @@ const devOwnOptions: ICliCommandOptions<IDevOwnArgs> = {
genesisValidators: {
alias: ["c"],
description: "If present it will create genesis with interop validators and start chain.",
default: 8,
type: "number",
group: "dev",
},
Expand All @@ -42,6 +36,7 @@ const devOwnOptions: ICliCommandOptions<IDevOwnArgs> = {

genesisTime: {
description: "genesis_time to initialize interop genesis state",
defaultDescription: "now",
type: "number",
group: "dev",
},
Expand All @@ -52,12 +47,6 @@ const devOwnOptions: ICliCommandOptions<IDevOwnArgs> = {
type: "boolean",
group: "dev",
},

server: {
description: "Address to connect to BeaconNode. Pass 'memory' for in memory communication",
default: "http://127.0.0.1:9596",
type: "string",
},
};

/**
Expand All @@ -67,7 +56,7 @@ const devOwnOptions: ICliCommandOptions<IDevOwnArgs> = {
* - and have api enabled by default (as it's used by validator)
* Note: use beaconNodeOptions and globalOptions to make sure option key is correct
*/
const externalOptionsOverrides: {[k: string]: Options} = {
const externalOptionsOverrides: Partial<Record<"network" | keyof typeof beaconNodeOptions, Options>> = {
// Custom paths different than regular beacon, validator paths
// network="dev" will store all data in separate dir than other networks
network: {
Expand Down Expand Up @@ -109,8 +98,9 @@ const externalOptionsOverrides: {[k: string]: Options} = {

export const devOptions = {
...beaconOptions,
...validatorOptions,
...externalOptionsOverrides,
...devOwnOptions,
};

export type IDevArgs = IBeaconArgs & IDevOwnArgs;
export type IDevArgs = IBeaconArgs & IValidatorCliArgs & IDevOwnArgs;
4 changes: 2 additions & 2 deletions packages/state-transition/src/util/genesis.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {IBeaconConfig, IChainForkConfig} from "@lodestar/config";
import {IChainForkConfig} from "@lodestar/config";
import {
EFFECTIVE_BALANCE_INCREMENT,
EPOCHS_PER_HISTORICAL_VECTOR,
Expand Down Expand Up @@ -219,7 +219,7 @@ export function initializeBeaconStateFromEth1(
const stateView = getGenesisBeaconState(
// CachedBeaconcState is used for convinience only, we return BeaconStateAllForks anyway
// so it's safe to do a cast here, we can't use get domain until we have genesisValidatorRoot
config as IBeaconConfig,
config,
ssz.phase0.Eth1Data.defaultValue(),
getTemporaryBlockHeader(config, config.getForkTypes(GENESIS_SLOT).BeaconBlock.defaultValue())
);
Expand Down