diff --git a/cmd/validator/accounts/backup_test.go b/cmd/validator/accounts/backup_test.go index 24044cd313c1..7c3fd167bc0c 100644 --- a/cmd/validator/accounts/backup_test.go +++ b/cmd/validator/accounts/backup_test.go @@ -18,7 +18,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/accounts" "github.com/prysmaticlabs/prysm/v3/validator/accounts/iface" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived" constant "github.com/prysmaticlabs/prysm/v3/validator/testing" @@ -53,13 +52,14 @@ func TestBackupAccounts_Noninteractive_Derived(t *testing.T) { backupPasswordFile: backupPasswordFile, backupDir: backupDir, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Derived, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) @@ -170,13 +170,14 @@ func TestBackupAccounts_Noninteractive_Imported(t *testing.T) { backupPasswordFile: backupPasswordFile, backupDir: backupDir, }) - _, err = accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + _, err = acc.WalletCreate(cliCtx.Context) require.NoError(t, err) // We attempt to import accounts we wrote to the keys directory diff --git a/cmd/validator/accounts/delete_test.go b/cmd/validator/accounts/delete_test.go index 2705fca38f65..b8f847a69b64 100644 --- a/cmd/validator/accounts/delete_test.go +++ b/cmd/validator/accounts/delete_test.go @@ -21,7 +21,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/testing/require" prysmTime "github.com/prysmaticlabs/prysm/v3/time" "github.com/prysmaticlabs/prysm/v3/validator/accounts" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/local" "github.com/urfave/cli/v2" @@ -160,13 +159,14 @@ func TestDeleteAccounts_Noninteractive(t *testing.T) { // Flags required for DeleteAccounts to work. deletePublicKeys: deletePublicKeys, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) // We attempt to import accounts. diff --git a/cmd/validator/accounts/exit_test.go b/cmd/validator/accounts/exit_test.go index 9694a87e9787..25095d6d417b 100644 --- a/cmd/validator/accounts/exit_test.go +++ b/cmd/validator/accounts/exit_test.go @@ -14,7 +14,6 @@ import ( mock2 "github.com/prysmaticlabs/prysm/v3/testing/mock" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/accounts" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -67,13 +66,14 @@ func TestExitAccountsCli_OK(t *testing.T) { // Flag required for ExitAccounts to work. voluntaryExitPublicKeys: keystore.Pubkey, }) - _, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + _, err = acc.WalletCreate(cliCtx.Context) require.NoError(t, err) require.NoError(t, accountsImport(cliCtx)) @@ -167,13 +167,14 @@ func TestExitAccountsCli_OK_AllPublicKeys(t *testing.T) { // Exit all public keys. exitAll: true, }) - _, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + _, err = acc.WalletCreate(cliCtx.Context) require.NoError(t, err) require.NoError(t, accountsImport(cliCtx)) diff --git a/cmd/validator/accounts/import.go b/cmd/validator/accounts/import.go index bd48c21e3363..bbc0310a7104 100644 --- a/cmd/validator/accounts/import.go +++ b/cmd/validator/accounts/import.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v3/cmd" "github.com/prysmaticlabs/prysm/v3/cmd/validator/flags" + "github.com/prysmaticlabs/prysm/v3/io/prompt" "github.com/prysmaticlabs/prysm/v3/validator/accounts" "github.com/prysmaticlabs/prysm/v3/validator/accounts/iface" "github.com/prysmaticlabs/prysm/v3/validator/accounts/userprompt" @@ -93,21 +94,51 @@ func walletImport(c *cli.Context) (*wallet.Wallet, error) { }) } - cfg, err := accounts.ExtractWalletCreationConfigFromCli(cliCtx, keymanager.Local) + wCfg, err := ExtractWalletDirPassword(cliCtx) if err != nil { return nil, err } w := wallet.New(&wallet.Config{ - KeymanagerKind: cfg.WalletCfg.KeymanagerKind, - WalletDir: cfg.WalletCfg.WalletDir, - WalletPassword: cfg.WalletCfg.WalletPassword, + KeymanagerKind: keymanager.Local, + WalletDir: wCfg.Dir, + WalletPassword: wCfg.Password, }) if err = accounts.CreateLocalKeymanagerWallet(cliCtx.Context, w); err != nil { return nil, errors.Wrap(err, "could not create keymanager") } - log.WithField("wallet-path", cfg.WalletCfg.WalletDir).Info( + log.WithField("wallet-path", wCfg.Dir).Info( "Successfully created new wallet", ) return w, nil }) } + +// WalletDirPassword holds the directory and password of a wallet. +type WalletDirPassword struct { + Dir string + Password string +} + +// ExtractWalletDirPassword prompts the user for wallet directory and password. +func ExtractWalletDirPassword(cliCtx *cli.Context) (WalletDirPassword, error) { + // Get wallet dir and check that no wallet exists at the location. + walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag) + if err != nil { + return WalletDirPassword{}, err + } + walletPassword, err := prompt.InputPassword( + cliCtx, + flags.WalletPasswordFileFlag, + wallet.NewWalletPasswordPromptText, + wallet.ConfirmPasswordPromptText, + true, /* Should confirm password */ + prompt.ValidatePasswordInput, + ) + if err != nil { + return WalletDirPassword{}, err + } + return WalletDirPassword{ + Dir: walletDir, + Password: walletPassword, + }, nil +} diff --git a/cmd/validator/accounts/import_test.go b/cmd/validator/accounts/import_test.go index 7dd328148b1d..afd8e4331977 100644 --- a/cmd/validator/accounts/import_test.go +++ b/cmd/validator/accounts/import_test.go @@ -36,13 +36,14 @@ func TestImport_Noninteractive(t *testing.T) { walletPasswordFile: passwordFilePath, accountPasswordFile: passwordFilePath, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) newKm, err := local.NewKeymanager( cliCtx.Context, @@ -93,13 +94,14 @@ func TestImport_DuplicateKeys(t *testing.T) { walletPasswordFile: passwordFilePath, accountPasswordFile: passwordFilePath, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) // Create a key and then copy it to create a duplicate @@ -141,13 +143,14 @@ func TestImport_Noninteractive_RandomName(t *testing.T) { walletPasswordFile: passwordFilePath, accountPasswordFile: passwordFilePath, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) newKm, err := local.NewKeymanager( cliCtx.Context, @@ -224,13 +227,14 @@ func TestImport_Noninteractive_Filepath(t *testing.T) { walletPasswordFile: passwordFilePath, accountPasswordFile: passwordFilePath, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(password), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) newKm, err := local.NewKeymanager( cliCtx.Context, diff --git a/cmd/validator/wallet/BUILD.bazel b/cmd/validator/wallet/BUILD.bazel index ee70f765a293..b2daa08528be 100644 --- a/cmd/validator/wallet/BUILD.bazel +++ b/cmd/validator/wallet/BUILD.bazel @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "create.go", "edit.go", "recover.go", "wallet.go", @@ -20,6 +21,7 @@ go_library( "//validator/accounts/wallet:go_default_library", "//validator/keymanager:go_default_library", "//validator/keymanager/remote:go_default_library", + "@com_github_manifoldco_promptui//:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_tyler_smith_go_bip39//:go_default_library", @@ -31,11 +33,13 @@ go_library( go_test( name = "go_default_test", srcs = [ + "create_test.go", "edit_test.go", "recover_test.go", ], embed = [":go_default_library"], deps = [ + "//cmd/validator/accounts:go_default_library", "//cmd/validator/flags:go_default_library", "//config/params:go_default_library", "//testing/assert:go_default_library", @@ -45,7 +49,11 @@ go_test( "//validator/accounts/wallet:go_default_library", "//validator/keymanager:go_default_library", "//validator/keymanager/derived:go_default_library", + "//validator/keymanager/local:go_default_library", "//validator/keymanager/remote:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", ], ) diff --git a/cmd/validator/wallet/create.go b/cmd/validator/wallet/create.go new file mode 100644 index 000000000000..3d6ebabf119e --- /dev/null +++ b/cmd/validator/wallet/create.go @@ -0,0 +1,171 @@ +package wallet + +import ( + "fmt" + "os" + "strings" + + "github.com/manifoldco/promptui" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v3/cmd/validator/flags" + "github.com/prysmaticlabs/prysm/v3/io/prompt" + "github.com/prysmaticlabs/prysm/v3/validator/accounts" + "github.com/prysmaticlabs/prysm/v3/validator/accounts/userprompt" + "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" + "github.com/prysmaticlabs/prysm/v3/validator/keymanager" + "github.com/urfave/cli/v2" +) + +const ( + // #nosec G101 -- Not sensitive data + newMnemonicPassphraseYesNoText = "(Advanced) Do you want to setup a '25th word' passphrase for your mnemonic? [y/n]" + // #nosec G101 -- Not sensitive data + newMnemonicPassphrasePromptText = "(Advanced) Setup a passphrase '25th word' for your mnemonic " + + "(WARNING: You cannot recover your keys from your mnemonic if you forget this passphrase!)" +) + +func walletCreate(c *cli.Context) error { + keymanagerKind, err := inputKeymanagerKind(c) + if err != nil { + return err + } + + opts, err := ConstructCLIManagerOpts(c, keymanagerKind) + if err != nil { + return err + } + + acc, err := accounts.NewCLIManager(opts...) + if err != nil { + return err + } + if _, err := acc.WalletCreate(c.Context); err != nil { + return errors.Wrap(err, "could not create wallet") + } + return nil +} + +// ConstructCLIManagerOpts prompts the user for wallet creation input. +func ConstructCLIManagerOpts(cliCtx *cli.Context, keymanagerKind keymanager.Kind) ([]accounts.Option, error) { + cliOpts := []accounts.Option{} + // Get wallet dir and check that no wallet exists at the location. + walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag) + if err != nil { + return []accounts.Option{}, err + } + dirExists, err := wallet.Exists(walletDir) + if err != nil { + return []accounts.Option{}, err + } + if dirExists { + return []accounts.Option{}, errors.New("a wallet already exists at this location. Please input an" + + " alternative location for the new wallet or remove the current wallet") + } + + walletPassword, err := prompt.InputPassword( + cliCtx, + flags.WalletPasswordFileFlag, + wallet.NewWalletPasswordPromptText, + wallet.ConfirmPasswordPromptText, + true, /* Should confirm password */ + prompt.ValidatePasswordInput, + ) + if err != nil { + return []accounts.Option{}, err + } + cliOpts = append(cliOpts, accounts.WithWalletDir(walletDir)) + cliOpts = append(cliOpts, accounts.WithWalletPassword(walletPassword)) + cliOpts = append(cliOpts, accounts.WithKeymanagerType(keymanagerKind)) + cliOpts = append(cliOpts, accounts.WithSkipMnemonicConfirm(cliCtx.Bool(flags.SkipDepositConfirmationFlag.Name))) + + skipMnemonic25thWord := cliCtx.IsSet(flags.SkipMnemonic25thWordCheckFlag.Name) + has25thWordFile := cliCtx.IsSet(flags.Mnemonic25thWordFileFlag.Name) + if keymanagerKind == keymanager.Derived { + numAccounts, err := inputNumAccounts(cliCtx) + if err != nil { + return []accounts.Option{}, errors.Wrap(err, "could not get number of accounts to generate") + } + cliOpts = append(cliOpts, accounts.WithNumAccounts(int(numAccounts))) + } + if keymanagerKind == keymanager.Derived && !skipMnemonic25thWord && !has25thWordFile { + resp, err := prompt.ValidatePrompt( + os.Stdin, newMnemonicPassphraseYesNoText, prompt.ValidateYesOrNo, + ) + if err != nil { + return []accounts.Option{}, errors.Wrap(err, "could not validate choice") + } + if strings.EqualFold(resp, "y") { + mnemonicPassphrase, err := prompt.InputPassword( + cliCtx, + flags.Mnemonic25thWordFileFlag, + newMnemonicPassphrasePromptText, + "Confirm mnemonic passphrase", + true, /* Should confirm password */ + func(input string) error { + if strings.TrimSpace(input) == "" { + return errors.New("input cannot be empty") + } + return nil + }, + ) + if err != nil { + return []accounts.Option{}, err + } + cliOpts = append(cliOpts, accounts.WithMnemonic25thWord(mnemonicPassphrase)) + } + } + if keymanagerKind == keymanager.Remote { + opts, err := userprompt.InputRemoteKeymanagerConfig(cliCtx) + if err != nil { + return []accounts.Option{}, errors.Wrap(err, "could not input remote keymanager config") + } + cliOpts = append(cliOpts, accounts.WithKeymanagerOpts(opts)) + } + if keymanagerKind == keymanager.Web3Signer { + return []accounts.Option{}, errors.New("web3signer keymanager does not require persistent wallets.") + } + return cliOpts, nil +} + +func inputKeymanagerKind(cliCtx *cli.Context) (keymanager.Kind, error) { + if cliCtx.IsSet(flags.KeymanagerKindFlag.Name) { + return keymanager.ParseKind(cliCtx.String(flags.KeymanagerKindFlag.Name)) + } + promptSelect := promptui.Select{ + Label: "Select a type of wallet", + Items: []string{ + wallet.KeymanagerKindSelections[keymanager.Local], + wallet.KeymanagerKindSelections[keymanager.Derived], + wallet.KeymanagerKindSelections[keymanager.Remote], + wallet.KeymanagerKindSelections[keymanager.Web3Signer], + }, + } + selection, _, err := promptSelect.Run() + if err != nil { + return keymanager.Local, fmt.Errorf("could not select wallet type: %w", userprompt.FormatPromptError(err)) + } + return keymanager.Kind(selection), nil +} + +// CreateAndSaveWalletCli from user input with a desired keymanager. If a +// wallet already exists in the path, it suggests the user alternatives +// such as how to edit their existing wallet configuration. +func CreateAndSaveWalletCli(cliCtx *cli.Context) (*wallet.Wallet, error) { + keymanagerKind, err := inputKeymanagerKind(cliCtx) + if err != nil { + return nil, err + } + opts, err := ConstructCLIManagerOpts(cliCtx, keymanagerKind) + if err != nil { + return nil, err + } + acc, err := accounts.NewCLIManager(opts...) + if err != nil { + return nil, err + } + w, err := acc.WalletCreate(cliCtx.Context) + if err != nil { + return nil, errors.Wrap(err, "could not create wallet") + } + return w, nil +} diff --git a/validator/accounts/wallet_create_test.go b/cmd/validator/wallet/create_test.go similarity index 64% rename from validator/accounts/wallet_create_test.go rename to cmd/validator/wallet/create_test.go index ee05e2fdfbe9..20dddbeb30de 100644 --- a/validator/accounts/wallet_create_test.go +++ b/cmd/validator/wallet/create_test.go @@ -1,19 +1,17 @@ -package accounts +package wallet import ( "context" "flag" "io" - "os" - "path/filepath" - "strconv" "testing" "github.com/pkg/errors" + cmdacc "github.com/prysmaticlabs/prysm/v3/cmd/validator/accounts" "github.com/prysmaticlabs/prysm/v3/cmd/validator/flags" - "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" + "github.com/prysmaticlabs/prysm/v3/validator/accounts" "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/local" @@ -23,89 +21,11 @@ import ( "github.com/urfave/cli/v2" ) -const ( - passwordFileName = "password.txt" - password = "OhWOWthisisatest42!$" -) - func init() { logrus.SetLevel(logrus.DebugLevel) logrus.SetOutput(io.Discard) } -type testWalletConfig struct { - exitAll bool - skipDepositConfirm bool - keymanagerKind keymanager.Kind - numAccounts int64 - grpcHeaders string - privateKeyFile string - accountPasswordFile string - walletPasswordFile string - backupPasswordFile string - backupPublicKeys string - voluntaryExitPublicKeys string - deletePublicKeys string - keysDir string - backupDir string - passwordsDir string - walletDir string -} - -func setupWalletCtx( - tb testing.TB, - cfg *testWalletConfig, -) *cli.Context { - app := cli.App{} - set := flag.NewFlagSet("test", 0) - set.String(flags.WalletDirFlag.Name, cfg.walletDir, "") - set.String(flags.KeysDirFlag.Name, cfg.keysDir, "") - set.String(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String(), "") - set.String(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys, "") - set.String(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys, "") - set.String(flags.BackupDirFlag.Name, cfg.backupDir, "") - set.String(flags.BackupPasswordFile.Name, cfg.backupPasswordFile, "") - set.String(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys, "") - set.String(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile, "") - set.String(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile, "") - set.Int64(flags.NumAccountsFlag.Name, cfg.numAccounts, "") - set.Bool(flags.SkipDepositConfirmationFlag.Name, cfg.skipDepositConfirm, "") - set.Bool(flags.SkipMnemonic25thWordCheckFlag.Name, true, "") - set.Bool(flags.ExitAllFlag.Name, cfg.exitAll, "") - set.String(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders, "") - - if cfg.privateKeyFile != "" { - set.String(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile, "") - assert.NoError(tb, set.Set(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile)) - } - assert.NoError(tb, set.Set(flags.WalletDirFlag.Name, cfg.walletDir)) - assert.NoError(tb, set.Set(flags.SkipMnemonic25thWordCheckFlag.Name, "true")) - assert.NoError(tb, set.Set(flags.KeysDirFlag.Name, cfg.keysDir)) - assert.NoError(tb, set.Set(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String())) - assert.NoError(tb, set.Set(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys)) - assert.NoError(tb, set.Set(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys)) - assert.NoError(tb, set.Set(flags.BackupDirFlag.Name, cfg.backupDir)) - assert.NoError(tb, set.Set(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys)) - assert.NoError(tb, set.Set(flags.BackupPasswordFile.Name, cfg.backupPasswordFile)) - assert.NoError(tb, set.Set(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile)) - assert.NoError(tb, set.Set(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile)) - assert.NoError(tb, set.Set(flags.NumAccountsFlag.Name, strconv.Itoa(int(cfg.numAccounts)))) - assert.NoError(tb, set.Set(flags.SkipDepositConfirmationFlag.Name, strconv.FormatBool(cfg.skipDepositConfirm))) - assert.NoError(tb, set.Set(flags.ExitAllFlag.Name, strconv.FormatBool(cfg.exitAll))) - assert.NoError(tb, set.Set(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders)) - return cli.NewContext(&app, set, nil) -} - -func setupWalletAndPasswordsDir(t testing.TB) (string, string, string) { - walletDir := filepath.Join(t.TempDir(), "wallet") - passwordsDir := filepath.Join(t.TempDir(), "passwords") - passwordFileDir := filepath.Join(t.TempDir(), "passwordFile") - require.NoError(t, os.MkdirAll(passwordFileDir, params.BeaconIoConfig().ReadWriteExecutePermissions)) - passwordFilePath := filepath.Join(passwordFileDir, passwordFileName) - require.NoError(t, os.WriteFile(passwordFilePath, []byte(password), os.ModePerm)) - return walletDir, passwordsDir, passwordFilePath -} - func TestCreateOrOpenWallet(t *testing.T) { hook := logTest.NewGlobal() walletDir, passwordsDir, walletPasswordFile := setupWalletAndPasswordsDir(t) @@ -116,19 +36,19 @@ func TestCreateOrOpenWallet(t *testing.T) { walletPasswordFile: walletPasswordFile, }) createLocalWallet := func(cliCtx *cli.Context) (*wallet.Wallet, error) { - cfg, err := ExtractWalletCreationConfigFromCli(cliCtx, keymanager.Local) + cfg, err := cmdacc.ExtractWalletDirPassword(cliCtx) if err != nil { return nil, err } w := wallet.New(&wallet.Config{ - KeymanagerKind: cfg.WalletCfg.KeymanagerKind, - WalletDir: cfg.WalletCfg.WalletDir, - WalletPassword: cfg.WalletCfg.WalletPassword, + KeymanagerKind: keymanager.Local, + WalletDir: cfg.Dir, + WalletPassword: cfg.Password, }) - if err = CreateLocalKeymanagerWallet(cliCtx.Context, w); err != nil { + if err = accounts.CreateLocalKeymanagerWallet(cliCtx.Context, w); err != nil { return nil, errors.Wrap(err, "could not create keymanager") } - log.WithField("wallet-path", cfg.WalletCfg.WalletDir).Info( + log.WithField("wallet-path", cfg.Dir).Info( "Successfully created new wallet", ) return w, nil diff --git a/cmd/validator/wallet/edit_test.go b/cmd/validator/wallet/edit_test.go index 81a8129f3c65..000c463eac14 100644 --- a/cmd/validator/wallet/edit_test.go +++ b/cmd/validator/wallet/edit_test.go @@ -12,7 +12,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/accounts" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote" "github.com/urfave/cli/v2" @@ -52,6 +51,7 @@ type testWalletConfig struct { keysDir string backupDir string walletDir string + passwordsDir string } func setupWalletCtx( @@ -104,13 +104,14 @@ func TestEditWalletConfiguration(t *testing.T) { walletDir: walletDir, keymanagerKind: keymanager.Remote, }) - w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Remote, - WalletPassword: "Passwordz0320$", - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Remote), + accounts.WithWalletPassword("Passwordz0320$"), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) originalCfg := &remote.KeymanagerOpts{ diff --git a/cmd/validator/wallet/wallet.go b/cmd/validator/wallet/wallet.go index 5122190b1588..3599ddbede56 100644 --- a/cmd/validator/wallet/wallet.go +++ b/cmd/validator/wallet/wallet.go @@ -5,7 +5,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/cmd/validator/flags" "github.com/prysmaticlabs/prysm/v3/config/features" "github.com/prysmaticlabs/prysm/v3/runtime/tos" - "github.com/prysmaticlabs/prysm/v3/validator/accounts" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) @@ -43,13 +42,13 @@ var Commands = &cli.Command{ if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil { return err } - return tos.VerifyTosAcceptedOrPrompt(cliCtx) - }, - Action: func(cliCtx *cli.Context) error { - if err := features.ConfigureValidator(cliCtx); err != nil { + if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil { return err } - if _, err := accounts.CreateAndSaveWalletCli(cliCtx); err != nil { + return features.ConfigureValidator(cliCtx) + }, + Action: func(cliCtx *cli.Context) error { + if err := walletCreate(cliCtx); err != nil { log.WithError(err).Fatal("Could not create a wallet") } return nil diff --git a/validator/accounts/BUILD.bazel b/validator/accounts/BUILD.bazel index c80f98d667f0..9416450fc09f 100644 --- a/validator/accounts/BUILD.bazel +++ b/validator/accounts/BUILD.bazel @@ -45,7 +45,6 @@ go_library( "//validator/keymanager/derived:go_default_library", "//validator/keymanager/local:go_default_library", "//validator/keymanager/remote:go_default_library", - "//validator/keymanager/remote-web3signer:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_google_uuid//:go_default_library", "@com_github_logrusorgru_aurora//:go_default_library", @@ -65,7 +64,6 @@ go_test( "accounts_exit_test.go", "accounts_import_test.go", "accounts_list_test.go", - "wallet_create_test.go", "wallet_recover_fuzz_test.go", "wallet_recover_test.go", ], @@ -96,7 +94,6 @@ go_test( "@com_github_golang_mock//gomock:go_default_library", "@com_github_google_uuid//:go_default_library", "@com_github_pkg_errors//:go_default_library", - "@com_github_sirupsen_logrus//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", "@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library", diff --git a/validator/accounts/accounts_import_test.go b/validator/accounts/accounts_import_test.go index 5e80868f5e5a..df1e6ff26e4a 100644 --- a/validator/accounts/accounts_import_test.go +++ b/validator/accounts/accounts_import_test.go @@ -15,7 +15,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/accounts/iface" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/local" ) @@ -34,13 +33,14 @@ func TestImportAccounts_NoPassword(t *testing.T) { walletPasswordFile: passwordFilePath, accountPasswordFile: passwordFilePath, }) - w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) + opts := []Option{ + WithWalletDir(walletDir), + WithKeymanagerType(keymanager.Local), + WithWalletPassword(password), + } + acc, err := NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) @@ -141,13 +141,14 @@ func Test_importPrivateKeyAsAccount(t *testing.T) { privateKeyFile: privKeyFileName, }) walletPass := "Passwordz0320$" - w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: walletPass, - }, - }) + opts := []Option{ + WithWalletDir(walletDir), + WithKeymanagerType(keymanager.Local), + WithWalletPassword(walletPass), + } + acc, err := NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) km, err := local.NewKeymanager( cliCtx.Context, diff --git a/validator/accounts/accounts_list_test.go b/validator/accounts/accounts_list_test.go index b13899adfbef..8712f17fe606 100644 --- a/validator/accounts/accounts_list_test.go +++ b/validator/accounts/accounts_list_test.go @@ -2,10 +2,12 @@ package accounts import ( "context" + "flag" "fmt" "io" "math" "os" + "path/filepath" "strconv" "strings" "testing" @@ -13,7 +15,9 @@ import ( "github.com/golang/mock/gomock" "github.com/google/uuid" "github.com/prysmaticlabs/prysm/v3/async/event" + "github.com/prysmaticlabs/prysm/v3/cmd/validator/flags" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" + "github.com/prysmaticlabs/prysm/v3/config/params" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/crypto/bls" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" @@ -30,9 +34,88 @@ import ( "github.com/prysmaticlabs/prysm/v3/validator/keymanager/local" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote" constant "github.com/prysmaticlabs/prysm/v3/validator/testing" + "github.com/urfave/cli/v2" keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" ) +const ( + passwordFileName = "password.txt" + password = "OhWOWthisisatest42!$" +) + +type testWalletConfig struct { + exitAll bool + skipDepositConfirm bool + keymanagerKind keymanager.Kind + numAccounts int64 + grpcHeaders string + privateKeyFile string + accountPasswordFile string + walletPasswordFile string + backupPasswordFile string + backupPublicKeys string + voluntaryExitPublicKeys string + deletePublicKeys string + keysDir string + backupDir string + passwordsDir string + walletDir string +} + +func setupWalletCtx( + tb testing.TB, + cfg *testWalletConfig, +) *cli.Context { + app := cli.App{} + set := flag.NewFlagSet("test", 0) + set.String(flags.WalletDirFlag.Name, cfg.walletDir, "") + set.String(flags.KeysDirFlag.Name, cfg.keysDir, "") + set.String(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String(), "") + set.String(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys, "") + set.String(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys, "") + set.String(flags.BackupDirFlag.Name, cfg.backupDir, "") + set.String(flags.BackupPasswordFile.Name, cfg.backupPasswordFile, "") + set.String(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys, "") + set.String(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile, "") + set.String(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile, "") + set.Int64(flags.NumAccountsFlag.Name, cfg.numAccounts, "") + set.Bool(flags.SkipDepositConfirmationFlag.Name, cfg.skipDepositConfirm, "") + set.Bool(flags.SkipMnemonic25thWordCheckFlag.Name, true, "") + set.Bool(flags.ExitAllFlag.Name, cfg.exitAll, "") + set.String(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders, "") + + if cfg.privateKeyFile != "" { + set.String(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile, "") + assert.NoError(tb, set.Set(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile)) + } + assert.NoError(tb, set.Set(flags.WalletDirFlag.Name, cfg.walletDir)) + assert.NoError(tb, set.Set(flags.SkipMnemonic25thWordCheckFlag.Name, "true")) + assert.NoError(tb, set.Set(flags.KeysDirFlag.Name, cfg.keysDir)) + assert.NoError(tb, set.Set(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String())) + assert.NoError(tb, set.Set(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys)) + assert.NoError(tb, set.Set(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys)) + assert.NoError(tb, set.Set(flags.BackupDirFlag.Name, cfg.backupDir)) + assert.NoError(tb, set.Set(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys)) + assert.NoError(tb, set.Set(flags.BackupPasswordFile.Name, cfg.backupPasswordFile)) + assert.NoError(tb, set.Set(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile)) + assert.NoError(tb, set.Set(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile)) + assert.NoError(tb, set.Set(flags.NumAccountsFlag.Name, strconv.Itoa(int(cfg.numAccounts)))) + assert.NoError(tb, set.Set(flags.SkipDepositConfirmationFlag.Name, strconv.FormatBool(cfg.skipDepositConfirm))) + assert.NoError(tb, set.Set(flags.ExitAllFlag.Name, strconv.FormatBool(cfg.exitAll))) + assert.NoError(tb, set.Set(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders)) + return cli.NewContext(&app, set, nil) +} + +func setupWalletAndPasswordsDir(t testing.TB) (string, string, string) { + walletDir := filepath.Join(t.TempDir(), "wallet") + passwordsDir := filepath.Join(t.TempDir(), "passwords") + passwordFileDir := filepath.Join(t.TempDir(), "passwordFile") + require.NoError(t, os.MkdirAll(passwordFileDir, params.BeaconIoConfig().ReadWriteExecutePermissions)) + passwordFilePath := filepath.Join(passwordFileDir, passwordFileName) + require.NoError(t, os.WriteFile(passwordFilePath, []byte(password), os.ModePerm)) + return walletDir, passwordsDir, passwordFilePath +} + type mockRemoteKeymanager struct { publicKeys [][fieldparams.BLSPubkeyLength]byte opts *remote.KeymanagerOpts @@ -42,15 +125,15 @@ func (m *mockRemoteKeymanager) FetchValidatingPublicKeys(_ context.Context) ([][ return m.publicKeys, nil } -func (_ *mockRemoteKeymanager) Sign(context.Context, *validatorpb.SignRequest) (bls.Signature, error) { +func (*mockRemoteKeymanager) Sign(context.Context, *validatorpb.SignRequest) (bls.Signature, error) { return nil, nil } -func (_ *mockRemoteKeymanager) SubscribeAccountChanges(_ chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription { +func (*mockRemoteKeymanager) SubscribeAccountChanges(_ chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription { return nil } -func (_ *mockRemoteKeymanager) ExtractKeystores( +func (*mockRemoteKeymanager) ExtractKeystores( _ context.Context, _ []bls.PublicKey, _ string, ) ([]*keymanager.Keystore, error) { return nil, nil @@ -90,13 +173,14 @@ func TestListAccounts_LocalKeymanager(t *testing.T) { keymanagerKind: keymanager.Local, walletPasswordFile: walletPasswordFile, }) - w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: "Passwordz0320$", - }, - }) + opts := []Option{ + WithWalletDir(walletDir), + WithKeymanagerType(keymanager.Local), + WithWalletPassword("Passwordz0320$"), + } + acc, err := NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) km, err := local.NewKeymanager( cliCtx.Context, @@ -245,13 +329,14 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) { keymanagerKind: keymanager.Derived, walletPasswordFile: passwordFilePath, }) - w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Derived, - WalletPassword: "Passwordz0320$", - }, - }) + opts := []Option{ + WithWalletDir(walletDir), + WithKeymanagerType(keymanager.Derived), + WithWalletPassword("Passwordz0320$"), + } + acc, err := NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) km, err := derived.NewKeymanager( @@ -384,13 +469,14 @@ func TestListAccounts_RemoteKeymanager(t *testing.T) { walletDir: walletDir, keymanagerKind: keymanager.Remote, }) - w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Remote, - WalletPassword: password, - }, - }) + opts := []Option{ + WithWalletDir(walletDir), + WithKeymanagerType(keymanager.Remote), + WithWalletPassword(password), + } + acc, err := NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(cliCtx.Context) require.NoError(t, err) rescueStdout := os.Stdout diff --git a/validator/accounts/cli_manager.go b/validator/accounts/cli_manager.go index 990a09e12fb8..4766b46b285f 100644 --- a/validator/accounts/cli_manager.go +++ b/validator/accounts/cli_manager.go @@ -29,6 +29,7 @@ func NewCLIManager(opts ...Option) (*AccountsCLIManager, error) { type AccountsCLIManager struct { wallet *wallet.Wallet keymanager keymanager.IKeymanager + keymanagerKind keymanager.Kind keymanagerOpts *remote.KeymanagerOpts showDepositData bool showPrivateKeys bool @@ -36,6 +37,7 @@ type AccountsCLIManager struct { deletePublicKeys bool importPrivateKeys bool readPasswordFile bool + skipMnemonicConfirm bool dialOpts []grpc.DialOption grpcHeaders []string beaconRPCProvider string diff --git a/validator/accounts/cli_options.go b/validator/accounts/cli_options.go index 15ccef9ba814..0dec8af4ed51 100644 --- a/validator/accounts/cli_options.go +++ b/validator/accounts/cli_options.go @@ -27,6 +27,14 @@ func WithKeymanager(km keymanager.IKeymanager) Option { } } +// WithKeymanagerType provides a keymanager to the accounts cli manager. +func WithKeymanagerType(k keymanager.Kind) Option { + return func(acc *AccountsCLIManager) error { + acc.keymanagerKind = k + return nil + } +} + // WithKeymanagerOpts provides a keymanager configuration to the accounts cli manager. func WithKeymanagerOpts(kmo *remote.KeymanagerOpts) Option { return func(acc *AccountsCLIManager) error { @@ -115,6 +123,14 @@ func WithImportPrivateKeys(importPrivateKeys bool) Option { } } +// WithSkipMnemonicConfirm indicates whether to skip the mnemonic confirmation. +func WithSkipMnemonicConfirm(s bool) Option { + return func(acc *AccountsCLIManager) error { + acc.skipMnemonicConfirm = s + return nil + } +} + // WithPrivateKeyFile specifies the private key path. func WithPrivateKeyFile(privateKeyFile string) Option { return func(acc *AccountsCLIManager) error { diff --git a/validator/accounts/wallet_create.go b/validator/accounts/wallet_create.go index 62f366cd6d30..fbb69eede8e1 100644 --- a/validator/accounts/wallet_create.go +++ b/validator/accounts/wallet_create.go @@ -3,80 +3,22 @@ package accounts import ( "context" "encoding/json" - "fmt" - "os" - "strconv" - "strings" - "github.com/manifoldco/promptui" "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v3/cmd/validator/flags" - "github.com/prysmaticlabs/prysm/v3/io/prompt" "github.com/prysmaticlabs/prysm/v3/validator/accounts/iface" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/userprompt" "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/local" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote" - remoteweb3signer "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer" - "github.com/urfave/cli/v2" ) -const ( - // #nosec G101 -- Not sensitive data - newMnemonicPassphraseYesNoText = "(Advanced) Do you want to setup a '25th word' passphrase for your mnemonic? [y/n]" - // #nosec G101 -- Not sensitive data - newMnemonicPassphrasePromptText = "(Advanced) Setup a passphrase '25th word' for your mnemonic " + - "(WARNING: You cannot recover your keys from your mnemonic if you forget this passphrase!)" -) - -// CreateWalletConfig defines the parameters needed to call the create wallet functions. -type CreateWalletConfig struct { - SkipMnemonicConfirm bool - NumAccounts int - RemoteKeymanagerOpts *remote.KeymanagerOpts - Web3SignerSetupConfig *remoteweb3signer.SetupConfig - WalletCfg *wallet.Config - Mnemonic25thWord string -} - -// CreateAndSaveWalletCli from user input with a desired keymanager. If a -// wallet already exists in the path, it suggests the user alternatives -// such as how to edit their existing wallet configuration. -func CreateAndSaveWalletCli(cliCtx *cli.Context) (*wallet.Wallet, error) { - keymanagerKind, err := extractKeymanagerKindFromCli(cliCtx) - if err != nil { - return nil, err - } - createWalletConfig, err := ExtractWalletCreationConfigFromCli(cliCtx, keymanagerKind) - if err != nil { - return nil, err - } - - dir := createWalletConfig.WalletCfg.WalletDir - dirExists, err := wallet.Exists(dir) - if err != nil { - return nil, err - } - if dirExists { - return nil, errors.New("a wallet already exists at this location. Please input an" + - " alternative location for the new wallet or remove the current wallet") - } - - w, err := CreateWalletWithKeymanager(cliCtx.Context, createWalletConfig) - if err != nil { - return nil, errors.Wrap(err, "could not create wallet") - } - return w, nil -} - // CreateWalletWithKeymanager specified by configuration options. -func CreateWalletWithKeymanager(ctx context.Context, cfg *CreateWalletConfig) (*wallet.Wallet, error) { +func (acm *AccountsCLIManager) WalletCreate(ctx context.Context) (*wallet.Wallet, error) { w := wallet.New(&wallet.Config{ - WalletDir: cfg.WalletCfg.WalletDir, - KeymanagerKind: cfg.WalletCfg.KeymanagerKind, - WalletPassword: cfg.WalletCfg.WalletPassword, + WalletDir: acm.walletDir, + KeymanagerKind: acm.keymanagerKind, + WalletPassword: acm.walletPassword, }) var err error switch w.KeymanagerKind() { @@ -105,27 +47,27 @@ func CreateWalletWithKeymanager(ctx context.Context, cfg *CreateWalletConfig) (* return nil, err } - log.WithField("--wallet-dir", cfg.WalletCfg.WalletDir).Info( + log.WithField("--wallet-dir", acm.walletDir).Info( "Successfully created wallet with ability to import keystores", ) case keymanager.Derived: if err = createDerivedKeymanagerWallet( ctx, w, - cfg.Mnemonic25thWord, - cfg.SkipMnemonicConfirm, - cfg.NumAccounts, + acm.mnemonic25thWord, + acm.skipMnemonicConfirm, + acm.numAccounts, ); err != nil { return nil, errors.Wrap(err, "could not initialize wallet") } - log.WithField("--wallet-dir", cfg.WalletCfg.WalletDir).Info( + log.WithField("--wallet-dir", acm.walletDir).Info( "Successfully created HD wallet from mnemonic and regenerated accounts", ) case keymanager.Remote: - if err = createRemoteKeymanagerWallet(ctx, w, cfg.RemoteKeymanagerOpts); err != nil { + if err = createRemoteKeymanagerWallet(ctx, w, acm.keymanagerOpts); err != nil { return nil, errors.Wrap(err, "could not initialize wallet") } - log.WithField("--wallet-dir", cfg.WalletCfg.WalletDir).Info( + log.WithField("--wallet-dir", acm.walletDir).Info( "Successfully created wallet with remote keymanager configuration", ) case keymanager.Web3Signer: @@ -136,84 +78,6 @@ func CreateWalletWithKeymanager(ctx context.Context, cfg *CreateWalletConfig) (* return w, nil } -func extractKeymanagerKindFromCli(cliCtx *cli.Context) (keymanager.Kind, error) { - return inputKeymanagerKind(cliCtx) -} - -// ExtractWalletCreationConfigFromCli prompts the user for wallet creation input. -func ExtractWalletCreationConfigFromCli(cliCtx *cli.Context, keymanagerKind keymanager.Kind) (*CreateWalletConfig, error) { - walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag) - if err != nil { - return nil, err - } - walletPassword, err := prompt.InputPassword( - cliCtx, - flags.WalletPasswordFileFlag, - wallet.NewWalletPasswordPromptText, - wallet.ConfirmPasswordPromptText, - true, /* Should confirm password */ - prompt.ValidatePasswordInput, - ) - if err != nil { - return nil, err - } - createWalletConfig := &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanagerKind, - WalletPassword: walletPassword, - }, - SkipMnemonicConfirm: cliCtx.Bool(flags.SkipDepositConfirmationFlag.Name), - } - skipMnemonic25thWord := cliCtx.IsSet(flags.SkipMnemonic25thWordCheckFlag.Name) - has25thWordFile := cliCtx.IsSet(flags.Mnemonic25thWordFileFlag.Name) - if keymanagerKind == keymanager.Derived { - numAccounts, err := inputNumAccounts(cliCtx) - if err != nil { - return nil, errors.Wrap(err, "could not get number of accounts to generate") - } - createWalletConfig.NumAccounts = int(numAccounts) - } - if keymanagerKind == keymanager.Derived && !skipMnemonic25thWord && !has25thWordFile { - resp, err := prompt.ValidatePrompt( - os.Stdin, newMnemonicPassphraseYesNoText, prompt.ValidateYesOrNo, - ) - if err != nil { - return nil, errors.Wrap(err, "could not validate choice") - } - if strings.EqualFold(resp, "y") { - mnemonicPassphrase, err := prompt.InputPassword( - cliCtx, - flags.Mnemonic25thWordFileFlag, - newMnemonicPassphrasePromptText, - "Confirm mnemonic passphrase", - true, /* Should confirm password */ - func(input string) error { - if strings.TrimSpace(input) == "" { - return errors.New("input cannot be empty") - } - return nil - }, - ) - if err != nil { - return nil, err - } - createWalletConfig.Mnemonic25thWord = mnemonicPassphrase - } - } - if keymanagerKind == keymanager.Remote { - opts, err := userprompt.InputRemoteKeymanagerConfig(cliCtx) - if err != nil { - return nil, errors.Wrap(err, "could not input remote keymanager config") - } - createWalletConfig.RemoteKeymanagerOpts = opts - } - if keymanagerKind == keymanager.Web3Signer { - return nil, errors.New("web3signer keymanager does not require persistent wallets.") - } - return createWalletConfig, nil -} - func CreateLocalKeymanagerWallet(_ context.Context, wallet *wallet.Wallet) error { if wallet == nil { return errors.New("nil wallet") @@ -267,47 +131,3 @@ func createRemoteKeymanagerWallet(ctx context.Context, wallet *wallet.Wallet, op } return nil } - -func inputKeymanagerKind(cliCtx *cli.Context) (keymanager.Kind, error) { - if cliCtx.IsSet(flags.KeymanagerKindFlag.Name) { - return keymanager.ParseKind(cliCtx.String(flags.KeymanagerKindFlag.Name)) - } - promptSelect := promptui.Select{ - Label: "Select a type of wallet", - Items: []string{ - wallet.KeymanagerKindSelections[keymanager.Local], - wallet.KeymanagerKindSelections[keymanager.Derived], - wallet.KeymanagerKindSelections[keymanager.Remote], - wallet.KeymanagerKindSelections[keymanager.Web3Signer], - }, - } - selection, _, err := promptSelect.Run() - if err != nil { - return keymanager.Local, fmt.Errorf("could not select wallet type: %w", userprompt.FormatPromptError(err)) - } - return keymanager.Kind(selection), nil -} - -// TODO(mikeneuder): Remove duplicate function when migration wallet create -// to cmd/validator/wallet. -func inputNumAccounts(cliCtx *cli.Context) (int64, error) { - if cliCtx.IsSet(flags.NumAccountsFlag.Name) { - numAccounts := cliCtx.Int64(flags.NumAccountsFlag.Name) - if numAccounts <= 0 { - return 0, errors.New("must recover at least 1 account") - } - return numAccounts, nil - } - numAccounts, err := prompt.ValidatePrompt(os.Stdin, "Enter how many accounts you would like to generate from the mnemonic", prompt.ValidateNumber) - if err != nil { - return 0, err - } - numAccountsInt, err := strconv.Atoi(numAccounts) - if err != nil { - return 0, err - } - if numAccountsInt <= 0 { - return 0, errors.New("must recover at least 1 account") - } - return int64(numAccountsInt), nil -} diff --git a/validator/node/BUILD.bazel b/validator/node/BUILD.bazel index ba82c5eb2299..6937a7009bf0 100644 --- a/validator/node/BUILD.bazel +++ b/validator/node/BUILD.bazel @@ -15,7 +15,6 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "//validator/accounts:go_default_library", - "//validator/accounts/wallet:go_default_library", "//validator/keymanager:go_default_library", "//validator/keymanager/remote-web3signer:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", diff --git a/validator/node/node_test.go b/validator/node/node_test.go index 01a89478c310..3ef843601444 100644 --- a/validator/node/node_test.go +++ b/validator/node/node_test.go @@ -20,7 +20,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/accounts" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" remoteweb3signer "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer" logtest "github.com/sirupsen/logrus/hooks/test" @@ -48,13 +47,15 @@ func TestNode_Builds(t *testing.T) { set.String("verbosity", "debug", "log verbosity") require.NoError(t, set.Set(flags.WalletPasswordFileFlag.Name, passwordFile)) ctx := cli.NewContext(&app, set, nil) - _, err := accounts.CreateWalletWithKeymanager(ctx.Context, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: dir, - KeymanagerKind: keymanager.Local, - WalletPassword: walletPassword, - }, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(dir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(walletPassword), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + _, err = acc.WalletCreate(ctx.Context) require.NoError(t, err) valClient, err := NewValidatorClient(ctx) diff --git a/validator/rpc/accounts_test.go b/validator/rpc/accounts_test.go index be8ed5042a6a..447ece200c0d 100644 --- a/validator/rpc/accounts_test.go +++ b/validator/rpc/accounts_test.go @@ -21,7 +21,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/validator/accounts" "github.com/prysmaticlabs/prysm/v3/validator/accounts/iface" mock "github.com/prysmaticlabs/prysm/v3/validator/accounts/testing" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/client" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived" @@ -38,14 +37,15 @@ func TestServer_ListAccounts(t *testing.T) { localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir // We attempt to create the wallet. - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) @@ -110,14 +110,15 @@ func TestServer_BackupAccounts(t *testing.T) { localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir // We attempt to create the wallet. - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) @@ -220,14 +221,15 @@ func TestServer_VoluntaryExit(t *testing.T) { localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir // We attempt to create the wallet. - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) diff --git a/validator/rpc/health.go b/validator/rpc/health.go index 6c4cc7f7b580..a86d8c73d26a 100644 --- a/validator/rpc/health.go +++ b/validator/rpc/health.go @@ -39,7 +39,7 @@ func (s *Server) GetBeaconNodeConnection(ctx context.Context, _ *emptypb.Empty) } // GetLogsEndpoints for the beacon and validator client. -func (_ *Server) GetLogsEndpoints(_ context.Context, _ *emptypb.Empty) (*validatorpb.LogsEndpointResponse, error) { +func (*Server) GetLogsEndpoints(_ context.Context, _ *emptypb.Empty) (*validatorpb.LogsEndpointResponse, error) { return nil, status.Error(codes.Unimplemented, "unimplemented") } diff --git a/validator/rpc/slashing_test.go b/validator/rpc/slashing_test.go index cae718778c87..3c8ff2b5061a 100644 --- a/validator/rpc/slashing_test.go +++ b/validator/rpc/slashing_test.go @@ -9,7 +9,6 @@ import ( pb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/accounts" - "github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v3/validator/db/kv" "github.com/prysmaticlabs/prysm/v3/validator/keymanager" "github.com/prysmaticlabs/prysm/v3/validator/slashing-protection-history/format" @@ -34,14 +33,15 @@ func TestImportSlashingProtection_Preconditions(t *testing.T) { require.ErrorContains(t, "err finding validator database at path", err) // Create Wallet and add to server for more realistic testing. - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Local, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) s.wallet = w diff --git a/validator/rpc/standard_api_test.go b/validator/rpc/standard_api_test.go index 11f376901bfd..039bff3e7d5e 100644 --- a/validator/rpc/standard_api_test.go +++ b/validator/rpc/standard_api_test.go @@ -46,14 +46,15 @@ func TestServer_ListKeystores(t *testing.T) { ctx := context.Background() localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) @@ -102,14 +103,15 @@ func TestServer_ImportKeystores(t *testing.T) { ctx := context.Background() localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) @@ -489,14 +491,15 @@ func setupServerWithWallet(t testing.TB) *Server { ctx := context.Background() localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) diff --git a/validator/rpc/wallet.go b/validator/rpc/wallet.go index 67e7c0956426..afd3232f5218 100644 --- a/validator/rpc/wallet.go +++ b/validator/rpc/wallet.go @@ -65,14 +65,17 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest) return nil, status.Errorf(codes.InvalidArgument, "Password too weak: %v", err) } if req.Keymanager == pb.KeymanagerKind_IMPORTED { - _, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: req.WalletPassword, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(walletDir), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(req.WalletPassword), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + if err != nil { + return nil, err + } + _, err = acc.WalletCreate(ctx) if err != nil { return nil, err } @@ -223,7 +226,7 @@ func (s *Server) RecoverWallet(ctx context.Context, req *pb.RecoverWalletRequest // can indeed be decrypted using a password in the request. If there is no issue, // we return an empty response with no error. If the password is incorrect for a single keystore, // we return an appropriate error. -func (_ *Server) ValidateKeystores( +func (*Server) ValidateKeystores( _ context.Context, req *pb.ValidateKeystoresRequest, ) (*emptypb.Empty, error) { if req.KeystoresPassword == "" { diff --git a/validator/rpc/wallet_test.go b/validator/rpc/wallet_test.go index afb9fa91cc3c..447787d0c4fa 100644 --- a/validator/rpc/wallet_test.go +++ b/validator/rpc/wallet_test.go @@ -34,14 +34,15 @@ func TestServer_CreateWallet_Local(t *testing.T) { ctx := context.Background() localWalletDir := setupWalletDir(t) defaultWalletPath = localWalletDir - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Derived, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Derived), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err) @@ -309,14 +310,15 @@ func TestServer_WalletConfig(t *testing.T) { walletDir: defaultWalletPath, } // We attempt to create the wallet. - w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: defaultWalletPath, - KeymanagerKind: keymanager.Local, - WalletPassword: strongPass, - }, - SkipMnemonicConfirm: true, - }) + opts := []accounts.Option{ + accounts.WithWalletDir(defaultWalletPath), + accounts.WithKeymanagerType(keymanager.Local), + accounts.WithWalletPassword(strongPass), + accounts.WithSkipMnemonicConfirm(true), + } + acc, err := accounts.NewCLIManager(opts...) + require.NoError(t, err) + w, err := acc.WalletCreate(ctx) require.NoError(t, err) km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false}) require.NoError(t, err)