diff --git a/cmd/validator/accounts/BUILD.bazel b/cmd/validator/accounts/BUILD.bazel index 8b77ebb83e..6c34932749 100644 --- a/cmd/validator/accounts/BUILD.bazel +++ b/cmd/validator/accounts/BUILD.bazel @@ -1,10 +1,12 @@ -load("@prysm//tools/go:def.bzl", "go_library") +load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ "accounts.go", + "delete.go", "list.go", + "wallet_utils.go", ], importpath = "github.com/prysmaticlabs/prysm/cmd/validator/accounts", visibility = ["//visibility:public"], @@ -15,6 +17,7 @@ go_library( "//runtime/tos:go_default_library", "//validator/accounts:go_default_library", "//validator/accounts/iface:go_default_library", + "//validator/accounts/userprompt:go_default_library", "//validator/accounts/wallet:go_default_library", "//validator/client:go_default_library", "//validator/keymanager:go_default_library", @@ -23,3 +26,25 @@ go_library( "@com_github_urfave_cli_v2//:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["delete_test.go"], + embed = [":go_default_library"], + deps = [ + "//cmd/validator/flags:go_default_library", + "//config/params:go_default_library", + "//crypto/bls:go_default_library", + "//encoding/bytesutil:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + "//time:go_default_library", + "//validator/accounts:go_default_library", + "//validator/accounts/wallet:go_default_library", + "//validator/keymanager:go_default_library", + "//validator/keymanager/local:go_default_library", + "@com_github_google_uuid//: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/cmd/validator/accounts/accounts.go b/cmd/validator/accounts/accounts.go index 137f714099..2bc9a5cbc2 100644 --- a/cmd/validator/accounts/accounts.go +++ b/cmd/validator/accounts/accounts.go @@ -35,11 +35,14 @@ var Commands = &cli.Command{ if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil { return err } - return tos.VerifyTosAcceptedOrPrompt(cliCtx) + if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil { + return err + } + features.ConfigureValidator(cliCtx) + return nil }, Action: func(cliCtx *cli.Context) error { - features.ConfigureValidator(cliCtx) - if err := accounts.DeleteAccountCli(cliCtx); err != nil { + if err := accountsDelete(cliCtx); err != nil { log.Fatalf("Could not delete account: %v", err) } return nil diff --git a/cmd/validator/accounts/delete.go b/cmd/validator/accounts/delete.go new file mode 100644 index 0000000000..3d7525190c --- /dev/null +++ b/cmd/validator/accounts/delete.go @@ -0,0 +1,63 @@ +package accounts + +import ( + "strings" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/cmd" + "github.com/prysmaticlabs/prysm/cmd/validator/flags" + "github.com/prysmaticlabs/prysm/validator/accounts" + "github.com/prysmaticlabs/prysm/validator/accounts/userprompt" + "github.com/prysmaticlabs/prysm/validator/client" + "github.com/urfave/cli/v2" +) + +func accountsDelete(c *cli.Context) error { + w, km, err := walletWithKeymanager(c) + if err != nil { + return err + } + dialOpts := client.ConstructDialOptions( + c.Int(cmd.GrpcMaxCallRecvMsgSizeFlag.Name), + c.String(flags.CertFlag.Name), + c.Uint(flags.GrpcRetriesFlag.Name), + c.Duration(flags.GrpcRetryDelayFlag.Name), + ) + grpcHeaders := strings.Split(c.String(flags.GrpcHeadersFlag.Name), ",") + + opts := []accounts.Option{ + accounts.WithWallet(w), + accounts.WithKeymanager(km), + accounts.WithGRPCDialOpts(dialOpts), + accounts.WithBeaconRPCProvider(c.String(flags.BeaconRPCProviderFlag.Name)), + accounts.WithGRPCHeaders(grpcHeaders), + } + + // Get full set of public keys from the keymanager. + validatingPublicKeys, err := km.FetchValidatingPublicKeys(c.Context) + if err != nil { + return err + } + if len(validatingPublicKeys) == 0 { + return errors.New("wallet is empty, no accounts to delete") + } + // Filter keys either from CLI flag or from interactive session. + filteredPubKeys, err := accounts.FilterPublicKeysFromUserInput( + c, + flags.DeletePublicKeysFlag, + validatingPublicKeys, + userprompt.SelectAccountsDeletePromptText, + ) + if err != nil { + return errors.Wrap(err, "could not filter public keys for deletion") + } + opts = append(opts, accounts.WithFilteredPubKeys(filteredPubKeys)) + opts = append(opts, accounts.WithWalletKeyCount(len(validatingPublicKeys))) + opts = append(opts, accounts.WithDeletePublicKeys(c.IsSet(flags.DeletePublicKeysFlag.Name))) + + acc, err := accounts.NewCLIManager(opts...) + if err != nil { + return err + } + return acc.Delete(c.Context) +} diff --git a/cmd/validator/accounts/delete_test.go b/cmd/validator/accounts/delete_test.go new file mode 100644 index 0000000000..416aa75c26 --- /dev/null +++ b/cmd/validator/accounts/delete_test.go @@ -0,0 +1,191 @@ +package accounts + +import ( + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/uuid" + "github.com/prysmaticlabs/prysm/cmd/validator/flags" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/crypto/bls" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" + prysmTime "github.com/prysmaticlabs/prysm/time" + "github.com/prysmaticlabs/prysm/validator/accounts" + "github.com/prysmaticlabs/prysm/validator/accounts/wallet" + "github.com/prysmaticlabs/prysm/validator/keymanager" + "github.com/prysmaticlabs/prysm/validator/keymanager/local" + "github.com/urfave/cli/v2" + keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" +) + +const ( + passwordFileName = "password.txt" + password = "OhWOWthisisatest42!$" +) + +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 +} + +// Returns the fullPath to the newly created keystore file. +func createKeystore(t *testing.T, path string) (*keymanager.Keystore, string) { + validatingKey, err := bls.RandKey() + require.NoError(t, err) + encryptor := keystorev4.New() + cryptoFields, err := encryptor.Encrypt(validatingKey.Marshal(), password) + require.NoError(t, err) + id, err := uuid.NewRandom() + require.NoError(t, err) + keystoreFile := &keymanager.Keystore{ + Crypto: cryptoFields, + ID: id.String(), + Pubkey: fmt.Sprintf("%x", validatingKey.PublicKey().Marshal()), + Version: encryptor.Version(), + Name: encryptor.Name(), + } + encoded, err := json.MarshalIndent(keystoreFile, "", "\t") + require.NoError(t, err) + // Write the encoded keystore to disk with the timestamp appended + createdAt := prysmTime.Now().Unix() + fullPath := filepath.Join(path, fmt.Sprintf(local.KeystoreFileNameFormat, createdAt)) + require.NoError(t, os.WriteFile(fullPath, encoded, os.ModePerm)) + return keystoreFile, fullPath +} + +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 + 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 TestDeleteAccounts_Noninteractive(t *testing.T) { + walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) + // Write a directory where we will import keys from. + keysDir := filepath.Join(t.TempDir(), "keysDir") + require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) + + // Create 3 keystore files in the keys directory we can then + // import from in our wallet. + k1, _ := createKeystore(t, keysDir) + time.Sleep(time.Second) + k2, _ := createKeystore(t, keysDir) + time.Sleep(time.Second) + k3, _ := createKeystore(t, keysDir) + generatedPubKeys := []string{k1.Pubkey, k2.Pubkey, k3.Pubkey} + // Only delete keys 0 and 1. + deletePublicKeys := strings.Join(generatedPubKeys[0:2], ",") + + // We initialize a wallet with a local keymanager. + cliCtx := setupWalletCtx(t, &testWalletConfig{ + // Wallet configuration flags. + walletDir: walletDir, + keymanagerKind: keymanager.Local, + walletPasswordFile: passwordFilePath, + accountPasswordFile: passwordFilePath, + // Flags required for ImportAccounts to work. + keysDir: keysDir, + // 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, + }, + }) + require.NoError(t, err) + + // We attempt to import accounts. + require.NoError(t, accounts.ImportAccountsCli(cliCtx)) + + // We attempt to delete the accounts specified. + require.NoError(t, accountsDelete(cliCtx)) + + keymanager, err := local.NewKeymanager( + cliCtx.Context, + &local.SetupConfig{ + Wallet: w, + ListenForChanges: false, + }, + ) + require.NoError(t, err) + remainingAccounts, err := keymanager.FetchValidatingPublicKeys(cliCtx.Context) + require.NoError(t, err) + require.Equal(t, len(remainingAccounts), 1) + remainingPublicKey, err := hex.DecodeString(k3.Pubkey) + require.NoError(t, err) + assert.DeepEqual(t, remainingAccounts[0], bytesutil.ToBytes48(remainingPublicKey)) +} diff --git a/cmd/validator/accounts/list.go b/cmd/validator/accounts/list.go index 9ab44fdb85..76ba4ca541 100644 --- a/cmd/validator/accounts/list.go +++ b/cmd/validator/accounts/list.go @@ -3,14 +3,10 @@ package accounts import ( "strings" - "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/cmd" "github.com/prysmaticlabs/prysm/cmd/validator/flags" "github.com/prysmaticlabs/prysm/validator/accounts" - "github.com/prysmaticlabs/prysm/validator/accounts/iface" - "github.com/prysmaticlabs/prysm/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/validator/client" - "github.com/prysmaticlabs/prysm/validator/keymanager" "github.com/urfave/cli/v2" ) @@ -51,22 +47,3 @@ func accountsList(c *cli.Context) error { c.Context, ) } - -func walletWithKeymanager(c *cli.Context) (*wallet.Wallet, keymanager.IKeymanager, error) { - w, err := wallet.OpenWalletOrElseCli(c, func(cliCtx *cli.Context) (*wallet.Wallet, error) { - return nil, wallet.ErrNoWalletFound - }) - if err != nil { - return nil, nil, errors.Wrap(err, "could not open wallet") - } - // TODO(#9883) - Remove this when we have a better way to handle this. this is fine. - // genesis root is not set here which is used for sign function, but fetch keys should be fine. - km, err := w.InitializeKeymanager(c.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) - if err != nil && strings.Contains(err.Error(), keymanager.IncorrectPasswordErrMsg) { - return nil, nil, errors.New("wrong wallet password entered") - } - if err != nil { - return nil, nil, errors.Wrap(err, accounts.ErrCouldNotInitializeKeymanager) - } - return w, km, nil -} diff --git a/cmd/validator/accounts/wallet_utils.go b/cmd/validator/accounts/wallet_utils.go new file mode 100644 index 0000000000..2055f25f5e --- /dev/null +++ b/cmd/validator/accounts/wallet_utils.go @@ -0,0 +1,31 @@ +package accounts + +import ( + "strings" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/validator/accounts" + "github.com/prysmaticlabs/prysm/validator/accounts/iface" + "github.com/prysmaticlabs/prysm/validator/accounts/wallet" + "github.com/prysmaticlabs/prysm/validator/keymanager" + "github.com/urfave/cli/v2" +) + +func walletWithKeymanager(c *cli.Context) (*wallet.Wallet, keymanager.IKeymanager, error) { + w, err := wallet.OpenWalletOrElseCli(c, func(cliCtx *cli.Context) (*wallet.Wallet, error) { + return nil, wallet.ErrNoWalletFound + }) + if err != nil { + return nil, nil, errors.Wrap(err, "could not open wallet") + } + // TODO(#9883) - Remove this when we have a better way to handle this. this is fine. + // genesis root is not set here which is used for sign function, but fetch keys should be fine. + km, err := w.InitializeKeymanager(c.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) + if err != nil && strings.Contains(err.Error(), keymanager.IncorrectPasswordErrMsg) { + return nil, nil, errors.New("wrong wallet password entered") + } + if err != nil { + return nil, nil, errors.Wrap(err, accounts.ErrCouldNotInitializeKeymanager) + } + return w, km, nil +} diff --git a/validator/accounts/BUILD.bazel b/validator/accounts/BUILD.bazel index 370b0694a7..b191acae74 100644 --- a/validator/accounts/BUILD.bazel +++ b/validator/accounts/BUILD.bazel @@ -66,7 +66,6 @@ go_test( name = "go_default_test", srcs = [ "accounts_backup_test.go", - "accounts_delete_test.go", "accounts_exit_test.go", "accounts_import_test.go", "accounts_list_test.go", diff --git a/validator/accounts/accounts_backup.go b/validator/accounts/accounts_backup.go index ff8ba16053..8e1cb876b3 100644 --- a/validator/accounts/accounts_backup.go +++ b/validator/accounts/accounts_backup.go @@ -69,7 +69,7 @@ func BackupAccountsCli(cliCtx *cli.Context) error { // Allow the user to interactively select the accounts to backup or optionally // provide them via cli flags as a string of comma-separated, hex strings. - filteredPubKeys, err := filterPublicKeysFromUserInput( + filteredPubKeys, err := FilterPublicKeysFromUserInput( cliCtx, flags.BackupPublicKeysFlag, pubKeys, diff --git a/validator/accounts/accounts_delete.go b/validator/accounts/accounts_delete.go index 48c87cb8c0..02f8f6e570 100644 --- a/validator/accounts/accounts_delete.go +++ b/validator/accounts/accounts_delete.go @@ -7,64 +7,23 @@ import ( "strings" "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/cmd/validator/flags" "github.com/prysmaticlabs/prysm/encoding/bytesutil" "github.com/prysmaticlabs/prysm/io/prompt" ethpbservice "github.com/prysmaticlabs/prysm/proto/eth/service" - "github.com/prysmaticlabs/prysm/validator/accounts/iface" - "github.com/prysmaticlabs/prysm/validator/accounts/userprompt" - "github.com/prysmaticlabs/prysm/validator/accounts/wallet" - "github.com/prysmaticlabs/prysm/validator/keymanager" - "github.com/urfave/cli/v2" ) -// DeleteAccountCli deletes the accounts that the user requests to be deleted from the wallet. -// This function uses the CLI to extract necessary values. -func DeleteAccountCli(cliCtx *cli.Context) error { - w, err := wallet.OpenWalletOrElseCli(cliCtx, func(cliCtx *cli.Context) (*wallet.Wallet, error) { - return nil, wallet.ErrNoWalletFound - }) - if err != nil { - return errors.Wrap(err, "could not open wallet") - } - // TODO(#9883) - Remove this when we have a better way to handle this. - if w.KeymanagerKind() == keymanager.Remote || w.KeymanagerKind() == keymanager.Web3Signer { - return errors.New( - "remote and web3signer wallets cannot delete accounts locally. please delete the account on the remote signer node", - ) - } - kManager, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) - if err != nil { - return errors.Wrap(err, ErrCouldNotInitializeKeymanager) - } - validatingPublicKeys, err := kManager.FetchValidatingPublicKeys(cliCtx.Context) - if err != nil { - return err - } - if len(validatingPublicKeys) == 0 { - return errors.New("wallet is empty, no accounts to delete") - } - // Allow the user to interactively select the accounts to delete or optionally - // provide them via cli flags as a string of comma-separated, hex strings. - filteredPubKeys, err := filterPublicKeysFromUserInput( - cliCtx, - flags.DeletePublicKeysFlag, - validatingPublicKeys, - userprompt.SelectAccountsDeletePromptText, - ) - if err != nil { - return errors.Wrap(err, "could not filter public keys for deletion") - } - rawPublicKeys := make([][]byte, len(filteredPubKeys)) - formattedPubKeys := make([]string, len(filteredPubKeys)) - for i, pk := range filteredPubKeys { +// Deletes the accounts that the user requests to be deleted from the wallet. +func (acm *AccountsCLIManager) Delete(ctx context.Context) error { + rawPublicKeys := make([][]byte, len(acm.filteredPubKeys)) + formattedPubKeys := make([]string, len(acm.filteredPubKeys)) + for i, pk := range acm.filteredPubKeys { pubKeyBytes := pk.Marshal() rawPublicKeys[i] = pubKeyBytes formattedPubKeys[i] = fmt.Sprintf("%#x", bytesutil.Trunc(pubKeyBytes)) } allAccountStr := strings.Join(formattedPubKeys, ", ") - if !cliCtx.IsSet(flags.DeletePublicKeysFlag.Name) { - if len(filteredPubKeys) == 1 { + if !acm.deletePublicKeys { + if len(acm.filteredPubKeys) == 1 { promptText := "Are you sure you want to delete 1 account? (%s) Y/N" resp, err := prompt.ValidatePrompt( os.Stdin, fmt.Sprintf(promptText, au.BrightGreen(formattedPubKeys[0])), prompt.ValidateYesOrNo, @@ -77,10 +36,10 @@ func DeleteAccountCli(cliCtx *cli.Context) error { } } else { promptText := "Are you sure you want to delete %d accounts? (%s) Y/N" - if len(filteredPubKeys) == len(validatingPublicKeys) { + if len(acm.filteredPubKeys) == acm.walletKeyCount { promptText = fmt.Sprintf("Are you sure you want to delete all accounts? Y/N (%s)", au.BrightGreen(allAccountStr)) } else { - promptText = fmt.Sprintf(promptText, len(filteredPubKeys), au.BrightGreen(allAccountStr)) + promptText = fmt.Sprintf(promptText, len(acm.filteredPubKeys), au.BrightGreen(allAccountStr)) } resp, err := prompt.ValidatePrompt(os.Stdin, promptText, prompt.ValidateYesOrNo) if err != nil { @@ -91,8 +50,8 @@ func DeleteAccountCli(cliCtx *cli.Context) error { } } } - if err := DeleteAccount(cliCtx.Context, &DeleteConfig{ - Keymanager: kManager, + if err := DeleteAccount(ctx, &DeleteConfig{ + Keymanager: acm.keymanager, DeletePublicKeys: rawPublicKeys, }); err != nil { return err @@ -104,7 +63,7 @@ func DeleteAccountCli(cliCtx *cli.Context) error { return nil } -// DeleteAccount deletes the accounts that the user requests to be deleted from the wallet. +// DeleteAccount permforms the deletion on the Keymanager. func DeleteAccount(ctx context.Context, cfg *DeleteConfig) error { if len(cfg.DeletePublicKeys) == 1 { log.Info("Deleting account...") diff --git a/validator/accounts/accounts_delete_test.go b/validator/accounts/accounts_delete_test.go deleted file mode 100644 index 498fa08d0d..0000000000 --- a/validator/accounts/accounts_delete_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package accounts - -import ( - "encoding/hex" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/prysmaticlabs/prysm/encoding/bytesutil" - "github.com/prysmaticlabs/prysm/testing/assert" - "github.com/prysmaticlabs/prysm/testing/require" - "github.com/prysmaticlabs/prysm/validator/accounts/wallet" - "github.com/prysmaticlabs/prysm/validator/keymanager" - "github.com/prysmaticlabs/prysm/validator/keymanager/local" -) - -func TestDeleteAccounts_Noninteractive(t *testing.T) { - walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) - // Write a directory where we will import keys from. - keysDir := filepath.Join(t.TempDir(), "keysDir") - require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) - - // Create 3 keystore files in the keys directory we can then - // import from in our wallet. - k1, _ := createKeystore(t, keysDir) - time.Sleep(time.Second) - k2, _ := createKeystore(t, keysDir) - time.Sleep(time.Second) - k3, _ := createKeystore(t, keysDir) - generatedPubKeys := []string{k1.Pubkey, k2.Pubkey, k3.Pubkey} - // Only delete keys 0 and 1. - deletePublicKeys := strings.Join(generatedPubKeys[0:2], ",") - - // We initialize a wallet with a local keymanager. - cliCtx := setupWalletCtx(t, &testWalletConfig{ - // Wallet configuration flags. - walletDir: walletDir, - keymanagerKind: keymanager.Local, - walletPasswordFile: passwordFilePath, - accountPasswordFile: passwordFilePath, - // Flags required for ImportAccounts to work. - keysDir: keysDir, - // Flags required for DeleteAccounts to work. - deletePublicKeys: deletePublicKeys, - }) - w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ - WalletCfg: &wallet.Config{ - WalletDir: walletDir, - KeymanagerKind: keymanager.Local, - WalletPassword: password, - }, - }) - require.NoError(t, err) - - // We attempt to import accounts. - require.NoError(t, ImportAccountsCli(cliCtx)) - - // We attempt to delete the accounts specified. - require.NoError(t, DeleteAccountCli(cliCtx)) - - keymanager, err := local.NewKeymanager( - cliCtx.Context, - &local.SetupConfig{ - Wallet: w, - ListenForChanges: false, - }, - ) - require.NoError(t, err) - remainingAccounts, err := keymanager.FetchValidatingPublicKeys(cliCtx.Context) - require.NoError(t, err) - require.Equal(t, len(remainingAccounts), 1) - remainingPublicKey, err := hex.DecodeString(k3.Pubkey) - require.NoError(t, err) - assert.DeepEqual(t, remainingAccounts[0], bytesutil.ToBytes48(remainingPublicKey)) -} diff --git a/validator/accounts/accounts_exit.go b/validator/accounts/accounts_exit.go index 17fb94556b..8331966034 100644 --- a/validator/accounts/accounts_exit.go +++ b/validator/accounts/accounts_exit.go @@ -164,7 +164,7 @@ func interact( if !cliCtx.IsSet(flags.ExitAllFlag.Name) { // Allow the user to interactively select the accounts to exit or optionally // provide them via cli flags as a string of comma-separated, hex strings. - filteredPubKeys, err := filterPublicKeysFromUserInput( + filteredPubKeys, err := FilterPublicKeysFromUserInput( cliCtx, flags.VoluntaryExitPublicKeysFlag, validatingPublicKeys, diff --git a/validator/accounts/accounts_helper.go b/validator/accounts/accounts_helper.go index 2f431d4f32..6a74f4eaf4 100644 --- a/validator/accounts/accounts_helper.go +++ b/validator/accounts/accounts_helper.go @@ -11,13 +11,14 @@ import ( "github.com/urfave/cli/v2" ) -func filterPublicKeysFromUserInput( +// FilterPublicKeysFromUserInput collects the set of public keys from the +// command line or an interactive session. +func FilterPublicKeysFromUserInput( cliCtx *cli.Context, publicKeysFlag *cli.StringFlag, validatingPublicKeys [][fieldparams.BLSPubkeyLength]byte, selectionPrompt string, ) ([]bls.PublicKey, error) { - var filteredPubKeys []bls.PublicKey if cliCtx.IsSet(publicKeysFlag.Name) { pubKeyStrings := strings.Split(cliCtx.String(publicKeysFlag.Name), ",") if len(pubKeyStrings) == 0 { @@ -26,22 +27,27 @@ func filterPublicKeysFromUserInput( publicKeysFlag.Name, ) } - for _, str := range pubKeyStrings { - pkString := str - if strings.Contains(pkString, "0x") { - pkString = pkString[2:] - } - pubKeyBytes, err := hex.DecodeString(pkString) - if err != nil { - return nil, errors.Wrapf(err, "could not decode string %s as hex", pkString) - } - blsPublicKey, err := bls.PublicKeyFromBytes(pubKeyBytes) - if err != nil { - return nil, errors.Wrapf(err, "%#x is not a valid BLS public key", pubKeyBytes) - } - filteredPubKeys = append(filteredPubKeys, blsPublicKey) - } - return filteredPubKeys, nil + return filterPublicKeys(pubKeyStrings) } return selectAccounts(selectionPrompt, validatingPublicKeys) } + +func filterPublicKeys(pubKeyStrings []string) ([]bls.PublicKey, error) { + var filteredPubKeys []bls.PublicKey + for _, str := range pubKeyStrings { + pkString := str + if strings.Contains(pkString, "0x") { + pkString = pkString[2:] + } + pubKeyBytes, err := hex.DecodeString(pkString) + if err != nil { + return nil, errors.Wrapf(err, "could not decode string %s as hex", pkString) + } + blsPublicKey, err := bls.PublicKeyFromBytes(pubKeyBytes) + if err != nil { + return nil, errors.Wrapf(err, "%#x is not a valid BLS public key", pubKeyBytes) + } + filteredPubKeys = append(filteredPubKeys, blsPublicKey) + } + return filteredPubKeys, nil +} diff --git a/validator/accounts/cli_manager.go b/validator/accounts/cli_manager.go index 87d00883ad..1ef118d4e6 100644 --- a/validator/accounts/cli_manager.go +++ b/validator/accounts/cli_manager.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" grpcutil "github.com/prysmaticlabs/prysm/api/grpc" + "github.com/prysmaticlabs/prysm/crypto/bls" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/validator/keymanager" @@ -30,9 +31,12 @@ type AccountsCLIManager struct { showDepositData bool showPrivateKeys bool listValidatorIndices bool + deletePublicKeys bool dialOpts []grpc.DialOption grpcHeaders []string beaconRPCProvider string + filteredPubKeys []bls.PublicKey + walletKeyCount int } func (acm *AccountsCLIManager) prepareBeaconClients(ctx context.Context) (*ethpb.BeaconNodeValidatorClient, *ethpb.NodeClient, error) { diff --git a/validator/accounts/cli_options.go b/validator/accounts/cli_options.go index 14c0978229..211d7e6526 100644 --- a/validator/accounts/cli_options.go +++ b/validator/accounts/cli_options.go @@ -1,6 +1,7 @@ package accounts import ( + "github.com/prysmaticlabs/prysm/crypto/bls" "github.com/prysmaticlabs/prysm/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/validator/keymanager" "google.golang.org/grpc" @@ -72,3 +73,26 @@ func WithBeaconRPCProvider(provider string) Option { return nil } } + +// WithFilteredPubKeys adds public key strings parsed from CLI. +func WithFilteredPubKeys(filteredPubKeys []bls.PublicKey) Option { + return func(acc *AccountsCLIManager) error { + acc.filteredPubKeys = filteredPubKeys + return nil + } +} + +// WithWalletKeyCount tracks the number of keys in a wallet. +func WithWalletKeyCount(walletKeyCount int) Option { + return func(acc *AccountsCLIManager) error { + acc.walletKeyCount = walletKeyCount + return nil + } +} + +func WithDeletePublicKeys(deletePublicKeys bool) Option { + return func(acc *AccountsCLIManager) error { + acc.deletePublicKeys = deletePublicKeys + return nil + } +} diff --git a/validator/accounts/petnames/BUILD.bazel b/validator/accounts/petnames/BUILD.bazel index 5ee3621344..d282de844f 100644 --- a/validator/accounts/petnames/BUILD.bazel +++ b/validator/accounts/petnames/BUILD.bazel @@ -4,7 +4,10 @@ go_library( name = "go_default_library", srcs = ["names.go"], importpath = "github.com/prysmaticlabs/prysm/validator/accounts/petnames", - visibility = ["//validator:__subpackages__"], + visibility = [ + "//cmd/validator:__subpackages__", + "//validator:__subpackages__", + ], deps = [ "//crypto/hash:go_default_library", "//crypto/rand:go_default_library", diff --git a/validator/keymanager/derived/BUILD.bazel b/validator/keymanager/derived/BUILD.bazel index 8680f1a5d0..c3396e55f6 100644 --- a/validator/keymanager/derived/BUILD.bazel +++ b/validator/keymanager/derived/BUILD.bazel @@ -9,6 +9,7 @@ go_library( ], importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/derived", visibility = [ + "//cmd/validator:__subpackages__", "//tools:__subpackages__", "//validator:__subpackages__", ], diff --git a/validator/keymanager/local/BUILD.bazel b/validator/keymanager/local/BUILD.bazel index de2d13f866..422f4bd9b7 100644 --- a/validator/keymanager/local/BUILD.bazel +++ b/validator/keymanager/local/BUILD.bazel @@ -14,6 +14,7 @@ go_library( ], importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/local", visibility = [ + "//cmd/validator:__subpackages__", "//tools:__subpackages__", "//validator:__pkg__", "//validator:__subpackages__", diff --git a/validator/keymanager/remote/BUILD.bazel b/validator/keymanager/remote/BUILD.bazel index 553d024171..a094470b16 100644 --- a/validator/keymanager/remote/BUILD.bazel +++ b/validator/keymanager/remote/BUILD.bazel @@ -9,6 +9,7 @@ go_library( ], importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/remote", visibility = [ + "//cmd/validator:__subpackages__", "//validator:__pkg__", "//validator:__subpackages__", ],