From f74f9ef0874c01d625c04edcaca9489c1fdbe179 Mon Sep 17 00:00:00 2001 From: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 7 Jun 2022 18:50:29 +0200 Subject: [PATCH] Deprecate accounts CLI (#4104) * Deprecate account cli cmd * Merge options * Delete stable account e2e test * Log error.message not the error object * Review PR --- .../src/cmds/account/cmds/validator/create.ts | 110 --------------- .../cmds/account/cmds/validator/deposit.ts | 18 --- .../src/cmds/account/cmds/validator/index.ts | 17 --- .../cmds/account/cmds/validator/options.ts | 21 --- .../cmds/account/cmds/validator/recover.ts | 131 ------------------ .../src/cmds/account/cmds/wallet/create.ts | 108 --------------- .../cli/src/cmds/account/cmds/wallet/index.ts | 13 -- .../cli/src/cmds/account/cmds/wallet/list.ts | 31 ----- .../src/cmds/account/cmds/wallet/options.ts | 14 -- .../src/cmds/account/cmds/wallet/recover.ts | 67 --------- .../cli/src/cmds/account/cmds/wallet/utils.ts | 35 ----- packages/cli/src/cmds/account/index.ts | 27 +++- packages/cli/src/cmds/account/paths.ts | 56 -------- .../{account/cmds => }/validator/import.ts | 12 +- packages/cli/src/cmds/validator/index.ts | 7 +- packages/cli/src/cmds/validator/keys.ts | 2 +- .../cmds/{account/cmds => }/validator/list.ts | 14 +- packages/cli/src/cmds/validator/options.ts | 23 ++- packages/cli/src/cmds/validator/paths.ts | 53 +++++++ .../validator/slashingProtection/export.ts | 8 +- .../validator/slashingProtection/import.ts | 8 +- .../validator/slashingProtection/index.ts | 6 +- .../validator/slashingProtection/options.ts | 4 +- .../validator/slashingProtection/utils.ts | 10 +- .../cmds => }/validator/voluntaryExit.ts | 26 ++-- packages/cli/test/e2e/cmds/account.test.ts | 124 ----------------- packages/cli/test/e2e/example.test.ts | 89 ++++++++++++ 27 files changed, 231 insertions(+), 803 deletions(-) delete mode 100644 packages/cli/src/cmds/account/cmds/validator/create.ts delete mode 100644 packages/cli/src/cmds/account/cmds/validator/deposit.ts delete mode 100644 packages/cli/src/cmds/account/cmds/validator/index.ts delete mode 100644 packages/cli/src/cmds/account/cmds/validator/options.ts delete mode 100644 packages/cli/src/cmds/account/cmds/validator/recover.ts delete mode 100644 packages/cli/src/cmds/account/cmds/wallet/create.ts delete mode 100644 packages/cli/src/cmds/account/cmds/wallet/index.ts delete mode 100644 packages/cli/src/cmds/account/cmds/wallet/list.ts delete mode 100644 packages/cli/src/cmds/account/cmds/wallet/options.ts delete mode 100644 packages/cli/src/cmds/account/cmds/wallet/recover.ts delete mode 100644 packages/cli/src/cmds/account/cmds/wallet/utils.ts delete mode 100644 packages/cli/src/cmds/account/paths.ts rename packages/cli/src/cmds/{account/cmds => }/validator/import.ts (94%) rename packages/cli/src/cmds/{account/cmds => }/validator/list.ts (60%) rename packages/cli/src/cmds/{account/cmds => }/validator/slashingProtection/export.ts (85%) rename packages/cli/src/cmds/{account/cmds => }/validator/slashingProtection/import.ts (84%) rename packages/cli/src/cmds/{account/cmds => }/validator/slashingProtection/index.ts (75%) rename packages/cli/src/cmds/{account/cmds => }/validator/slashingProtection/options.ts (70%) rename packages/cli/src/cmds/{account/cmds => }/validator/slashingProtection/utils.ts (81%) rename packages/cli/src/cmds/{account/cmds => }/validator/voluntaryExit.ts (80%) delete mode 100644 packages/cli/test/e2e/cmds/account.test.ts create mode 100644 packages/cli/test/e2e/example.test.ts diff --git a/packages/cli/src/cmds/account/cmds/validator/create.ts b/packages/cli/src/cmds/account/cmds/validator/create.ts deleted file mode 100644 index 82cfb6a65bf..00000000000 --- a/packages/cli/src/cmds/account/cmds/validator/create.ts +++ /dev/null @@ -1,110 +0,0 @@ -import {MAX_EFFECTIVE_BALANCE} from "@chainsafe/lodestar-params"; -import {getAccountPaths} from "../../paths.js"; -import {WalletManager} from "../../../../wallet/index.js"; -import {ValidatorDirBuilder} from "../../../../validatorDir/index.js"; -import {getBeaconConfigFromArgs} from "../../../../config/index.js"; -import {ICliCommand, YargsError, readPassphraseFile, add0xPrefix, ICliCommandOptions} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {IAccountValidatorArgs} from "./options.js"; - -export interface IValidatorCreateArgs { - name: string; - passphraseFile: string; - depositGwei?: string; - storeWithdrawalKeystore?: boolean; - count: number; -} - -export type ReturnType = string[]; - -export const validatorCreateOptions: ICliCommandOptions = { - name: { - description: "Use the wallet identified by this name", - alias: ["n"], - demandOption: true, - type: "string", - }, - - passphraseFile: { - description: "A path to a file containing the password which will unlock the wallet.", - alias: ["p"], - demandOption: true, - type: "string", - }, - - depositGwei: { - description: - "The GWEI value of the deposit amount. Defaults to the minimum amount \ -required for an active validator (MAX_EFFECTIVE_BALANCE)", - type: "string", - }, - - storeWithdrawalKeystore: { - description: - "If present, the withdrawal keystore will be stored alongside the voting \ -keypair. It is generally recommended to *not* store the withdrawal key and \ -instead generate them from the wallet seed when required.", - type: "boolean", - }, - - count: { - description: "The number of validators to create", - default: 1, - type: "number", - }, -}; - -export const create: ICliCommand = { - command: "create", - - describe: - "Creates new validators from an existing EIP-2386 wallet using the EIP-2333 HD key \ -derivation scheme. Creates a new directory per validator with a voting keystore, withdrawal keystore, \ -and pre-computed deposit RPL data", - - examples: [ - { - command: "account validator create --name primary --passphraseFile primary.pass", - description: "Create a validator from HD wallet named 'primary'", - }, - ], - - options: validatorCreateOptions, - - handler: async (args) => { - const config = getBeaconConfigFromArgs(args); - - const {name, passphraseFile, storeWithdrawalKeystore, count} = args; - const accountPaths = getAccountPaths(args); - const maxEffectiveBalance = MAX_EFFECTIVE_BALANCE; - const depositGwei = Number(args.depositGwei || 0) || maxEffectiveBalance; - - if (depositGwei > maxEffectiveBalance) - throw new YargsError(`depositGwei ${depositGwei} is higher than MAX_EFFECTIVE_BALANCE ${maxEffectiveBalance}`); - - const validatorDirBuilder = new ValidatorDirBuilder(accountPaths); - const walletManager = new WalletManager(accountPaths); - const wallet = walletManager.openByName(name); - if (count <= 0) throw new YargsError("No validators to create"); - - const walletPassword = readPassphraseFile(passphraseFile); - - const pubkeys: string[] = []; - for (let i = 0; i < count; i++) { - const passwords = wallet.randomPasswords(); - const keystores = await wallet.nextValidator(walletPassword, passwords); - await validatorDirBuilder.build({keystores, passwords, storeWithdrawalKeystore, depositGwei, config}); - - // Persist the nextaccount index after successfully creating the validator directory - walletManager.writeWallet(wallet); - - const pubkey = add0xPrefix(keystores.signing.pubkey); - // eslint-disable-next-line no-console - console.log(`${i}/${count}\t${pubkey}`); - pubkeys.push(pubkey); - } - - // Return values for testing - return pubkeys; - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/validator/deposit.ts b/packages/cli/src/cmds/account/cmds/validator/deposit.ts deleted file mode 100644 index 5f9c6f07c12..00000000000 --- a/packages/cli/src/cmds/account/cmds/validator/deposit.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {YargsError, ICliCommand} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {IAccountValidatorArgs} from "./options.js"; - -const deprecatedDescription = - "DEPRECATED. Please use the official tools to perform your deposits \ -- eth2.0-deposit-cli: https://github.com/ethereum/eth2.0-deposit-cli \ -- Ethereum Foundation launchpad: https://launchpad.ethereum.org"; - -export const deposit: ICliCommand, IAccountValidatorArgs & IGlobalArgs> = { - command: "deposit", - describe: deprecatedDescription, - examples: [], - options: {}, - handler: async () => { - throw new YargsError(deprecatedDescription); - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/validator/index.ts b/packages/cli/src/cmds/account/cmds/validator/index.ts deleted file mode 100644 index b14344e803f..00000000000 --- a/packages/cli/src/cmds/account/cmds/validator/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {ICliCommand} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {accountValidatorOptions, IAccountValidatorArgs} from "./options.js"; -import {create} from "./create.js"; -import {deposit} from "./deposit.js"; -import {importCmd} from "./import.js"; -import {list} from "./list.js"; -import {slashingProtection} from "./slashingProtection/index.js"; -import {voluntaryExit} from "./voluntaryExit.js"; -import {recover} from "./recover.js"; - -export const validator: ICliCommand = { - command: "validator ", - describe: "Provides commands for managing Ethereum Consensus validators.", - options: accountValidatorOptions, - subcommands: [create, deposit, importCmd, list, recover, slashingProtection, voluntaryExit], -}; diff --git a/packages/cli/src/cmds/account/cmds/validator/options.ts b/packages/cli/src/cmds/account/cmds/validator/options.ts deleted file mode 100644 index a215d60e68c..00000000000 --- a/packages/cli/src/cmds/account/cmds/validator/options.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ICliCommandOptions} from "../../../../util/index.js"; -import {defaultAccountPaths} from "../../paths.js"; - -export interface IAccountValidatorArgs { - keystoresDir?: string; - secretsDir?: string; -} - -export const accountValidatorOptions: ICliCommandOptions = { - keystoresDir: { - description: "Directory for storing validator keystores.", - defaultDescription: defaultAccountPaths.keystoresDir, - type: "string", - }, - - secretsDir: { - description: "Directory for storing validator keystore secrets.", - defaultDescription: defaultAccountPaths.secretsDir, - type: "string", - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/validator/recover.ts b/packages/cli/src/cmds/account/cmds/validator/recover.ts deleted file mode 100644 index a6e7969cb70..00000000000 --- a/packages/cli/src/cmds/account/cmds/validator/recover.ts +++ /dev/null @@ -1,131 +0,0 @@ -import * as fs from "node:fs"; -import inquirer from "inquirer"; -import {validateMnemonic} from "bip39"; -import mapValues from "lodash/mapValues.js"; -import { - deriveEth2ValidatorKeys, - deriveKeyFromMnemonic, - eth2ValidatorPaths, - IEth2ValidatorKeys, -} from "@chainsafe/bls-keygen"; -import bls from "@chainsafe/bls"; -import {Keystore} from "@chainsafe/bls-keystore"; -import {MAX_EFFECTIVE_BALANCE} from "@chainsafe/lodestar-params"; -import {getBeaconConfigFromArgs} from "../../../../config/index.js"; -import {getAccountPaths} from "../../paths.js"; -import {ValidatorDirBuilder} from "../../../../validatorDir/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {add0xPrefix, ICliCommand, randomPassword} from "../../../../util/index.js"; -import {IValidatorCreateArgs, validatorCreateOptions} from "./create.js"; - -/* eslint-disable no-console */ - -export type IValidatorRecoverArgs = Pick & { - mnemonicInputPath: string; - firstIndex: number; -}; - -export type ReturnType = string[]; - -export const recover: ICliCommand = { - command: "recover", - - describe: - "Recovers validator private keys given a BIP-39 mnemonic phrase. \ - If you did not specify a `--firstIndex` or count `--count`, by default this will \ - only recover the keys associated with the validator at index 0 for an HD wallet \ - in accordance with the EIP-2333 spec.", - - examples: [ - { - command: "account validator recover", - description: "Recover validator", - }, - ], - - options: { - count: validatorCreateOptions.count, - depositGwei: validatorCreateOptions.depositGwei, - storeWithdrawalKeystore: validatorCreateOptions.storeWithdrawalKeystore, - mnemonicInputPath: { - description: "If present, the mnemonic will be read in from this file.", - type: "string", - }, - firstIndex: { - default: 0, - description: "The first of consecutive key indexes you wish to recover.", - type: "number", - }, - }, - - handler: async (args) => { - const config = getBeaconConfigFromArgs(args); - - const {mnemonicInputPath, count, storeWithdrawalKeystore, firstIndex} = args; - const maxEffectiveBalance = MAX_EFFECTIVE_BALANCE; - const depositGwei = Number(args.depositGwei || 0) || maxEffectiveBalance; - let mnemonic; - - console.log("\nWARNING: KEY RECOVERY CAN LEAD TO DUPLICATING VALIDATORS KEYS, WHICH CAN LEAD TO SLASHING.\n"); - - if (mnemonicInputPath) { - mnemonic = fs.readFileSync(mnemonicInputPath, "utf8").trim(); - } else { - const input = await inquirer.prompt<{mnemonic: string}>([ - { - name: "mnemonic", - type: "input", - message: "Enter the mnemonic phrase:", - }, - ]); - mnemonic = input.mnemonic; - } - - const isValid = validateMnemonic(mnemonic); - - if (!isValid) { - throw new Error("not a valid mnemonic"); - } - - const masterSK = deriveKeyFromMnemonic(mnemonic); - - const accountPaths = getAccountPaths(args); - const validatorDirBuilder = new ValidatorDirBuilder(accountPaths); - - const pubkeys: string[] = []; - for (let i = firstIndex; i < count; i++) { - const signing = randomPassword(); - const withdrawal = randomPassword(); - const passwords: {[key in keyof IEth2ValidatorKeys]: string} = {signing, withdrawal}; - const privKeys = deriveEth2ValidatorKeys(masterSK, i); - const paths = eth2ValidatorPaths(i); - - const keystoreRequests = mapValues(privKeys, async (privKey, key) => { - const type = key as keyof typeof privKeys; - const publicKey = bls.SecretKey.fromBytes(privKey).toPublicKey().toBytes(); - const keystore = await Keystore.create(passwords[type], privKey, publicKey, paths[type]); - return keystore; - }); - - const keystores = await Promise.all(Object.values(keystoreRequests)); - - await validatorDirBuilder.build({ - keystores: { - withdrawal: keystores[0], - signing: keystores[1], - }, - passwords, - storeWithdrawalKeystore, - depositGwei, - config, - }); - - const pubkey = add0xPrefix(keystores[1].pubkey); - console.log(`${i}/${count}\t${pubkey}`); - pubkeys.push(pubkey); - } - - // Return values for testing - return pubkeys; - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/wallet/create.ts b/packages/cli/src/cmds/account/cmds/wallet/create.ts deleted file mode 100644 index 5f44f3d1484..00000000000 --- a/packages/cli/src/cmds/account/cmds/wallet/create.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as bip39 from "bip39"; -import {ICliCommand, ICliCommandOptions} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {accountWalletsOptions, IAccountWalletArgs} from "./options.js"; -import {createWalletFromArgsAndMnemonic} from "./utils.js"; - -export const command = "create"; - -export const description = "Creates a new HD (hierarchical-deterministic) EIP-2386 wallet"; - -export type IWalletCreateArgs = { - name: string; - passphraseFile: string; - type: string; - mnemonicOutputPath?: string; -}; - -export const walletCreateOptions: ICliCommandOptions = { - ...accountWalletsOptions, - name: { - description: - "The wallet will be created with this name. It is not allowed to \ -create two wallets with the same name for the same --base-dir.", - alias: ["n"], - demandOption: true, - type: "string", - }, - - passphraseFile: { - description: - "A path to a file containing the password which will unlock the wallet. \ -If the file does not exist, a random password will be generated and saved at that \ -path. To avoid confusion, if the file does not already exist it must include a \ -'.pass' suffix.", - alias: ["p"], - demandOption: true, - type: "string", - }, - - type: { - description: "The type of wallet to create. Only HD (hierarchical-deterministic) \ -wallets are supported presently.", - choices: ["hd"], - default: "hd", - type: "string", - }, - - mnemonicOutputPath: { - description: "If present, the mnemonic will be saved to this file", - type: "string", - }, -}; - -export type ReturnType = { - mnemonic: string; - uuid: string; - password: string; -}; - -export const create: ICliCommand = { - command: "create", - - describe: "Creates a new HD (hierarchical-deterministic) EIP-2386 wallet", - - examples: [ - { - command: "account wallet create --name primary --passphraseFile primary.pass", - description: "Create an HD wallet named 'primary'", - }, - ], - - options: walletCreateOptions, - - handler: async (args) => { - // Create a new random mnemonic. - const mnemonic = bip39.generateMnemonic(); - - const {uuid, password} = await createWalletFromArgsAndMnemonic(args, mnemonic); - - // eslint-disable-next-line no-console - console.log( - ` - Your wallet's 12-word BIP-39 mnemonic is: - - \t${mnemonic} - - This mnemonic can be used to fully restore your wallet, should - you lose the JSON file or your password. - - It is very important that you DO NOT SHARE this mnemonic as it will - reveal the private keys of all validators and keys generated with - this wallet. That would be catastrophic. - - It is also important to store a backup of this mnemonic so you can - recover your private keys in the case of data loss. Writing it on - a piece of paper and storing it in a safe place would be prudent. - - Your wallet's UUID is: - - \t${uuid} - - You do not need to backup your UUID or keep it secret.` - ); - - // Return values for testing - return {mnemonic, uuid, password}; - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/wallet/index.ts b/packages/cli/src/cmds/account/cmds/wallet/index.ts deleted file mode 100644 index 3deebc3ad72..00000000000 --- a/packages/cli/src/cmds/account/cmds/wallet/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {ICliCommand} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {accountWalletsOptions, IAccountWalletArgs} from "./options.js"; -import {create} from "./create.js"; -import {list} from "./list.js"; -import {recover} from "./recover.js"; - -export const wallet: ICliCommand = { - command: "wallet ", - describe: "Provides commands for managing Ethereum Consensus wallets.", - options: accountWalletsOptions, - subcommands: [create, list, recover], -}; diff --git a/packages/cli/src/cmds/account/cmds/wallet/list.ts b/packages/cli/src/cmds/account/cmds/wallet/list.ts deleted file mode 100644 index 86609d8c1ed..00000000000 --- a/packages/cli/src/cmds/account/cmds/wallet/list.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {ICliCommand} from "../../../../util/index.js"; -import {WalletManager} from "../../../../wallet/index.js"; -import {getAccountPaths} from "../../paths.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {IAccountWalletArgs} from "./options.js"; - -export type ReturnType = string[]; - -export const list: ICliCommand, IAccountWalletArgs & IGlobalArgs, ReturnType> = { - command: "list", - - describe: "Lists the names of all wallets", - - examples: [ - { - command: "account wallet list --walletsDir .network/wallets", - description: "List all wallets in .network/wallets", - }, - ], - - handler: async (args) => { - const accountPaths = getAccountPaths(args); - const walletManager = new WalletManager(accountPaths); - const walletNames = walletManager.wallets().map(({name}) => name); - // eslint-disable-next-line no-console - console.log(walletNames.join("\n")); - - // Return values for testing - return walletNames; - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/wallet/options.ts b/packages/cli/src/cmds/account/cmds/wallet/options.ts deleted file mode 100644 index 2d08e3d8331..00000000000 --- a/packages/cli/src/cmds/account/cmds/wallet/options.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {defaultAccountPaths} from "../../paths.js"; -import {ICliCommandOptions} from "../../../../util/index.js"; - -export interface IAccountWalletArgs { - walletsDir?: string; -} - -export const accountWalletsOptions: ICliCommandOptions = { - walletsDir: { - description: "Directory for storing wallets.", - defaultDescription: defaultAccountPaths.walletsDir, - type: "string", - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/wallet/recover.ts b/packages/cli/src/cmds/account/cmds/wallet/recover.ts deleted file mode 100644 index c4dfc96e99a..00000000000 --- a/packages/cli/src/cmds/account/cmds/wallet/recover.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as fs from "node:fs"; -import inquirer from "inquirer"; -import {ICliCommand} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {createWalletFromArgsAndMnemonic} from "./utils.js"; -import {IWalletCreateArgs, walletCreateOptions} from "./create.js"; - -/* eslint-disable no-console */ - -export type IWalletRecoverArgs = IWalletCreateArgs & { - mnemonicInputPath: string; -}; - -export type ReturnType = string[]; - -export const recover: ICliCommand = { - command: "recover", - - describe: "Recovers an EIP-2386 wallet from a given a BIP-39 mnemonic phrase.", - - examples: [ - { - command: "account wallet recover", - description: "Recover wallet", - }, - ], - - options: { - ...walletCreateOptions, - mnemonicInputPath: { - description: "If present, the mnemonic will be read in from this file.", - type: "string", - }, - }, - - handler: async (args) => { - const {mnemonicInputPath} = args; - let mnemonic; - - console.log("\nWARNING: KEY RECOVERY CAN LEAD TO DUPLICATING VALIDATORS KEYS, WHICH CAN LEAD TO SLASHING.\n"); - - if (mnemonicInputPath) { - mnemonic = fs.readFileSync(mnemonicInputPath, "utf8").trim(); - } else { - const input = await inquirer.prompt<{mnemonic: string}>([ - { - name: "mnemonic", - type: "input", - message: "Enter the mnemonic phrase:", - }, - ]); - mnemonic = input.mnemonic; - } - - const {uuid} = await createWalletFromArgsAndMnemonic(args, mnemonic); - - console.log(`Your wallet has been successfully recovered. -Your wallet's UUID is: - -\t${uuid} - -You do not need to backup your UUID or keep it secret. -`); - - return [uuid]; - }, -}; diff --git a/packages/cli/src/cmds/account/cmds/wallet/utils.ts b/packages/cli/src/cmds/account/cmds/wallet/utils.ts deleted file mode 100644 index f7eb7ef5596..00000000000 --- a/packages/cli/src/cmds/account/cmds/wallet/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {YargsError, writeFile600Perm, randomPassword, readPassphraseFile} from "../../../../util/index.js"; -import {WalletManager} from "../../../../wallet/index.js"; -import {getAccountPaths} from "../../paths.js"; -import {IWalletRecoverArgs} from "./recover.js"; - -export async function createWalletFromArgsAndMnemonic( - args: Pick, - mnemonic: string -): Promise<{uuid: string; password: string}> { - const {name, type, passphraseFile, mnemonicOutputPath} = args; - const accountPaths = getAccountPaths(args); - - if (path.parse(passphraseFile).ext !== ".pass") { - throw new YargsError("passphraseFile must end with .pass, make sure to not provide the actual password"); - } - - if (!fs.existsSync(passphraseFile)) { - writeFile600Perm(passphraseFile, randomPassword()); - } - - const password = readPassphraseFile(passphraseFile); - - const walletManager = new WalletManager(accountPaths); - const wallet = await walletManager.createWallet(name, type, mnemonic, password); - const uuid = wallet.toWalletObject().uuid; - - if (mnemonicOutputPath) { - writeFile600Perm(mnemonicOutputPath, mnemonic); - } - - return {uuid, password}; -} diff --git a/packages/cli/src/cmds/account/index.ts b/packages/cli/src/cmds/account/index.ts index c58a0948bab..b5ba6254bbc 100644 --- a/packages/cli/src/cmds/account/index.ts +++ b/packages/cli/src/cmds/account/index.ts @@ -1,10 +1,25 @@ -import {ICliCommand} from "../../util/index.js"; +import {ICliCommand, YargsError} from "../../util/index.js"; import {IGlobalArgs} from "../../options/index.js"; -import {validator} from "./cmds/validator/index.js"; -import {wallet} from "./cmds/wallet/index.js"; + +const deprecatedDescription = `DEPRECATED + +Please use the official tools to perform your deposits +- eth2.0-deposit-cli: https://github.com/ethereum/eth2.0-deposit-cli +- Ethereum Foundation launchpad: https://launchpad.ethereum.org + +For commands slashing-protection, voluntary-exit and import, use the validator command: +- validator slashing-protection +- validator voluntary-exit +- validator import +`; export const account: ICliCommand, IGlobalArgs> = { - command: "account ", - describe: "Utilities for generating and managing Ethereum Consensus accounts", - subcommands: [validator, wallet], + // [x..] captures 0 or more positional arguments. This is catch-all to show deprecation notice with any command: + // $ account + // $ account validator slashing-protection export + command: "account [subcommands..]", + describe: "DEPRECATED", + handler: async () => { + throw new YargsError(deprecatedDescription); + }, }; diff --git a/packages/cli/src/cmds/account/paths.ts b/packages/cli/src/cmds/account/paths.ts deleted file mode 100644 index 391299ae4aa..00000000000 --- a/packages/cli/src/cmds/account/paths.ts +++ /dev/null @@ -1,56 +0,0 @@ -import path from "node:path"; -import {IGlobalArgs} from "../../options/index.js"; -import {IGlobalPaths, getGlobalPaths} from "../../paths/global.js"; - -export interface IAccountPaths { - keystoresDir: string; - secretsDir: string; - walletsDir: string; -} - -/** - * Defines the path structure of the account files - * - * ```bash - * $accountsRootDir - * ├── secrets - * | ├── 0x8e41b969493454318c27ec6fac90645769331c07ebc8db5037... - * | └── 0xa329f988c16993768299643d918a2694892c012765d896a16f... - * ├── keystores - * | ├── 0x8e41b969493454318c27ec6fac90645769331c07ebc8db5037... - * | | ├── eth1-deposit-data.rlp - * | | ├── eth1-deposit-gwei.txt - * | | └── voting-keystore.json - * | └── 0xa329f988c16993768299643d918a2694892c012765d896a16f... - * | ├── eth1-deposit-data.rlp - * | ├── eth1-deposit-gwei.txt - * | └── voting-keystore.json - * ├── wallet1.pass (arbitrary path) - * └── wallets - * └── 96ae14b4-46d7-42dc-afd8-c782e9af87ef (dir) - * └── 96ae14b4-46d7-42dc-afd8-c782e9af87ef (json) - * ``` - */ -// Using Pick make changes in IGlobalArgs throw a type error here -export function getAccountPaths( - args: Partial & Pick -): IAccountPaths & IGlobalPaths { - // Compute global paths first - const globalPaths = getGlobalPaths(args); - - const rootDir = globalPaths.rootDir; - const keystoresDir = args.keystoresDir || path.join(rootDir, "keystores"); - const secretsDir = args.secretsDir || path.join(rootDir, "secrets"); - const walletsDir = args.walletsDir || path.join(rootDir, "wallets"); - return { - ...globalPaths, - keystoresDir, - secretsDir, - walletsDir, - }; -} - -/** - * Constructs representations of the path structure to show in command's description - */ -export const defaultAccountPaths = getAccountPaths({rootDir: "$rootDir"}); diff --git a/packages/cli/src/cmds/account/cmds/validator/import.ts b/packages/cli/src/cmds/validator/import.ts similarity index 94% rename from packages/cli/src/cmds/account/cmds/validator/import.ts rename to packages/cli/src/cmds/validator/import.ts index ea5775b7747..a588f5213a4 100644 --- a/packages/cli/src/cmds/account/cmds/validator/import.ts +++ b/packages/cli/src/cmds/validator/import.ts @@ -11,11 +11,11 @@ import { isPassphraseFile, writeValidatorPassphrase, ICliCommand, -} from "../../../../util/index.js"; -import {VOTING_KEYSTORE_FILE, getValidatorDirPath} from "../../../../validatorDir/paths.js"; -import {getAccountPaths} from "../../paths.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {IAccountValidatorArgs} from "./options.js"; +} from "../../util/index.js"; +import {VOTING_KEYSTORE_FILE, getValidatorDirPath} from "../../validatorDir/paths.js"; +import {IGlobalArgs} from "../../options/index.js"; +import {AccountValidatorArgs} from "./options.js"; +import {getAccountPaths} from "./paths.js"; /* eslint-disable no-console */ @@ -25,7 +25,7 @@ interface IValidatorImportArgs { passphraseFile?: string; } -export const importCmd: ICliCommand = { +export const importCmd: ICliCommand = { command: "import", describe: diff --git a/packages/cli/src/cmds/validator/index.ts b/packages/cli/src/cmds/validator/index.ts index 988806c4ac4..08f0461e4f5 100644 --- a/packages/cli/src/cmds/validator/index.ts +++ b/packages/cli/src/cmds/validator/index.ts @@ -1,6 +1,10 @@ import {ICliCommand} from "../../util/index.js"; import {IGlobalArgs} from "../../options/index.js"; -import {getAccountPaths} from "../account/paths.js"; +import {getAccountPaths} from "./paths.js"; +import {slashingProtection} from "./slashingProtection/index.js"; +import {voluntaryExit} from "./voluntaryExit.js"; +import {importCmd} from "./import.js"; +import {list} from "./list.js"; import {validatorOptions, IValidatorCliArgs} from "./options.js"; import {validatorHandler} from "./handler.js"; @@ -17,4 +21,5 @@ export const validator: ICliCommand = { ], options: validatorOptions, handler: validatorHandler, + subcommands: [slashingProtection, importCmd, list, voluntaryExit], }; diff --git a/packages/cli/src/cmds/validator/keys.ts b/packages/cli/src/cmds/validator/keys.ts index b36d5a46ab4..98b93759043 100644 --- a/packages/cli/src/cmds/validator/keys.ts +++ b/packages/cli/src/cmds/validator/keys.ts @@ -11,7 +11,7 @@ import {fromHexString} from "@chainsafe/ssz"; import {defaultNetwork, IGlobalArgs} from "../../options/index.js"; import {parseRange, stripOffNewlines, YargsError} from "../../util/index.js"; import {ValidatorDirManager} from "../../validatorDir/index.js"; -import {getAccountPaths} from "../account/paths.js"; +import {getAccountPaths} from "./paths.js"; import {IValidatorCliArgs} from "./options.js"; const depositDataPattern = new RegExp(/^deposit_data-\d+\.json$/gi); diff --git a/packages/cli/src/cmds/account/cmds/validator/list.ts b/packages/cli/src/cmds/validator/list.ts similarity index 60% rename from packages/cli/src/cmds/account/cmds/validator/list.ts rename to packages/cli/src/cmds/validator/list.ts index bddbee7ead7..55163263b4e 100644 --- a/packages/cli/src/cmds/account/cmds/validator/list.ts +++ b/packages/cli/src/cmds/validator/list.ts @@ -1,13 +1,13 @@ -import {ValidatorDirManager} from "../../../../validatorDir/index.js"; -import {getAccountPaths} from "../../paths.js"; -import {ICliCommand} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {add0xPrefix} from "../../../../util/format.js"; -import {IAccountValidatorArgs} from "./options.js"; +import {ValidatorDirManager} from "../../validatorDir/index.js"; +import {ICliCommand} from "../../util/index.js"; +import {IGlobalArgs} from "../../options/index.js"; +import {add0xPrefix} from "../../util/format.js"; +import {getAccountPaths} from "./paths.js"; +import {AccountValidatorArgs} from "./options.js"; export type ReturnType = string[]; -export const list: ICliCommand, IAccountValidatorArgs & IGlobalArgs, ReturnType> = { +export const list: ICliCommand, AccountValidatorArgs & IGlobalArgs, ReturnType> = { command: "list", describe: "Lists the public keys of all validators", diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index fc7815225db..1f5dd07efae 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -1,10 +1,14 @@ import {defaultOptions} from "@chainsafe/lodestar"; import {ICliCommandOptions, ILogArgs} from "../../util/index.js"; -import {accountValidatorOptions, IAccountValidatorArgs} from "../account/cmds/validator/options.js"; import {logOptions, beaconPathsOptions} from "../beacon/options.js"; import {IBeaconPaths} from "../beacon/paths.js"; import {KeymanagerArgs, keymanagerOptions} from "../../options/keymanagerOptions.js"; -import {defaultValidatorPaths} from "./paths.js"; +import {defaultAccountPaths, defaultValidatorPaths} from "./paths.js"; + +export type AccountValidatorArgs = { + keystoresDir?: string; + secretsDir?: string; +}; export const validatorMetricsDefaultOptions = { enabled: false, @@ -14,7 +18,7 @@ export const validatorMetricsDefaultOptions = { export const defaultDefaultFeeRecipient = defaultOptions.chain.defaultFeeRecipient; -export type IValidatorCliArgs = IAccountValidatorArgs & +export type IValidatorCliArgs = AccountValidatorArgs & ILogArgs & { logFile: IBeaconPaths["logFile"]; validatorsDbDir?: string; @@ -40,11 +44,22 @@ export type IValidatorCliArgs = IAccountValidatorArgs & } & KeymanagerArgs; export const validatorOptions: ICliCommandOptions = { - ...accountValidatorOptions, ...logOptions, ...keymanagerOptions, logFile: beaconPathsOptions.logFile, + keystoresDir: { + description: "Directory for storing validator keystores.", + defaultDescription: defaultAccountPaths.keystoresDir, + type: "string", + }, + + secretsDir: { + description: "Directory for storing validator keystore secrets.", + defaultDescription: defaultAccountPaths.secretsDir, + type: "string", + }, + validatorsDbDir: { description: "Data directory for validator databases.", defaultDescription: defaultValidatorPaths.validatorsDbDir, diff --git a/packages/cli/src/cmds/validator/paths.ts b/packages/cli/src/cmds/validator/paths.ts index 10a9a5fa3f3..eee579bfe6c 100644 --- a/packages/cli/src/cmds/validator/paths.ts +++ b/packages/cli/src/cmds/validator/paths.ts @@ -6,6 +6,12 @@ export type IValidatorPaths = { validatorsDbDir: string; }; +export type AccountPaths = { + keystoresDir: string; + secretsDir: string; + walletsDir: string; +}; + /** * Defines the path structure of the validator files * @@ -33,3 +39,50 @@ export function getValidatorPaths( * Constructs representations of the path structure to show in command's description */ export const defaultValidatorPaths = getValidatorPaths({rootDir: "$rootDir"}); + +/** + * Defines the path structure of the account files + * + * ```bash + * $accountsRootDir + * ├── secrets + * | ├── 0x8e41b969493454318c27ec6fac90645769331c07ebc8db5037... + * | └── 0xa329f988c16993768299643d918a2694892c012765d896a16f... + * ├── keystores + * | ├── 0x8e41b969493454318c27ec6fac90645769331c07ebc8db5037... + * | | ├── eth1-deposit-data.rlp + * | | ├── eth1-deposit-gwei.txt + * | | └── voting-keystore.json + * | └── 0xa329f988c16993768299643d918a2694892c012765d896a16f... + * | ├── eth1-deposit-data.rlp + * | ├── eth1-deposit-gwei.txt + * | └── voting-keystore.json + * ├── wallet1.pass (arbitrary path) + * └── wallets + * └── 96ae14b4-46d7-42dc-afd8-c782e9af87ef (dir) + * └── 96ae14b4-46d7-42dc-afd8-c782e9af87ef (json) + * ``` + */ +// Using Pick make changes in IGlobalArgs throw a type error here +export function getAccountPaths( + args: Partial & Pick +): AccountPaths & IGlobalPaths { + // Compute global paths first + const globalPaths = getGlobalPaths(args); + + const rootDir = globalPaths.rootDir; + const keystoresDir = args.keystoresDir || path.join(rootDir, "keystores"); + const secretsDir = args.secretsDir || path.join(rootDir, "secrets"); + const walletsDir = args.walletsDir || path.join(rootDir, "wallets"); + return { + ...globalPaths, + keystoresDir, + secretsDir, + walletsDir, + }; +} + +/** + * Constructs representations of the path structure to show in command's description + */ +export const defaultAccountPaths = getAccountPaths({rootDir: "$rootDir"}); diff --git a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/export.ts b/packages/cli/src/cmds/validator/slashingProtection/export.ts similarity index 85% rename from packages/cli/src/cmds/account/cmds/validator/slashingProtection/export.ts rename to packages/cli/src/cmds/validator/slashingProtection/export.ts index e8e55d4d48c..12baa9dacd8 100644 --- a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/export.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/export.ts @@ -1,7 +1,7 @@ import {InterchangeFormatVersion} from "@chainsafe/lodestar-validator"; -import {ICliCommand, writeFile} from "../../../../../util/index.js"; -import {IGlobalArgs} from "../../../../../options/index.js"; -import {IAccountValidatorArgs} from "../options.js"; +import {ICliCommand, writeFile} from "../../../util/index.js"; +import {IGlobalArgs} from "../../../options/index.js"; +import {AccountValidatorArgs} from "../options.js"; import {ISlashingProtectionArgs} from "./options.js"; import {getGenesisValidatorsRoot, getSlashingProtection} from "./utils.js"; @@ -11,7 +11,7 @@ interface IExportArgs { file: string; } -export const exportCmd: ICliCommand = { +export const exportCmd: ICliCommand = { command: "export", describe: "Export an interchange file.", diff --git a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/import.ts b/packages/cli/src/cmds/validator/slashingProtection/import.ts similarity index 84% rename from packages/cli/src/cmds/account/cmds/validator/slashingProtection/import.ts rename to packages/cli/src/cmds/validator/slashingProtection/import.ts index 44c47224f60..1d951641c78 100644 --- a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/import.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/import.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import {Interchange} from "@chainsafe/lodestar-validator"; -import {ICliCommand} from "../../../../../util/index.js"; -import {IGlobalArgs} from "../../../../../options/index.js"; -import {IAccountValidatorArgs} from "../options.js"; +import {ICliCommand} from "../../../util/index.js"; +import {IGlobalArgs} from "../../../options/index.js"; +import {AccountValidatorArgs} from "../options.js"; import {ISlashingProtectionArgs} from "./options.js"; import {getGenesisValidatorsRoot, getSlashingProtection} from "./utils.js"; @@ -12,7 +12,7 @@ interface IImportArgs { file: string; } -export const importCmd: ICliCommand = { +export const importCmd: ICliCommand = { command: "import", describe: "Import an interchange file.", diff --git a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/index.ts b/packages/cli/src/cmds/validator/slashingProtection/index.ts similarity index 75% rename from packages/cli/src/cmds/account/cmds/validator/slashingProtection/index.ts rename to packages/cli/src/cmds/validator/slashingProtection/index.ts index 72b9e5774b4..dc97c234e83 100644 --- a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/index.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/index.ts @@ -1,10 +1,10 @@ -import {ICliCommand} from "../../../../../util/index.js"; -import {IAccountValidatorArgs} from "../options.js"; +import {ICliCommand} from "../../../util/index.js"; +import {AccountValidatorArgs} from "../options.js"; import {ISlashingProtectionArgs, slashingProtectionOptions} from "./options.js"; import {importCmd} from "./import.js"; import {exportCmd} from "./export.js"; -export const slashingProtection: ICliCommand = { +export const slashingProtection: ICliCommand = { command: "slashing-protection ", describe: "Import or export slashing protection data to or from another client.", options: slashingProtectionOptions, diff --git a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/options.ts b/packages/cli/src/cmds/validator/slashingProtection/options.ts similarity index 70% rename from packages/cli/src/cmds/account/cmds/validator/slashingProtection/options.ts rename to packages/cli/src/cmds/validator/slashingProtection/options.ts index 3a0ae700ea0..a0a324c6214 100644 --- a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/options.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/options.ts @@ -1,5 +1,5 @@ -import {ICliCommandOptions} from "../../../../../util/index.js"; -import {IValidatorCliArgs, validatorOptions} from "../../../../validator/options.js"; +import {ICliCommandOptions} from "../../../util/index.js"; +import {IValidatorCliArgs, validatorOptions} from "../options.js"; export type ISlashingProtectionArgs = Pick & { force?: boolean; diff --git a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/utils.ts b/packages/cli/src/cmds/validator/slashingProtection/utils.ts similarity index 81% rename from packages/cli/src/cmds/account/cmds/validator/slashingProtection/utils.ts rename to packages/cli/src/cmds/validator/slashingProtection/utils.ts index e47866b0811..9c3eacf4e7d 100644 --- a/packages/cli/src/cmds/account/cmds/validator/slashingProtection/utils.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/utils.ts @@ -2,11 +2,11 @@ import {Root} from "@chainsafe/lodestar-types"; import {getClient} from "@chainsafe/lodestar-api"; import {SlashingProtection} from "@chainsafe/lodestar-validator"; import {LevelDbController} from "@chainsafe/lodestar-db"; -import {YargsError} from "../../../../../util/index.js"; -import {IGlobalArgs} from "../../../../../options/index.js"; -import {getValidatorPaths} from "../../../../validator/paths.js"; -import {getBeaconConfigFromArgs} from "../../../../../config/index.js"; -import {errorLogger} from "../../../../../util/logger.js"; +import {YargsError} from "../../../util/index.js"; +import {IGlobalArgs} from "../../../options/index.js"; +import {getValidatorPaths} from "../paths.js"; +import {getBeaconConfigFromArgs} from "../../../config/index.js"; +import {errorLogger} from "../../../util/logger.js"; import {ISlashingProtectionArgs} from "./options.js"; /** diff --git a/packages/cli/src/cmds/account/cmds/validator/voluntaryExit.ts b/packages/cli/src/cmds/validator/voluntaryExit.ts similarity index 80% rename from packages/cli/src/cmds/account/cmds/validator/voluntaryExit.ts rename to packages/cli/src/cmds/validator/voluntaryExit.ts index 91a29a36f84..5f61368c2a5 100644 --- a/packages/cli/src/cmds/account/cmds/validator/voluntaryExit.ts +++ b/packages/cli/src/cmds/validator/voluntaryExit.ts @@ -2,14 +2,13 @@ import {readdirSync} from "node:fs"; import inquirer from "inquirer"; import {SignerType, SlashingProtection, Validator} from "@chainsafe/lodestar-validator"; import {LevelDbController} from "@chainsafe/lodestar-db"; -import {ICliCommand} from "../../../../util/index.js"; -import {IGlobalArgs} from "../../../../options/index.js"; -import {ValidatorDirManager} from "../../../../validatorDir/index.js"; -import {getAccountPaths} from "../../paths.js"; -import {getBeaconConfigFromArgs} from "../../../../config/index.js"; -import {errorLogger} from "../../../../util/logger.js"; -import {IValidatorCliArgs, validatorOptions} from "../../../validator/options.js"; -import {getValidatorPaths} from "../../../validator/paths.js"; +import {ICliCommand} from "../../util/index.js"; +import {IGlobalArgs} from "../../options/index.js"; +import {ValidatorDirManager} from "../../validatorDir/index.js"; +import {getBeaconConfigFromArgs} from "../../config/index.js"; +import {errorLogger} from "../../util/logger.js"; +import {IValidatorCliArgs, validatorOptions} from "./options.js"; +import {getAccountPaths, getValidatorPaths} from "./paths.js"; /* eslint-disable no-console */ @@ -89,15 +88,12 @@ BE UNTIL AT LEAST TWO YEARS AFTER THE PHASE 0 MAINNET LAUNCH. console.log(`Initiating voluntary exit for validator ${publicKey}`); - let secretKey; - try { - secretKey = await validatorDirManager.decryptValidator(publicKey, {force}); - } catch (e) { - if ((e as Error).message.indexOf("EEXIST") !== -1) { - console.log(`Decrypting keystore failed with error ${e}. use --force to override`); + const secretKey = await validatorDirManager.decryptValidator(publicKey, {force}).catch((e: Error) => { + if (e.message.includes("EEXIST")) { + console.log(`Decrypting keystore failed with error ${e.message}. use --force to override`); } throw e; - } + }); console.log(`Decrypted keystore for validator ${publicKey}`); diff --git a/packages/cli/test/e2e/cmds/account.test.ts b/packages/cli/test/e2e/cmds/account.test.ts deleted file mode 100644 index b44c91b8966..00000000000 --- a/packages/cli/test/e2e/cmds/account.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import rimraf from "rimraf"; -import {expect} from "chai"; -import {getAccountPaths} from "../../../src/cmds/account/paths.js"; -import {ReturnType as WalletCreateReturnType} from "../../../src/cmds/account/cmds/wallet/create.js"; -import {ReturnType as WalletListReturnType} from "../../../src/cmds/account/cmds/wallet/list.js"; -import {ReturnType as ValidatorCreateReturnType} from "../../../src/cmds/account/cmds/validator/create.js"; -import {ReturnType as ValidatorListReturnType} from "../../../src/cmds/account/cmds/validator/list.js"; -import {VOTING_KEYSTORE_FILE, getValidatorDirPath} from "../../../src/validatorDir/paths.js"; -import {testFilesDir} from "../../utils.js"; -import {getLodestarCliTestRunner} from "../commandRunner.js"; - -/* eslint-disable no-console */ - -type ConsoleKeys = "log" | "warn" | "error"; -const consoleKeys: ConsoleKeys[] = ["log", "warn", "error"]; - -describe("cmds / account", function () { - const lodestar = getLodestarCliTestRunner(); - - const rootDir = testFilesDir; - const walletName = "primary"; - const walletPasswordPath = path.join(testFilesDir, "primary.pass"); - const validatorCount = 2; - const accountPaths = getAccountPaths({rootDir}); - let createdPubkeys: string[] | null = null; - - const consoleData: {[P in ConsoleKeys]: string} = { - log: "", - warn: "", - error: "", - }; - const consoleCache: {[P in ConsoleKeys]: typeof console.log} = { - log: console.log, - warn: console.warn, - error: console.error, - }; - - beforeEach("Hijack console", () => { - for (const key of consoleKeys) { - consoleData[key] = ""; - console[key] = (...args: any[]) => { - consoleData.log += args.map(String).join(" "); - }; - } - }); - - afterEach("Release console", () => { - for (const key of consoleKeys) { - console[key] = consoleCache[key]; - } - }); - - before("Clean rootDir", () => { - rimraf.sync(rootDir); - }); - - it("should create a wallet", async function () { - const {mnemonic, uuid, password} = await lodestar([ - "account wallet create", - `--name ${walletName}`, - `--passphraseFile ${walletPasswordPath}`, - `--rootDir ${rootDir}`, - ]); - - expect(mnemonic, "Empty mnemonic").to.be.ok; - - // Assert that the password file contains a password - const passwordInFile = fs.readFileSync(walletPasswordPath, "utf8"); - expect(passwordInFile.trim()).to.equal(password, "Wrong password stored in disk"); - - // Assert the wallet directory is created - const walletDirs = fs.readdirSync(accountPaths.walletsDir); - expect(walletDirs).to.deep.equal([uuid], "Wallet dir should contain one wallet with it UUID"); - }); - - it("should list existing wallets", async function () { - // Should return an array of wallet names - const walletNames = await lodestar([ - // ⏎ - "account wallet list", - `--rootDir ${rootDir}`, - ]); - expect(walletNames).to.deep.equal([walletName]); - }); - - it("should create new validators", async function () { - const pubkeys = await lodestar([ - "account validator create", - `--count ${validatorCount}`, - `--name ${walletName}`, - `--passphraseFile ${walletPasswordPath}`, - `--rootDir ${rootDir}`, - ]); - // For next test - createdPubkeys = pubkeys; - - expect(pubkeys).length(validatorCount, `Should create ${validatorCount} validators`); - - for (const pubkey of pubkeys) { - const validatorDir = getValidatorDirPath({ - keystoresDir: accountPaths.keystoresDir, - pubkey, - prefixed: true, - }); - const keystorePath = path.join(validatorDir, VOTING_KEYSTORE_FILE); - expect(fs.existsSync(keystorePath), `Validator keystore ${keystorePath} does not exist`).to.be.true; - } - }); - - it("should list validators", async function () { - if (!createdPubkeys) throw Error("Previous test failed"); - - const validatorPubKeys = await lodestar([ - // ⏎ - "account validator list", - `--rootDir ${rootDir}`, - ]); - - // Write order is not guarranteed, sort before comparing - expect(validatorPubKeys.sort()).to.deep.equal(createdPubkeys.sort(), "Wrong validator pubkeys"); - }); -}); diff --git a/packages/cli/test/e2e/example.test.ts b/packages/cli/test/e2e/example.test.ts new file mode 100644 index 00000000000..8032df4be05 --- /dev/null +++ b/packages/cli/test/e2e/example.test.ts @@ -0,0 +1,89 @@ +import fs from "node:fs"; +import path from "node:path"; +import rimraf from "rimraf"; +import {expect} from "chai"; +import {Keystore} from "@chainsafe/bls-keystore"; +import {fromHex} from "@chainsafe/lodestar-utils"; +import {ReturnType as ValidatorListReturnType} from "../../src/cmds/validator/list.js"; +import {testFilesDir} from "../utils.js"; +import {getLodestarCliTestRunner} from "./commandRunner.js"; + +/* eslint-disable no-console */ + +type ConsoleKeys = "log" | "warn" | "error"; +const consoleKeys: ConsoleKeys[] = ["log", "warn", "error"]; + +describe("cmds / validator", function () { + const lodestar = getLodestarCliTestRunner(); + + const rootDir = testFilesDir; + + const consoleData: {[P in ConsoleKeys]: string} = { + log: "", + warn: "", + error: "", + }; + const consoleCache: {[P in ConsoleKeys]: typeof console.log} = { + log: console.log, + warn: console.warn, + error: console.error, + }; + + beforeEach("Hijack console", () => { + for (const key of consoleKeys) { + consoleData[key] = ""; + console[key] = (...args: any[]) => { + consoleData.log += args.map(String).join(" "); + }; + } + }); + + afterEach("Release console", () => { + for (const key of consoleKeys) { + console[key] = consoleCache[key]; + } + }); + + before("Clean rootDir", () => { + rimraf.sync(rootDir); + }); + + /** Generated from const sk = bls.SecretKey.fromKeygen(Buffer.alloc(32, 0xaa)); */ + const skHex = "0x0e5bd52621b6a8956086dcf0ecc89f0cdca56cebb2a8516c2d4252a9867fc551"; + const pkHex = "0x8be678633e927aa0435addad5dcd5283fef6110d91362519cd6d43e61f6c017d724fa579cc4b2972134e050b6ba120c0"; + + it("Should import validator keystore", async () => { + const passphrase = "AAAAAAAA0000000000"; + const keystore = await Keystore.create(passphrase, fromHex(skHex), fromHex(pkHex), ""); + + fs.mkdirSync(rootDir, {recursive: true}); + const keystoreFilepath = path.join(rootDir, "keystore.json"); + const passphraseFilepath = path.join(rootDir, "password.text"); + fs.writeFileSync(passphraseFilepath, passphrase); + fs.writeFileSync(keystoreFilepath, keystore.stringify()); + + const res = await lodestar([ + // ⏎ + "validator import", + `--rootDir ${rootDir}`, + `--keystore ${keystoreFilepath}`, + `--passphraseFile ${passphraseFilepath}`, + ]); + + console.log(res); + }); + + it("should list validators", async function () { + fs.mkdirSync(path.join(rootDir, "keystores"), {recursive: true}); + fs.mkdirSync(path.join(rootDir, "secrets"), {recursive: true}); + + const validatorPubKeys = await lodestar([ + // ⏎ + "validator list", + `--rootDir ${rootDir}`, + ]); + + // No keys are imported before this test. TODO: Import some + expect(validatorPubKeys.sort()).to.deep.equal([pkHex], "Wrong validator pubkeys"); + }); +});