From 54541c71899c6ea9b9b7fb97bed06bf958651a63 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 14 Aug 2023 17:39:42 +0200 Subject: [PATCH 01/15] Add support for multi client sim tests --- .github/workflows/test-sim.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-sim.yml b/.github/workflows/test-sim.yml index a03815abd64c..9e6491a1cede 100644 --- a/.github/workflows/test-sim.yml +++ b/.github/workflows/test-sim.yml @@ -56,6 +56,10 @@ jobs: run: DEBUG='${{github.event.inputs.debug}}' yarn test:sim:multifork working-directory: packages/cli + - name: Sim tests multi client + run: DEBUG='${{github.event.inputs.debug}}' yarn test:sim:multiclient + working-directory: packages/cli + - name: Sim tests endpoints run: yarn test:sim:endpoints working-directory: packages/cli From a737e584fa32eb4f9dd389c0e1c360352f0afb1e Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 14 Aug 2023 17:39:58 +0200 Subject: [PATCH 02/15] Add support for multi client sim tests --- packages/cli/package.json | 1 + packages/cli/test/sim/deneb.test.ts | 8 +- packages/cli/test/sim/multi_client.test.ts | 78 +++++++++++++++++++ packages/cli/test/sim/multi_fork.test.ts | 10 +-- .../utils/simulation/SimulationEnvironment.ts | 51 +++++++----- .../test/utils/simulation/cl_clients/index.ts | 41 ++++++++-- .../utils/simulation/cl_clients/lighthouse.ts | 6 +- .../utils/simulation/cl_clients/lodestar.ts | 7 +- .../cli/test/utils/simulation/interfaces.ts | 26 ++++++- 9 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 packages/cli/test/sim/multi_client.test.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 77176fe0ff99..51b14ab3fe85 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,6 +34,7 @@ "test:unit": "nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit/**/*.test.ts'", "test:e2e": "mocha --timeout 30000 'test/e2e/**/*.test.ts'", "test:sim:multifork": "LODESTAR_PRESET=minimal node --loader ts-node/esm test/sim/multi_fork.test.ts", + "test:sim:multiclient": "LODESTAR_PRESET=minimal node --loader ts-node/esm test/sim/multi_client.test.ts", "test:sim:endpoints": "LODESTAR_PRESET=minimal node --loader ts-node/esm test/sim/endpoints.test.ts", "test:sim:deneb": "LODESTAR_PRESET=minimal node --loader ts-node/esm test/sim/deneb.test.ts", "test:sim:backup_eth_provider": "LODESTAR_PRESET=minimal node --loader ts-node/esm test/sim/backup_eth_provider.test.ts", diff --git a/packages/cli/test/sim/deneb.test.ts b/packages/cli/test/sim/deneb.test.ts index c7f99a3ba242..b21a34f3d58d 100644 --- a/packages/cli/test/sim/deneb.test.ts +++ b/packages/cli/test/sim/deneb.test.ts @@ -96,11 +96,11 @@ const checkpointSync = await env.createNodePair({ }); await rangeSync.el.job.start(); -await rangeSync.cl.job.start(); +await rangeSync.cl.beaconJob.start(); await connectNewNode(rangeSync, env.nodes); await checkpointSync.el.job.start(); -await checkpointSync.cl.job.start(); +await checkpointSync.cl.beaconJob.start(); await connectNewNode(checkpointSync, env.nodes); await Promise.all([ @@ -114,9 +114,9 @@ await Promise.all([ }), ]); -await rangeSync.cl.job.stop(); +await rangeSync.cl.beaconJob.stop(); await rangeSync.el.job.stop(); -await checkpointSync.cl.job.stop(); +await checkpointSync.cl.beaconJob.stop(); await checkpointSync.el.job.stop(); await env.stop(); diff --git a/packages/cli/test/sim/multi_client.test.ts b/packages/cli/test/sim/multi_client.test.ts new file mode 100644 index 000000000000..0f4f7332802b --- /dev/null +++ b/packages/cli/test/sim/multi_client.test.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import path from "node:path"; +import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; +import {nodeAssertion} from "../utils/simulation/assertions/nodeAssertion.js"; +import {CLIQUE_SEALING_PERIOD, SIM_TESTS_SECONDS_PER_SLOT} from "../utils/simulation/constants.js"; +import {AssertionMatch, CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {getEstimatedTTD, getEstimatedTimeInSecForRun, logFilesDir} from "../utils/simulation/utils/index.js"; +import {connectAllNodes, waitForSlot} from "../utils/simulation/utils/network.js"; + +const genesisDelaySeconds = 20 * SIM_TESTS_SECONDS_PER_SLOT; +const altairForkEpoch = 2; +const bellatrixForkEpoch = 4; +const capellaForkEpoch = 6; +// Make sure bellatrix started before TTD reach +const additionalSlotsForTTD = 2; +const runTillEpoch = 8; +const syncWaitEpoch = 2; + +const runTimeoutMs = + getEstimatedTimeInSecForRun({ + genesisDelaySeconds, + secondsPerSlot: SIM_TESTS_SECONDS_PER_SLOT, + runTill: runTillEpoch + syncWaitEpoch, + // After adding Nethermind its took longer to complete + graceExtraTimeFraction: 0.3, + }) * 1000; + +const ttd = getEstimatedTTD({ + genesisDelaySeconds, + bellatrixForkEpoch, + secondsPerSlot: SIM_TESTS_SECONDS_PER_SLOT, + cliqueSealingPeriod: CLIQUE_SEALING_PERIOD, + additionalSlots: additionalSlotsForTTD, +}); + +const env = await SimulationEnvironment.initWithDefaults( + { + id: "multi-clients", + logsDir: path.join(logFilesDir, "multi-clients"), + chainConfig: { + ALTAIR_FORK_EPOCH: altairForkEpoch, + BELLATRIX_FORK_EPOCH: bellatrixForkEpoch, + CAPELLA_FORK_EPOCH: capellaForkEpoch, + GENESIS_DELAY: genesisDelaySeconds, + TERMINAL_TOTAL_DIFFICULTY: ttd, + }, + }, + [ + { + id: "node-1", + el: ELClient.Geth, + keysCount: 32, + mining: true, + cl: {beacon: CLClient.Lodestar, validator: CLClient.Lighthouse}, + }, + { + id: "node-2", + el: ELClient.Geth, + keysCount: 32, + remote: true, + cl: {beacon: CLClient.Lighthouse, validator: CLClient.Lodestar}, + }, + ] +); + +env.tracker.register({ + ...nodeAssertion, + match: ({slot}) => { + return slot === 1 ? AssertionMatch.Assert | AssertionMatch.Capture | AssertionMatch.Remove : AssertionMatch.None; + }, +}); + +await env.start({runTimeoutMs}); +await connectAllNodes(env.nodes); + +await waitForSlot(env.clock.getLastSlotOfEpoch(capellaForkEpoch + 1), env.nodes, {env, silent: true}); + +await env.stop(); diff --git a/packages/cli/test/sim/multi_fork.test.ts b/packages/cli/test/sim/multi_fork.test.ts index 1ee732caad6e..d0a58a75b36b 100644 --- a/packages/cli/test/sim/multi_fork.test.ts +++ b/packages/cli/test/sim/multi_fork.test.ts @@ -123,11 +123,11 @@ const checkpointSync = await env.createNodePair({ }); await rangeSync.el.job.start(); -await rangeSync.cl.job.start(); +await rangeSync.cl.beaconJob.start(); await connectNewNode(rangeSync, env.nodes); await checkpointSync.el.job.start(); -await checkpointSync.cl.job.start(); +await checkpointSync.cl.beaconJob.start(); await connectNewNode(checkpointSync, env.nodes); await Promise.all([ @@ -141,9 +141,9 @@ await Promise.all([ }), ]); -await rangeSync.cl.job.stop(); +await rangeSync.cl.beaconJob.stop(); await rangeSync.el.job.stop(); -await checkpointSync.cl.job.stop(); +await checkpointSync.cl.beaconJob.stop(); await checkpointSync.el.job.stop(); // Unknown block sync @@ -158,7 +158,7 @@ const unknownBlockSync = await env.createNodePair({ keysCount: 0, }); await unknownBlockSync.el.job.start(); -await unknownBlockSync.cl.job.start(); +await unknownBlockSync.cl.beaconJob.start(); const headForUnknownBlockSync = await env.nodes[0].cl.api.beacon.getBlockV2("head"); ApiError.assert(headForUnknownBlockSync); await connectNewNode(unknownBlockSync, env.nodes); diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 38e26d4582b8..88ce838c583c 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -160,7 +160,8 @@ export class SimulationEnvironment { throw new Error("The genesis state for CL clients is not defined."); } - await Promise.all(this.nodes.map((node) => node.cl.job.start())); + await Promise.all(this.nodes.map((node) => node.cl.beaconJob?.start())); + await Promise.all(this.nodes.map((node) => node.cl.validatorJob?.start())); if (this.nodes.some((node) => node.cl.keys.type === "remote")) { console.log("Starting external signer..."); @@ -194,8 +195,9 @@ export class SimulationEnvironment { process.removeAllListeners("SIGINT"); console.log(`Simulation environment "${this.options.id}" is stopping: ${message}`); await this.tracker.stop(); + await Promise.all(this.nodes.map((node) => node.cl.validatorJob?.stop())); + await Promise.all(this.nodes.map((node) => node.cl.beaconJob?.stop())); await Promise.all(this.nodes.map((node) => node.el.job.stop())); - await Promise.all(this.nodes.map((node) => node.cl.job.stop())); await this.externalSigner.stop(); await this.runner.stop(); this.options.controller.abort(); @@ -208,14 +210,14 @@ export class SimulationEnvironment { } } - async createNodePair({ + async createNodePair({ el, cl, keysCount, id, remote, mining, - }: NodePairOptions): Promise { + }: NodePairOptions): Promise { if (this.genesisState && keysCount > 0) { throw new Error("Genesis state already initialized. Can not add more keys to it."); } @@ -231,9 +233,8 @@ export class SimulationEnvironment { ? {type: "local", secretKeys: interopKeys} : {type: "no-keys"}; + // Execution Node const elType = typeof el === "object" ? el.type : el; - const clType = typeof cl === "object" ? cl.type : cl; - const elOptions = typeof el === "object" ? el.options : {}; const elNode = await createELNode(elType, { ...elOptions, @@ -252,15 +253,21 @@ export class SimulationEnvironment { clientOptions: elOptions.clientOptions, }); - const clOptions = typeof cl === "object" ? cl.options : {}; + // Beacon Node + const {beacon, beaconOptions, validator, validatorOptions} = + typeof cl === "object" + ? "type" in cl + ? {beacon: cl.type, validator: cl.type, beaconOptions: cl.options, validatorOptions: cl.options} + : cl + : {beacon: cl, validator: cl, beaconOptions: {}, validatorOptions: {}}; + const engineUrls = [ // As lodestar is running on host machine, need to connect through local exposed ports - clType === CLClient.Lodestar ? replaceIpFromUrl(elNode.engineRpcUrl, "127.0.0.1") : elNode.engineRpcUrl, - ...(clOptions.engineUrls || []), + beacon === CLClient.Lodestar ? replaceIpFromUrl(elNode.engineRpcUrl, "127.0.0.1") : elNode.engineRpcUrl, + ...(beaconOptions?.engineUrls ?? []), ]; - const clNode = await createCLNode(clType, { - ...clOptions, + const commonOptions = { id, keys, engineMock: typeof el === "string" ? el === ELClient.Mock : el.type === ELClient.Mock, @@ -270,13 +277,21 @@ export class SimulationEnvironment { runner: this.runner, genesisTime: this.options.elGenesisTime, genesisState: this.genesisState, - paths: getCLNodePaths({ - root: this.options.rootDir, - id, - client: clType, - logsDir: this.options.logsDir, - }), - clientOptions: clOptions.clientOptions, + }; + + const clNode = await createCLNode({ + beacon, + beaconOptions: { + ...beaconOptions, + ...commonOptions, + paths: getCLNodePaths({id, logsDir: this.options.logsDir, client: beacon, root: this.options.rootDir}), + }, + validator, + validatorOptions: { + ...validatorOptions, + ...commonOptions, + paths: getCLNodePaths({id, logsDir: this.options.logsDir, client: validator, root: this.options.rootDir}), + }, }); this.nodePairCount += 1; diff --git a/packages/cli/test/utils/simulation/cl_clients/index.ts b/packages/cli/test/utils/simulation/cl_clients/index.ts index 5357d62c9a77..18a5428f7da7 100644 --- a/packages/cli/test/utils/simulation/cl_clients/index.ts +++ b/packages/cli/test/utils/simulation/cl_clients/index.ts @@ -8,15 +8,42 @@ import {createCLNodePaths} from "../utils/paths.js"; import {generateLighthouseBeaconNode} from "./lighthouse.js"; import {generateLodestarBeaconNode} from "./lodestar.js"; -export async function createCLNode( +type GeneratorRequiredOptions = AtLeast< + CLClientGeneratorOptions, + "id" | "paths" | "config" | "nodeIndex" | "genesisTime" +> & { + genesisState?: BeaconStateAllForks; + runner: IRunner; +}; + +export async function createCLNode({ + beacon, + beaconOptions, + validator, + validatorOptions, +}: { + beacon: B; + beaconOptions: GeneratorRequiredOptions; + validator: V; + validatorOptions: GeneratorRequiredOptions; +}): Promise { + const beaconNode = await createCLNodeComponent(beacon, beaconOptions, "beacon"); + const validatorNode = await createCLNodeComponent(validator, validatorOptions, "validator"); + + return { + ...beaconNode, + beaconJob: beaconNode.beaconJob, + validatorJob: validatorNode.validatorJob, + }; +} + +async function createCLNodeComponent( client: C, - options: AtLeast, "id" | "paths" | "config" | "paths" | "nodeIndex" | "genesisTime"> & { - genesisState?: BeaconStateAllForks; - runner: IRunner; - } + options: GeneratorRequiredOptions, + component: "beacon" | "validator" ): Promise { const {runner, config, genesisState} = options; - const clId = `${options.id}-cl-${client}`; + const clId = `${options.id}-cl-${client}-${component}`; const opts: CLClientGeneratorOptions = { ...options, @@ -27,6 +54,8 @@ export async function createCLNode( clientOptions: options.clientOptions ?? {}, address: "127.0.0.1", engineUrls: options.engineUrls ?? [], + beacon: component === "beacon", + validator: component === "validator", }; const metricServer = process.env.SIM_METRIC_SERVER_URL; diff --git a/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts b/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts index 9b29ed856c2d..56b0e7c2ae12 100644 --- a/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts +++ b/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts @@ -132,6 +132,9 @@ export const generateLighthouseBeaconNode: CLClientGenerator = ); } - const job = runner.create([ + const validatorJob = runner.create(validatorClientsJobs); + const beaconJob = runner.create([ { id, bootstrap: async () => { @@ -108,7 +109,6 @@ export const generateLodestarBeaconNode: CLClientGenerator = return {ok: false, reason: (err as Error).message, checkId: "eth/v1/node/health query"}; } }, - children: validatorClientsJobs, }, ]); @@ -119,7 +119,8 @@ export const generateLodestarBeaconNode: CLClientGenerator = keys, api: getClient({baseUrl: `http://127.0.0.1:${ports.cl.httpPort}`}, {config}), keyManager: keyManagerGetClient({baseUrl: `http://127.0.0.1:${ports.cl.keymanagerPort}`}, {config}), - job, + beaconJob: opts.beacon ? beaconJob : undefined, + validatorJob: opts.validator ? validatorJob : undefined, }; }; diff --git a/packages/cli/test/utils/simulation/interfaces.ts b/packages/cli/test/utils/simulation/interfaces.ts index f2124ad0edcf..0b4a0f03cfaa 100644 --- a/packages/cli/test/utils/simulation/interfaces.ts +++ b/packages/cli/test/utils/simulation/interfaces.ts @@ -54,13 +54,28 @@ export type ELClientsOptions = { [ELClient.Nethermind]: string[]; }; -export interface NodePairOptions { +export type ELNodeDefinition = E | {type: E; options: Partial>}; +export type CLNodeDefinition = + | B + | {type: B; options: Partial>} + | { + beacon: B; + beaconOptions?: Partial>; + validator: V; + validatorOptions?: Partial>; + }; + +export interface NodePairOptions< + B extends CLClient = CLClient, + V extends CLClient = CLClient, + E extends ELClient = ELClient, +> { keysCount: number; remote?: boolean; mining?: boolean; id: string; - cl: C | {type: C; options: Partial>}; - el: E | {type: E; options: Partial>}; + cl: CLNodeDefinition; + el: ELNodeDefinition; } export type CLClientKeys = @@ -83,6 +98,8 @@ export interface CLClientGeneratorOptions { host: string; port: number; }; + beacon: boolean; + validator: boolean; } export interface ELGeneratorGenesisOptions { @@ -133,7 +150,8 @@ export interface CLNode { readonly api: C extends CLClient.Lodestar ? LodestarAPI : LighthouseAPI; readonly keyManager: KeyManagerApi; readonly keys: CLClientKeys; - readonly job: Job; + readonly beaconJob?: Job; + readonly validatorJob?: Job; } export interface ELNode { From 0108f9d444b1310b25538e24ee7b9aebc58fa365 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 15 Aug 2023 18:18:41 +0200 Subject: [PATCH 03/15] Decouple beacon and validator --- packages/cli/test/scripts/e2e_test_env.ts | 6 +- .../cli/test/sim/backup_eth_provider.test.ts | 16 +- packages/cli/test/sim/deneb.test.ts | 36 +-- packages/cli/test/sim/endpoints.test.ts | 8 +- packages/cli/test/sim/multi_client.test.ts | 12 +- packages/cli/test/sim/multi_fork.test.ts | 54 ++-- packages/cli/test/utils/simulation/README.md | 11 +- .../utils/simulation/SimulationEnvironment.ts | 144 +++++------ .../utils/simulation/SimulationTracker.ts | 36 +-- .../test/utils/simulation/TableReporter.ts | 14 +- .../defaults/attestationCountAssertion.ts | 2 +- .../attestationParticipationAssertion.ts | 2 +- .../defaults/connectedPeerCountAssertion.ts | 2 +- .../assertions/defaults/finalizedAssertion.ts | 2 +- .../assertions/defaults/headAssertion.ts | 4 +- .../defaults/missedBlocksAssertion.ts | 4 +- .../simulation/assertions/forkAssertion.ts | 2 +- .../lighthousePeerScoreAssertion.ts | 12 +- .../simulation/assertions/mergeAssertion.ts | 2 +- .../simulation/assertions/nodeAssertion.ts | 16 +- .../{cl_clients => beacon_clients}/index.ts | 69 ++--- .../simulation/beacon_clients/lighthouse.ts | 125 ++++++++++ .../simulation/beacon_clients/lodestar.ts | 115 +++++++++ .../utils/simulation/cl_clients/lighthouse.ts | 236 ------------------ .../utils/simulation/cl_clients/lodestar.ts | 185 -------------- .../{el_clients => execution_clients}/geth.ts | 8 +- .../index.ts | 49 ++-- .../{el_clients => execution_clients}/mock.ts | 6 +- .../nethermind.ts | 6 +- .../cli/test/utils/simulation/interfaces.ts | 176 ++++++++----- .../test/utils/simulation/utils/el_genesis.ts | 10 +- .../cli/test/utils/simulation/utils/keys.ts | 6 +- .../test/utils/simulation/utils/network.ts | 28 +-- .../cli/test/utils/simulation/utils/paths.ts | 162 +++++------- .../simulation/validator_clients/index.ts | 84 +++++++ .../validator_clients/lighthouse.ts | 104 ++++++++ .../simulation/validator_clients/lodestar.ts | 85 +++++++ 37 files changed, 953 insertions(+), 886 deletions(-) rename packages/cli/test/utils/simulation/{cl_clients => beacon_clients}/index.ts (52%) create mode 100644 packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts create mode 100644 packages/cli/test/utils/simulation/beacon_clients/lodestar.ts delete mode 100644 packages/cli/test/utils/simulation/cl_clients/lighthouse.ts delete mode 100644 packages/cli/test/utils/simulation/cl_clients/lodestar.ts rename packages/cli/test/utils/simulation/{el_clients => execution_clients}/geth.ts (94%) rename packages/cli/test/utils/simulation/{el_clients => execution_clients}/index.ts (54%) rename packages/cli/test/utils/simulation/{el_clients => execution_clients}/mock.ts (70%) rename packages/cli/test/utils/simulation/{el_clients => execution_clients}/nethermind.ts (94%) create mode 100644 packages/cli/test/utils/simulation/validator_clients/index.ts create mode 100644 packages/cli/test/utils/simulation/validator_clients/lighthouse.ts create mode 100644 packages/cli/test/utils/simulation/validator_clients/lodestar.ts diff --git a/packages/cli/test/scripts/e2e_test_env.ts b/packages/cli/test/scripts/e2e_test_env.ts index 87e7a9f2f7ca..ef0f8851403f 100644 --- a/packages/cli/test/scripts/e2e_test_env.ts +++ b/packages/cli/test/scripts/e2e_test_env.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; -import {CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {BeaconClient, ExecutionClient} from "../utils/simulation/interfaces.js"; import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; import {getEstimatedTTD, logFilesDir} from "../utils/simulation/utils/index.js"; import {connectAllNodes} from "../utils/simulation/utils/network.js"; @@ -35,8 +35,8 @@ const env = await SimulationEnvironment.initWithDefaults( }, }, [ - {id: "node-1", cl: CLClient.Lodestar, el: ELClient.Geth, keysCount: 32, mining: true}, - {id: "node-2", cl: CLClient.Lodestar, el: ELClient.Nethermind, keysCount: 32}, + {id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Geth, keysCount: 32, mining: true}, + {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32}, ] ); diff --git a/packages/cli/test/sim/backup_eth_provider.test.ts b/packages/cli/test/sim/backup_eth_provider.test.ts index dd04edcaee9e..efb7590ace7c 100644 --- a/packages/cli/test/sim/backup_eth_provider.test.ts +++ b/packages/cli/test/sim/backup_eth_provider.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import {activePreset} from "@lodestar/params"; import {nodeAssertion} from "../utils/simulation/assertions/nodeAssertion.js"; import {CLIQUE_SEALING_PERIOD, SIM_TESTS_SECONDS_PER_SLOT} from "../utils/simulation/constants.js"; -import {AssertionMatch, CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {AssertionMatch, BeaconClient, ExecutionClient} from "../utils/simulation/interfaces.js"; import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; import { getEstimatedTimeInSecForRun, @@ -49,7 +49,7 @@ const env = await SimulationEnvironment.initWithDefaults( TERMINAL_TOTAL_DIFFICULTY: ttd, }, }, - [{id: "node-1", cl: CLClient.Lodestar, el: ELClient.Geth, keysCount: 32, mining: true}] + [{id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Geth, keysCount: 32, mining: true}] ); env.tracker.register({ @@ -64,8 +64,8 @@ const node2 = await env.createNodePair({ id: "node-2", // As the Lodestar running on host and the geth running in docker container // we have to replace the IP with the local ip to connect to the geth - cl: {type: CLClient.Lodestar, options: {engineUrls: [replaceIpFromUrl(env.nodes[0].el.engineRpcUrl, "127.0.0.1")]}}, - el: ELClient.Geth, + beacon: {type: BeaconClient.Lodestar, options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}}, + execution: ExecutionClient.Geth, keysCount: 32, }); @@ -74,8 +74,8 @@ const node3 = await env.createNodePair({ id: "node-3", // As the Lodestar running on host and the geth running in docker container // we have to replace the IP with the local ip to connect to the geth - cl: {type: CLClient.Lodestar, options: {engineUrls: [replaceIpFromUrl(env.nodes[0].el.engineRpcUrl, "127.0.0.1")]}}, - el: ELClient.Geth, + beacon: {type: BeaconClient.Lodestar, options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}}, + execution: ExecutionClient.Geth, keysCount: 0, }); @@ -88,8 +88,8 @@ await connectAllNodes(env.nodes); await waitForSlot(env.clock.getLastSlotOfEpoch(1), env.nodes, {silent: true, env}); // Stop node2, node3 EL, so the only way they produce blocks is via node1 EL -await node2.el.job.stop(); -await node3.el.job.stop(); +await node2.execution.job.stop(); +await node3.execution.job.stop(); // node2 and node3 will successfully reach TTD if they can communicate to an EL on node1 await waitForSlot(env.clock.getLastSlotOfEpoch(bellatrixForkEpoch) + activePreset.SLOTS_PER_EPOCH / 2, env.nodes, { diff --git a/packages/cli/test/sim/deneb.test.ts b/packages/cli/test/sim/deneb.test.ts index b21a34f3d58d..04def4004902 100644 --- a/packages/cli/test/sim/deneb.test.ts +++ b/packages/cli/test/sim/deneb.test.ts @@ -5,7 +5,7 @@ import {toHex, toHexString} from "@lodestar/utils"; import {ApiError} from "@lodestar/api"; import {nodeAssertion} from "../utils/simulation/assertions/nodeAssertion.js"; import {CLIQUE_SEALING_PERIOD, SIM_TESTS_SECONDS_PER_SLOT} from "../utils/simulation/constants.js"; -import {AssertionMatch, CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {AssertionMatch, BeaconClient, ExecutionClient} from "../utils/simulation/interfaces.js"; import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; import {getEstimatedTimeInSecForRun, getEstimatedTTD, logFilesDir} from "../utils/simulation/utils/index.js"; import {connectAllNodes, connectNewNode, waitForNodeSync, waitForSlot} from "../utils/simulation/utils/network.js"; @@ -47,8 +47,8 @@ const env = await SimulationEnvironment.initWithDefaults( }, }, [ - {id: "node-1", cl: CLClient.Lodestar, el: ELClient.Mock, keysCount: 32}, - {id: "node-2", cl: CLClient.Lodestar, el: ELClient.Mock, keysCount: 32, remote: true}, + {id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Mock, keysCount: 32}, + {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Mock, keysCount: 32, remote: true}, ] ); @@ -71,36 +71,36 @@ await waitForSlot(env.clock.getLastSlotOfEpoch(bellatrixForkEpoch) + activePrese // Range Sync // ======================================================== -const headForRangeSync = await env.nodes[0].cl.api.beacon.getBlockHeader("head"); +const headForRangeSync = await env.nodes[0].beacon.api.beacon.getBlockHeader("head"); ApiError.assert(headForRangeSync); const rangeSync = await env.createNodePair({ id: "range-sync-node", - cl: CLClient.Lodestar, - el: ELClient.Geth, + beacon: BeaconClient.Lodestar, + execution: ExecutionClient.Geth, keysCount: 0, }); // Checkpoint sync involves Weak Subjectivity Checkpoint // ======================================================== -const res = await env.nodes[0].cl.api.beacon.getStateFinalityCheckpoints("head"); +const res = await env.nodes[0].beacon.api.beacon.getStateFinalityCheckpoints("head"); ApiError.assert(res); const headForCheckpointSync = res.response.data.finalized; const checkpointSync = await env.createNodePair({ id: "checkpoint-sync-node", - cl: { - type: CLClient.Lodestar, + beacon: { + type: BeaconClient.Lodestar, options: {clientOptions: {wssCheckpoint: `${toHex(headForCheckpointSync.root)}:${headForCheckpointSync.epoch}`}}, }, - el: ELClient.Geth, + execution: ExecutionClient.Geth, keysCount: 0, }); -await rangeSync.el.job.start(); -await rangeSync.cl.beaconJob.start(); +await rangeSync.execution.job.start(); +await rangeSync.beacon.job.start(); await connectNewNode(rangeSync, env.nodes); -await checkpointSync.el.job.start(); -await checkpointSync.cl.beaconJob.start(); +await checkpointSync.execution.job.start(); +await checkpointSync.beacon.job.start(); await connectNewNode(checkpointSync, env.nodes); await Promise.all([ @@ -114,9 +114,9 @@ await Promise.all([ }), ]); -await rangeSync.cl.beaconJob.stop(); -await rangeSync.el.job.stop(); -await checkpointSync.cl.beaconJob.stop(); -await checkpointSync.el.job.stop(); +await rangeSync.beacon.job.stop(); +await rangeSync.execution.job.stop(); +await checkpointSync.beacon.job.stop(); +await checkpointSync.execution.job.stop(); await env.stop(); diff --git a/packages/cli/test/sim/endpoints.test.ts b/packages/cli/test/sim/endpoints.test.ts index 955214863ed5..89d5428057f2 100644 --- a/packages/cli/test/sim/endpoints.test.ts +++ b/packages/cli/test/sim/endpoints.test.ts @@ -4,7 +4,7 @@ import {expect} from "chai"; import {toHexString} from "@chainsafe/ssz"; import {routes} from "@lodestar/api"; import {ApiError} from "@lodestar/api"; -import {CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {BeaconClient, ExecutionClient} from "../utils/simulation/interfaces.js"; import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; import {getEstimatedTimeInSecForRun, logFilesDir} from "../utils/simulation/utils/index.js"; import {waitForSlot} from "../utils/simulation/utils/network.js"; @@ -36,8 +36,8 @@ const env = await SimulationEnvironment.initWithDefaults( [ { id: "node-1", - cl: {type: CLClient.Lodestar, options: {clientOptions: {"sync.isSingleNode": true}}}, - el: ELClient.Geth, + beacon: {type: BeaconClient.Lodestar, options: {clientOptions: {"sync.isSingleNode": true}}}, + execution: ExecutionClient.Geth, keysCount: validatorCount, mining: true, }, @@ -45,7 +45,7 @@ const env = await SimulationEnvironment.initWithDefaults( ); await env.start({runTimeoutMs}); -const node = env.nodes[0].cl; +const node = env.nodes[0].beacon; await waitForSlot(2, env.nodes, {env, silent: true}); const res = await node.api.beacon.getStateValidators("head"); diff --git a/packages/cli/test/sim/multi_client.test.ts b/packages/cli/test/sim/multi_client.test.ts index 0f4f7332802b..d9004044a3f3 100644 --- a/packages/cli/test/sim/multi_client.test.ts +++ b/packages/cli/test/sim/multi_client.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; import {nodeAssertion} from "../utils/simulation/assertions/nodeAssertion.js"; import {CLIQUE_SEALING_PERIOD, SIM_TESTS_SECONDS_PER_SLOT} from "../utils/simulation/constants.js"; -import {AssertionMatch, CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {AssertionMatch, BeaconClient, ExecutionClient, ValidatorClient} from "../utils/simulation/interfaces.js"; import {getEstimatedTTD, getEstimatedTimeInSecForRun, logFilesDir} from "../utils/simulation/utils/index.js"; import {connectAllNodes, waitForSlot} from "../utils/simulation/utils/network.js"; @@ -48,17 +48,19 @@ const env = await SimulationEnvironment.initWithDefaults( [ { id: "node-1", - el: ELClient.Geth, + execution: ExecutionClient.Geth, keysCount: 32, mining: true, - cl: {beacon: CLClient.Lodestar, validator: CLClient.Lighthouse}, + beacon: BeaconClient.Lodestar, + validator: ValidatorClient.Lighthouse, }, { id: "node-2", - el: ELClient.Geth, + execution: ExecutionClient.Geth, keysCount: 32, remote: true, - cl: {beacon: CLClient.Lighthouse, validator: CLClient.Lodestar}, + beacon: BeaconClient.Lighthouse, + validator: ValidatorClient.Lodestar, }, ] ); diff --git a/packages/cli/test/sim/multi_fork.test.ts b/packages/cli/test/sim/multi_fork.test.ts index d0a58a75b36b..117ad42e53eb 100644 --- a/packages/cli/test/sim/multi_fork.test.ts +++ b/packages/cli/test/sim/multi_fork.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import {sleep, toHex, toHexString} from "@lodestar/utils"; import {ApiError} from "@lodestar/api"; import {CLIQUE_SEALING_PERIOD, SIM_TESTS_SECONDS_PER_SLOT} from "../utils/simulation/constants.js"; -import {AssertionMatch, CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {AssertionMatch, BeaconClient, ExecutionClient} from "../utils/simulation/interfaces.js"; import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; import {getEstimatedTimeInSecForRun, getEstimatedTTD, logFilesDir} from "../utils/simulation/utils/index.js"; import { @@ -56,10 +56,10 @@ const env = await SimulationEnvironment.initWithDefaults( }, }, [ - {id: "node-1", cl: CLClient.Lodestar, el: ELClient.Geth, keysCount: 32, mining: true}, - {id: "node-2", cl: CLClient.Lodestar, el: ELClient.Nethermind, keysCount: 32, remote: true}, - {id: "node-3", cl: CLClient.Lodestar, el: ELClient.Nethermind, keysCount: 32}, - {id: "node-4", cl: CLClient.Lighthouse, el: ELClient.Geth, keysCount: 32}, + {id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Geth, keysCount: 32, mining: true}, + {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32, remote: true}, + {id: "node-3", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32}, + {id: "node-4", beacon: BeaconClient.Lighthouse, execution: ExecutionClient.Geth, keysCount: 32}, ] ); @@ -98,36 +98,36 @@ await waitForSlot(env.clock.getLastSlotOfEpoch(lastForkEpoch + 1), env.nodes, { // Range Sync // ======================================================== -const headForRangeSync = await env.nodes[0].cl.api.beacon.getBlockHeader("head"); +const headForRangeSync = await env.nodes[0].beacon.api.beacon.getBlockHeader("head"); ApiError.assert(headForRangeSync); const rangeSync = await env.createNodePair({ id: "range-sync-node", - cl: CLClient.Lodestar, - el: ELClient.Geth, + beacon: BeaconClient.Lodestar, + execution: ExecutionClient.Geth, keysCount: 0, }); // Checkpoint sync involves Weak Subjectivity Checkpoint // ======================================================== -const res = await env.nodes[0].cl.api.beacon.getStateFinalityCheckpoints("head"); +const res = await env.nodes[0].beacon.api.beacon.getStateFinalityCheckpoints("head"); ApiError.assert(res); const headForCheckpointSync = res.response.data.finalized; const checkpointSync = await env.createNodePair({ id: "checkpoint-sync-node", - cl: { - type: CLClient.Lodestar, + beacon: { + type: BeaconClient.Lodestar, options: {clientOptions: {wssCheckpoint: `${toHex(headForCheckpointSync.root)}:${headForCheckpointSync.epoch}`}}, }, - el: ELClient.Geth, + execution: ExecutionClient.Geth, keysCount: 0, }); -await rangeSync.el.job.start(); -await rangeSync.cl.beaconJob.start(); +await rangeSync.execution.job.start(); +await rangeSync.beacon.job.start(); await connectNewNode(rangeSync, env.nodes); -await checkpointSync.el.job.start(); -await checkpointSync.cl.beaconJob.start(); +await checkpointSync.execution.job.start(); +await checkpointSync.beacon.job.start(); await connectNewNode(checkpointSync, env.nodes); await Promise.all([ @@ -141,25 +141,25 @@ await Promise.all([ }), ]); -await rangeSync.cl.beaconJob.stop(); -await rangeSync.el.job.stop(); -await checkpointSync.cl.beaconJob.stop(); -await checkpointSync.el.job.stop(); +await rangeSync.beacon.job.stop(); +await rangeSync.execution.job.stop(); +await checkpointSync.beacon.job.stop(); +await checkpointSync.execution.job.stop(); // Unknown block sync // ======================================================== const unknownBlockSync = await env.createNodePair({ id: "unknown-block-sync-node", - cl: { - type: CLClient.Lodestar, + beacon: { + type: BeaconClient.Lodestar, options: {clientOptions: {"network.allowPublishToZeroPeers": true, "sync.disableRangeSync": true}}, }, - el: ELClient.Geth, + execution: ExecutionClient.Geth, keysCount: 0, }); -await unknownBlockSync.el.job.start(); -await unknownBlockSync.cl.beaconJob.start(); -const headForUnknownBlockSync = await env.nodes[0].cl.api.beacon.getBlockV2("head"); +await unknownBlockSync.execution.job.start(); +await unknownBlockSync.beacon.job.start(); +const headForUnknownBlockSync = await env.nodes[0].beacon.api.beacon.getBlockV2("head"); ApiError.assert(headForUnknownBlockSync); await connectNewNode(unknownBlockSync, env.nodes); @@ -167,7 +167,7 @@ await connectNewNode(unknownBlockSync, env.nodes); await sleep(5000); try { - ApiError.assert(await unknownBlockSync.cl.api.beacon.publishBlock(headForUnknownBlockSync.response.data)); + ApiError.assert(await unknownBlockSync.beacon.api.beacon.publishBlock(headForUnknownBlockSync.response.data)); env.tracker.record({ message: "Publishing unknown block should fail", diff --git a/packages/cli/test/utils/simulation/README.md b/packages/cli/test/utils/simulation/README.md index 69653098fc26..4948a638067f 100644 --- a/packages/cli/test/utils/simulation/README.md +++ b/packages/cli/test/utils/simulation/README.md @@ -35,9 +35,10 @@ Based on the parameters passed to `SimulationEnvironment.initWithDefaults` the f ```bash # Here multi-fork is the simulation id +# The `client` suffixed `beacon_`, `validator_` or `execution_` /tmp/random-directory/multi-fork /node-1 - /cl-${client} + /${client} genesis.ssz jwtsecret.txt /validators @@ -49,16 +50,16 @@ Based on the parameters passed to `SimulationEnvironment.initWithDefaults` the f /keystores # Public key prefixed with 0x, EIP-2335 keystore file 0x18302981aadffccc123313.json - /el-${client} + /${client} genesis.json jwtsecret.txt # Here multi-fork is the simulation id $logsDir/multi-fork/ docker_runner.log - node-1-cl-${client}.log - node-1-cl-${client}-validator.log - node-1-el-${client}.log + node-1-${client}.log + node-1-${client}.log + node-1-${client}.log ``` ### Running a client in docker diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 88ce838c583c..b9dd24c10ce0 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -12,7 +12,8 @@ import {BeaconStateAllForks, interopSecretKey} from "@lodestar/state-transition" import {EpochClock, MS_IN_SEC} from "./EpochClock.js"; import {ExternalSignerServer} from "./ExternalSignerServer.js"; import {SimulationTracker} from "./SimulationTracker.js"; -import {createCLNode} from "./cl_clients/index.js"; +import {createBeaconNode} from "./beacon_clients/index.js"; +import {createValidatorNode, getValidatorForBeaconNode} from "./validator_clients/index.js"; import { CLIQUE_SEALING_PERIOD, MOCK_ETH1_GENESIS_HASH, @@ -20,20 +21,22 @@ import { SIM_ENV_NETWORK_ID, SIM_TESTS_SECONDS_PER_SLOT, } from "./constants.js"; -import {createELNode} from "./el_clients/index.js"; +import {createExecutionNode} from "./execution_clients/index.js"; import { - CLClient, - CLClientKeys, - ELClient, + BeaconClient, + ValidatorClientKeys, + ExecutionClient, IRunner, NodePair, - NodePairOptions, + NodePairDefinition, SimulationInitOptions, SimulationOptions, + ValidatorClient, + GeneratorOptions, } from "./interfaces.js"; import {Runner} from "./runner/index.js"; import {getEstimatedTTD, registerProcessHandler, replaceIpFromUrl} from "./utils/index.js"; -import {getCLNodePaths, getELNodePaths} from "./utils/paths.js"; +import {getNodePaths} from "./utils/paths.js"; interface StartOpts { runTimeoutMs: number; @@ -80,7 +83,7 @@ export class SimulationEnvironment { static async initWithDefaults( {chainConfig, logsDir, id}: SimulationInitOptions, - clients: NodePairOptions[] + clients: NodePairDefinition[] ): Promise { const secondsPerSlot = chainConfig.SECONDS_PER_SLOT ?? SIM_TESTS_SECONDS_PER_SLOT; const genesisTime = Math.floor(Date.now() / 1000); @@ -153,26 +156,29 @@ export class SimulationEnvironment { } await this.runner.start(); - await Promise.all(this.nodes.map((node) => node.el.job.start())); + await Promise.all(this.nodes.map((node) => node.execution.job.start())); await this.initGenesisState(); if (!this.genesisState) { throw new Error("The genesis state for CL clients is not defined."); } - await Promise.all(this.nodes.map((node) => node.cl.beaconJob?.start())); - await Promise.all(this.nodes.map((node) => node.cl.validatorJob?.start())); + await Promise.all(this.nodes.map((node) => node.beacon.job.start())); + await Promise.all(this.nodes.map((node) => node.validator?.job.start())); - if (this.nodes.some((node) => node.cl.keys.type === "remote")) { + if (this.nodes.some((node) => node.validator?.keys.type === "remote")) { console.log("Starting external signer..."); await this.externalSigner.start(); console.log("Started external signer"); for (const node of this.nodes) { - if (node.cl.keys.type === "remote") { - this.externalSigner.addKeys(node.cl.keys.secretKeys); - await node.cl.keyManager.importRemoteKeys( - node.cl.keys.secretKeys.map((sk) => ({pubkey: sk.toPublicKey().toHex(), url: this.externalSigner.url})) + if (node.validator?.keys.type === "remote") { + this.externalSigner.addKeys(node.validator?.keys.secretKeys); + await node.validator.keyManager.importRemoteKeys( + node.validator.keys.secretKeys.map((sk) => ({ + pubkey: sk.toPublicKey().toHex(), + url: this.externalSigner.url, + })) ); console.log(`Imported remote keys for node ${node.id}`); } @@ -195,9 +201,9 @@ export class SimulationEnvironment { process.removeAllListeners("SIGINT"); console.log(`Simulation environment "${this.options.id}" is stopping: ${message}`); await this.tracker.stop(); - await Promise.all(this.nodes.map((node) => node.cl.validatorJob?.stop())); - await Promise.all(this.nodes.map((node) => node.cl.beaconJob?.stop())); - await Promise.all(this.nodes.map((node) => node.el.job.stop())); + await Promise.all(this.nodes.map((node) => node.validator?.job.stop())); + await Promise.all(this.nodes.map((node) => node.beacon.job.stop())); + await Promise.all(this.nodes.map((node) => node.execution.job.stop())); await this.externalSigner.stop(); await this.runner.stop(); this.options.controller.abort(); @@ -210,14 +216,15 @@ export class SimulationEnvironment { } } - async createNodePair({ - el, - cl, + async createNodePair({ + execution, + beacon, + validator, keysCount, id, remote, mining, - }: NodePairOptions): Promise { + }: NodePairDefinition): Promise { if (this.genesisState && keysCount > 0) { throw new Error("Genesis state already initialized. Can not add more keys to it."); } @@ -226,83 +233,80 @@ export class SimulationEnvironment { }); this.keysCount += keysCount; - const keys: CLClientKeys = + const keys: ValidatorClientKeys = interopKeys.length > 0 && remote ? {type: "remote", secretKeys: interopKeys} : interopKeys.length > 0 ? {type: "local", secretKeys: interopKeys} : {type: "no-keys"}; - // Execution Node - const elType = typeof el === "object" ? el.type : el; - const elOptions = typeof el === "object" ? el.options : {}; - const elNode = await createELNode(elType, { - ...elOptions, + const commonOptions: GeneratorOptions = { id, - mining, nodeIndex: this.nodePairCount, forkConfig: this.forkConfig, runner: this.runner, - paths: getELNodePaths({ + address: "0.0.0.0", + genesisTime: this.options.elGenesisTime, + }; + + // Execution Node + const executionType = typeof execution === "object" ? execution.type : execution; + const executionOptions = typeof execution === "object" ? execution.options : {}; + const executionNode = await createExecutionNode(executionType, { + ...executionOptions, + ...commonOptions, + mining, + paths: getNodePaths({ root: this.options.rootDir, id, - client: elType, + client: executionType, logsDir: this.options.logsDir, }), - genesisTime: this.options.elGenesisTime, - clientOptions: elOptions.clientOptions, + clientOptions: executionOptions.clientOptions, }); // Beacon Node - const {beacon, beaconOptions, validator, validatorOptions} = - typeof cl === "object" - ? "type" in cl - ? {beacon: cl.type, validator: cl.type, beaconOptions: cl.options, validatorOptions: cl.options} - : cl - : {beacon: cl, validator: cl, beaconOptions: {}, validatorOptions: {}}; - + const beaconType = typeof beacon === "object" ? beacon.type : beacon; + const beaconOptions = typeof beacon === "object" ? beacon.options : {}; const engineUrls = [ // As lodestar is running on host machine, need to connect through local exposed ports - beacon === CLClient.Lodestar ? replaceIpFromUrl(elNode.engineRpcUrl, "127.0.0.1") : elNode.engineRpcUrl, + beaconType === BeaconClient.Lodestar + ? replaceIpFromUrl(executionNode.engineRpcUrl, "127.0.0.1") + : executionNode.engineRpcUrl, ...(beaconOptions?.engineUrls ?? []), ]; - - const commonOptions = { - id, - keys, - engineMock: typeof el === "string" ? el === ELClient.Mock : el.type === ELClient.Mock, + const beaconNode = await createBeaconNode(beaconType, { + ...beaconOptions, + ...commonOptions, engineUrls, - nodeIndex: this.nodePairCount, - config: this.forkConfig, - runner: this.runner, - genesisTime: this.options.elGenesisTime, - genesisState: this.genesisState, - }; + paths: getNodePaths({id, logsDir: this.options.logsDir, client: beaconType, root: this.options.rootDir}), + }); - const clNode = await createCLNode({ - beacon, - beaconOptions: { - ...beaconOptions, - ...commonOptions, - paths: getCLNodePaths({id, logsDir: this.options.logsDir, client: beacon, root: this.options.rootDir}), - }, - validator, - validatorOptions: { - ...validatorOptions, - ...commonOptions, - paths: getCLNodePaths({id, logsDir: this.options.logsDir, client: validator, root: this.options.rootDir}), - }, + // If no validator configuration is specified we will consider that beacon type is also same as validator type + const validatorType = + typeof validator === "object" + ? validator.type + : validator === undefined + ? getValidatorForBeaconNode(beaconType) + : validator; + const validatorOptions = typeof validator === "object" ? validator.options : {}; + const validatorNode = await createValidatorNode(validatorType, { + ...validatorOptions, + ...commonOptions, + keys, + beaconUrls: [beaconNode.url], + paths: getNodePaths({id, logsDir: this.options.logsDir, client: validatorType, root: this.options.rootDir}), }); this.nodePairCount += 1; - return {id, el: elNode, cl: clNode}; + return {id, execution: executionNode, beacon: beaconNode, validator: validatorNode}; } private async initGenesisState(): Promise { for (let i = 0; i < this.nodes.length; i++) { // Get genesis block hash - const el = this.nodes[i].el; + const el = this.nodes[i].execution; // If eth1 is mock then genesis hash would be empty const eth1Genesis = el.provider === null ? {hash: MOCK_ETH1_GENESIS_HASH} : await el.provider.getBlockByNumber(0); @@ -320,11 +324,11 @@ export class SimulationEnvironment { // Write the genesis state for all nodes for (const node of this.nodes) { - const {genesisFilePath} = getCLNodePaths({ + const {genesisFilePath} = getNodePaths({ root: this.options.rootDir, id: node.id, logsDir: this.options.logsDir, - client: node.cl.client, + client: node.beacon.client, }); await writeFile(genesisFilePath, this.genesisState.serialize()); } diff --git a/packages/cli/test/utils/simulation/SimulationTracker.ts b/packages/cli/test/utils/simulation/SimulationTracker.ts index f353a7b4949f..b1da9a899008 100644 --- a/packages/cli/test/utils/simulation/SimulationTracker.ts +++ b/packages/cli/test/utils/simulation/SimulationTracker.ts @@ -145,7 +145,7 @@ export class SimulationTracker { } track(node: NodePair): void { - debug("track", node.cl.id); + debug("track", node.beacon.id); this.initDataForNode(node); this.initEventStreamForNode(node); this.nodes.push(node); @@ -184,7 +184,7 @@ export class SimulationTracker { } onSlot(slot: Slot, node: NodePair, cb: (slot: Slot) => void): void { - this.emitter.once(`${node.cl.id}:slot:${slot}`, cb); + this.emitter.once(`${node.beacon.id}:slot:${slot}`, cb); } register(assertion: SimulationAssertion): void { @@ -203,7 +203,7 @@ export class SimulationTracker { this.stores[assertion.id] = {}; for (const node of this.nodes) { - this.stores[assertion.id][node.cl.id] = {}; + this.stores[assertion.id][node.beacon.id] = {}; } } @@ -228,9 +228,9 @@ export class SimulationTracker { } private initDataForNode(node: NodePair): void { - this.lastSeenSlot.set(node.cl.id, 0); + this.lastSeenSlot.set(node.beacon.id, 0); for (const assertion of this.assertions) { - this.stores[assertion.id][node.cl.id] = {}; + this.stores[assertion.id][node.beacon.id] = {}; } } @@ -240,11 +240,11 @@ export class SimulationTracker { ): Promise { const slot = event.slot; const epoch = this.clock.getEpochForSlot(slot); - const lastSeenSlot = this.lastSeenSlot.get(node.cl.id); - debug(`processing block node=${node.cl.id} slot=${slot} lastSeenSlot=${lastSeenSlot}`); + const lastSeenSlot = this.lastSeenSlot.get(node.beacon.id); + debug(`processing block node=${node.beacon.id} slot=${slot} lastSeenSlot=${lastSeenSlot}`); if (lastSeenSlot !== undefined && slot > lastSeenSlot) { - this.lastSeenSlot.set(node.cl.id, slot); + this.lastSeenSlot.set(node.beacon.id, slot); } else { // We don't need to process old blocks return; @@ -271,14 +271,14 @@ export class SimulationTracker { } private async processCapture({slot, epoch}: {slot: Slot; epoch: Epoch}, node: NodePair): Promise { - debug(`processing capture node=${node.cl.id} slot=${slot}`); + debug(`processing capture node=${node.beacon.id} slot=${slot}`); // It is observed that sometimes block is received on the node event stream // But the http-api does not respond with the block // This is a workaround to fetch the block with retries const block = await fetchBlock(node, {slot, tries: 2, delay: 250, signal: this.signal}); if (!block) { - debug(`block could not be found node=${node.cl.id} slot=${slot}`); + debug(`block could not be found node=${node.beacon.id} slot=${slot}`); // Incase of reorg the block may not be available return; } @@ -311,12 +311,12 @@ export class SimulationTracker { }); if (!isNullish(value)) { - this.stores[assertion.id][node.cl.id][slot] = value; + this.stores[assertion.id][node.beacon.id][slot] = value; } } const capturedSlot = this.slotCapture.get(slot) ?? []; - capturedSlot.push(node.cl.id); + capturedSlot.push(node.beacon.id); this.slotCapture.set(slot, capturedSlot); } @@ -351,20 +351,20 @@ export class SimulationTracker { nodes: this.nodes, clock: this.clock, forkConfig: this.forkConfig, - store: this.stores[assertion.id][node.cl.id], + store: this.stores[assertion.id][node.beacon.id], dependantStores: getStoresForAssertions(this.stores, [assertion, ...(assertion.dependencies ?? [])]), }); for (const err of errors) { const message = typeof err === "string" ? err : err[0]; const data = typeof err === "string" ? {} : {...err[1]}; - this.errors.push({slot, epoch, assertionId: assertion.id, nodeId: node.cl.id, message, data}); + this.errors.push({slot, epoch, assertionId: assertion.id, nodeId: node.beacon.id, message, data}); } } catch (err: unknown) { this.errors.push({ slot, epoch, - nodeId: node.cl.id, + nodeId: node.beacon.id, assertionId: assertion.id, message: (err as Error).message, }); @@ -390,11 +390,11 @@ export class SimulationTracker { ], signal?: AbortSignal ): void { - debug("event stream initialized for", node.cl.id); - void node.cl.api.events.eventstream(events, signal ?? this.signal, async (event) => { + debug("event stream initialized for", node.beacon.id); + void node.beacon.api.events.eventstream(events, signal ?? this.signal, async (event) => { switch (event.type) { case routes.events.EventType.block: - debug(`block received node=${node.cl.id} slot=${event.message.slot}`); + debug(`block received node=${node.beacon.id} slot=${event.message.slot}`); await this.processOnBlock(event.message, node); return; case routes.events.EventType.head: diff --git a/packages/cli/test/utils/simulation/TableReporter.ts b/packages/cli/test/utils/simulation/TableReporter.ts index 7d7599a7f43e..93d1834768bd 100644 --- a/packages/cli/test/utils/simulation/TableReporter.ts +++ b/packages/cli/test/utils/simulation/TableReporter.ts @@ -56,10 +56,10 @@ export class TableReporter extends SimulationReporter const participation: {head: number; source: number; target: number}[] = []; for (const node of nodes) { - participation.push(stores["attestationParticipation"][node.cl.id][slot] ?? {head: 0, source: 0, target: 0}); + participation.push(stores["attestationParticipation"][node.beacon.id][slot] ?? {head: 0, source: 0, target: 0}); const syncCommitteeParticipation: number[] = []; for (let slot = startSlot; slot <= endSlot; slot++) { - syncCommitteeParticipation.push(stores["syncCommitteeParticipation"][node.cl.id][slot] ?? 0); + syncCommitteeParticipation.push(stores["syncCommitteeParticipation"][node.beacon.id][slot] ?? 0); } nodesSyncParticipationAvg.push(avg(syncCommitteeParticipation)); } @@ -83,19 +83,19 @@ export class TableReporter extends SimulationReporter const peersCount: number[] = []; for (const node of nodes) { - const finalized = stores["finalized"][node.cl.id][slot]; + const finalized = stores["finalized"][node.beacon.id][slot]; !isNullish(finalized) && finalizedSlots.push(finalized); - const inclusionDelay = stores["inclusionDelay"][node.cl.id][slot]; + const inclusionDelay = stores["inclusionDelay"][node.beacon.id][slot]; !isNullish(inclusionDelay) && inclusionDelays.push(inclusionDelay); - const attestationsCount = stores["attestationsCount"][node.cl.id][slot]; + const attestationsCount = stores["attestationsCount"][node.beacon.id][slot]; !isNullish(attestationsCount) && attestationCounts.push(attestationsCount); - const head = stores["head"][node.cl.id][slot]; + const head = stores["head"][node.beacon.id][slot]; !isNullish(head) && heads.push(head); - const connectedPeerCount = stores["connectedPeerCount"][node.cl.id][slot]; + const connectedPeerCount = stores["connectedPeerCount"][node.beacon.id][slot]; !isNullish(connectedPeerCount) && peersCount.push(connectedPeerCount); } diff --git a/packages/cli/test/utils/simulation/assertions/defaults/attestationCountAssertion.ts b/packages/cli/test/utils/simulation/assertions/defaults/attestationCountAssertion.ts index 96ca099cd644..ed9f9bfc64e4 100644 --- a/packages/cli/test/utils/simulation/assertions/defaults/attestationCountAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/defaults/attestationCountAssertion.ts @@ -45,7 +45,7 @@ export const attestationsCountAssertion: SimulationAssertion< const attestationsCount = store[slot] ?? 0; // Inclusion delay for future slot - const nextSlotInclusionDelay = inclusionDelayStore[node.cl.id][slot + 1] ?? 0; + const nextSlotInclusionDelay = inclusionDelayStore[node.beacon.id][slot + 1] ?? 0; // If some attestations are not included, probably will be included in next slot. // In that case next slot inclusion delay will be higher than expected. diff --git a/packages/cli/test/utils/simulation/assertions/defaults/attestationParticipationAssertion.ts b/packages/cli/test/utils/simulation/assertions/defaults/attestationParticipationAssertion.ts index c229a0ef7436..38403f7dc69a 100644 --- a/packages/cli/test/utils/simulation/assertions/defaults/attestationParticipationAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/defaults/attestationParticipationAssertion.ts @@ -26,7 +26,7 @@ export const attestationParticipationAssertion: SimulationAssertion< }, async capture({node, epoch}) { - const res = await node.cl.api.debug.getStateV2("head"); + const res = await node.beacon.api.debug.getStateV2("head"); ApiError.assert(res); const state = res.response.data as altair.BeaconState; diff --git a/packages/cli/test/utils/simulation/assertions/defaults/connectedPeerCountAssertion.ts b/packages/cli/test/utils/simulation/assertions/defaults/connectedPeerCountAssertion.ts index 1078d5b47d95..8669938c5254 100644 --- a/packages/cli/test/utils/simulation/assertions/defaults/connectedPeerCountAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/defaults/connectedPeerCountAssertion.ts @@ -6,7 +6,7 @@ export const connectedPeerCountAssertion: SimulationAssertion<"connectedPeerCoun id: "connectedPeerCount", match: everySlotMatcher, async capture({node}) { - const res = await node.cl.api.node.getPeerCount(); + const res = await node.beacon.api.node.getPeerCount(); ApiError.assert(res); return res.response.data.connected; }, diff --git a/packages/cli/test/utils/simulation/assertions/defaults/finalizedAssertion.ts b/packages/cli/test/utils/simulation/assertions/defaults/finalizedAssertion.ts index 4f486ebf056d..4d07e06193d1 100644 --- a/packages/cli/test/utils/simulation/assertions/defaults/finalizedAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/defaults/finalizedAssertion.ts @@ -7,7 +7,7 @@ export const finalizedAssertion: SimulationAssertion<"finalized", Slot> = { id: "finalized", match: everySlotMatcher, async capture({node}) { - const finalized = await node.cl.api.beacon.getBlockHeader("finalized"); + const finalized = await node.beacon.api.beacon.getBlockHeader("finalized"); ApiError.assert(finalized); return finalized.response.data.header.message.slot ?? 0; }, diff --git a/packages/cli/test/utils/simulation/assertions/defaults/headAssertion.ts b/packages/cli/test/utils/simulation/assertions/defaults/headAssertion.ts index 8e483198ec1d..74021abfc310 100644 --- a/packages/cli/test/utils/simulation/assertions/defaults/headAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/defaults/headAssertion.ts @@ -13,7 +13,7 @@ export const headAssertion: SimulationAssertion<"head", HeadSummary> = { id: "head", match: everySlotMatcher, async capture({node}) { - const head = await node.cl.api.beacon.getBlockHeader("head"); + const head = await node.beacon.api.beacon.getBlockHeader("head"); ApiError.assert(head); return { @@ -27,7 +27,7 @@ export const headAssertion: SimulationAssertion<"head", HeadSummary> = { // For first node we don't need to match the head if (node.id === nodes[0].id) return errors; - const headRootNode0 = dependantStores["head" as const][nodes[0].cl.id][slot].blockRoot; + const headRootNode0 = dependantStores["head" as const][nodes[0].beacon.id][slot].blockRoot; const headRootNode = store[slot].blockRoot; diff --git a/packages/cli/test/utils/simulation/assertions/defaults/missedBlocksAssertion.ts b/packages/cli/test/utils/simulation/assertions/defaults/missedBlocksAssertion.ts index db3ca5c26c55..299280a37c7d 100644 --- a/packages/cli/test/utils/simulation/assertions/defaults/missedBlocksAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/defaults/missedBlocksAssertion.ts @@ -17,7 +17,7 @@ export const missedBlocksAssertion: SimulationAssertion<"missedBlocks", number[] for (let slot = startSlot; slot < endSlot; slot++) { // If some value of head is present for that slot then it was not missed - if (isNullish(dependantStores[headAssertion.id][node.cl.id][slot])) { + if (isNullish(dependantStores[headAssertion.id][node.beacon.id][slot])) { missedBlocks.push(slot); } } @@ -31,7 +31,7 @@ export const missedBlocksAssertion: SimulationAssertion<"missedBlocks", number[] // For first node we don't need to match if (node.id === nodes[0].id) return errors; - const missedBlocksOnFirstNode = dependantStores["missedBlocks" as const][nodes[0].cl.id][slot]; + const missedBlocksOnFirstNode = dependantStores["missedBlocks" as const][nodes[0].beacon.id][slot]; const missedBlocks = store[slot]; diff --git a/packages/cli/test/utils/simulation/assertions/forkAssertion.ts b/packages/cli/test/utils/simulation/assertions/forkAssertion.ts index a1f99275f5ea..c78a617efce0 100644 --- a/packages/cli/test/utils/simulation/assertions/forkAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/forkAssertion.ts @@ -15,7 +15,7 @@ export function createForkAssertion(fork: ForkName, epoch: Epoch): SimulationAss assert: async ({node, slot, forkConfig}) => { const errors: AssertionResult[] = []; - const res = await node.cl.api.debug.getStateV2("head"); + const res = await node.beacon.api.debug.getStateV2("head"); ApiError.assert(res); const expectedForkVersion = toHexString(forkConfig.getForkInfo(slot).version); const currentForkVersion = toHexString(res.response.data.fork.currentVersion); diff --git a/packages/cli/test/utils/simulation/assertions/lighthousePeerScoreAssertion.ts b/packages/cli/test/utils/simulation/assertions/lighthousePeerScoreAssertion.ts index 3a99f2b42749..d7b5e4d6c087 100644 --- a/packages/cli/test/utils/simulation/assertions/lighthousePeerScoreAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/lighthousePeerScoreAssertion.ts @@ -1,5 +1,5 @@ import {ApiError} from "@lodestar/api"; -import {AssertionResult, CLClient, LighthouseAPI, NodePair, SimulationAssertion} from "../interfaces.js"; +import {AssertionResult, BeaconClient, LighthouseAPI, NodePair, SimulationAssertion} from "../interfaces.js"; import {neverMatcher} from "./matchers.js"; const MIN_GOSSIPSUB_SCORE = 10; @@ -13,7 +13,7 @@ export const lighthousePeerScoreAssertion: SimulationAssertion<"lighthousePeerSc // We want to run this only once, not every node if (node.id !== nodes[0].id) return []; - const lighthousePeer = nodes.find((n) => n.cl.client === CLClient.Lighthouse); + const lighthousePeer = nodes.find((n) => n.beacon.client === BeaconClient.Lighthouse); if (peersIdMapCache === undefined) { peersIdMapCache = await getLodestarPeerIds(nodes); } @@ -21,7 +21,7 @@ export const lighthousePeerScoreAssertion: SimulationAssertion<"lighthousePeerSc const errors: AssertionResult[] = []; try { - const peerScores = await (lighthousePeer?.cl.api as LighthouseAPI).lighthouse.getPeers(); + const peerScores = await (lighthousePeer?.beacon.api as LighthouseAPI).lighthouse.getPeers(); for (const peerScore of peerScores.body) { const { peer_id, @@ -47,13 +47,13 @@ export const lighthousePeerScoreAssertion: SimulationAssertion<"lighthousePeerSc }; async function getLodestarPeerIds(nodes: NodePair[]): Promise> { - const lodestartPeers = nodes.filter((n) => n.cl.client === CLClient.Lodestar); + const lodestartPeers = nodes.filter((n) => n.beacon.client === BeaconClient.Lodestar); const peerIdMap: Record = {}; for (const p of lodestartPeers) { - const res = await p.cl.api.node.getNetworkIdentity(); + const res = await p.beacon.api.node.getNetworkIdentity(); ApiError.assert(res); - peerIdMap[res.response.data.peerId] = p.cl.id; + peerIdMap[res.response.data.peerId] = p.beacon.id; } return peerIdMap; diff --git a/packages/cli/test/utils/simulation/assertions/mergeAssertion.ts b/packages/cli/test/utils/simulation/assertions/mergeAssertion.ts index 5c2e51fd2489..a7b522504dfc 100644 --- a/packages/cli/test/utils/simulation/assertions/mergeAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/mergeAssertion.ts @@ -10,7 +10,7 @@ export const mergeAssertion: SimulationAssertion<"merge", string> = { async assert({node}) { const errors: AssertionResult[] = []; - const res = await node.cl.api.debug.getStateV2("head"); + const res = await node.beacon.api.debug.getStateV2("head"); ApiError.assert(res); const state = res.response.data as unknown as BeaconStateAllForks; diff --git a/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts b/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts index 91ddfca9388d..7c6f030be781 100644 --- a/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts @@ -1,7 +1,7 @@ import type {SecretKey} from "@chainsafe/bls/types"; import {routes} from "@lodestar/api/beacon"; import {ApiError} from "@lodestar/api"; -import {AssertionResult, CLClient, CLClientKeys, SimulationAssertion} from "../interfaces.js"; +import {AssertionResult, BeaconClient, ValidatorClientKeys, SimulationAssertion} from "../interfaces.js"; import {arrayEquals} from "../utils/index.js"; import {neverMatcher} from "./matchers.js"; @@ -10,14 +10,14 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana // Include into particular test with custom condition match: neverMatcher, capture: async ({node}) => { - const {status: health} = await node.cl.api.node.getHealth(); + const {status: health} = await node.beacon.api.node.getHealth(); let keyManagerKeys: string[]; // There is an authentication issue with the lighthouse keymanager client - if (node.cl.client == CLClient.Lighthouse || getAllKeys(node.cl.keys).length === 0) { + if (node.beacon.client == BeaconClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { keyManagerKeys = []; } else { - const res = await node.cl.keyManager.listKeys(); + const res = await node.validator?.keyManager.listKeys(); ApiError.assert(res); keyManagerKeys = res.response.data.map((k) => k.validatingPubkey); } @@ -28,15 +28,15 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana const errors: AssertionResult[] = []; // There is an authentication issue with the lighthouse keymanager client - if (node.cl.client == CLClient.Lighthouse) return errors; + if (node.beacon.client == BeaconClient.Lighthouse) return errors; const {health, keyManagerKeys} = store[slot]; if (health !== routes.node.NodeHealth.SYNCING && health !== routes.node.NodeHealth.READY) { - errors.push(["node health is neither READY or SYNCING", {node: node.cl.id}]); + errors.push(["node health is neither READY or SYNCING", {node: node.beacon.id}]); } - const expectedPublicKeys = getAllKeys(node.cl.keys).map((k) => k.toPublicKey().toHex()); + const expectedPublicKeys = getAllKeys(node.validator?.keys).map((k) => k.toPublicKey().toHex()); if (!arrayEquals(keyManagerKeys.sort(), expectedPublicKeys.sort())) { errors.push([ @@ -52,7 +52,7 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana }, }; -function getAllKeys(keys: CLClientKeys): SecretKey[] { +function getAllKeys(keys: ValidatorClientKeys): SecretKey[] { switch (keys.type) { case "local": return keys.secretKeys; diff --git a/packages/cli/test/utils/simulation/cl_clients/index.ts b/packages/cli/test/utils/simulation/beacon_clients/index.ts similarity index 52% rename from packages/cli/test/utils/simulation/cl_clients/index.ts rename to packages/cli/test/utils/simulation/beacon_clients/index.ts index 18a5428f7da7..606fc97bc46b 100644 --- a/packages/cli/test/utils/simulation/cl_clients/index.ts +++ b/packages/cli/test/utils/simulation/beacon_clients/index.ts @@ -1,61 +1,32 @@ import {writeFile} from "node:fs/promises"; import {BeaconStateAllForks} from "@lodestar/state-transition"; -import {SHARED_JWT_SECRET, SHARED_VALIDATOR_PASSWORD, EL_ENGINE_BASE_PORT} from "../constants.js"; -import {AtLeast, CLClient, CLClientGeneratorOptions, CLNode, IRunner} from "../interfaces.js"; +import {EL_ENGINE_BASE_PORT, SHARED_JWT_SECRET} from "../constants.js"; +import {AtLeast, BeaconClient, BeaconGeneratorOptions, BeaconNode} from "../interfaces.js"; import {makeUniqueArray} from "../utils/index.js"; -import {createKeystores} from "../utils/keys.js"; -import {createCLNodePaths} from "../utils/paths.js"; +import {ensureDirectories} from "../utils/paths.js"; import {generateLighthouseBeaconNode} from "./lighthouse.js"; import {generateLodestarBeaconNode} from "./lodestar.js"; -type GeneratorRequiredOptions = AtLeast< - CLClientGeneratorOptions, - "id" | "paths" | "config" | "nodeIndex" | "genesisTime" -> & { - genesisState?: BeaconStateAllForks; - runner: IRunner; -}; - -export async function createCLNode({ - beacon, - beaconOptions, - validator, - validatorOptions, -}: { - beacon: B; - beaconOptions: GeneratorRequiredOptions; - validator: V; - validatorOptions: GeneratorRequiredOptions; -}): Promise { - const beaconNode = await createCLNodeComponent(beacon, beaconOptions, "beacon"); - const validatorNode = await createCLNodeComponent(validator, validatorOptions, "validator"); - - return { - ...beaconNode, - beaconJob: beaconNode.beaconJob, - validatorJob: validatorNode.validatorJob, - }; -} - -async function createCLNodeComponent( - client: C, - options: GeneratorRequiredOptions, - component: "beacon" | "validator" -): Promise { - const {runner, config, genesisState} = options; - const clId = `${options.id}-cl-${client}-${component}`; +export async function createBeaconNode( + client: B, + options: AtLeast< + BeaconGeneratorOptions, + "id" | "paths" | "forkConfig" | "nodeIndex" | "genesisTime" | "runner" + > & { + genesisState?: BeaconStateAllForks; + } +): Promise { + const {runner, forkConfig: config, genesisState} = options; + const clId = `${options.id}-${client}`; - const opts: CLClientGeneratorOptions = { + const opts: BeaconGeneratorOptions = { ...options, id: clId, - keys: options.keys ?? {type: "no-keys"}, genesisTime: options.genesisTime + config.GENESIS_DELAY, engineMock: options.engineMock ?? false, clientOptions: options.clientOptions ?? {}, address: "127.0.0.1", engineUrls: options.engineUrls ?? [], - beacon: component === "beacon", - validator: component === "validator", }; const metricServer = process.env.SIM_METRIC_SERVER_URL; @@ -67,10 +38,8 @@ async function createCLNodeComponent( }; } - await createCLNodePaths(opts.paths); - await createKeystores(opts.paths, opts.keys); + await ensureDirectories(opts.paths); await writeFile(opts.paths.jwtsecretFilePath, SHARED_JWT_SECRET); - await writeFile(opts.paths.keystoresSecretFilePath, SHARED_VALIDATOR_PASSWORD); // We have to wite the genesis state but can't do that without starting up // at least one EL node and getting ETH_HASH, so will do in startup @@ -81,7 +50,7 @@ async function createCLNodeComponent( } switch (client) { - case CLClient.Lodestar: { + case BeaconClient.Lodestar: { return generateLodestarBeaconNode( { ...opts, @@ -94,7 +63,7 @@ async function createCLNodeComponent( runner ); } - case CLClient.Lighthouse: { + case BeaconClient.Lighthouse: { return generateLighthouseBeaconNode( { ...opts, @@ -108,6 +77,6 @@ async function createCLNodeComponent( ); } default: - throw new Error(`CL Client "${client}" not supported`); + throw new Error(`Beacon Client "${client}" not supported`); } } diff --git a/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts b/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts new file mode 100644 index 000000000000..048c3794cd92 --- /dev/null +++ b/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts @@ -0,0 +1,125 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {writeFile} from "node:fs/promises"; +import path from "node:path"; +import got, {RequestError} from "got"; +import yaml from "js-yaml"; +import {HttpClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api/beacon"; +import {chainConfigToJson} from "@lodestar/config"; +import {BeaconClient, BeaconNodeGenerator, LighthouseAPI, RunnerType} from "../interfaces.js"; +import {getNodeMountedPaths} from "../utils/paths.js"; +import {getNodePorts} from "../utils/ports.js"; + +export const generateLighthouseBeaconNode: BeaconNodeGenerator = (opts, runner) => { + if (!process.env.LIGHTHOUSE_BINARY_PATH && !process.env.LIGHTHOUSE_DOCKER_IMAGE) { + throw new Error("LIGHTHOUSE_BINARY_PATH or LIGHTHOUSE_DOCKER_IMAGE must be provided"); + } + + const isDocker = process.env.LIGHTHOUSE_DOCKER_IMAGE !== undefined; + + const {address, id, forkConfig: config, nodeIndex, metrics} = opts; + const {engineUrls, engineMock, clientOptions} = opts; + const { + cl: {httpPort, port}, + } = getNodePorts(nodeIndex); + + const {rootDir, rootDirMounted, jwtsecretFilePathMounted, logFilePath} = getNodeMountedPaths( + opts.paths, + "/data", + isDocker + ); + + const cliParams: Record = { + "testnet-dir": rootDirMounted, + datadir: rootDirMounted, + http: null, + // Enable the RESTful HTTP API server. Disabled by default. + // Forces the HTTP to indicate that the node is synced when sync is actually + // stalled. This is useful for very small testnets. TESTING ONLY. DO NOT USE ON MAINNET. + "http-allow-sync-stalled": null, + "http-address": "0.0.0.0", + "http-port": httpPort, + "http-allow-origin": "*", + "listen-address": "0.0.0.0", + port: port, + "enr-address": address, + "enr-udp-port": port, + "enr-tcp-port": port, + "disable-discovery": null, + "enable-private-discovery": null, + "debug-level": "debug", + ...clientOptions, + }; + + if (engineMock) { + cliParams["dummy-eth1"] = null; + } else { + cliParams["execution-jwt"] = jwtsecretFilePathMounted; + cliParams["execution-endpoint"] = [...engineUrls].join(","); + } + + if (metrics) { + cliParams["metrics-allow-origin"] = "*"; + cliParams["metrics-port"] = metrics.port; + cliParams["metrics-address"] = metrics.host; + } + + const job = runner.create([ + { + id, + type: isDocker ? RunnerType.Docker : RunnerType.ChildProcess, + options: isDocker + ? { + image: process.env.LIGHTHOUSE_DOCKER_IMAGE as string, + mounts: [[rootDir, rootDirMounted]], + exposePorts: [httpPort, port], + dockerNetworkIp: address, + } + : undefined, + bootstrap: async () => { + await writeFile(path.join(rootDir, "config.yaml"), yaml.dump(chainConfigToJson(config))); + await writeFile(path.join(rootDir, "deploy_block.txt"), "0"); + }, + cli: { + command: isDocker ? "lighthouse" : (process.env.LIGHTHOUSE_BINARY_PATH as string), + args: [ + "beacon_node", + ...Object.entries(cliParams).flatMap(([key, value]) => + value === null ? [`--${key}`] : [`--${key}`, String(value)] + ), + ], + env: {}, + }, + logs: { + stdoutFilePath: logFilePath, + }, + health: async () => { + try { + await got.get(`http://127.0.0.1:${httpPort}/eth/v1/node/health`); + return {ok: true}; + } catch (err) { + if (err instanceof RequestError && err.code !== "ECONNREFUSED") { + return {ok: true}; + } + return {ok: false, reason: (err as Error).message, checkId: "/eth/v1/node/health query"}; + } + }, + }, + ]); + + const httpClient = new HttpClient({baseUrl: `http://127.0.0.1:${httpPort}`}); + const api = getClient({baseUrl: `http://127.0.0.1:${httpPort}`}, {config}) as unknown as LighthouseAPI; + api.lighthouse = { + async getPeers() { + return httpClient.json({url: "/lighthouse/peers", method: "GET"}); + }, + }; + + return { + id, + client: BeaconClient.Lighthouse, + url: `http://127.0.0.1:${httpPort}`, + api, + job, + }; +}; diff --git a/packages/cli/test/utils/simulation/beacon_clients/lodestar.ts b/packages/cli/test/utils/simulation/beacon_clients/lodestar.ts new file mode 100644 index 000000000000..30d731b605c5 --- /dev/null +++ b/packages/cli/test/utils/simulation/beacon_clients/lodestar.ts @@ -0,0 +1,115 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {writeFile} from "node:fs/promises"; +import path from "node:path"; +import got from "got"; +import {getClient} from "@lodestar/api/beacon"; +import {chainConfigToJson} from "@lodestar/config"; +import {LogLevel} from "@lodestar/utils"; +import {BeaconArgs} from "../../../../src/cmds/beacon/options.js"; +import {GlobalArgs} from "../../../../src/options/globalOptions.js"; +import {LODESTAR_BINARY_PATH} from "../constants.js"; +import {BeaconClient, BeaconNodeGenerator, RunnerType} from "../interfaces.js"; +import {getNodePorts} from "../utils/ports.js"; + +export const generateLodestarBeaconNode: BeaconNodeGenerator = (opts, runner) => { + const { + address, + id, + forkConfig: config, + genesisTime, + engineUrls, + engineMock, + clientOptions, + nodeIndex, + metrics, + } = opts; + const { + paths: {jwtsecretFilePath, rootDir, genesisFilePath, logFilePath}, + } = opts; + const ports = getNodePorts(nodeIndex); + + const rcConfigPath = path.join(rootDir, "rc_config.json"); + const paramsPath = path.join(rootDir, "params.json"); + + const rcConfig = { + network: "dev", + preset: "minimal", + dataDir: rootDir, + genesisStateFile: genesisFilePath, + rest: true, + "rest.address": "0.0.0.0", + "rest.port": ports.cl.httpPort, + "rest.namespace": "*", + "sync.isSingleNode": false, + "network.allowPublishToZeroPeers": false, + discv5: true, + "network.connectToDiscv5Bootnodes": true, + "network.rateLimitMultiplier": 0, + listenAddress: "0.0.0.0", + port: ports.cl.port, + bootnodes: [], + logPrefix: id, + logFormatGenesisTime: `${genesisTime}`, + logLevel: LogLevel.debug, + logFileDailyRotate: 0, + logFile: "none", + "jwt-secret": jwtsecretFilePath, + paramsFile: paramsPath, + ...clientOptions, + } as unknown as BeaconArgs & GlobalArgs; + + if (engineMock) { + rcConfig["eth1"] = false; + rcConfig["execution.engineMock"] = true; + rcConfig["execution.urls"] = []; + } else { + rcConfig["eth1"] = true; + rcConfig["execution.engineMock"] = false; + rcConfig["execution.urls"] = [...engineUrls]; + } + + if (metrics) { + rcConfig.metrics = true; + rcConfig["metrics.port"] = metrics.port; + rcConfig["metrics.address"] = metrics.host; + } else { + rcConfig.metrics = false; + } + + const job = runner.create([ + { + id, + bootstrap: async () => { + await writeFile(rcConfigPath, JSON.stringify(rcConfig, null, 2)); + await writeFile(paramsPath, JSON.stringify(chainConfigToJson(config), null, 2)); + }, + type: RunnerType.ChildProcess, + cli: { + command: LODESTAR_BINARY_PATH, + args: ["beacon", "--rcConfig", rcConfigPath, "--paramsFile", paramsPath], + env: { + DEBUG: process.env.DISABLE_DEBUG_LOGS ? "" : "*,-winston:*", + }, + }, + logs: { + stdoutFilePath: logFilePath, + }, + health: async () => { + try { + await got.get(`http://${address}:${ports.cl.httpPort}/eth/v1/node/health`); + return {ok: true}; + } catch (err) { + return {ok: false, reason: (err as Error).message, checkId: "eth/v1/node/health query"}; + } + }, + }, + ]); + + return { + id, + client: BeaconClient.Lodestar, + url: `http://127.0.0.1:${ports.cl.httpPort}`, + api: getClient({baseUrl: `http://127.0.0.1:${ports.cl.httpPort}`}, {config}), + job, + }; +}; diff --git a/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts b/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts deleted file mode 100644 index 56b0e7c2ae12..000000000000 --- a/packages/cli/test/utils/simulation/cl_clients/lighthouse.ts +++ /dev/null @@ -1,236 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {writeFile} from "node:fs/promises"; -import path from "node:path"; -import got, {RequestError} from "got"; -import yaml from "js-yaml"; -import {HttpClient} from "@lodestar/api"; -import {getClient} from "@lodestar/api/beacon"; -import {getClient as keyManagerGetClient} from "@lodestar/api/keymanager"; -import {chainConfigToJson} from "@lodestar/config"; -import { - CLClient, - CLClientGenerator, - CLClientGeneratorOptions, - IRunner, - JobOptions, - LighthouseAPI, - RunnerType, -} from "../interfaces.js"; -import {getNodePorts} from "../utils/ports.js"; -import {getNodeMountedPaths} from "../utils/paths.js"; -import {updateKeystoresPath} from "../utils/keys.js"; - -export const generateLighthouseBeaconNode: CLClientGenerator = (opts, runner) => { - if (!process.env.LIGHTHOUSE_BINARY_PATH && !process.env.LIGHTHOUSE_DOCKER_IMAGE) { - throw new Error("LIGHTHOUSE_BINARY_PATH or LIGHTHOUSE_DOCKER_IMAGE must be provided"); - } - - const isDocker = process.env.LIGHTHOUSE_DOCKER_IMAGE !== undefined; - - const {address, id, config, keys, nodeIndex, metrics} = opts; - const {engineUrls, engineMock, clientOptions} = opts; - const { - cl: {httpPort, port, keymanagerPort}, - } = getNodePorts(nodeIndex); - - const {rootDir, rootDirMounted, jwtsecretFilePathMounted, logFilePath} = getNodeMountedPaths( - opts.paths, - "/data", - isDocker - ); - - const cliParams: Record = { - "testnet-dir": rootDirMounted, - datadir: rootDirMounted, - http: null, - // Enable the RESTful HTTP API server. Disabled by default. - // Forces the HTTP to indicate that the node is synced when sync is actually - // stalled. This is useful for very small testnets. TESTING ONLY. DO NOT USE ON MAINNET. - "http-allow-sync-stalled": null, - "http-address": "0.0.0.0", - "http-port": httpPort, - "http-allow-origin": "*", - "listen-address": "0.0.0.0", - port: port, - "enr-address": address, - "enr-udp-port": port, - "enr-tcp-port": port, - "disable-discovery": null, - "enable-private-discovery": null, - "debug-level": "debug", - ...clientOptions, - }; - - if (engineMock) { - cliParams["dummy-eth1"] = null; - } else { - cliParams["execution-jwt"] = jwtsecretFilePathMounted; - cliParams["execution-endpoint"] = [...engineUrls].join(","); - } - - if (metrics) { - cliParams["metrics-allow-origin"] = "*"; - cliParams["metrics-port"] = metrics.port; - cliParams["metrics-address"] = metrics.host; - } - - const beaconNodeJob: JobOptions = { - id, - type: isDocker ? RunnerType.Docker : RunnerType.ChildProcess, - options: isDocker - ? { - image: process.env.LIGHTHOUSE_DOCKER_IMAGE as string, - mounts: [[rootDir, rootDirMounted]], - exposePorts: [httpPort, port], - dockerNetworkIp: address, - } - : undefined, - bootstrap: async () => { - await writeFile(path.join(rootDir, "config.yaml"), yaml.dump(chainConfigToJson(config))); - await writeFile(path.join(rootDir, "deploy_block.txt"), "0"); - }, - cli: { - command: isDocker ? "lighthouse" : (process.env.LIGHTHOUSE_BINARY_PATH as string), - args: [ - "beacon_node", - ...Object.entries(cliParams).flatMap(([key, value]) => - value === null ? [`--${key}`] : [`--${key}`, String(value)] - ), - ], - env: {}, - }, - logs: { - stdoutFilePath: logFilePath, - }, - health: async () => { - try { - await got.get(`http://127.0.0.1:${httpPort}/eth/v1/node/health`); - return {ok: true}; - } catch (err) { - if (err instanceof RequestError && err.code !== "ECONNREFUSED") { - return {ok: true}; - } - return {ok: false, reason: (err as Error).message, checkId: "/eth/v1/node/health query"}; - } - }, - }; - - const validatorClientsJobs: JobOptions[] = []; - if (keys.type !== "no-keys") { - validatorClientsJobs.push( - generateLighthouseValidatorJobs( - { - ...opts, - id: `${id}-validator`, - paths: { - ...opts.paths, - logFilePath: path.join(path.dirname(logFilePath), `${id}-validator.log`), - }, - }, - runner - ) - ); - } - - const beaconJob = runner.create([{...beaconNodeJob, children: [...validatorClientsJobs]}]); - const validatorJob = runner.create(validatorClientsJobs); - - const httpClient = new HttpClient({baseUrl: `http://127.0.0.1:${httpPort}`}); - const api = getClient({baseUrl: `http://127.0.0.1:${httpPort}`}, {config}) as unknown as LighthouseAPI; - api.lighthouse = { - async getPeers() { - return httpClient.json({url: "/lighthouse/peers", method: "GET"}); - }, - }; - - return { - id, - client: CLClient.Lighthouse, - url: `http://127.0.0.1:${httpPort}`, - keys, - api, - keyManager: keyManagerGetClient({baseUrl: `http://127.0.0.1:${keymanagerPort}`}, {config}), - beaconJob: opts.beacon ? beaconJob : undefined, - validatorJob: opts.validator ? validatorJob : undefined, - }; -}; - -export const generateLighthouseValidatorJobs = (opts: CLClientGeneratorOptions, runner: IRunner): JobOptions => { - const isDocker = process.env.LIGHTHOUSE_DOCKER_IMAGE !== undefined; - - const binaryPath = isDocker ? "lighthouse" : `${process.env.LIGHTHOUSE_BINARY_PATH}`; - const {id, keys, address} = opts; - const { - rootDir, - rootDirMounted, - logFilePath, - validatorsDirMounted, - validatorsDefinitionFilePath, - validatorsDefinitionFilePathMounted, - } = getNodeMountedPaths(opts.paths, "/data", isDocker); - const { - cl: {httpPort, keymanagerPort}, - } = getNodePorts(opts.nodeIndex); - - if (keys.type === "no-keys") { - throw Error("Attempting to run a vc with keys.type == 'no-keys'"); - } - - const params = { - "testnet-dir": rootDirMounted, - "beacon-nodes": `http://${address}:${httpPort}/`, - "debug-level": "debug", - "init-slashing-protection": null, - "allow-unsynced": null, - http: null, - "unencrypted-http-transport": null, - "http-address": "0.0.0.0", - "http-port": keymanagerPort, - "validators-dir": validatorsDirMounted, - }; - - return { - id, - type: isDocker ? RunnerType.Docker : RunnerType.ChildProcess, - options: isDocker - ? { - image: process.env.LIGHTHOUSE_DOCKER_IMAGE as string, - mounts: [[rootDir, rootDirMounted]], - dockerNetworkIp: runner.getNextIp(), - } - : undefined, - bootstrap: async () => { - if (isDocker) { - await updateKeystoresPath( - validatorsDefinitionFilePath, - path.dirname(validatorsDefinitionFilePathMounted), - validatorsDefinitionFilePath - ); - } - }, - cli: { - command: binaryPath, - args: [ - "validator_client", - ...Object.entries(params).flatMap(([key, value]) => - value === null ? [`--${key}`] : [`--${key}`, String(value)] - ), - ], - env: {}, - }, - logs: { - stdoutFilePath: logFilePath, - }, - health: async () => { - try { - await got.get(`http://127.0.0.1:${keymanagerPort}/lighthouse/health`); - return {ok: true}; - } catch (err) { - if (err instanceof RequestError) { - return {ok: true}; - } - return {ok: false, reason: (err as Error).message, checkId: "/lighthouse/health query"}; - } - }, - }; -}; diff --git a/packages/cli/test/utils/simulation/cl_clients/lodestar.ts b/packages/cli/test/utils/simulation/cl_clients/lodestar.ts deleted file mode 100644 index 30af5daf6273..000000000000 --- a/packages/cli/test/utils/simulation/cl_clients/lodestar.ts +++ /dev/null @@ -1,185 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {writeFile} from "node:fs/promises"; -import path from "node:path"; -import got from "got"; -import {getClient} from "@lodestar/api/beacon"; -import {getClient as keyManagerGetClient} from "@lodestar/api/keymanager"; -import {chainConfigToJson} from "@lodestar/config"; -import {LogLevel} from "@lodestar/utils"; -import {BeaconArgs} from "../../../../src/cmds/beacon/options.js"; -import {IValidatorCliArgs} from "../../../../src/cmds/validator/options.js"; -import {GlobalArgs} from "../../../../src/options/globalOptions.js"; -import {LODESTAR_BINARY_PATH} from "../constants.js"; -import {CLClient, CLClientGenerator, CLClientGeneratorOptions, JobOptions, RunnerType} from "../interfaces.js"; -import {getNodePorts} from "../utils/ports.js"; - -export const generateLodestarBeaconNode: CLClientGenerator = (opts, runner) => { - const {address, id, config, keys, genesisTime, engineUrls, engineMock, clientOptions, nodeIndex, metrics} = opts; - const { - paths: {jwtsecretFilePath, rootDir, genesisFilePath, logFilePath}, - } = opts; - const ports = getNodePorts(nodeIndex); - - const rcConfigPath = path.join(rootDir, "rc_config.json"); - const paramsPath = path.join(rootDir, "params.json"); - - const rcConfig = { - network: "dev", - preset: "minimal", - dataDir: rootDir, - genesisStateFile: genesisFilePath, - rest: true, - "rest.address": "0.0.0.0", - "rest.port": ports.cl.httpPort, - "rest.namespace": "*", - "sync.isSingleNode": false, - "network.allowPublishToZeroPeers": false, - discv5: true, - "network.connectToDiscv5Bootnodes": true, - "network.rateLimitMultiplier": 0, - listenAddress: "0.0.0.0", - port: ports.cl.port, - bootnodes: [], - logPrefix: id, - logFormatGenesisTime: `${genesisTime}`, - logLevel: LogLevel.debug, - logFileDailyRotate: 0, - logFile: "none", - "jwt-secret": jwtsecretFilePath, - paramsFile: paramsPath, - ...clientOptions, - } as unknown as BeaconArgs & GlobalArgs; - - if (engineMock) { - rcConfig["eth1"] = false; - rcConfig["execution.engineMock"] = true; - rcConfig["execution.urls"] = []; - } else { - rcConfig["eth1"] = true; - rcConfig["execution.engineMock"] = false; - rcConfig["execution.urls"] = [...engineUrls]; - } - - if (metrics) { - rcConfig.metrics = true; - rcConfig["metrics.port"] = metrics.port; - rcConfig["metrics.address"] = metrics.host; - } else { - rcConfig.metrics = false; - } - - const validatorClientsJobs: JobOptions[] = []; - if (keys.type !== "no-keys") { - validatorClientsJobs.push( - generateLodestarValidatorJobs({ - ...opts, - id: `${id}-validator`, - paths: { - ...opts.paths, - logFilePath: path.join(path.dirname(logFilePath), `${id}-validator.log`), - }, - }) - ); - } - - const validatorJob = runner.create(validatorClientsJobs); - const beaconJob = runner.create([ - { - id, - bootstrap: async () => { - await writeFile(rcConfigPath, JSON.stringify(rcConfig, null, 2)); - await writeFile(paramsPath, JSON.stringify(chainConfigToJson(config), null, 2)); - }, - type: RunnerType.ChildProcess, - cli: { - command: LODESTAR_BINARY_PATH, - args: ["beacon", "--rcConfig", rcConfigPath, "--paramsFile", paramsPath], - env: { - DEBUG: process.env.DISABLE_DEBUG_LOGS ? "" : "*,-winston:*", - }, - }, - logs: { - stdoutFilePath: logFilePath, - }, - health: async () => { - try { - await got.get(`http://${address}:${ports.cl.httpPort}/eth/v1/node/health`); - return {ok: true}; - } catch (err) { - return {ok: false, reason: (err as Error).message, checkId: "eth/v1/node/health query"}; - } - }, - }, - ]); - - return { - id, - client: CLClient.Lodestar, - url: `http://127.0.0.1:${ports.cl.httpPort}`, - keys, - api: getClient({baseUrl: `http://127.0.0.1:${ports.cl.httpPort}`}, {config}), - keyManager: keyManagerGetClient({baseUrl: `http://127.0.0.1:${ports.cl.keymanagerPort}`}, {config}), - beaconJob: opts.beacon ? beaconJob : undefined, - validatorJob: opts.validator ? validatorJob : undefined, - }; -}; - -export const generateLodestarValidatorJobs = (opts: CLClientGeneratorOptions): JobOptions => { - const {paths, id, keys, config, genesisTime, nodeIndex} = opts; - const {rootDir, keystoresDir, keystoresSecretFilePath, logFilePath} = paths; - const ports = getNodePorts(nodeIndex); - - if (keys.type === "no-keys") { - throw Error("Attempting to run a vc with keys.type == 'no-keys'"); - } - - const rcConfig = { - network: "dev", - preset: "minimal", - dataDir: rootDir, - server: `http://0.0.0.0:${ports.cl.httpPort}/`, - keymanager: true, - "keymanager.authEnabled": false, - "keymanager.address": "127.0.0.1", - "keymanager.port": ports.cl.keymanagerPort, - logPrefix: id, - logFormatGenesisTime: genesisTime, - logLevel: LogLevel.debug, - logFile: "none", - importKeystores: keystoresDir, - importKeystoresPassword: keystoresSecretFilePath, - } as unknown as IValidatorCliArgs & GlobalArgs; - - return { - id, - type: RunnerType.ChildProcess, - bootstrap: async () => { - await writeFile(path.join(rootDir, "rc_config.json"), JSON.stringify(rcConfig, null, 2)); - await writeFile(path.join(rootDir, "params.json"), JSON.stringify(chainConfigToJson(config), null, 2)); - }, - cli: { - command: LODESTAR_BINARY_PATH, - args: [ - "validator", - "--rcConfig", - path.join(rootDir, "rc_config.json"), - "--paramsFile", - path.join(rootDir, "params.json"), - ], - env: { - DEBUG: process.env.DISABLE_DEBUG_LOGS ? "" : "*,-winston:*", - }, - }, - logs: { - stdoutFilePath: logFilePath, - }, - health: async () => { - try { - await got.get(`http://127.0.0.1:${ports.cl.keymanagerPort}/eth/v1/keystores`); - return {ok: true}; - } catch (err) { - return {ok: false, reason: (err as Error).message, checkId: "eth/v1/keystores query"}; - } - }, - }; -}; diff --git a/packages/cli/test/utils/simulation/el_clients/geth.ts b/packages/cli/test/utils/simulation/execution_clients/geth.ts similarity index 94% rename from packages/cli/test/utils/simulation/el_clients/geth.ts rename to packages/cli/test/utils/simulation/execution_clients/geth.ts index 4d08a9095049..c9b432a33e3c 100644 --- a/packages/cli/test/utils/simulation/el_clients/geth.ts +++ b/packages/cli/test/utils/simulation/execution_clients/geth.ts @@ -5,7 +5,7 @@ import got from "got"; import {ZERO_HASH} from "@lodestar/state-transition"; import {SHARED_JWT_SECRET, SIM_ENV_NETWORK_ID} from "../constants.js"; import {Eth1ProviderWithAdmin} from "../Eth1ProviderWithAdmin.js"; -import {ELClient, ELClientGenerator, ELStartMode, JobOptions, RunnerType} from "../interfaces.js"; +import {ExecutionClient, ExecutionNodeGenerator, ExecutionStartMode, JobOptions, RunnerType} from "../interfaces.js"; import {getNodeMountedPaths} from "../utils/paths.js"; import {getNodePorts} from "../utils/ports.js"; @@ -13,7 +13,7 @@ const SECRET_KEY = "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065f const PASSWORD = "12345678"; const GENESIS_ACCOUNT = "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"; -export const generateGethNode: ELClientGenerator = (opts, runner) => { +export const generateGethNode: ExecutionNodeGenerator = (opts, runner) => { if (!process.env.GETH_BINARY_DIR && !process.env.GETH_DOCKER_IMAGE) { throw new Error("GETH_BINARY_DIR or GETH_DOCKER_IMAGE must be provided"); } @@ -143,7 +143,7 @@ export const generateGethNode: ELClientGenerator = (opts, runner) "--verbosity", "5", ...(mining ? ["--mine", "--miner.etherbase", GENESIS_ACCOUNT] : []), - ...(mode == ELStartMode.PreMerge ? ["--nodiscover"] : []), + ...(mode == ExecutionStartMode.PreMerge ? ["--nodiscover"] : []), ...clientOptions, ], env: {}, @@ -170,7 +170,7 @@ export const generateGethNode: ELClientGenerator = (opts, runner) ); return { - client: ELClient.Geth, + client: ExecutionClient.Geth, id, engineRpcUrl, ethRpcUrl, diff --git a/packages/cli/test/utils/simulation/el_clients/index.ts b/packages/cli/test/utils/simulation/execution_clients/index.ts similarity index 54% rename from packages/cli/test/utils/simulation/el_clients/index.ts rename to packages/cli/test/utils/simulation/execution_clients/index.ts index 8f8b405a9269..840be742f3fa 100644 --- a/packages/cli/test/utils/simulation/el_clients/index.ts +++ b/packages/cli/test/utils/simulation/execution_clients/index.ts @@ -1,33 +1,28 @@ import {writeFile} from "node:fs/promises"; -import {ChainForkConfig} from "@lodestar/config"; import {SHARED_JWT_SECRET, CLIQUE_SEALING_PERIOD} from "../constants.js"; import { AtLeast, - ELClient, - ELGeneratorClientOptions, - ELGeneratorGenesisOptions, - ELNode, - ELStartMode, - IRunner, + ExecutionClient, + ExecutionGeneratorOptions, + ExecutionGenesisOptions, + ExecutionNode, + ExecutionStartMode, } from "../interfaces.js"; import {getEstimatedShanghaiTime} from "../utils/index.js"; import {getGethGenesisBlock} from "../utils/el_genesis.js"; -import {createELNodePaths} from "../utils/paths.js"; +import {ensureDirectories} from "../utils/paths.js"; import {generateGethNode} from "./geth.js"; import {generateMockNode} from "./mock.js"; import {generateNethermindNode} from "./nethermind.js"; -export async function createELNode( +export async function createExecutionNode( client: E, - options: AtLeast, "genesisTime" | "paths" | "nodeIndex"> & { - forkConfig: ChainForkConfig; - runner: IRunner; - } -): Promise { + options: AtLeast, "genesisTime" | "paths" | "nodeIndex" | "forkConfig" | "runner"> +): Promise { const {forkConfig, runner} = options; - const elId = `${options.id}-el-${client}`; + const elId = `${options.id}-${client}`; - const genesisOptions: ELGeneratorGenesisOptions = { + const genesisOptions: ExecutionGenesisOptions = { ...options, ttd: options.ttd ?? forkConfig.TERMINAL_TOTAL_DIFFICULTY, cliqueSealingPeriod: options.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, @@ -43,30 +38,32 @@ export async function createELNode( clientOptions: options.clientOptions ?? [], }; - const opts: ELGeneratorClientOptions = { + const opts: ExecutionGeneratorOptions = { ...options, ...genesisOptions, id: elId, - mode: options.mode ?? (forkConfig.BELLATRIX_FORK_EPOCH > 0 ? ELStartMode.PreMerge : ELStartMode.PostMerge), + mode: + options.mode ?? + (forkConfig.BELLATRIX_FORK_EPOCH > 0 ? ExecutionStartMode.PreMerge : ExecutionStartMode.PostMerge), address: runner.getNextIp(), mining: options.mining ?? false, }; - await createELNodePaths(opts.paths); + await ensureDirectories(opts.paths); await writeFile(opts.paths.jwtsecretFilePath, SHARED_JWT_SECRET); await writeFile(opts.paths.genesisFilePath, JSON.stringify(getGethGenesisBlock(opts.mode, genesisOptions))); switch (client) { - case ELClient.Mock: { - return generateMockNode(opts as ELGeneratorClientOptions, runner); + case ExecutionClient.Mock: { + return generateMockNode(opts as ExecutionGeneratorOptions, runner); } - case ELClient.Geth: { - return generateGethNode(opts as ELGeneratorClientOptions, runner); + case ExecutionClient.Geth: { + return generateGethNode(opts as ExecutionGeneratorOptions, runner); } - case ELClient.Nethermind: { - return generateNethermindNode(opts as ELGeneratorClientOptions, runner); + case ExecutionClient.Nethermind: { + return generateNethermindNode(opts as ExecutionGeneratorOptions, runner); } default: - throw new Error(`EL Client "${client}" not supported`); + throw new Error(`Execution Client "${client}" not supported`); } } diff --git a/packages/cli/test/utils/simulation/el_clients/mock.ts b/packages/cli/test/utils/simulation/execution_clients/mock.ts similarity index 70% rename from packages/cli/test/utils/simulation/el_clients/mock.ts rename to packages/cli/test/utils/simulation/execution_clients/mock.ts index a06c17a3daae..f0c48ca9ce33 100644 --- a/packages/cli/test/utils/simulation/el_clients/mock.ts +++ b/packages/cli/test/utils/simulation/execution_clients/mock.ts @@ -1,8 +1,8 @@ import {SHARED_JWT_SECRET} from "../constants.js"; -import {ELClient, ELClientGenerator} from "../interfaces.js"; +import {ExecutionClient, ExecutionNodeGenerator} from "../interfaces.js"; import {getNodePorts} from "../utils/ports.js"; -export const generateMockNode: ELClientGenerator = (opts, runner) => { +export const generateMockNode: ExecutionNodeGenerator = (opts, runner) => { const {id, ttd, nodeIndex} = opts; const { el: {enginePort, httpPort}, @@ -13,7 +13,7 @@ export const generateMockNode: ELClientGenerator = (opts, runner) const job = runner.create([]); return { - client: ELClient.Mock, + client: ExecutionClient.Mock, id, engineRpcUrl, ethRpcUrl, diff --git a/packages/cli/test/utils/simulation/el_clients/nethermind.ts b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts similarity index 94% rename from packages/cli/test/utils/simulation/el_clients/nethermind.ts rename to packages/cli/test/utils/simulation/execution_clients/nethermind.ts index 8da5b2ebbbfd..3f8f5cfc52d7 100644 --- a/packages/cli/test/utils/simulation/el_clients/nethermind.ts +++ b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts @@ -4,13 +4,13 @@ import path from "node:path"; import got from "got"; import {ZERO_HASH} from "@lodestar/state-transition"; import {Eth1ProviderWithAdmin} from "../Eth1ProviderWithAdmin.js"; -import {ELClient, ELClientGenerator, JobOptions, RunnerType} from "../interfaces.js"; +import {ExecutionClient, ExecutionNodeGenerator, JobOptions, RunnerType} from "../interfaces.js"; import {getNethermindChainSpec} from "../utils/el_genesis.js"; import {getNodeMountedPaths} from "../utils/paths.js"; import {SHARED_JWT_SECRET} from "../constants.js"; import {getNodePorts} from "../utils/ports.js"; -export const generateNethermindNode: ELClientGenerator = (opts, runner) => { +export const generateNethermindNode: ExecutionNodeGenerator = (opts, runner) => { if (!process.env.NETHERMIND_DOCKER_IMAGE) { throw Error(`EL ENV must be provided, NETHERMIND_DOCKER_IMAGE: ${process.env.NETHERMIND_DOCKER_IMAGE}`); } @@ -119,7 +119,7 @@ export const generateNethermindNode: ELClientGenerator = (o ); return { - client: ELClient.Nethermind, + client: ExecutionClient.Nethermind, id, engineRpcUrl, ethRpcUrl, diff --git a/packages/cli/test/utils/simulation/interfaces.ts b/packages/cli/test/utils/simulation/interfaces.ts index 0b4a0f03cfaa..ed75509c09ef 100644 --- a/packages/cli/test/utils/simulation/interfaces.ts +++ b/packages/cli/test/utils/simulation/interfaces.ts @@ -7,6 +7,7 @@ import {ChainConfig, ChainForkConfig} from "@lodestar/config"; import {ForkName} from "@lodestar/params"; import {Slot, allForks, Epoch} from "@lodestar/types"; import {BeaconArgs} from "../../../src/cmds/beacon/options.js"; +import {IValidatorCliArgs} from "../../../src/cmds/validator/options.js"; import {GlobalArgs} from "../../../src/options/index.js"; import {EpochClock} from "./EpochClock.js"; import {Eth1ProviderWithAdmin} from "./Eth1ProviderWithAdmin.js"; @@ -27,97 +28,116 @@ export type SimulationOptions = { elGenesisTime: number; }; -export enum CLClient { - Lodestar = "lodestar", - Lighthouse = "lighthouse", +export enum BeaconClient { + Lodestar = "beacon_loadstar", + Lighthouse = "beacon_lighthouse", } -export enum ELClient { - Mock = "mock", - Geth = "geth", - Nethermind = "nethermind", +export enum ValidatorClient { + Lodestar = "validator_loadstar", + Lighthouse = "validator_lighthouse", } -export enum ELStartMode { +export enum ExecutionClient { + Mock = "execution_mock", + Geth = "execution_geth", + Nethermind = "execution_nethermind", +} + +export enum ExecutionStartMode { PreMerge = "pre-merge", PostMerge = "post-merge", } -export type CLClientsOptions = { - [CLClient.Lodestar]: Partial; - [CLClient.Lighthouse]: Record; +export type BeaconClientsOptions = { + [BeaconClient.Lodestar]: Partial; + [BeaconClient.Lighthouse]: Record; }; -export type ELClientsOptions = { - [ELClient.Mock]: string[]; - [ELClient.Geth]: string[]; - [ELClient.Nethermind]: string[]; +export type ValidatorClientsOptions = { + [ValidatorClient.Lodestar]: Partial; + [ValidatorClient.Lighthouse]: Record; }; -export type ELNodeDefinition = E | {type: E; options: Partial>}; -export type CLNodeDefinition = - | B - | {type: B; options: Partial>} - | { - beacon: B; - beaconOptions?: Partial>; - validator: V; - validatorOptions?: Partial>; - }; - -export interface NodePairOptions< - B extends CLClient = CLClient, - V extends CLClient = CLClient, - E extends ELClient = ELClient, +export type ExecutionClientsOptions = { + [ExecutionClient.Mock]: string[]; + [ExecutionClient.Geth]: string[]; + [ExecutionClient.Nethermind]: string[]; +}; + +export type ExecutionNodeDefinition = + | E + | {type: E; options: Partial>}; +export type BeaconNodeDefinition = E | {type: E; options: Partial>}; +export type ValidatorNodeDefinition = + | E + | {type: E; options: Partial>}; + +export interface NodePairDefinition< + B extends BeaconClient = BeaconClient, + E extends ExecutionClient = ExecutionClient, + V extends ValidatorClient = ValidatorClient, > { keysCount: number; remote?: boolean; mining?: boolean; id: string; - cl: CLNodeDefinition; - el: ELNodeDefinition; + beacon: BeaconNodeDefinition; + execution: ExecutionNodeDefinition; + validator?: ValidatorNodeDefinition; } -export type CLClientKeys = +export type ValidatorClientKeys = | {type: "local"; secretKeys: SecretKey[]} | {type: "remote"; secretKeys: SecretKey[]} | {type: "no-keys"}; -export interface CLClientGeneratorOptions { +export interface GeneratorOptions { id: string; nodeIndex: number; - paths: CLPaths; address: string; - config: ChainForkConfig; - keys: CLClientKeys; + forkConfig: ChainForkConfig; genesisTime: number; + runner: IRunner; +} + +export interface BeaconGeneratorOptions extends GeneratorOptions { + paths: BeaconPaths; engineUrls: string[]; engineMock: boolean; - clientOptions: CLClientsOptions[C]; + clientOptions: BeaconClientsOptions[C]; + metrics?: { + host: string; + port: number; + }; +} + +export interface ValidatorGeneratorOptions extends GeneratorOptions { + paths: ValidatorPaths; + keys: ValidatorClientKeys; + beaconUrls: string[]; + clientOptions: ValidatorClientsOptions[V]; metrics?: { host: string; port: number; }; - beacon: boolean; - validator: boolean; } -export interface ELGeneratorGenesisOptions { +export interface ExecutionGenesisOptions { ttd: bigint; cliqueSealingPeriod: number; shanghaiTime: number; genesisTime: number; - clientOptions: ELClientsOptions[E]; + clientOptions: ExecutionClientsOptions[E]; } -export interface ELGeneratorClientOptions extends ELGeneratorGenesisOptions { - mode: ELStartMode; - nodeIndex: number; - id: string; - address: string; +export interface ExecutionGeneratorOptions + extends ExecutionGenesisOptions, + GeneratorOptions { + mode: ExecutionStartMode; mining: boolean; - paths: ELPaths; - clientOptions: ELClientsOptions[E]; + paths: ExecutionPaths; + clientOptions: ExecutionClientsOptions[E]; } export type LodestarAPI = Api; @@ -143,36 +163,52 @@ export type LighthouseAPI = Omit & { }; }; -export interface CLNode { +export interface BeaconNode { readonly client: C; readonly id: string; readonly url: string; - readonly api: C extends CLClient.Lodestar ? LodestarAPI : LighthouseAPI; + readonly api: C extends BeaconClient.Lodestar ? LodestarAPI : LighthouseAPI; + readonly job: Job; +} + +export interface ValidatorNode { + readonly client: C; + readonly id: string; readonly keyManager: KeyManagerApi; - readonly keys: CLClientKeys; - readonly beaconJob?: Job; - readonly validatorJob?: Job; + readonly keys: ValidatorClientKeys; + readonly job: Job; } -export interface ELNode { +export interface ExecutionNode { readonly client: E; readonly id: string; readonly ttd: bigint; readonly engineRpcUrl: string; readonly ethRpcUrl: string; readonly jwtSecretHex: string; - readonly provider: E extends ELClient.Mock ? null : Eth1ProviderWithAdmin; + readonly provider: E extends ExecutionClient.Mock ? null : Eth1ProviderWithAdmin; readonly job: Job; } export interface NodePair { readonly id: string; - readonly cl: CLNode; - readonly el: ELNode; + readonly beacon: BeaconNode; + readonly execution: ExecutionNode; + readonly validator: ValidatorNode; } -export type CLClientGenerator = (opts: CLClientGeneratorOptions, runner: IRunner) => CLNode; -export type ELClientGenerator = (opts: ELGeneratorClientOptions, runner: IRunner) => ELNode; +export type BeaconNodeGenerator = ( + opts: BeaconGeneratorOptions, + runner: IRunner +) => BeaconNode; +export type ValidatorNodeGenerator = ( + opts: ValidatorGeneratorOptions, + runner: IRunner +) => ValidatorNode; +export type ExecutionNodeGenerator = ( + opts: ExecutionGeneratorOptions, + runner: IRunner +) => ExecutionNode; export type HealthStatus = {ok: true} | {ok: false; reason: string; checkId: string}; @@ -351,25 +387,27 @@ export abstract class SimulationReporter { abstract summary(): void; } -export interface CLPaths { +export interface CommonPaths { rootDir: string; dataDir: string; - genesisFilePath: string; jwtsecretFilePath: string; - validatorsDir: string; + logFilePath: string; +} + +export interface BeaconPaths extends CommonPaths { + genesisFilePath: string; +} + +export interface ValidatorPaths extends CommonPaths { keystoresDir: string; keystoresSecretsDir: string; keystoresSecretFilePath: string; + validatorsDir: string; validatorsDefinitionFilePath: string; - logFilePath: string; } -export interface ELPaths { - rootDir: string; - dataDir: string; +export interface ExecutionPaths extends CommonPaths { genesisFilePath: string; - jwtsecretFilePath: string; - logFilePath: string; } export type MountedPaths = T & { diff --git a/packages/cli/test/utils/simulation/utils/el_genesis.ts b/packages/cli/test/utils/simulation/utils/el_genesis.ts index ed4ea355a27e..aed911527bad 100644 --- a/packages/cli/test/utils/simulation/utils/el_genesis.ts +++ b/packages/cli/test/utils/simulation/utils/el_genesis.ts @@ -1,7 +1,7 @@ import {SIM_ENV_CHAIN_ID, SIM_ENV_NETWORK_ID} from "../constants.js"; -import {ELGeneratorGenesisOptions, ELStartMode, Eth1GenesisBlock} from "../interfaces.js"; +import {ExecutionGenesisOptions, ExecutionStartMode, Eth1GenesisBlock} from "../interfaces.js"; -export const getGethGenesisBlock = (mode: ELStartMode, options: ELGeneratorGenesisOptions): Record => { +export const getGethGenesisBlock = (mode: ExecutionStartMode, options: ExecutionGenesisOptions): Record => { const {ttd, cliqueSealingPeriod, shanghaiTime, genesisTime} = options; const genesis = { @@ -49,7 +49,7 @@ export const getGethGenesisBlock = (mode: ELStartMode, options: ELGeneratorGenes baseFeePerGas: "0x0", }; - if (mode === ELStartMode.PreMerge) { + if (mode === ExecutionStartMode.PreMerge) { return genesis; } @@ -58,8 +58,8 @@ export const getGethGenesisBlock = (mode: ELStartMode, options: ELGeneratorGenes }; export const getNethermindChainSpec = ( - mode: ELStartMode, - options: ELGeneratorGenesisOptions + mode: ExecutionStartMode, + options: ExecutionGenesisOptions ): Record => { const {ttd, shanghaiTime} = options; const genesis = getGethGenesisBlock(mode, options) as Eth1GenesisBlock; diff --git a/packages/cli/test/utils/simulation/utils/keys.ts b/packages/cli/test/utils/simulation/utils/keys.ts index 8608ae73bb12..27983c04cd38 100644 --- a/packages/cli/test/utils/simulation/utils/keys.ts +++ b/packages/cli/test/utils/simulation/utils/keys.ts @@ -4,7 +4,7 @@ import path from "node:path"; import yaml from "js-yaml"; import {Keystore} from "@chainsafe/bls-keystore"; import {SHARED_VALIDATOR_PASSWORD} from "../constants.js"; -import {CLClientKeys, CLPaths} from "../interfaces.js"; +import {ValidatorClientKeys, ValidatorPaths} from "../interfaces.js"; type KeystoreDefinition = { enabled: boolean; @@ -15,8 +15,8 @@ type KeystoreDefinition = { }; export const createKeystores = async ( - {validatorsDefinitionFilePath, keystoresDir, keystoresSecretsDir}: CLPaths, - keys: CLClientKeys + {validatorsDefinitionFilePath, keystoresDir, keystoresSecretsDir}: ValidatorPaths, + keys: ValidatorClientKeys ): Promise => { const definition: KeystoreDefinition[] = []; diff --git a/packages/cli/test/utils/simulation/utils/network.ts b/packages/cli/test/utils/simulation/utils/network.ts index 8c7a3d5918cb..145c2c28ada5 100644 --- a/packages/cli/test/utils/simulation/utils/network.ts +++ b/packages/cli/test/utils/simulation/utils/network.ts @@ -2,7 +2,7 @@ import {ApiError} from "@lodestar/api"; import {Slot, allForks} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; -import {CLClient, CLNode, ELClient, ELNode, NodePair} from "../interfaces.js"; +import {BeaconClient, BeaconNode, ExecutionClient, ExecutionNode, NodePair} from "../interfaces.js"; import {SimulationEnvironment} from "../SimulationEnvironment.js"; import {SimulationTrackerEvent} from "../SimulationTracker.js"; @@ -14,16 +14,16 @@ export async function connectAllNodes(nodes: NodePair[]): Promise { export async function connectNewNode(newNode: NodePair, nodes: NodePair[]): Promise { await connectNewELNode( - newNode.el, - nodes.map((node) => node.el) + newNode.execution, + nodes.map((node) => node.execution) ); await connectNewCLNode( - newNode.cl, - nodes.map((node) => node.cl) + newNode.beacon, + nodes.map((node) => node.beacon) ); } -export async function connectNewCLNode(newNode: CLNode, nodes: CLNode[]): Promise { +export async function connectNewCLNode(newNode: BeaconNode, nodes: BeaconNode[]): Promise { const res = await newNode.api.node.getNetworkIdentity(); ApiError.assert(res); const clIdentity = res.response.data; @@ -32,8 +32,8 @@ export async function connectNewCLNode(newNode: CLNode, nodes: CLNode[]): Promis for (const node of nodes) { if (node === newNode) continue; - if (node.client === CLClient.Lodestar) { - const res = await (node as CLNode).api.lodestar.connectPeer( + if (node.client === BeaconClient.Lodestar) { + const res = await (node as BeaconNode).api.lodestar.connectPeer( clIdentity.peerId, // As the lodestar is always running on host // convert the address to local host to connect the container node @@ -44,7 +44,7 @@ export async function connectNewCLNode(newNode: CLNode, nodes: CLNode[]): Promis } } -export async function connectNewELNode(newNode: ELNode, nodes: ELNode[]): Promise { +export async function connectNewELNode(newNode: ExecutionNode, nodes: ExecutionNode[]): Promise { const elIdentity = newNode.provider === null ? null : await newNode.provider.admin.nodeInfo(); if (elIdentity && !elIdentity.enode) return; @@ -53,7 +53,7 @@ export async function connectNewELNode(newNode: ELNode, nodes: ELNode[]): Promis // Nethermind had a bug in admin_addPeer RPC call // https://github.com/NethermindEth/nethermind/issues/4876 - if (node.provider !== null && node.client !== ELClient.Nethermind && elIdentity) { + if (node.provider !== null && node.client !== ExecutionClient.Nethermind && elIdentity) { await node.provider.admin.addPeer(elIdentity.enode); } } @@ -75,7 +75,7 @@ export async function waitForNodeSync( export async function waitForNodeSyncStatus(env: SimulationEnvironment, node: NodePair): Promise { // eslint-disable-next-line no-constant-condition while (true) { - const result = await node.cl.api.node.getSyncingStatus(); + const result = await node.beacon.api.node.getSyncingStatus(); ApiError.assert(result); if (!result.response.data.isSyncing) { break; @@ -125,7 +125,7 @@ export async function waitForSlot( {silent, env}: {silent?: boolean; env: SimulationEnvironment} ): Promise { if (!silent) { - console.log(`\nWaiting for slot on "${nodes.map((n) => n.cl.id).join(",")}"`, { + console.log(`\nWaiting for slot on "${nodes.map((n) => n.beacon.id).join(",")}"`, { target: slot, current: env.clock.currentSlot, }); @@ -143,7 +143,7 @@ export async function waitForSlot( } if (event.slot >= slot) { - reject(new Error(`${node.cl.id} had passed target slot ${slot}. Current slot ${event.slot}`)); + reject(new Error(`${node.beacon.id} had passed target slot ${slot}. Current slot ${event.slot}`)); } }; env.tracker.on(node, SimulationTrackerEvent.Slot, cb); @@ -157,7 +157,7 @@ export async function fetchBlock( {tries, delay, slot, signal}: {slot: number; tries: number; delay: number; signal?: AbortSignal} ): Promise { for (let i = 0; i < tries; i++) { - const res = await node.cl.api.beacon.getBlockV2(slot); + const res = await node.beacon.api.beacon.getBlockV2(slot); if (!res.ok) { await sleep(delay, signal); continue; diff --git a/packages/cli/test/utils/simulation/utils/paths.ts b/packages/cli/test/utils/simulation/utils/paths.ts index 98a42012faed..0a7ffb716dd1 100644 --- a/packages/cli/test/utils/simulation/utils/paths.ts +++ b/packages/cli/test/utils/simulation/utils/paths.ts @@ -1,107 +1,79 @@ import path from "node:path"; import fs from "node:fs"; import {mkdir} from "node:fs/promises"; -import {CLClient, CLPaths, ELClient, ELPaths, MountedPaths} from "../interfaces.js"; +import { + BeaconClient, + BeaconPaths, + ExecutionClient, + ExecutionPaths, + MountedPaths, + ValidatorClient, + ValidatorPaths, +} from "../interfaces.js"; -export const getCLNodePaths = ({ - root, - id, - client, - logsDir, -}: { - root: string; - id: string; - client: CLClient; - logsDir: string; -}): CLPaths => { - const clRootDir = path.join(root, id, `cl_${client}`); - const dataDir = path.join(clRootDir, "data"); - const genesisFilePath = path.join(clRootDir, "genesis.ssz"); - const jwtsecretFilePath = path.join(clRootDir, "jwtsecret.txt"); - const validatorsDir = path.join(clRootDir, "validators"); - const keystoresDir = path.join(clRootDir, "validators", "keystores"); - const keystoresSecretsDir = path.join(clRootDir, "validators", "secretts"); - const keystoresSecretFilePath = path.join(clRootDir, "validators", "password.txt"); - const validatorsDefinitionFilePath = path.join(clRootDir, "validators", "validator_definitions.yml"); - const logFilePath = path.join(logsDir, `${id}-cl-${client}.log`); +export function getNodePaths< + C extends BeaconClient | ValidatorClient | ExecutionClient, + R = C extends BeaconClient ? BeaconPaths : C extends ValidatorClient ? ValidatorPaths : ExecutionPaths, + // Mount path will be used when running the node in docker +>(opts: {root: string; id: string; logsDir: string; client: C; mountPath?: string}): R { + const {root, id, client, logsDir, mountPath} = opts; - return { - rootDir: clRootDir, - dataDir, - genesisFilePath, - jwtsecretFilePath, - validatorsDir, - keystoresDir, - keystoresSecretsDir, - validatorsDefinitionFilePath, - keystoresSecretFilePath, - logFilePath, - }; -}; - -export const getCLNodePathsForDocker = (paths: CLPaths, mountPath: string): CLPaths => { - const { - rootDir, - dataDir, - genesisFilePath, - jwtsecretFilePath, - validatorsDefinitionFilePath, - validatorsDir, - keystoresDir, - keystoresSecretFilePath, - keystoresSecretsDir, - logFilePath, - } = paths; + if (Object.values(ExecutionClient).includes(client as ExecutionClient)) { + const executionRootDir = path.join(mountPath ?? root, id, client); + return { + rootDir: executionRootDir, + dataDir: path.join(executionRootDir, "data"), + genesisFilePath: path.join(executionRootDir, "genesis.ssz"), + jwtsecretFilePath: path.join(executionRootDir, "jwtsecret.txt"), + logFilePath: path.join(logsDir, `${id}-${client}.log`), + } as R; + } - return { - rootDir: mountPath, - dataDir: dataDir.replace(rootDir, mountPath), - genesisFilePath: genesisFilePath.replace(rootDir, mountPath), - jwtsecretFilePath: jwtsecretFilePath.replace(rootDir, mountPath), - validatorsDir: validatorsDir.replace(rootDir, mountPath), - keystoresDir: keystoresDir.replace(rootDir, mountPath), - keystoresSecretsDir: keystoresSecretsDir.replace(rootDir, mountPath), - validatorsDefinitionFilePath: validatorsDefinitionFilePath.replace(rootDir, mountPath), - keystoresSecretFilePath: keystoresSecretFilePath.replace(rootDir, mountPath), - logFilePath, - }; -}; + if (Object.values(BeaconClient).includes(client as BeaconClient)) { + const beaconRootDir = path.join(mountPath ?? root, id, client); + return { + rootDir: beaconRootDir, + dataDir: path.join(beaconRootDir, "data"), + genesisFilePath: path.join(beaconRootDir, "genesis.ssz"), + jwtsecretFilePath: path.join(beaconRootDir, "jwtsecret.txt"), + logFilePath: path.join(logsDir, `${id}-${client}.log`), + } as R; + } -export const createCLNodePaths = async (paths: CLPaths): Promise => { - const {dataDir, keystoresDir, keystoresSecretsDir} = paths; + if (Object.values(ValidatorClient).includes(client as ValidatorClient)) { + const validatorRootDir = path.join(mountPath ?? root, id, client); + return { + rootDir: validatorRootDir, + dataDir: path.join(validatorRootDir, "data"), + jwtsecretFilePath: path.join(validatorRootDir, "jwtsecret.txt"), + keystoresDir: path.join(validatorRootDir, "validators", "keystores"), + keystoresSecretsDir: path.join(validatorRootDir, "validators", "secretts"), + keystoresSecretFilePath: path.join(validatorRootDir, "validators", "password.txt"), + validatorsDefinitionFilePath: path.join(validatorRootDir, "validators", "validator_definitions.yml"), + validatorsDir: path.join(validatorRootDir, "validators"), + logFilePath: path.join(logsDir, `${id}-${client}.log`), + } as R; + } - if (!fs.existsSync(dataDir)) await mkdir(dataDir, {recursive: true}); - if (!fs.existsSync(keystoresDir)) await mkdir(keystoresDir, {recursive: true}); - if (!fs.existsSync(keystoresSecretsDir)) await mkdir(keystoresSecretsDir, {recursive: true}); + throw new Error(`Unknown client type: ${client}`); +} - return paths; -}; +export const ensureDirectories = async (paths: T): Promise => { + for (const dirName of Object.values(paths)) { + if (fs.existsSync(dirName)) continue; -export const getELNodePaths = ({ - root, - id, - client, - logsDir, -}: { - root: string; - id: string; - client: ELClient; - logsDir: string; -}): ELPaths => { - const elRootDir = path.join(root, id, `el_${client}`); - const dataDir = path.join(elRootDir, "data"); - const logFilePath = path.join(logsDir, `${id}-el-${client}.log`); + if (path.extname(dirName) === "") { + await mkdir(dirName, {recursive: true}); + } else { + const parentDir = path.dirname(dirName); + await mkdir(parentDir, {recursive: true}); + } + } - return { - rootDir: elRootDir, - dataDir, - genesisFilePath: path.join(elRootDir, "genesis.json"), - jwtsecretFilePath: path.join(elRootDir, "jwtsecret.txt"), - logFilePath, - }; + return paths; }; -export const getNodeMountedPaths = ( +export const getNodeMountedPaths = ( paths: T, mountPath: string, mount: boolean @@ -114,11 +86,3 @@ export const getNodeMountedPaths = ( .flat() .reduce((o, [key, value]) => ({...o, [key]: value as string}), {}) as MountedPaths; }; - -export const createELNodePaths = async (paths: ELPaths): Promise => { - const {dataDir} = paths; - - if (!fs.existsSync(dataDir)) await mkdir(dataDir, {recursive: true}); - - return paths; -}; diff --git a/packages/cli/test/utils/simulation/validator_clients/index.ts b/packages/cli/test/utils/simulation/validator_clients/index.ts new file mode 100644 index 000000000000..1334382407e8 --- /dev/null +++ b/packages/cli/test/utils/simulation/validator_clients/index.ts @@ -0,0 +1,84 @@ +import {writeFile} from "node:fs/promises"; +import {SHARED_JWT_SECRET, SHARED_VALIDATOR_PASSWORD, BN_REST_BASE_PORT} from "../constants.js"; +import {AtLeast, BeaconClient, ValidatorClient, ValidatorGeneratorOptions, ValidatorNode} from "../interfaces.js"; +import {makeUniqueArray} from "../utils/index.js"; +import {createKeystores} from "../utils/keys.js"; +import {ensureDirectories} from "../utils/paths.js"; +import {generateLodestarValidatorNode} from "./lodestar.js"; +import {generateLighthouseValidatorNode} from "./lighthouse.js"; + +export async function createValidatorNode( + client: V, + options: AtLeast< + ValidatorGeneratorOptions, + "id" | "paths" | "forkConfig" | "nodeIndex" | "genesisTime" | "runner" | "beaconUrls" + > +): Promise { + const {runner, forkConfig} = options; + const clId = `${options.id}-${client}`; + + const opts: ValidatorGeneratorOptions = { + ...options, + id: clId, + keys: options.keys ?? {type: "no-keys"}, + genesisTime: options.genesisTime + forkConfig.GENESIS_DELAY, + clientOptions: options.clientOptions ?? {}, + address: "127.0.0.1", + }; + + const metricServer = process.env.SIM_METRIC_SERVER_URL; + if (metricServer) { + const server = new URL(metricServer.startsWith("http") ? metricServer : `http://${metricServer}`); + opts.metrics = { + host: server.hostname, + port: parseInt(server.port as string), + }; + } + + await ensureDirectories(opts.paths); + await createKeystores(opts.paths, opts.keys); + await writeFile(opts.paths.jwtsecretFilePath, SHARED_JWT_SECRET); + await writeFile(opts.paths.keystoresSecretFilePath, SHARED_VALIDATOR_PASSWORD); + + switch (client) { + case ValidatorClient.Lodestar: { + return generateLodestarValidatorNode( + { + ...opts, + address: "127.0.0.1", + beaconUrls: + opts.beaconUrls.length > 0 + ? makeUniqueArray([`http://127.0.0.1:${BN_REST_BASE_PORT + opts.nodeIndex + 1}`, ...opts.beaconUrls]) + : [`http://127.0.0.1:${BN_REST_BASE_PORT + opts.nodeIndex + 1}`], + }, + runner + ); + } + case ValidatorClient.Lighthouse: { + return generateLighthouseValidatorNode( + { + ...opts, + address: runner.getNextIp(), + beaconUrls: + opts.beaconUrls.length > 0 + ? makeUniqueArray([...opts.beaconUrls]) + : [`http://127.0.0.1:${BN_REST_BASE_PORT + opts.nodeIndex + 1}`], + }, + runner + ); + } + default: + throw new Error(`Validator Client "${client}" not supported`); + } +} + +export function getValidatorForBeaconNode(beacon: BeaconClient): ValidatorClient { + switch (beacon) { + case BeaconClient.Lodestar: + return ValidatorClient.Lodestar; + case BeaconClient.Lighthouse: + return ValidatorClient.Lighthouse; + default: + throw new Error(`Beacon Client "${beacon}" not supported`); + } +} diff --git a/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts b/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts new file mode 100644 index 000000000000..2c5df1afb7b6 --- /dev/null +++ b/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts @@ -0,0 +1,104 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import path from "node:path"; +import got, {RequestError} from "got"; +import {getClient as keyManagerGetClient} from "@lodestar/api/keymanager"; +import {RunnerType, ValidatorClient, ValidatorNodeGenerator} from "../interfaces.js"; +import {updateKeystoresPath} from "../utils/keys.js"; +import {getNodeMountedPaths} from "../utils/paths.js"; +import {getNodePorts} from "../utils/ports.js"; + +export const generateLighthouseValidatorNode: ValidatorNodeGenerator = (opts, runner) => { + if (!process.env.LIGHTHOUSE_BINARY_PATH && !process.env.LIGHTHOUSE_DOCKER_IMAGE) { + throw new Error("LIGHTHOUSE_BINARY_PATH or LIGHTHOUSE_DOCKER_IMAGE must be provided"); + } + + const isDocker = process.env.LIGHTHOUSE_DOCKER_IMAGE !== undefined; + const binaryPath = isDocker ? "lighthouse" : `${process.env.LIGHTHOUSE_BINARY_PATH}`; + + const {address, id, forkConfig, keys} = opts; + + const { + rootDir, + rootDirMounted, + logFilePath, + validatorsDirMounted, + validatorsDefinitionFilePath, + validatorsDefinitionFilePathMounted, + } = getNodeMountedPaths(opts.paths, "/data", isDocker); + const { + cl: {httpPort, keymanagerPort}, + } = getNodePorts(opts.nodeIndex); + + if (keys.type === "no-keys") { + throw Error("Attempting to run a vc with keys.type == 'no-keys'"); + } + + const params = { + "testnet-dir": rootDirMounted, + "beacon-nodes": `http://${address}:${httpPort}/`, + "debug-level": "debug", + "init-slashing-protection": null, + "allow-unsynced": null, + http: null, + "unencrypted-http-transport": null, + "http-address": "0.0.0.0", + "http-port": keymanagerPort, + "validators-dir": validatorsDirMounted, + }; + + const job = runner.create([ + { + id, + type: isDocker ? RunnerType.Docker : RunnerType.ChildProcess, + options: isDocker + ? { + image: process.env.LIGHTHOUSE_DOCKER_IMAGE as string, + mounts: [[rootDir, rootDirMounted]], + dockerNetworkIp: runner.getNextIp(), + } + : undefined, + bootstrap: async () => { + if (isDocker) { + await updateKeystoresPath( + validatorsDefinitionFilePath, + path.dirname(validatorsDefinitionFilePathMounted), + validatorsDefinitionFilePath + ); + } + }, + cli: { + command: binaryPath, + args: [ + "validator_client", + ...Object.entries(params).flatMap(([key, value]) => + value === null ? [`--${key}`] : [`--${key}`, String(value)] + ), + ], + env: {}, + }, + logs: { + stdoutFilePath: logFilePath, + }, + health: async () => { + try { + await got.get(`http://127.0.0.1:${keymanagerPort}/lighthouse/health`); + return {ok: true}; + } catch (err) { + if (err instanceof RequestError) { + return {ok: true}; + } + return {ok: false, reason: (err as Error).message, checkId: "/lighthouse/health query"}; + } + }, + }, + ]); + + return { + id, + client: ValidatorClient.Lighthouse, + url: `http://127.0.0.1:${httpPort}`, + keys, + keyManager: keyManagerGetClient({baseUrl: `http://127.0.0.1:${keymanagerPort}`}, {config: forkConfig}), + job, + }; +}; diff --git a/packages/cli/test/utils/simulation/validator_clients/lodestar.ts b/packages/cli/test/utils/simulation/validator_clients/lodestar.ts new file mode 100644 index 000000000000..4a14a0e2d3c8 --- /dev/null +++ b/packages/cli/test/utils/simulation/validator_clients/lodestar.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {writeFile} from "node:fs/promises"; +import path from "node:path"; +import got from "got"; +import {getClient as keyManagerGetClient} from "@lodestar/api/keymanager"; +import {chainConfigToJson} from "@lodestar/config"; +import {LogLevel} from "@lodestar/utils"; +import {IValidatorCliArgs} from "../../../../src/cmds/validator/options.js"; +import {GlobalArgs} from "../../../../src/options/globalOptions.js"; +import {LODESTAR_BINARY_PATH} from "../constants.js"; +import {RunnerType, ValidatorClient, ValidatorNodeGenerator} from "../interfaces.js"; +import {getNodePorts} from "../utils/ports.js"; + +export const generateLodestarValidatorNode: ValidatorNodeGenerator = (opts, runner) => { + const {paths, id, keys, forkConfig, genesisTime, nodeIndex} = opts; + const {rootDir, keystoresDir, keystoresSecretFilePath, logFilePath} = paths; + const ports = getNodePorts(nodeIndex); + const rcConfigPath = path.join(rootDir, "rc_config.json"); + const paramsPath = path.join(rootDir, "params.json"); + + if (keys.type === "no-keys") { + throw Error("Attempting to run a vc with keys.type == 'no-keys'"); + } + + const rcConfig = { + network: "dev", + preset: "minimal", + dataDir: rootDir, + server: `http://0.0.0.0:${ports.cl.httpPort}/`, + keymanager: true, + "keymanager.authEnabled": false, + "keymanager.address": "127.0.0.1", + "keymanager.port": ports.cl.keymanagerPort, + logPrefix: id, + logFormatGenesisTime: genesisTime, + logLevel: LogLevel.debug, + logFile: "none", + importKeystores: keystoresDir, + importKeystoresPassword: keystoresSecretFilePath, + } as unknown as IValidatorCliArgs & GlobalArgs; + + const job = runner.create([ + { + id, + type: RunnerType.ChildProcess, + bootstrap: async () => { + await writeFile(rcConfigPath, JSON.stringify(rcConfig, null, 2)); + await writeFile(paramsPath, JSON.stringify(chainConfigToJson(forkConfig), null, 2)); + }, + cli: { + command: LODESTAR_BINARY_PATH, + args: [ + "validator", + "--rcConfig", + path.join(rootDir, "rc_config.json"), + "--paramsFile", + path.join(rootDir, "params.json"), + ], + env: { + DEBUG: process.env.DISABLE_DEBUG_LOGS ? "" : "*,-winston:*", + }, + }, + logs: { + stdoutFilePath: logFilePath, + }, + health: async () => { + try { + await got.get(`http://127.0.0.1:${ports.cl.keymanagerPort}/eth/v1/keystores`); + return {ok: true}; + } catch (err) { + return {ok: false, reason: (err as Error).message, checkId: "eth/v1/keystores query"}; + } + }, + }, + ]); + + return { + id, + client: ValidatorClient.Lodestar, + url: `http://127.0.0.1:${ports.cl.httpPort}`, + keys, + keyManager: keyManagerGetClient({baseUrl: `http://127.0.0.1:${ports.cl.keymanagerPort}`}, {config: forkConfig}), + job, + }; +}; From 8221ea751074090d9a2efed4b80b194c2ecf714b Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 15 Aug 2023 18:29:49 +0200 Subject: [PATCH 04/15] Fix lint errors --- .github/workflows/test-sim.yml | 2 +- packages/cli/test/sim/backup_eth_provider.test.ts | 10 ++++++++-- packages/cli/test/utils/simulation/TableReporter.ts | 4 +++- .../test/utils/simulation/execution_clients/index.ts | 2 +- .../utils/simulation/execution_clients/nethermind.ts | 2 +- .../utils/{el_genesis.ts => execution_genesis.ts} | 5 ++++- 6 files changed, 18 insertions(+), 7 deletions(-) rename packages/cli/test/utils/simulation/utils/{el_genesis.ts => execution_genesis.ts} (96%) diff --git a/.github/workflows/test-sim.yml b/.github/workflows/test-sim.yml index 9e6491a1cede..5e64c2673982 100644 --- a/.github/workflows/test-sim.yml +++ b/.github/workflows/test-sim.yml @@ -74,7 +74,7 @@ jobs: - name: Upload debug log test files for "packages/cli" if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: debug-test-logs-cli path: packages/cli/test-logs diff --git a/packages/cli/test/sim/backup_eth_provider.test.ts b/packages/cli/test/sim/backup_eth_provider.test.ts index efb7590ace7c..fbeb2104a9dd 100644 --- a/packages/cli/test/sim/backup_eth_provider.test.ts +++ b/packages/cli/test/sim/backup_eth_provider.test.ts @@ -64,7 +64,10 @@ const node2 = await env.createNodePair({ id: "node-2", // As the Lodestar running on host and the geth running in docker container // we have to replace the IP with the local ip to connect to the geth - beacon: {type: BeaconClient.Lodestar, options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}}, + beacon: { + type: BeaconClient.Lodestar, + options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}, + }, execution: ExecutionClient.Geth, keysCount: 32, }); @@ -74,7 +77,10 @@ const node3 = await env.createNodePair({ id: "node-3", // As the Lodestar running on host and the geth running in docker container // we have to replace the IP with the local ip to connect to the geth - beacon: {type: BeaconClient.Lodestar, options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}}, + beacon: { + type: BeaconClient.Lodestar, + options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}, + }, execution: ExecutionClient.Geth, keysCount: 0, }); diff --git a/packages/cli/test/utils/simulation/TableReporter.ts b/packages/cli/test/utils/simulation/TableReporter.ts index 93d1834768bd..fd9ec6daabdc 100644 --- a/packages/cli/test/utils/simulation/TableReporter.ts +++ b/packages/cli/test/utils/simulation/TableReporter.ts @@ -56,7 +56,9 @@ export class TableReporter extends SimulationReporter const participation: {head: number; source: number; target: number}[] = []; for (const node of nodes) { - participation.push(stores["attestationParticipation"][node.beacon.id][slot] ?? {head: 0, source: 0, target: 0}); + participation.push( + stores["attestationParticipation"][node.beacon.id][slot] ?? {head: 0, source: 0, target: 0} + ); const syncCommitteeParticipation: number[] = []; for (let slot = startSlot; slot <= endSlot; slot++) { syncCommitteeParticipation.push(stores["syncCommitteeParticipation"][node.beacon.id][slot] ?? 0); diff --git a/packages/cli/test/utils/simulation/execution_clients/index.ts b/packages/cli/test/utils/simulation/execution_clients/index.ts index 840be742f3fa..62e35284677b 100644 --- a/packages/cli/test/utils/simulation/execution_clients/index.ts +++ b/packages/cli/test/utils/simulation/execution_clients/index.ts @@ -9,7 +9,7 @@ import { ExecutionStartMode, } from "../interfaces.js"; import {getEstimatedShanghaiTime} from "../utils/index.js"; -import {getGethGenesisBlock} from "../utils/el_genesis.js"; +import {getGethGenesisBlock} from "../utils/execution_genesis.js"; import {ensureDirectories} from "../utils/paths.js"; import {generateGethNode} from "./geth.js"; import {generateMockNode} from "./mock.js"; diff --git a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts index 3f8f5cfc52d7..ff57680edded 100644 --- a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts +++ b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts @@ -5,7 +5,7 @@ import got from "got"; import {ZERO_HASH} from "@lodestar/state-transition"; import {Eth1ProviderWithAdmin} from "../Eth1ProviderWithAdmin.js"; import {ExecutionClient, ExecutionNodeGenerator, JobOptions, RunnerType} from "../interfaces.js"; -import {getNethermindChainSpec} from "../utils/el_genesis.js"; +import {getNethermindChainSpec} from "../utils/execution_genesis.js"; import {getNodeMountedPaths} from "../utils/paths.js"; import {SHARED_JWT_SECRET} from "../constants.js"; import {getNodePorts} from "../utils/ports.js"; diff --git a/packages/cli/test/utils/simulation/utils/el_genesis.ts b/packages/cli/test/utils/simulation/utils/execution_genesis.ts similarity index 96% rename from packages/cli/test/utils/simulation/utils/el_genesis.ts rename to packages/cli/test/utils/simulation/utils/execution_genesis.ts index aed911527bad..9dd464379b02 100644 --- a/packages/cli/test/utils/simulation/utils/el_genesis.ts +++ b/packages/cli/test/utils/simulation/utils/execution_genesis.ts @@ -1,7 +1,10 @@ import {SIM_ENV_CHAIN_ID, SIM_ENV_NETWORK_ID} from "../constants.js"; import {ExecutionGenesisOptions, ExecutionStartMode, Eth1GenesisBlock} from "../interfaces.js"; -export const getGethGenesisBlock = (mode: ExecutionStartMode, options: ExecutionGenesisOptions): Record => { +export const getGethGenesisBlock = ( + mode: ExecutionStartMode, + options: ExecutionGenesisOptions +): Record => { const {ttd, cliqueSealingPeriod, shanghaiTime, genesisTime} = options; const genesis = { From f7bb5a0c5d2b2d9ccb7f40f85e58158d2fbdd74e Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 15 Aug 2023 19:02:04 +0200 Subject: [PATCH 05/15] Update name for ports --- .../utils/simulation/SimulationEnvironment.ts | 4 ++-- .../simulation/beacon_clients/lighthouse.ts | 2 +- .../simulation/beacon_clients/lodestar.ts | 10 +++++----- .../utils/simulation/execution_clients/geth.ts | 2 +- .../utils/simulation/execution_clients/mock.ts | 2 +- .../simulation/execution_clients/nethermind.ts | 2 +- .../cli/test/utils/simulation/utils/ports.ts | 11 +++++++---- .../simulation/validator_clients/lighthouse.ts | 18 +++++++++--------- .../simulation/validator_clients/lodestar.ts | 14 ++++++++------ 9 files changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index b9dd24c10ce0..cf2d1fd8b76e 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -164,7 +164,7 @@ export class SimulationEnvironment { } await Promise.all(this.nodes.map((node) => node.beacon.job.start())); - await Promise.all(this.nodes.map((node) => node.validator?.job.start())); + await Promise.all(this.nodes.map((node) => node.validator.job.start())); if (this.nodes.some((node) => node.validator?.keys.type === "remote")) { console.log("Starting external signer..."); @@ -201,7 +201,7 @@ export class SimulationEnvironment { process.removeAllListeners("SIGINT"); console.log(`Simulation environment "${this.options.id}" is stopping: ${message}`); await this.tracker.stop(); - await Promise.all(this.nodes.map((node) => node.validator?.job.stop())); + await Promise.all(this.nodes.map((node) => node.validator.job.stop())); await Promise.all(this.nodes.map((node) => node.beacon.job.stop())); await Promise.all(this.nodes.map((node) => node.execution.job.stop())); await this.externalSigner.stop(); diff --git a/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts b/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts index 048c3794cd92..08837f05e8e2 100644 --- a/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts +++ b/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts @@ -20,7 +20,7 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator { try { - await got.get(`http://${address}:${ports.cl.httpPort}/eth/v1/node/health`); + await got.get(`http://${address}:${ports.beacon.httpPort}/eth/v1/node/health`); return {ok: true}; } catch (err) { return {ok: false, reason: (err as Error).message, checkId: "eth/v1/node/health query"}; @@ -108,8 +108,8 @@ export const generateLodestarBeaconNode: BeaconNodeGenerator = (o const {id, mode, ttd, address, mining, clientOptions, nodeIndex} = opts; const { - el: {httpPort, enginePort, port}, + execution: {httpPort, enginePort, port}, } = getNodePorts(nodeIndex); const isDocker = process.env.GETH_DOCKER_IMAGE !== undefined; diff --git a/packages/cli/test/utils/simulation/execution_clients/mock.ts b/packages/cli/test/utils/simulation/execution_clients/mock.ts index f0c48ca9ce33..6f2633324edf 100644 --- a/packages/cli/test/utils/simulation/execution_clients/mock.ts +++ b/packages/cli/test/utils/simulation/execution_clients/mock.ts @@ -5,7 +5,7 @@ import {getNodePorts} from "../utils/ports.js"; export const generateMockNode: ExecutionNodeGenerator = (opts, runner) => { const {id, ttd, nodeIndex} = opts; const { - el: {enginePort, httpPort}, + execution: {enginePort, httpPort}, } = getNodePorts(nodeIndex); const ethRpcUrl = `http://127.0.0.1:${httpPort}`; const engineRpcUrl = `http://127.0.0.1:${enginePort}`; diff --git a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts index ff57680edded..18bffb89745b 100644 --- a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts +++ b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts @@ -18,7 +18,7 @@ export const generateNethermindNode: ExecutionNodeGenerator ({ - cl: { + beacon: { port: BN_P2P_BASE_PORT + 1 + nodeIndex, httpPort: BN_REST_BASE_PORT + 1 + nodeIndex, + }, + validator: { keymanagerPort: KEY_MANAGER_BASE_PORT + 1 + nodeIndex, }, - el: { + execution: { port: EL_P2P_BASE_PORT + 1 + nodeIndex, httpPort: EL_ETH_BASE_PORT + 1 + nodeIndex, enginePort: EL_ENGINE_BASE_PORT + 1 + nodeIndex, diff --git a/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts b/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts index 2c5df1afb7b6..cba619f78389 100644 --- a/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts +++ b/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts @@ -15,7 +15,7 @@ export const generateLighthouseValidatorNode: ValidatorNodeGenerator { try { - await got.get(`http://127.0.0.1:${keymanagerPort}/lighthouse/health`); + await got.get(`http://127.0.0.1:${ports.validator.keymanagerPort}/lighthouse/health`); return {ok: true}; } catch (err) { if (err instanceof RequestError) { @@ -96,9 +94,11 @@ export const generateLighthouseValidatorNode: ValidatorNodeGenerator = (opts, runner) => { - const {paths, id, keys, forkConfig, genesisTime, nodeIndex} = opts; + const {paths, id, keys, forkConfig, genesisTime, nodeIndex, beaconUrls} = opts; const {rootDir, keystoresDir, keystoresSecretFilePath, logFilePath} = paths; const ports = getNodePorts(nodeIndex); const rcConfigPath = path.join(rootDir, "rc_config.json"); @@ -26,11 +26,11 @@ export const generateLodestarValidatorNode: ValidatorNodeGenerator { try { - await got.get(`http://127.0.0.1:${ports.cl.keymanagerPort}/eth/v1/keystores`); + await got.get(`http://127.0.0.1:${ports.validator.keymanagerPort}/eth/v1/keystores`); return {ok: true}; } catch (err) { return {ok: false, reason: (err as Error).message, checkId: "eth/v1/keystores query"}; @@ -77,9 +77,11 @@ export const generateLodestarValidatorNode: ValidatorNodeGenerator Date: Tue, 15 Aug 2023 19:23:12 +0200 Subject: [PATCH 06/15] Update the validator optional case --- .../test/utils/simulation/SimulationEnvironment.ts | 9 +++++++-- .../test/utils/simulation/assertions/nodeAssertion.ts | 11 ++++++++--- packages/cli/test/utils/simulation/interfaces.ts | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index cf2d1fd8b76e..532b7e0f02a8 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -164,7 +164,7 @@ export class SimulationEnvironment { } await Promise.all(this.nodes.map((node) => node.beacon.job.start())); - await Promise.all(this.nodes.map((node) => node.validator.job.start())); + await Promise.all(this.nodes.map((node) => node.validator?.job.start())); if (this.nodes.some((node) => node.validator?.keys.type === "remote")) { console.log("Starting external signer..."); @@ -201,7 +201,7 @@ export class SimulationEnvironment { process.removeAllListeners("SIGINT"); console.log(`Simulation environment "${this.options.id}" is stopping: ${message}`); await this.tracker.stop(); - await Promise.all(this.nodes.map((node) => node.validator.job.stop())); + await Promise.all(this.nodes.map((node) => node.validator?.job.stop())); await Promise.all(this.nodes.map((node) => node.beacon.job.stop())); await Promise.all(this.nodes.map((node) => node.execution.job.stop())); await this.externalSigner.stop(); @@ -282,6 +282,11 @@ export class SimulationEnvironment { paths: getNodePaths({id, logsDir: this.options.logsDir, client: beaconType, root: this.options.rootDir}), }); + if (keys.type === "no-keys") { + this.nodePairCount += 1; + return {id, execution: executionNode, beacon: beaconNode}; + } + // If no validator configuration is specified we will consider that beacon type is also same as validator type const validatorType = typeof validator === "object" diff --git a/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts b/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts index 7c6f030be781..184fe54e1767 100644 --- a/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts @@ -11,13 +11,16 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana match: neverMatcher, capture: async ({node}) => { const {status: health} = await node.beacon.api.node.getHealth(); - let keyManagerKeys: string[]; + if (!node.validator) { + return {health, keyManagerKeys: []}; + } + let keyManagerKeys: string[]; // There is an authentication issue with the lighthouse keymanager client if (node.beacon.client == BeaconClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { keyManagerKeys = []; } else { - const res = await node.validator?.keyManager.listKeys(); + const res = await node.validator.keyManager.listKeys(); ApiError.assert(res); keyManagerKeys = res.response.data.map((k) => k.validatingPubkey); } @@ -36,7 +39,9 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana errors.push(["node health is neither READY or SYNCING", {node: node.beacon.id}]); } - const expectedPublicKeys = getAllKeys(node.validator?.keys).map((k) => k.toPublicKey().toHex()); + const expectedPublicKeys = node.validator + ? getAllKeys(node.validator.keys).map((k) => k.toPublicKey().toHex()) + : []; if (!arrayEquals(keyManagerKeys.sort(), expectedPublicKeys.sort())) { errors.push([ diff --git a/packages/cli/test/utils/simulation/interfaces.ts b/packages/cli/test/utils/simulation/interfaces.ts index ed75509c09ef..f30185e746f7 100644 --- a/packages/cli/test/utils/simulation/interfaces.ts +++ b/packages/cli/test/utils/simulation/interfaces.ts @@ -194,7 +194,7 @@ export interface NodePair { readonly id: string; readonly beacon: BeaconNode; readonly execution: ExecutionNode; - readonly validator: ValidatorNode; + readonly validator?: ValidatorNode; } export type BeaconNodeGenerator = ( From 451bd5432436c820b05a6966e9a7ca6a884adc9f Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Aug 2023 12:44:01 +0200 Subject: [PATCH 07/15] Update the valdiator to connect in private network --- .../cli/test/sim/backup_eth_provider.test.ts | 4 +-- packages/cli/test/sim/multi_fork.test.ts | 4 +-- .../utils/simulation/SimulationEnvironment.ts | 6 ++-- .../simulation/beacon_clients/lighthouse.ts | 30 ++++++++++--------- .../simulation/beacon_clients/lodestar.ts | 6 ++-- .../simulation/execution_clients/geth.ts | 28 +++++++++-------- .../simulation/execution_clients/mock.ts | 16 +++++----- .../execution_clients/nethermind.ts | 30 ++++++++++--------- .../cli/test/utils/simulation/interfaces.ts | 27 +++++++++++++++-- .../cli/test/utils/simulation/utils/ports.ts | 8 ++--- .../validator_clients/lighthouse.ts | 5 ++++ 11 files changed, 100 insertions(+), 64 deletions(-) diff --git a/packages/cli/test/sim/backup_eth_provider.test.ts b/packages/cli/test/sim/backup_eth_provider.test.ts index fbeb2104a9dd..4f5c0a15f9be 100644 --- a/packages/cli/test/sim/backup_eth_provider.test.ts +++ b/packages/cli/test/sim/backup_eth_provider.test.ts @@ -66,7 +66,7 @@ const node2 = await env.createNodePair({ // we have to replace the IP with the local ip to connect to the geth beacon: { type: BeaconClient.Lodestar, - options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}, + options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpPublicUrl, "127.0.0.1")]}, }, execution: ExecutionClient.Geth, keysCount: 32, @@ -79,7 +79,7 @@ const node3 = await env.createNodePair({ // we have to replace the IP with the local ip to connect to the geth beacon: { type: BeaconClient.Lodestar, - options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcUrl, "127.0.0.1")]}, + options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpPublicUrl, "127.0.0.1")]}, }, execution: ExecutionClient.Geth, keysCount: 0, diff --git a/packages/cli/test/sim/multi_fork.test.ts b/packages/cli/test/sim/multi_fork.test.ts index 117ad42e53eb..f0dbd3a8ca4f 100644 --- a/packages/cli/test/sim/multi_fork.test.ts +++ b/packages/cli/test/sim/multi_fork.test.ts @@ -57,8 +57,8 @@ const env = await SimulationEnvironment.initWithDefaults( }, [ {id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Geth, keysCount: 32, mining: true}, - {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32, remote: true}, - {id: "node-3", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32}, + // {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32, remote: true}, + // {id: "node-3", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32}, {id: "node-4", beacon: BeaconClient.Lighthouse, execution: ExecutionClient.Geth, keysCount: 32}, ] ); diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 532b7e0f02a8..7c6e8c646993 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -271,8 +271,8 @@ export class SimulationEnvironment { const engineUrls = [ // As lodestar is running on host machine, need to connect through local exposed ports beaconType === BeaconClient.Lodestar - ? replaceIpFromUrl(executionNode.engineRpcUrl, "127.0.0.1") - : executionNode.engineRpcUrl, + ? replaceIpFromUrl(executionNode.engineRpPublicUrl, "127.0.0.1") + : executionNode.engineRpPublicUrl, ...(beaconOptions?.engineUrls ?? []), ]; const beaconNode = await createBeaconNode(beaconType, { @@ -299,7 +299,7 @@ export class SimulationEnvironment { ...validatorOptions, ...commonOptions, keys, - beaconUrls: [beaconNode.url], + beaconUrls: [beaconNode.restPrivateUrl], paths: getNodePaths({id, logsDir: this.options.logsDir, client: validatorType, root: this.options.rootDir}), }); diff --git a/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts b/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts index 08837f05e8e2..159c9fa43475 100644 --- a/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts +++ b/packages/cli/test/utils/simulation/beacon_clients/lighthouse.ts @@ -17,11 +17,9 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator { - await writeFile(path.join(rootDir, "config.yaml"), yaml.dump(chainConfigToJson(config))); + await writeFile(path.join(rootDir, "config.yaml"), yaml.dump(chainConfigToJson(forkConfig))); await writeFile(path.join(rootDir, "deploy_block.txt"), "0"); }, cli: { @@ -95,7 +93,7 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator { try { - await got.get(`http://127.0.0.1:${httpPort}/eth/v1/node/health`); + await got.get(`http://127.0.0.1:${ports.beacon.httpPort}/eth/v1/node/health`); return {ok: true}; } catch (err) { if (err instanceof RequestError && err.code !== "ECONNREFUSED") { @@ -107,8 +105,11 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator = (o } const {id, mode, ttd, address, mining, clientOptions, nodeIndex} = opts; - const { - execution: {httpPort, enginePort, port}, - } = getNodePorts(nodeIndex); + const ports = getNodePorts(nodeIndex); const isDocker = process.env.GETH_DOCKER_IMAGE !== undefined; const binaryPath = isDocker ? "" : `${process.env.GETH_BINARY_DIR}/geth`; @@ -30,8 +28,10 @@ export const generateGethNode: ExecutionNodeGenerator = (o "/data", isDocker ); - const ethRpcUrl = `http://127.0.0.1:${httpPort}`; - const engineRpcUrl = `http://${address}:${enginePort}`; + const engineRpPublicUrl = `http://127.0.0.1:${ports.execution.enginePort}`; + const engineRpPrivateUrl = `http://${address}:${ports.execution.enginePort}`; + const ethRpPublicUrl = `http://127.0.0.1:${ports.execution.httpPort}`; + const ethRpPrivateUrl = `http://${address}:${ports.execution.httpPort}`; const skPath = path.join(rootDir, "sk.json"); const skPathMounted = path.join(rootDirMounted, "sk.json"); @@ -104,7 +104,7 @@ export const generateGethNode: ExecutionNodeGenerator = (o ? { image: process.env.GETH_DOCKER_IMAGE as string, mounts: [[rootDir, rootDirMounted]], - exposePorts: [enginePort, httpPort, port], + exposePorts: [ports.execution.enginePort, ports.execution.httpPort, ports.execution.p2pPort], dockerNetworkIp: address, } : undefined, @@ -115,15 +115,15 @@ export const generateGethNode: ExecutionNodeGenerator = (o "--http.api", "engine,net,eth,miner,admin", "--http.port", - String(httpPort as number), + String(ports.execution.httpPort as number), "--http.addr", "0.0.0.0", "--authrpc.port", - String(enginePort as number), + String(ports.execution.enginePort as number), "--authrpc.addr", "0.0.0.0", "--port", - String(port as number), + String(ports.execution.p2pPort as number), "--nat", `extip:${address}`, "--authrpc.jwtsecret", @@ -153,7 +153,7 @@ export const generateGethNode: ExecutionNodeGenerator = (o }, health: async () => { try { - await got.post(ethRpcUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); + await got.post(ethRpPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); return {ok: true}; } catch (err) { return {ok: false, reason: (err as Error).message, checkId: "JSON RPC query net_version"}; @@ -166,14 +166,16 @@ export const generateGethNode: ExecutionNodeGenerator = (o const provider = new Eth1ProviderWithAdmin( {DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, // To allow admin_* RPC methods had to add "ethRpcUrl" - {providerUrls: [`http://127.0.0.1:${httpPort}`, `http://127.0.0.1:${enginePort}`], jwtSecretHex: SHARED_JWT_SECRET} + {providerUrls: [ethRpPublicUrl, engineRpPublicUrl], jwtSecretHex: SHARED_JWT_SECRET} ); return { client: ExecutionClient.Geth, id, - engineRpcUrl, - ethRpcUrl, + engineRpPublicUrl, + engineRpPrivateUrl, + ethRpPublicUrl, + ethRpPrivateUrl, ttd, jwtSecretHex: SHARED_JWT_SECRET, provider, diff --git a/packages/cli/test/utils/simulation/execution_clients/mock.ts b/packages/cli/test/utils/simulation/execution_clients/mock.ts index 6f2633324edf..74984be74274 100644 --- a/packages/cli/test/utils/simulation/execution_clients/mock.ts +++ b/packages/cli/test/utils/simulation/execution_clients/mock.ts @@ -4,19 +4,21 @@ import {getNodePorts} from "../utils/ports.js"; export const generateMockNode: ExecutionNodeGenerator = (opts, runner) => { const {id, ttd, nodeIndex} = opts; - const { - execution: {enginePort, httpPort}, - } = getNodePorts(nodeIndex); - const ethRpcUrl = `http://127.0.0.1:${httpPort}`; - const engineRpcUrl = `http://127.0.0.1:${enginePort}`; + const ports = getNodePorts(nodeIndex); + const engineRpPublicUrl = `http://127.0.0.1:${ports.execution.enginePort}`; + const engineRpPrivateUrl = engineRpPublicUrl; + const ethRpPublicUrl = `http://127.0.0.1:${ports.execution.httpPort}`; + const ethRpPrivateUrl = ethRpPublicUrl; const job = runner.create([]); return { client: ExecutionClient.Mock, id, - engineRpcUrl, - ethRpcUrl, + engineRpPublicUrl, + engineRpPrivateUrl, + ethRpPublicUrl, + ethRpPrivateUrl, ttd, jwtSecretHex: SHARED_JWT_SECRET, provider: null, diff --git a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts index 18bffb89745b..2f0aba282917 100644 --- a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts +++ b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts @@ -17,9 +17,7 @@ export const generateNethermindNode: ExecutionNodeGenerator { @@ -62,11 +62,11 @@ export const generateNethermindNode: ExecutionNodeGenerator { try { - await got.post(ethRpcUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); + await got.post(ethRpPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); return {ok: true}; } catch (err) { return {ok: false, reason: (err as Error).message, checkId: "JSON RPC query net_version"}; @@ -115,14 +115,16 @@ export const generateNethermindNode: ExecutionNodeGenerator & { export interface BeaconNode { readonly client: C; readonly id: string; - readonly url: string; + /** + * Beacon Node Rest API URL accessible form the host machine if the process is running in private network inside docker + */ + readonly restPublicUrl: string; + /** + * Beacon Node Rest API URL accessible within private network + */ + readonly restPrivateUrl: string; readonly api: C extends BeaconClient.Lodestar ? LodestarAPI : LighthouseAPI; readonly job: Job; } @@ -183,8 +190,22 @@ export interface ExecutionNode { readonly client: E; readonly id: string; readonly ttd: bigint; - readonly engineRpcUrl: string; - readonly ethRpcUrl: string; + /** + * Engine URL accessible form the host machine if the process is running in private network inside docker + */ + readonly engineRpPublicUrl: string; + /** + * Engine URL accessible within private network inside docker + */ + readonly engineRpPrivateUrl: string; + /** + * RPC URL accessible form the host machine if the process is running in private network inside docker + */ + readonly ethRpPublicUrl: string; + /** + * RPC URL accessible within private network inside docker + */ + readonly ethRpPrivateUrl: string; readonly jwtSecretHex: string; readonly provider: E extends ExecutionClient.Mock ? null : Eth1ProviderWithAdmin; readonly job: Job; diff --git a/packages/cli/test/utils/simulation/utils/ports.ts b/packages/cli/test/utils/simulation/utils/ports.ts index bab2c434180d..be430598895e 100644 --- a/packages/cli/test/utils/simulation/utils/ports.ts +++ b/packages/cli/test/utils/simulation/utils/ports.ts @@ -10,19 +10,19 @@ import { export const getNodePorts = ( nodeIndex: number ): { - beacon: {port: number; httpPort: number}; + beacon: {p2pPort: number; httpPort: number}; validator: {keymanagerPort: number}; - execution: {port: number; enginePort: number; httpPort: number}; + execution: {p2pPort: number; enginePort: number; httpPort: number}; } => ({ beacon: { - port: BN_P2P_BASE_PORT + 1 + nodeIndex, + p2pPort: BN_P2P_BASE_PORT + 1 + nodeIndex, httpPort: BN_REST_BASE_PORT + 1 + nodeIndex, }, validator: { keymanagerPort: KEY_MANAGER_BASE_PORT + 1 + nodeIndex, }, execution: { - port: EL_P2P_BASE_PORT + 1 + nodeIndex, + p2pPort: EL_P2P_BASE_PORT + 1 + nodeIndex, httpPort: EL_ETH_BASE_PORT + 1 + nodeIndex, enginePort: EL_ENGINE_BASE_PORT + 1 + nodeIndex, }, diff --git a/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts b/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts index cba619f78389..27b2f485588d 100644 --- a/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts +++ b/packages/cli/test/utils/simulation/validator_clients/lighthouse.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; +import {writeFile} from "node:fs/promises"; import got, {RequestError} from "got"; +import yaml from "js-yaml"; import {getClient as keyManagerGetClient} from "@lodestar/api/keymanager"; +import {chainConfigToJson} from "@lodestar/config"; import {RunnerType, ValidatorClient, ValidatorNodeGenerator} from "../interfaces.js"; import {updateKeystoresPath} from "../utils/keys.js"; import {getNodeMountedPaths} from "../utils/paths.js"; @@ -63,6 +66,8 @@ export const generateLighthouseValidatorNode: ValidatorNodeGenerator Date: Wed, 16 Aug 2023 13:32:17 +0200 Subject: [PATCH 08/15] Fix private engine url --- packages/cli/test/sim/multi_fork.test.ts | 4 ++-- packages/cli/test/utils/simulation/SimulationEnvironment.ts | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/cli/test/sim/multi_fork.test.ts b/packages/cli/test/sim/multi_fork.test.ts index f0dbd3a8ca4f..117ad42e53eb 100644 --- a/packages/cli/test/sim/multi_fork.test.ts +++ b/packages/cli/test/sim/multi_fork.test.ts @@ -57,8 +57,8 @@ const env = await SimulationEnvironment.initWithDefaults( }, [ {id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Geth, keysCount: 32, mining: true}, - // {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32, remote: true}, - // {id: "node-3", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32}, + {id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32, remote: true}, + {id: "node-3", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Nethermind, keysCount: 32}, {id: "node-4", beacon: BeaconClient.Lighthouse, execution: ExecutionClient.Geth, keysCount: 32}, ] ); diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 7c6e8c646993..a4936cbd8a3f 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -35,7 +35,7 @@ import { GeneratorOptions, } from "./interfaces.js"; import {Runner} from "./runner/index.js"; -import {getEstimatedTTD, registerProcessHandler, replaceIpFromUrl} from "./utils/index.js"; +import {getEstimatedTTD, registerProcessHandler} from "./utils/index.js"; import {getNodePaths} from "./utils/paths.js"; interface StartOpts { @@ -270,9 +270,7 @@ export class SimulationEnvironment { const beaconOptions = typeof beacon === "object" ? beacon.options : {}; const engineUrls = [ // As lodestar is running on host machine, need to connect through local exposed ports - beaconType === BeaconClient.Lodestar - ? replaceIpFromUrl(executionNode.engineRpPublicUrl, "127.0.0.1") - : executionNode.engineRpPublicUrl, + beaconType === BeaconClient.Lodestar ? executionNode.engineRpPublicUrl : executionNode.engineRpPrivateUrl, ...(beaconOptions?.engineUrls ?? []), ]; const beaconNode = await createBeaconNode(beaconType, { From 1a62df0632b7c83d7cf13cb77ae5c7233f87f25a Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Aug 2023 13:45:15 +0200 Subject: [PATCH 09/15] Fix nethermind cli args typo --- .../cli/test/sim/backup_eth_provider.test.ts | 4 ++-- .../utils/simulation/SimulationEnvironment.ts | 2 +- .../simulation/execution_clients/geth.ts | 20 ++++++++--------- .../simulation/execution_clients/mock.ts | 16 +++++++------- .../execution_clients/nethermind.ts | 22 +++++++++---------- .../cli/test/utils/simulation/interfaces.ts | 8 +++---- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/cli/test/sim/backup_eth_provider.test.ts b/packages/cli/test/sim/backup_eth_provider.test.ts index 4f5c0a15f9be..a9f4fa00f1a4 100644 --- a/packages/cli/test/sim/backup_eth_provider.test.ts +++ b/packages/cli/test/sim/backup_eth_provider.test.ts @@ -66,7 +66,7 @@ const node2 = await env.createNodePair({ // we have to replace the IP with the local ip to connect to the geth beacon: { type: BeaconClient.Lodestar, - options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpPublicUrl, "127.0.0.1")]}, + options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcPublicUrl, "127.0.0.1")]}, }, execution: ExecutionClient.Geth, keysCount: 32, @@ -79,7 +79,7 @@ const node3 = await env.createNodePair({ // we have to replace the IP with the local ip to connect to the geth beacon: { type: BeaconClient.Lodestar, - options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpPublicUrl, "127.0.0.1")]}, + options: {engineUrls: [replaceIpFromUrl(env.nodes[0].execution.engineRpcPublicUrl, "127.0.0.1")]}, }, execution: ExecutionClient.Geth, keysCount: 0, diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index a4936cbd8a3f..82aabdf95ada 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -270,7 +270,7 @@ export class SimulationEnvironment { const beaconOptions = typeof beacon === "object" ? beacon.options : {}; const engineUrls = [ // As lodestar is running on host machine, need to connect through local exposed ports - beaconType === BeaconClient.Lodestar ? executionNode.engineRpPublicUrl : executionNode.engineRpPrivateUrl, + beaconType === BeaconClient.Lodestar ? executionNode.engineRpcPublicUrl : executionNode.engineRpcPrivateUrl, ...(beaconOptions?.engineUrls ?? []), ]; const beaconNode = await createBeaconNode(beaconType, { diff --git a/packages/cli/test/utils/simulation/execution_clients/geth.ts b/packages/cli/test/utils/simulation/execution_clients/geth.ts index 8a5dae327d5a..bb17d1698330 100644 --- a/packages/cli/test/utils/simulation/execution_clients/geth.ts +++ b/packages/cli/test/utils/simulation/execution_clients/geth.ts @@ -28,10 +28,10 @@ export const generateGethNode: ExecutionNodeGenerator = (o "/data", isDocker ); - const engineRpPublicUrl = `http://127.0.0.1:${ports.execution.enginePort}`; - const engineRpPrivateUrl = `http://${address}:${ports.execution.enginePort}`; - const ethRpPublicUrl = `http://127.0.0.1:${ports.execution.httpPort}`; - const ethRpPrivateUrl = `http://${address}:${ports.execution.httpPort}`; + const engineRpcPublicUrl = `http://127.0.0.1:${ports.execution.enginePort}`; + const engineRpcPrivateUrl = `http://${address}:${ports.execution.enginePort}`; + const ethRpcPublicUrl = `http://127.0.0.1:${ports.execution.httpPort}`; + const ethRpcPrivateUrl = `http://${address}:${ports.execution.httpPort}`; const skPath = path.join(rootDir, "sk.json"); const skPathMounted = path.join(rootDirMounted, "sk.json"); @@ -153,7 +153,7 @@ export const generateGethNode: ExecutionNodeGenerator = (o }, health: async () => { try { - await got.post(ethRpPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); + await got.post(ethRpcPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); return {ok: true}; } catch (err) { return {ok: false, reason: (err as Error).message, checkId: "JSON RPC query net_version"}; @@ -166,16 +166,16 @@ export const generateGethNode: ExecutionNodeGenerator = (o const provider = new Eth1ProviderWithAdmin( {DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, // To allow admin_* RPC methods had to add "ethRpcUrl" - {providerUrls: [ethRpPublicUrl, engineRpPublicUrl], jwtSecretHex: SHARED_JWT_SECRET} + {providerUrls: [ethRpcPublicUrl, engineRpcPublicUrl], jwtSecretHex: SHARED_JWT_SECRET} ); return { client: ExecutionClient.Geth, id, - engineRpPublicUrl, - engineRpPrivateUrl, - ethRpPublicUrl, - ethRpPrivateUrl, + engineRpcPublicUrl, + engineRpcPrivateUrl, + ethRpcPublicUrl, + ethRpcPrivateUrl, ttd, jwtSecretHex: SHARED_JWT_SECRET, provider, diff --git a/packages/cli/test/utils/simulation/execution_clients/mock.ts b/packages/cli/test/utils/simulation/execution_clients/mock.ts index 74984be74274..f10897f16d2e 100644 --- a/packages/cli/test/utils/simulation/execution_clients/mock.ts +++ b/packages/cli/test/utils/simulation/execution_clients/mock.ts @@ -5,20 +5,20 @@ import {getNodePorts} from "../utils/ports.js"; export const generateMockNode: ExecutionNodeGenerator = (opts, runner) => { const {id, ttd, nodeIndex} = opts; const ports = getNodePorts(nodeIndex); - const engineRpPublicUrl = `http://127.0.0.1:${ports.execution.enginePort}`; - const engineRpPrivateUrl = engineRpPublicUrl; - const ethRpPublicUrl = `http://127.0.0.1:${ports.execution.httpPort}`; - const ethRpPrivateUrl = ethRpPublicUrl; + const engineRpcPublicUrl = `http://127.0.0.1:${ports.execution.enginePort}`; + const engineRpcPrivateUrl = engineRpcPublicUrl; + const ethRpcPublicUrl = `http://127.0.0.1:${ports.execution.httpPort}`; + const ethRpcPrivateUrl = ethRpcPublicUrl; const job = runner.create([]); return { client: ExecutionClient.Mock, id, - engineRpPublicUrl, - engineRpPrivateUrl, - ethRpPublicUrl, - ethRpPrivateUrl, + engineRpcPublicUrl, + engineRpcPrivateUrl, + ethRpcPublicUrl, + ethRpcPrivateUrl, ttd, jwtSecretHex: SHARED_JWT_SECRET, provider: null, diff --git a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts index 2f0aba282917..88932f1ffc87 100644 --- a/packages/cli/test/utils/simulation/execution_clients/nethermind.ts +++ b/packages/cli/test/utils/simulation/execution_clients/nethermind.ts @@ -27,10 +27,10 @@ export const generateNethermindNode: ExecutionNodeGenerator { try { - await got.post(ethRpPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); + await got.post(ethRpcPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); return {ok: true}; } catch (err) { return {ok: false, reason: (err as Error).message, checkId: "JSON RPC query net_version"}; @@ -115,16 +115,16 @@ export const generateNethermindNode: ExecutionNodeGenerator { /** * Engine URL accessible form the host machine if the process is running in private network inside docker */ - readonly engineRpPublicUrl: string; + readonly engineRpcPublicUrl: string; /** * Engine URL accessible within private network inside docker */ - readonly engineRpPrivateUrl: string; + readonly engineRpcPrivateUrl: string; /** * RPC URL accessible form the host machine if the process is running in private network inside docker */ - readonly ethRpPublicUrl: string; + readonly ethRpcPublicUrl: string; /** * RPC URL accessible within private network inside docker */ - readonly ethRpPrivateUrl: string; + readonly ethRpcPrivateUrl: string; readonly jwtSecretHex: string; readonly provider: E extends ExecutionClient.Mock ? null : Eth1ProviderWithAdmin; readonly job: Job; From 29656148516b01d6d1d6096e8d866ef6174af526 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Aug 2023 14:09:26 +0200 Subject: [PATCH 10/15] Fix genesis state for nodes --- packages/cli/test/utils/simulation/SimulationEnvironment.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 82aabdf95ada..8f5e18b52234 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -276,6 +276,7 @@ export class SimulationEnvironment { const beaconNode = await createBeaconNode(beaconType, { ...beaconOptions, ...commonOptions, + genesisState: this.genesisState, engineUrls, paths: getNodePaths({id, logsDir: this.options.logsDir, client: beaconType, root: this.options.rootDir}), }); From 143d5f76535a127f4573136d9714e761a4312cfc Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Aug 2023 15:06:32 +0200 Subject: [PATCH 11/15] Fix the host url for docker --- .../test/utils/simulation/SimulationEnvironment.ts | 12 ++++++++++-- .../utils/simulation/assertions/nodeAssertion.ts | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 8f5e18b52234..f7104c61e1e3 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -35,7 +35,7 @@ import { GeneratorOptions, } from "./interfaces.js"; import {Runner} from "./runner/index.js"; -import {getEstimatedTTD, registerProcessHandler} from "./utils/index.js"; +import {getEstimatedTTD, registerProcessHandler, replaceIpFromUrl} from "./utils/index.js"; import {getNodePaths} from "./utils/paths.js"; interface StartOpts { @@ -294,11 +294,19 @@ export class SimulationEnvironment { ? getValidatorForBeaconNode(beaconType) : validator; const validatorOptions = typeof validator === "object" ? validator.options : {}; + const beaconUrls = [ + // As lodestar is running on host machine, need to connect through docker named host + beaconType === BeaconClient.Lodestar && validatorType !== ValidatorClient.Lodestar + ? replaceIpFromUrl(beaconNode.restPrivateUrl, "host.docker.internal") + : beaconNode.restPrivateUrl, + ...(validatorOptions?.beaconUrls ?? []), + ]; + const validatorNode = await createValidatorNode(validatorType, { ...validatorOptions, ...commonOptions, keys, - beaconUrls: [beaconNode.restPrivateUrl], + beaconUrls, paths: getNodePaths({id, logsDir: this.options.logsDir, client: validatorType, root: this.options.rootDir}), }); diff --git a/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts b/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts index 184fe54e1767..25784b688d46 100644 --- a/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts +++ b/packages/cli/test/utils/simulation/assertions/nodeAssertion.ts @@ -1,7 +1,7 @@ import type {SecretKey} from "@chainsafe/bls/types"; import {routes} from "@lodestar/api/beacon"; import {ApiError} from "@lodestar/api"; -import {AssertionResult, BeaconClient, ValidatorClientKeys, SimulationAssertion} from "../interfaces.js"; +import {AssertionResult, ValidatorClientKeys, SimulationAssertion, ValidatorClient} from "../interfaces.js"; import {arrayEquals} from "../utils/index.js"; import {neverMatcher} from "./matchers.js"; @@ -17,7 +17,7 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana let keyManagerKeys: string[]; // There is an authentication issue with the lighthouse keymanager client - if (node.beacon.client == BeaconClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { + if (node.validator.client == ValidatorClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { keyManagerKeys = []; } else { const res = await node.validator.keyManager.listKeys(); @@ -31,7 +31,7 @@ export const nodeAssertion: SimulationAssertion<"node", {health: number; keyMana const errors: AssertionResult[] = []; // There is an authentication issue with the lighthouse keymanager client - if (node.beacon.client == BeaconClient.Lighthouse) return errors; + if (node.validator?.client == ValidatorClient.Lighthouse) return errors; const {health, keyManagerKeys} = store[slot]; From 7d181962af8d101fa2f67e492ca40e7a2830a1f9 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Aug 2023 15:41:22 +0200 Subject: [PATCH 12/15] Disable the multi client test --- .github/workflows/test-sim.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-sim.yml b/.github/workflows/test-sim.yml index 5e64c2673982..6494acc3923d 100644 --- a/.github/workflows/test-sim.yml +++ b/.github/workflows/test-sim.yml @@ -15,7 +15,7 @@ on: debug: description: Runtime DEBUG value required: false - default: '' + default: "" env: GETH_DOCKER_IMAGE: ethereum/client-go:v1.11.6 @@ -56,10 +56,6 @@ jobs: run: DEBUG='${{github.event.inputs.debug}}' yarn test:sim:multifork working-directory: packages/cli - - name: Sim tests multi client - run: DEBUG='${{github.event.inputs.debug}}' yarn test:sim:multiclient - working-directory: packages/cli - - name: Sim tests endpoints run: yarn test:sim:endpoints working-directory: packages/cli @@ -72,6 +68,12 @@ jobs: run: yarn test:sim:backup_eth_provider working-directory: packages/cli + # Enable these tests after fixing the following issue + # https://github.com/ChainSafe/lodestar/issues/5553 + # - name: Sim tests multi client + # run: DEBUG='${{github.event.inputs.debug}}' yarn test:sim:multiclient + # working-directory: packages/cli + - name: Upload debug log test files for "packages/cli" if: ${{ always() }} uses: actions/upload-artifact@v3 From 1ed675b0d2f51349b0cc0b4f825e8f1b10d2b72b Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Aug 2023 16:18:54 +0200 Subject: [PATCH 13/15] Fix the client options --- packages/cli/test/utils/simulation/SimulationEnvironment.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index f7104c61e1e3..e981ec9fdba5 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -279,6 +279,7 @@ export class SimulationEnvironment { genesisState: this.genesisState, engineUrls, paths: getNodePaths({id, logsDir: this.options.logsDir, client: beaconType, root: this.options.rootDir}), + clientOptions: beaconOptions.clientOptions, }); if (keys.type === "no-keys") { @@ -308,6 +309,7 @@ export class SimulationEnvironment { keys, beaconUrls, paths: getNodePaths({id, logsDir: this.options.logsDir, client: validatorType, root: this.options.rootDir}), + clientOptions: validatorOptions.clientOptions, }); this.nodePairCount += 1; From d628ce8abc85241aa546bd6ed990ecef2fc745c8 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 17 Aug 2023 11:21:19 +0200 Subject: [PATCH 14/15] Update the client options --- packages/cli/test/utils/simulation/SimulationEnvironment.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index e981ec9fdba5..245f64c0139d 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -262,7 +262,6 @@ export class SimulationEnvironment { client: executionType, logsDir: this.options.logsDir, }), - clientOptions: executionOptions.clientOptions, }); // Beacon Node @@ -279,7 +278,6 @@ export class SimulationEnvironment { genesisState: this.genesisState, engineUrls, paths: getNodePaths({id, logsDir: this.options.logsDir, client: beaconType, root: this.options.rootDir}), - clientOptions: beaconOptions.clientOptions, }); if (keys.type === "no-keys") { @@ -309,7 +307,6 @@ export class SimulationEnvironment { keys, beaconUrls, paths: getNodePaths({id, logsDir: this.options.logsDir, client: validatorType, root: this.options.rootDir}), - clientOptions: validatorOptions.clientOptions, }); this.nodePairCount += 1; From c41acf953f0baa5fb8532b62f3e8658b99d93f32 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 17 Aug 2023 12:18:21 +0200 Subject: [PATCH 15/15] Update the tracker to stop printing progress when stopped --- packages/cli/test/utils/simulation/SimulationTracker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cli/test/utils/simulation/SimulationTracker.ts b/packages/cli/test/utils/simulation/SimulationTracker.ts index b1da9a899008..aa38a5c6dcec 100644 --- a/packages/cli/test/utils/simulation/SimulationTracker.ts +++ b/packages/cli/test/utils/simulation/SimulationTracker.ts @@ -73,6 +73,7 @@ export class SimulationTracker { private nodes: NodePair[]; private clock: EpochClock; private forkConfig: ChainForkConfig; + private running: boolean = false; private errors: SimulationAssertionError[] = []; private stores: Stores; @@ -153,6 +154,7 @@ export class SimulationTracker { async start(): Promise { debug("starting tracker"); + this.running = true; for (const node of this.nodes) { this.initEventStreamForNode(node); } @@ -165,11 +167,11 @@ export class SimulationTracker { } async stop(): Promise { - // Do nothing; + this.running = false; } async clockLoop(slot: number): Promise { - while (!this.signal.aborted) { + while (this.running && !this.signal.aborted) { // Wait for 2/3 of the slot to consider it missed await this.clock.waitForStartOfSlot(slot + 2 / 3, slot > 0).catch((e) => { console.error("error on waitForStartOfSlot", e);