diff --git a/packages/cli/src/cmds/dev/handler.ts b/packages/cli/src/cmds/dev/handler.ts index ee156c702706..6e571d3f293f 100644 --- a/packages/cli/src/cmds/dev/handler.ts +++ b/packages/cli/src/cmds/dev/handler.ts @@ -101,7 +101,7 @@ export async function devHandler(args: IDevArgs & IGlobalArgs): Promise { const onGracefulShutdownCbs: (() => Promise)[] = []; onGracefulShutdown(async () => { for (const cb of onGracefulShutdownCbs) await cb(); - await Promise.all([Promise.all(validators.map((v) => v.stop())), node.close()]); + await Promise.all([Promise.all(validators.map((v) => v.close())), node.close()]); if (args.reset) { logger.info("Cleaning db directories"); await promisify(rimraf)(beaconDbDir); @@ -138,7 +138,6 @@ export async function devHandler(args: IDevArgs & IGlobalArgs): Promise { })), }); - onGracefulShutdownCbs.push(() => validator.stop()); - await validator.start(); + onGracefulShutdownCbs.push(() => validator.close()); } } diff --git a/packages/cli/src/cmds/validator/handler.ts b/packages/cli/src/cmds/validator/handler.ts index 221a59a839f1..d3836055ddb5 100644 --- a/packages/cli/src/cmds/validator/handler.ts +++ b/packages/cli/src/cmds/validator/handler.ts @@ -133,8 +133,7 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P metrics ); - onGracefulShutdownCbs.push(() => validator.stop()); - await validator.start(); + onGracefulShutdownCbs.push(() => validator.close()); // Start keymanager API backend // Only if keymanagerEnabled flag is set to true diff --git a/packages/cli/src/cmds/validator/voluntaryExit.ts b/packages/cli/src/cmds/validator/voluntaryExit.ts index 5f61368c2a5f..2e1892b53fde 100644 --- a/packages/cli/src/cmds/validator/voluntaryExit.ts +++ b/packages/cli/src/cmds/validator/voluntaryExit.ts @@ -114,6 +114,7 @@ BE UNTIL AT LEAST TWO YEARS AFTER THE PHASE 0 MAINNET LAUNCH. signers: [{type: SignerType.Local, secretKey}], logger: errorLogger(), graffiti: args.graffiti, + closed: true, }); await validatorClient.voluntaryExit(publicKey, args.exitEpoch); diff --git a/packages/lodestar/test/e2e/api/impl/beacon/state/endpoint.test.ts b/packages/lodestar/test/e2e/api/impl/beacon/state/endpoint.test.ts index c8946d176a97..4eb86ae74e1c 100644 --- a/packages/lodestar/test/e2e/api/impl/beacon/state/endpoint.test.ts +++ b/packages/lodestar/test/e2e/api/impl/beacon/state/endpoint.test.ts @@ -56,9 +56,7 @@ describe("lodestar / api / impl / state", function () { useRestApi: false, testLoggerOpts, }); - afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.stop()))); - - await Promise.all(validators.map((validator) => validator.start())); + afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.close()))); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).beacon; @@ -92,9 +90,7 @@ describe("lodestar / api / impl / state", function () { useRestApi: false, testLoggerOpts, }); - afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.stop()))); - - await Promise.all(validators.map((validator) => validator.start())); + afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.close()))); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).beacon; diff --git a/packages/lodestar/test/e2e/chain/lightclient.test.ts b/packages/lodestar/test/e2e/chain/lightclient.test.ts index e27a79b19e48..734c5d598249 100644 --- a/packages/lodestar/test/e2e/chain/lightclient.test.ts +++ b/packages/lodestar/test/e2e/chain/lightclient.test.ts @@ -93,12 +93,7 @@ describe("chain / lightclient", function () { }); afterEachCallbacks.push(async () => { - await Promise.all(validators.map((v) => v.stop())); - }); - - await Promise.all(validators.map((validator) => validator.start())); - afterEachCallbacks.push(async () => { - await Promise.all(validators.map((v) => v.stop())); + await Promise.all(validators.map((v) => v.close())); }); // This promise chain does: diff --git a/packages/lodestar/test/e2e/keymanager/keymanager.test.ts b/packages/lodestar/test/e2e/keymanager/keymanager.test.ts index f09e884187b8..1a043e0d5467 100644 --- a/packages/lodestar/test/e2e/keymanager/keymanager.test.ts +++ b/packages/lodestar/test/e2e/keymanager/keymanager.test.ts @@ -66,7 +66,7 @@ describe("keymanager delete and import test", async function () { testLoggerOpts, }); - afterEachCallbacks.push(() => vc1Info.validator.stop()); + afterEachCallbacks.push(() => vc1Info.validator.close()); const vc2Info = await getAndInitValidatorsWithKeystore({ node: bn, @@ -76,7 +76,7 @@ describe("keymanager delete and import test", async function () { testLoggerOpts, }); - afterEachCallbacks.push(() => vc2Info.validator.stop()); + afterEachCallbacks.push(() => vc2Info.validator.close()); const portKM1 = 10000; const portKM2 = 10001; @@ -105,7 +105,7 @@ describe("keymanager delete and import test", async function () { // Register clean up // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access afterEachCallbacks.push(async () => { - await Promise.all([vc1Info.validator.stop(), vc2Info.validator.stop()]); + await Promise.all([vc1Info.validator.close(), vc2Info.validator.close()]); vc1Info.tempDirs.keystoreDir.removeCallback(); vc1Info.tempDirs.passwordFile.removeCallback(); vc2Info.tempDirs.keystoreDir.removeCallback(); @@ -337,7 +337,7 @@ describe("keymanager delete and import test", async function () { externalSignerUrl: opts?.useRemoteSigner ? "http://localhost:38000" : undefined, }); - afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.stop()))); + afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.close()))); const keystoresDir = tmp.dirSync({unsafeCleanup: true, prefix: "keystores"}); const tokenDir = tmp.dirSync({unsafeCleanup: true, prefix: "token"}); diff --git a/packages/lodestar/test/e2e/sync/finalizedSync.test.ts b/packages/lodestar/test/e2e/sync/finalizedSync.test.ts index 25d249c0caf8..6b807292aaae 100644 --- a/packages/lodestar/test/e2e/sync/finalizedSync.test.ts +++ b/packages/lodestar/test/e2e/sync/finalizedSync.test.ts @@ -56,10 +56,8 @@ describe("sync / finalized sync", function () { testLoggerOpts, }); - afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.stop()))); + afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.close()))); - await Promise.all(validators.map((validator) => validator.start())); - afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.stop()))); // stop beacon node after validators afterEachCallbacks.push(() => bn.close()); diff --git a/packages/lodestar/test/e2e/sync/unknownBlockSync.test.ts b/packages/lodestar/test/e2e/sync/unknownBlockSync.test.ts index c3413eb294e1..174cafa7a377 100644 --- a/packages/lodestar/test/e2e/sync/unknownBlockSync.test.ts +++ b/packages/lodestar/test/e2e/sync/unknownBlockSync.test.ts @@ -64,10 +64,8 @@ describe("sync / unknown block sync", function () { testLoggerOpts, }); - afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.stop()))); + afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); - await Promise.all(validators.map((validator) => validator.start())); - afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.stop()))); // stop bn after validators afterEachCallbacks.push(() => bn.close()); diff --git a/packages/lodestar/test/e2e/sync/wss.test.ts b/packages/lodestar/test/e2e/sync/wss.test.ts index 8ee331115680..ada33013d127 100644 --- a/packages/lodestar/test/e2e/sync/wss.test.ts +++ b/packages/lodestar/test/e2e/sync/wss.test.ts @@ -79,8 +79,7 @@ describe("Start from WSS", function () { testLoggerOpts, }); - afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.stop()))); - await Promise.all(validators.map((v) => v.start())); + afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); try { await finalizedEventistener; diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index 90bc5897616f..91aca7073a4d 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -353,11 +353,9 @@ describe("executionEngine / ExecutionEngineHttp", function () { }); afterEachCallbacks.push(async function () { - await Promise.all(validators.map((v) => v.stop())); + await Promise.all(validators.map((v) => v.close())); }); - await Promise.all(validators.map((v) => v.start())); - if (TX_SCENARIOS.includes("simple")) { // If bellatrixEpoch > 0, this is the case of pre-merge transaction submission on EL pow await sendTransaction(jsonRpcUrl, { @@ -418,7 +416,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { // Stop chain and un-subscribe events so the execution engine won't update it's head // Allow some time to broadcast finalized events and complete the importBlock routine - await Promise.all(validators.map((v) => v.stop())); + await Promise.all(validators.map((v) => v.close())); await bn.close(); await sleep(500); diff --git a/packages/lodestar/test/sim/multiNodeSingleThread.test.ts b/packages/lodestar/test/sim/multiNodeSingleThread.test.ts index c9e4698d7292..6df923a1a5b6 100644 --- a/packages/lodestar/test/sim/multiNodeSingleThread.test.ts +++ b/packages/lodestar/test/sim/multiNodeSingleThread.test.ts @@ -85,10 +85,10 @@ describe("Run multi node single thread interop validators (no eth1) until checkp startIndex: i * validatorsPerNode, testLoggerOpts, }); - afterEachCallbacks.push(async () => await Promise.all(validators.map((validator) => validator.stop()))); + afterEachCallbacks.push(async () => await Promise.all(validators.map((validator) => validator.close()))); afterEachCallbacks.push(async () => { - await Promise.all(validators.map((validator) => validator.stop())); + await Promise.all(validators.map((validator) => validator.close())); console.log("--- Stopped all validators ---"); // wait for 1 slot await sleep(1 * testParams.SECONDS_PER_SLOT * 1000); @@ -124,9 +124,6 @@ describe("Run multi node single thread interop validators (no eth1) until checkp } } - // Start all validators at once. - await Promise.all(validators.map((validator) => validator.start())); - // Wait for justified checkpoint on all nodes await Promise.all(nodes.map((node) => waitForEvent(node.chain.emitter, event, 240000))); console.log("--- All nodes reached justified checkpoint ---"); diff --git a/packages/lodestar/test/sim/singleNodeSingleThread.test.ts b/packages/lodestar/test/sim/singleNodeSingleThread.test.ts index 5127ed6fe452..6d77517ac3fc 100644 --- a/packages/lodestar/test/sim/singleNodeSingleThread.test.ts +++ b/packages/lodestar/test/sim/singleNodeSingleThread.test.ts @@ -132,8 +132,7 @@ describe("Run single node single thread interop validators (no eth1) until check } // TODO: Previous code waited for 1 slot between stopping the validators and stopInfoTracker() - afterEachCallbacks[1] = () => Promise.all(validators.map((v) => v.stop())); - await Promise.all(validators.map((v) => v.start())); + afterEachCallbacks[1] = () => Promise.all(validators.map((v) => v.close())); // Wait for test to complete await waitForEvent(bn.chain.emitter, event, timeout); diff --git a/packages/lodestar/test/sim/threaded/noEth1SimWorker.ts b/packages/lodestar/test/sim/threaded/noEth1SimWorker.ts index 46aec8f9374d..39667037148b 100644 --- a/packages/lodestar/test/sim/threaded/noEth1SimWorker.ts +++ b/packages/lodestar/test/sim/threaded/noEth1SimWorker.ts @@ -72,7 +72,7 @@ async function runWorker(): Promise { ); node.chain.emitter.on(checkpointEvent, async (checkpoint) => { - await Promise.all(validators.map((validator) => validator.stop())); + await Promise.all(validators.map((validator) => validator.close())); if (stopInfoTracker) stopInfoTracker(); await node.close(); parent.postMessage({ @@ -88,7 +88,6 @@ async function runWorker(): Promise { startIndex, testLoggerOpts, }); - await Promise.all(validators.map((validator) => validator.start())); } runWorker().catch((e: Error) => { diff --git a/packages/validator/src/validator.ts b/packages/validator/src/validator.ts index db202ec78556..a70c00034525 100644 --- a/packages/validator/src/validator.ts +++ b/packages/validator/src/validator.ts @@ -34,6 +34,7 @@ export type ValidatorOptions = { graffiti?: string; defaultFeeRecipient?: string; strictFeeRecipientCheck?: boolean; + closed?: boolean; }; // TODO: Extend the timeout, and let it be customizable @@ -42,11 +43,9 @@ export type ValidatorOptions = { enum Status { running, - stopped, + closed, } -type State = {status: Status.running; controller: AbortController} | {status: Status.stopped}; - /** * Main class for the Validator client. */ @@ -63,12 +62,15 @@ export class Validator { private readonly emitter: ValidatorEventEmitter; private readonly chainHeaderTracker: ChainHeaderTracker; private readonly logger: ILogger; - private state: State = {status: Status.stopped}; + private state: Status; + private readonly controller: AbortController; constructor(opts: ValidatorOptions, readonly genesis: Genesis, metrics: Metrics | null = null) { const {dbOps, logger, slashingProtection, signers, graffiti, defaultFeeRecipient, strictFeeRecipientCheck} = opts; const config = createIBeaconConfig(dbOps.config, genesis.genesisValidatorsRoot); + this.controller = new AbortController(); + const api = typeof opts.api === "string" ? getClient( @@ -76,7 +78,7 @@ export class Validator { baseUrl: opts.api, // Validator would need the beacon to respond within the slot timeoutMs: config.SECONDS_PER_SLOT * 1000, - getAbortSignal: this.getAbortSignal, + getAbortSignal: () => this.controller.signal, }, {config, logger, metrics: metrics?.restApiClient} ) @@ -140,6 +142,16 @@ export class Validator { if (metrics) { opts.dbOps.controller.setMetrics(metrics.db); } + + if (opts.closed) { + this.state = Status.closed; + } else { + // "start" the validator + // Instantiates block and attestation services and runs them once the chain has been started. + this.state = Status.running; + this.clock.start(this.controller.signal); + this.chainHeaderTracker.start(this.controller.signal); + } } /** Waits for genesis and genesis time */ @@ -177,25 +189,13 @@ export class Validator { this.syncCommitteeService.removeDutiesForKey(pubkey); } - /** - * Instantiates block and attestation services and runs them once the chain has been started. - */ - async start(): Promise { - if (this.state.status === Status.running) return; - const controller = new AbortController(); - this.state = {status: Status.running, controller}; - const {signal} = controller; - this.clock.start(signal); - this.chainHeaderTracker.start(signal); - } - /** * Stops all validator functions. */ - async stop(): Promise { - if (this.state.status === Status.stopped) return; - this.state.controller.abort(); - this.state = {status: Status.stopped}; + async close(): Promise { + if (this.state === Status.closed) return; + this.controller.abort(); + this.state = Status.closed; } /** @@ -218,11 +218,6 @@ export class Validator { this.logger.info(`Submitted voluntary exit for ${publicKey} to the network`); } - - /** Provide the current AbortSignal to the api instance */ - private getAbortSignal = (): AbortSignal | undefined => { - return this.state.status === Status.running ? this.state.controller.signal : undefined; - }; } /** Assert the same genesisValidatorRoot and genesisTime */