mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Wallet recover CLI Manager migration (#11278)
* Wallet recover CLI Manager migration * bazel run //:gazelle -- fix * fix lint and build errors * add TODO to remove duplicate code Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com> Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
@@ -4,6 +4,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"edit.go",
|
"edit.go",
|
||||||
|
"recover.go",
|
||||||
"wallet.go",
|
"wallet.go",
|
||||||
],
|
],
|
||||||
importpath = "github.com/prysmaticlabs/prysm/v3/cmd/validator/wallet",
|
importpath = "github.com/prysmaticlabs/prysm/v3/cmd/validator/wallet",
|
||||||
@@ -12,6 +13,7 @@ go_library(
|
|||||||
"//cmd:go_default_library",
|
"//cmd:go_default_library",
|
||||||
"//cmd/validator/flags:go_default_library",
|
"//cmd/validator/flags:go_default_library",
|
||||||
"//config/features:go_default_library",
|
"//config/features:go_default_library",
|
||||||
|
"//io/prompt:go_default_library",
|
||||||
"//runtime/tos:go_default_library",
|
"//runtime/tos:go_default_library",
|
||||||
"//validator/accounts:go_default_library",
|
"//validator/accounts:go_default_library",
|
||||||
"//validator/accounts/userprompt:go_default_library",
|
"//validator/accounts/userprompt:go_default_library",
|
||||||
@@ -20,13 +22,18 @@ go_library(
|
|||||||
"//validator/keymanager/remote:go_default_library",
|
"//validator/keymanager/remote:go_default_library",
|
||||||
"@com_github_pkg_errors//:go_default_library",
|
"@com_github_pkg_errors//:go_default_library",
|
||||||
"@com_github_sirupsen_logrus//:go_default_library",
|
"@com_github_sirupsen_logrus//:go_default_library",
|
||||||
|
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||||
|
"@com_github_tyler_smith_go_bip39//wordlists:go_default_library",
|
||||||
"@com_github_urfave_cli_v2//:go_default_library",
|
"@com_github_urfave_cli_v2//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["edit_test.go"],
|
srcs = [
|
||||||
|
"edit_test.go",
|
||||||
|
"recover_test.go",
|
||||||
|
],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/validator/flags:go_default_library",
|
"//cmd/validator/flags:go_default_library",
|
||||||
@@ -34,8 +41,10 @@ go_test(
|
|||||||
"//testing/assert:go_default_library",
|
"//testing/assert:go_default_library",
|
||||||
"//testing/require:go_default_library",
|
"//testing/require:go_default_library",
|
||||||
"//validator/accounts:go_default_library",
|
"//validator/accounts:go_default_library",
|
||||||
|
"//validator/accounts/iface:go_default_library",
|
||||||
"//validator/accounts/wallet:go_default_library",
|
"//validator/accounts/wallet:go_default_library",
|
||||||
"//validator/keymanager:go_default_library",
|
"//validator/keymanager:go_default_library",
|
||||||
|
"//validator/keymanager/derived:go_default_library",
|
||||||
"//validator/keymanager/remote:go_default_library",
|
"//validator/keymanager/remote:go_default_library",
|
||||||
"@com_github_urfave_cli_v2//:go_default_library",
|
"@com_github_urfave_cli_v2//:go_default_library",
|
||||||
],
|
],
|
||||||
|
|||||||
175
cmd/validator/wallet/recover.go
Normal file
175
cmd/validator/wallet/recover.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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/tyler-smith/go-bip39"
|
||||||
|
"github.com/tyler-smith/go-bip39/wordlists"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// #nosec G101 -- Not sensitive data
|
||||||
|
mnemonicPassphraseYesNoText = "(Advanced) Do you have an optional '25th word' passphrase for your mnemonic? [y/n]"
|
||||||
|
// #nosec G101 -- Not sensitive data
|
||||||
|
mnemonicPassphrasePromptText = "(Advanced) Enter the '25th word' passphrase for your mnemonic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func walletRecover(c *cli.Context) error {
|
||||||
|
mnemonic, err := inputMnemonic(c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get mnemonic phrase")
|
||||||
|
}
|
||||||
|
opts := []accounts.Option{
|
||||||
|
accounts.WithMnemonic(mnemonic),
|
||||||
|
}
|
||||||
|
|
||||||
|
skipMnemonic25thWord := c.IsSet(flags.SkipMnemonic25thWordCheckFlag.Name)
|
||||||
|
has25thWordFile := c.IsSet(flags.Mnemonic25thWordFileFlag.Name)
|
||||||
|
if !skipMnemonic25thWord && !has25thWordFile {
|
||||||
|
resp, err := prompt.ValidatePrompt(
|
||||||
|
os.Stdin, mnemonicPassphraseYesNoText, prompt.ValidateYesOrNo,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not validate choice")
|
||||||
|
}
|
||||||
|
if strings.EqualFold(resp, "y") {
|
||||||
|
mnemonicPassphrase, err := prompt.InputPassword(
|
||||||
|
c,
|
||||||
|
flags.Mnemonic25thWordFileFlag,
|
||||||
|
mnemonicPassphrasePromptText,
|
||||||
|
"Confirm mnemonic passphrase",
|
||||||
|
false, /* Should confirm password */
|
||||||
|
func(input string) error {
|
||||||
|
if strings.TrimSpace(input) == "" {
|
||||||
|
return errors.New("input cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts = append(opts, accounts.WithMnemonic25thWord(mnemonicPassphrase))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walletDir, err := userprompt.InputDirectory(c, userprompt.WalletDirPromptText, flags.WalletDirFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
walletPassword, err := prompt.InputPassword(
|
||||||
|
c,
|
||||||
|
flags.WalletPasswordFileFlag,
|
||||||
|
wallet.NewWalletPasswordPromptText,
|
||||||
|
wallet.ConfirmPasswordPromptText,
|
||||||
|
true, /* Should confirm password */
|
||||||
|
prompt.ValidatePasswordInput,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
numAccounts, err := inputNumAccounts(c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get number of accounts to recover")
|
||||||
|
}
|
||||||
|
opts = append(opts, accounts.WithWalletDir(walletDir))
|
||||||
|
opts = append(opts, accounts.WithWalletPassword(walletPassword))
|
||||||
|
opts = append(opts, accounts.WithNumAccounts(int(numAccounts)))
|
||||||
|
|
||||||
|
acc, err := accounts.NewCLIManager(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = acc.WalletRecover(c.Context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof(
|
||||||
|
"Successfully recovered HD wallet with accounts and saved configuration to disk",
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func inputMnemonic(cliCtx *cli.Context) (mnemonicPhrase string, err error) {
|
||||||
|
if cliCtx.IsSet(flags.MnemonicFileFlag.Name) {
|
||||||
|
mnemonicFilePath := cliCtx.String(flags.MnemonicFileFlag.Name)
|
||||||
|
data, err := os.ReadFile(mnemonicFilePath) // #nosec G304 -- ReadFile is safe
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
enteredMnemonic := string(data)
|
||||||
|
if err := accounts.ValidateMnemonic(enteredMnemonic); err != nil {
|
||||||
|
return "", errors.Wrap(err, "mnemonic phrase did not pass validation")
|
||||||
|
}
|
||||||
|
return enteredMnemonic, nil
|
||||||
|
}
|
||||||
|
allowedLanguages := map[string][]string{
|
||||||
|
"chinese_simplified": wordlists.ChineseSimplified,
|
||||||
|
"chinese_traditional": wordlists.ChineseTraditional,
|
||||||
|
"czech": wordlists.Czech,
|
||||||
|
"english": wordlists.English,
|
||||||
|
"french": wordlists.French,
|
||||||
|
"japanese": wordlists.Japanese,
|
||||||
|
"korean": wordlists.Korean,
|
||||||
|
"italian": wordlists.Italian,
|
||||||
|
"spanish": wordlists.Spanish,
|
||||||
|
}
|
||||||
|
languages := make([]string, 0)
|
||||||
|
for k := range allowedLanguages {
|
||||||
|
languages = append(languages, k)
|
||||||
|
}
|
||||||
|
sort.Strings(languages)
|
||||||
|
selectedLanguage, err := prompt.ValidatePrompt(
|
||||||
|
os.Stdin,
|
||||||
|
fmt.Sprintf("Enter the language of your seed phrase: %s", strings.Join(languages, ", ")),
|
||||||
|
func(input string) error {
|
||||||
|
if _, ok := allowedLanguages[input]; !ok {
|
||||||
|
return errors.New("input not in the list of allowed languages")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not get mnemonic language: %w", err)
|
||||||
|
}
|
||||||
|
bip39.SetWordList(allowedLanguages[selectedLanguage])
|
||||||
|
mnemonicPhrase, err = prompt.ValidatePrompt(
|
||||||
|
os.Stdin,
|
||||||
|
"Enter the seed phrase for the wallet you would like to recover",
|
||||||
|
accounts.ValidateMnemonic)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not get mnemonic phrase: %w", err)
|
||||||
|
}
|
||||||
|
return mnemonicPhrase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
113
cmd/validator/wallet/recover_test.go
Normal file
113
cmd/validator/wallet/recover_test.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
|
||||||
|
"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/derived"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
walletDirName = "wallet"
|
||||||
|
mnemonicFileName = "mnemonic.txt"
|
||||||
|
mnemonic = "garage car helmet trade salmon embrace market giant movie wet same champion dawn chair shield drill amazing panther accident puzzle garden mosquito kind arena"
|
||||||
|
)
|
||||||
|
|
||||||
|
type recoverCfgStruct struct {
|
||||||
|
walletDir string
|
||||||
|
passwordFilePath string
|
||||||
|
mnemonicFilePath string
|
||||||
|
numAccounts int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRecoverCfg(t *testing.T) *recoverCfgStruct {
|
||||||
|
testDir := t.TempDir()
|
||||||
|
walletDir := filepath.Join(testDir, walletDirName)
|
||||||
|
passwordFilePath := filepath.Join(testDir, passwordFileName)
|
||||||
|
require.NoError(t, os.WriteFile(passwordFilePath, []byte(password), os.ModePerm))
|
||||||
|
mnemonicFilePath := filepath.Join(testDir, mnemonicFileName)
|
||||||
|
require.NoError(t, os.WriteFile(mnemonicFilePath, []byte(mnemonic), os.ModePerm))
|
||||||
|
|
||||||
|
return &recoverCfgStruct{
|
||||||
|
walletDir: walletDir,
|
||||||
|
passwordFilePath: passwordFilePath,
|
||||||
|
mnemonicFilePath: mnemonicFilePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRecoverCliCtx(t *testing.T, cfg *recoverCfgStruct) *cli.Context {
|
||||||
|
app := cli.App{}
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.String(flags.WalletDirFlag.Name, cfg.walletDir, "")
|
||||||
|
set.String(flags.WalletPasswordFileFlag.Name, cfg.passwordFilePath, "")
|
||||||
|
set.String(flags.KeymanagerKindFlag.Name, keymanager.Derived.String(), "")
|
||||||
|
set.String(flags.MnemonicFileFlag.Name, cfg.mnemonicFilePath, "")
|
||||||
|
set.Bool(flags.SkipMnemonic25thWordCheckFlag.Name, true, "")
|
||||||
|
set.Int64(flags.NumAccountsFlag.Name, cfg.numAccounts, "")
|
||||||
|
assert.NoError(t, set.Set(flags.SkipMnemonic25thWordCheckFlag.Name, "true"))
|
||||||
|
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, cfg.walletDir))
|
||||||
|
assert.NoError(t, set.Set(flags.WalletPasswordFileFlag.Name, cfg.passwordFilePath))
|
||||||
|
assert.NoError(t, set.Set(flags.KeymanagerKindFlag.Name, keymanager.Derived.String()))
|
||||||
|
assert.NoError(t, set.Set(flags.MnemonicFileFlag.Name, cfg.mnemonicFilePath))
|
||||||
|
assert.NoError(t, set.Set(flags.NumAccountsFlag.Name, strconv.Itoa(int(cfg.numAccounts))))
|
||||||
|
return cli.NewContext(&app, set, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecoverDerivedWallet(t *testing.T) {
|
||||||
|
cfg := setupRecoverCfg(t)
|
||||||
|
cfg.numAccounts = 4
|
||||||
|
cliCtx := createRecoverCliCtx(t, cfg)
|
||||||
|
require.NoError(t, walletRecover(cliCtx))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := wallet.OpenWallet(cliCtx.Context, &wallet.Config{
|
||||||
|
WalletDir: cfg.walletDir,
|
||||||
|
WalletPassword: password,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
|
||||||
|
require.NoError(t, err)
|
||||||
|
derivedKM, ok := km.(*derived.Keymanager)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not a derived keymanager")
|
||||||
|
}
|
||||||
|
names, err := derivedKM.ValidatingAccountNames(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.Equal(t, len(names), int(cfg.numAccounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecoverDerivedWallet_OneAccount is a test for regression in cases where the number of accounts recovered is 1
|
||||||
|
func TestRecoverDerivedWallet_OneAccount(t *testing.T) {
|
||||||
|
cfg := setupRecoverCfg(t)
|
||||||
|
cfg.numAccounts = 1
|
||||||
|
cliCtx := createRecoverCliCtx(t, cfg)
|
||||||
|
require.NoError(t, walletRecover(cliCtx))
|
||||||
|
|
||||||
|
_, err := wallet.OpenWallet(cliCtx.Context, &wallet.Config{
|
||||||
|
WalletDir: cfg.walletDir,
|
||||||
|
WalletPassword: password,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecoverDerivedWallet_AlreadyExists(t *testing.T) {
|
||||||
|
cfg := setupRecoverCfg(t)
|
||||||
|
cfg.numAccounts = 4
|
||||||
|
cliCtx := createRecoverCliCtx(t, cfg)
|
||||||
|
require.NoError(t, walletRecover(cliCtx))
|
||||||
|
|
||||||
|
// Trying to recover an HD wallet into a directory that already exists should give an error
|
||||||
|
require.ErrorContains(t, "a wallet already exists at this location", walletRecover(cliCtx))
|
||||||
|
}
|
||||||
@@ -108,13 +108,13 @@ var Commands = &cli.Command{
|
|||||||
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
|
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tos.VerifyTosAcceptedOrPrompt(cliCtx)
|
if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil {
|
||||||
},
|
|
||||||
Action: func(cliCtx *cli.Context) error {
|
|
||||||
if err := features.ConfigureValidator(cliCtx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := accounts.RecoverWalletCli(cliCtx); err != nil {
|
return features.ConfigureBeaconChain(cliCtx)
|
||||||
|
},
|
||||||
|
Action: func(cliCtx *cli.Context) error {
|
||||||
|
if err := walletRecover(cliCtx); err != nil {
|
||||||
log.WithError(err).Fatal("Could not recover wallet")
|
log.WithError(err).Fatal("Could not recover wallet")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ go_library(
|
|||||||
"@com_github_manifoldco_promptui//:go_default_library",
|
"@com_github_manifoldco_promptui//:go_default_library",
|
||||||
"@com_github_pkg_errors//:go_default_library",
|
"@com_github_pkg_errors//:go_default_library",
|
||||||
"@com_github_sirupsen_logrus//:go_default_library",
|
"@com_github_sirupsen_logrus//:go_default_library",
|
||||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
|
||||||
"@com_github_tyler_smith_go_bip39//wordlists:go_default_library",
|
|
||||||
"@com_github_urfave_cli_v2//:go_default_library",
|
"@com_github_urfave_cli_v2//:go_default_library",
|
||||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||||
"@org_golang_google_grpc//:go_default_library",
|
"@org_golang_google_grpc//:go_default_library",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func NewCLIManager(opts ...Option) (*AccountsCLIManager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AccountsCLIManager defines a struct capable of performing various validator
|
// AccountsCLIManager defines a struct capable of performing various validator
|
||||||
// wallet account operations via the command line.
|
// wallet & account operations via the command line.
|
||||||
type AccountsCLIManager struct {
|
type AccountsCLIManager struct {
|
||||||
wallet *wallet.Wallet
|
wallet *wallet.Wallet
|
||||||
keymanager keymanager.IKeymanager
|
keymanager keymanager.IKeymanager
|
||||||
@@ -48,6 +48,11 @@ type AccountsCLIManager struct {
|
|||||||
filteredPubKeys []bls.PublicKey
|
filteredPubKeys []bls.PublicKey
|
||||||
rawPubKeys [][]byte
|
rawPubKeys [][]byte
|
||||||
formattedPubKeys []string
|
formattedPubKeys []string
|
||||||
|
walletDir string
|
||||||
|
walletPassword string
|
||||||
|
mnemonic string
|
||||||
|
numAccounts int
|
||||||
|
mnemonic25thWord string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acm *AccountsCLIManager) prepareBeaconClients(ctx context.Context) (*ethpb.BeaconNodeValidatorClient, *ethpb.NodeClient, error) {
|
func (acm *AccountsCLIManager) prepareBeaconClients(ctx context.Context) (*ethpb.BeaconNodeValidatorClient, *ethpb.NodeClient, error) {
|
||||||
|
|||||||
@@ -178,3 +178,43 @@ func WithFormattedPubKeys(formattedPubKeys []string) Option {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithWalletDir specifies the password for backups.
|
||||||
|
func WithWalletDir(walletDir string) Option {
|
||||||
|
return func(acc *AccountsCLIManager) error {
|
||||||
|
acc.walletDir = walletDir
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWalletPassword specifies the password for backups.
|
||||||
|
func WithWalletPassword(walletPassword string) Option {
|
||||||
|
return func(acc *AccountsCLIManager) error {
|
||||||
|
acc.walletPassword = walletPassword
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMnemonic specifies the password for backups.
|
||||||
|
func WithMnemonic(mnemonic string) Option {
|
||||||
|
return func(acc *AccountsCLIManager) error {
|
||||||
|
acc.mnemonic = mnemonic
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMnemonic25thWord specifies the password for backups.
|
||||||
|
func WithMnemonic25thWord(mnemonic25thWord string) Option {
|
||||||
|
return func(acc *AccountsCLIManager) error {
|
||||||
|
acc.mnemonic25thWord = mnemonic25thWord
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMnemonic25thWord specifies the password for backups.
|
||||||
|
func WithNumAccounts(numAccounts int) Option {
|
||||||
|
return func(acc *AccountsCLIManager) error {
|
||||||
|
acc.numAccounts = numAccounts
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
@@ -22,6 +23,14 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"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.
|
// CreateWalletConfig defines the parameters needed to call the create wallet functions.
|
||||||
type CreateWalletConfig struct {
|
type CreateWalletConfig struct {
|
||||||
SkipMnemonicConfirm bool
|
SkipMnemonicConfirm bool
|
||||||
@@ -278,3 +287,27 @@ func inputKeymanagerKind(cliCtx *cli.Context) (keymanager.Kind, error) {
|
|||||||
}
|
}
|
||||||
return keymanager.Kind(selection), nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,35 +2,16 @@ package accounts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"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/userprompt"
|
|
||||||
"github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet"
|
"github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet"
|
||||||
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
|
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
|
||||||
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
|
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
|
||||||
"github.com/tyler-smith/go-bip39"
|
|
||||||
"github.com/tyler-smith/go-bip39/wordlists"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
phraseWordCount = 24
|
phraseWordCount = 24
|
||||||
// #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!)"
|
|
||||||
// #nosec G101 -- Not sensitive data
|
|
||||||
mnemonicPassphraseYesNoText = "(Advanced) Do you have an optional '25th word' passphrase for your mnemonic? [y/n]"
|
|
||||||
// #nosec G101 -- Not sensitive data
|
|
||||||
mnemonicPassphrasePromptText = "(Advanced) Enter the '25th word' passphrase for your mnemonic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -38,89 +19,10 @@ var (
|
|||||||
ErrEmptyMnemonic = errors.New("phrase cannot be empty")
|
ErrEmptyMnemonic = errors.New("phrase cannot be empty")
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecoverWalletConfig to run the recover wallet function.
|
// WalletRecover uses a menmonic seed phrase to recover a wallet into the path provided.
|
||||||
type RecoverWalletConfig struct {
|
func (acm *AccountsCLIManager) WalletRecover(ctx context.Context) (*wallet.Wallet, error) {
|
||||||
WalletDir string
|
|
||||||
WalletPassword string
|
|
||||||
Mnemonic string
|
|
||||||
NumAccounts int
|
|
||||||
Mnemonic25thWord string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverWalletCli uses a menmonic seed phrase to recover a wallet into the path provided. This
|
|
||||||
// uses the CLI to extract necessary values to run the function.
|
|
||||||
func RecoverWalletCli(cliCtx *cli.Context) error {
|
|
||||||
mnemonic, err := inputMnemonic(cliCtx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not get mnemonic phrase")
|
|
||||||
}
|
|
||||||
config := &RecoverWalletConfig{
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
}
|
|
||||||
skipMnemonic25thWord := cliCtx.IsSet(flags.SkipMnemonic25thWordCheckFlag.Name)
|
|
||||||
has25thWordFile := cliCtx.IsSet(flags.Mnemonic25thWordFileFlag.Name)
|
|
||||||
if !skipMnemonic25thWord && !has25thWordFile {
|
|
||||||
resp, err := prompt.ValidatePrompt(
|
|
||||||
os.Stdin, mnemonicPassphraseYesNoText, prompt.ValidateYesOrNo,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not validate choice")
|
|
||||||
}
|
|
||||||
if strings.EqualFold(resp, "y") {
|
|
||||||
mnemonicPassphrase, err := prompt.InputPassword(
|
|
||||||
cliCtx,
|
|
||||||
flags.Mnemonic25thWordFileFlag,
|
|
||||||
mnemonicPassphrasePromptText,
|
|
||||||
"Confirm mnemonic passphrase",
|
|
||||||
false, /* Should confirm password */
|
|
||||||
func(input string) error {
|
|
||||||
if strings.TrimSpace(input) == "" {
|
|
||||||
return errors.New("input cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Mnemonic25thWord = mnemonicPassphrase
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
walletPassword, err := prompt.InputPassword(
|
|
||||||
cliCtx,
|
|
||||||
flags.WalletPasswordFileFlag,
|
|
||||||
wallet.NewWalletPasswordPromptText,
|
|
||||||
wallet.ConfirmPasswordPromptText,
|
|
||||||
true, /* Should confirm password */
|
|
||||||
prompt.ValidatePasswordInput,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
numAccounts, err := inputNumAccounts(cliCtx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not get number of accounts to recover")
|
|
||||||
}
|
|
||||||
config.WalletDir = walletDir
|
|
||||||
config.WalletPassword = walletPassword
|
|
||||||
config.NumAccounts = int(numAccounts)
|
|
||||||
if _, err = RecoverWallet(cliCtx.Context, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof(
|
|
||||||
"Successfully recovered HD wallet with accounts and saved configuration to disk",
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverWallet uses a menmonic seed phrase to recover a wallet into the path provided.
|
|
||||||
func RecoverWallet(ctx context.Context, cfg *RecoverWalletConfig) (*wallet.Wallet, error) {
|
|
||||||
// Ensure that the wallet directory does not contain a wallet already
|
// Ensure that the wallet directory does not contain a wallet already
|
||||||
dirExists, err := wallet.Exists(cfg.WalletDir)
|
dirExists, err := wallet.Exists(acm.walletDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -129,9 +31,9 @@ func RecoverWallet(ctx context.Context, cfg *RecoverWalletConfig) (*wallet.Walle
|
|||||||
" alternative location for the new wallet or remove the current wallet")
|
" alternative location for the new wallet or remove the current wallet")
|
||||||
}
|
}
|
||||||
w := wallet.New(&wallet.Config{
|
w := wallet.New(&wallet.Config{
|
||||||
WalletDir: cfg.WalletDir,
|
WalletDir: acm.walletDir,
|
||||||
KeymanagerKind: keymanager.Derived,
|
KeymanagerKind: keymanager.Derived,
|
||||||
WalletPassword: cfg.WalletPassword,
|
WalletPassword: acm.walletPassword,
|
||||||
})
|
})
|
||||||
if err := w.SaveWallet(); err != nil {
|
if err := w.SaveWallet(); err != nil {
|
||||||
return nil, errors.Wrap(err, "could not save wallet to disk")
|
return nil, errors.Wrap(err, "could not save wallet to disk")
|
||||||
@@ -143,91 +45,16 @@ func RecoverWallet(ctx context.Context, cfg *RecoverWalletConfig) (*wallet.Walle
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not make keymanager for given phrase")
|
return nil, errors.Wrap(err, "could not make keymanager for given phrase")
|
||||||
}
|
}
|
||||||
if err := km.RecoverAccountsFromMnemonic(ctx, cfg.Mnemonic, cfg.Mnemonic25thWord, cfg.NumAccounts); err != nil {
|
if err := km.RecoverAccountsFromMnemonic(ctx, acm.mnemonic, acm.mnemonic25thWord, acm.numAccounts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.WithField("wallet-path", w.AccountsDir()).Infof(
|
log.WithField("wallet-path", w.AccountsDir()).Infof(
|
||||||
"Successfully recovered HD wallet with %d accounts. Please use `accounts list` to view details for your accounts",
|
"Successfully recovered HD wallet with %d accounts. Please use `accounts list` to view details for your accounts",
|
||||||
cfg.NumAccounts,
|
acm.numAccounts,
|
||||||
)
|
)
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func inputMnemonic(cliCtx *cli.Context) (mnemonicPhrase string, err error) {
|
|
||||||
if cliCtx.IsSet(flags.MnemonicFileFlag.Name) {
|
|
||||||
mnemonicFilePath := cliCtx.String(flags.MnemonicFileFlag.Name)
|
|
||||||
data, err := os.ReadFile(mnemonicFilePath) // #nosec G304 -- ReadFile is safe
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
enteredMnemonic := string(data)
|
|
||||||
if err := ValidateMnemonic(enteredMnemonic); err != nil {
|
|
||||||
return "", errors.Wrap(err, "mnemonic phrase did not pass validation")
|
|
||||||
}
|
|
||||||
return enteredMnemonic, nil
|
|
||||||
}
|
|
||||||
allowedLanguages := map[string][]string{
|
|
||||||
"chinese_simplified": wordlists.ChineseSimplified,
|
|
||||||
"chinese_traditional": wordlists.ChineseTraditional,
|
|
||||||
"czech": wordlists.Czech,
|
|
||||||
"english": wordlists.English,
|
|
||||||
"french": wordlists.French,
|
|
||||||
"japanese": wordlists.Japanese,
|
|
||||||
"korean": wordlists.Korean,
|
|
||||||
"italian": wordlists.Italian,
|
|
||||||
"spanish": wordlists.Spanish,
|
|
||||||
}
|
|
||||||
languages := make([]string, 0)
|
|
||||||
for k := range allowedLanguages {
|
|
||||||
languages = append(languages, k)
|
|
||||||
}
|
|
||||||
sort.Strings(languages)
|
|
||||||
selectedLanguage, err := prompt.ValidatePrompt(
|
|
||||||
os.Stdin,
|
|
||||||
fmt.Sprintf("Enter the language of your seed phrase: %s", strings.Join(languages, ", ")),
|
|
||||||
func(input string) error {
|
|
||||||
if _, ok := allowedLanguages[input]; !ok {
|
|
||||||
return errors.New("input not in the list of allowed languages")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not get mnemonic language: %w", err)
|
|
||||||
}
|
|
||||||
bip39.SetWordList(allowedLanguages[selectedLanguage])
|
|
||||||
mnemonicPhrase, err = prompt.ValidatePrompt(
|
|
||||||
os.Stdin,
|
|
||||||
"Enter the seed phrase for the wallet you would like to recover",
|
|
||||||
ValidateMnemonic)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not get mnemonic phrase: %w", err)
|
|
||||||
}
|
|
||||||
return mnemonicPhrase, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateMnemonic ensures that it is not empty and that the count of the words are
|
// ValidateMnemonic ensures that it is not empty and that the count of the words are
|
||||||
// as specified(currently 24).
|
// as specified(currently 24).
|
||||||
func ValidateMnemonic(mnemonic string) error {
|
func ValidateMnemonic(mnemonic string) error {
|
||||||
|
|||||||
@@ -1,112 +1,11 @@
|
|||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
|
|
||||||
"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/derived"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type recoverCfgStruct struct {
|
|
||||||
walletDir string
|
|
||||||
passwordFilePath string
|
|
||||||
mnemonicFilePath string
|
|
||||||
numAccounts int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRecoverCfg(t *testing.T) *recoverCfgStruct {
|
|
||||||
testDir := t.TempDir()
|
|
||||||
walletDir := filepath.Join(testDir, walletDirName)
|
|
||||||
passwordFilePath := filepath.Join(testDir, passwordFileName)
|
|
||||||
require.NoError(t, os.WriteFile(passwordFilePath, []byte(password), os.ModePerm))
|
|
||||||
mnemonicFilePath := filepath.Join(testDir, mnemonicFileName)
|
|
||||||
require.NoError(t, os.WriteFile(mnemonicFilePath, []byte(mnemonic), os.ModePerm))
|
|
||||||
|
|
||||||
return &recoverCfgStruct{
|
|
||||||
walletDir: walletDir,
|
|
||||||
passwordFilePath: passwordFilePath,
|
|
||||||
mnemonicFilePath: mnemonicFilePath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRecoverCliCtx(t *testing.T, cfg *recoverCfgStruct) *cli.Context {
|
|
||||||
app := cli.App{}
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.String(flags.WalletDirFlag.Name, cfg.walletDir, "")
|
|
||||||
set.String(flags.WalletPasswordFileFlag.Name, cfg.passwordFilePath, "")
|
|
||||||
set.String(flags.KeymanagerKindFlag.Name, keymanager.Derived.String(), "")
|
|
||||||
set.String(flags.MnemonicFileFlag.Name, cfg.mnemonicFilePath, "")
|
|
||||||
set.Bool(flags.SkipMnemonic25thWordCheckFlag.Name, true, "")
|
|
||||||
set.Int64(flags.NumAccountsFlag.Name, cfg.numAccounts, "")
|
|
||||||
assert.NoError(t, set.Set(flags.SkipMnemonic25thWordCheckFlag.Name, "true"))
|
|
||||||
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, cfg.walletDir))
|
|
||||||
assert.NoError(t, set.Set(flags.WalletPasswordFileFlag.Name, cfg.passwordFilePath))
|
|
||||||
assert.NoError(t, set.Set(flags.KeymanagerKindFlag.Name, keymanager.Derived.String()))
|
|
||||||
assert.NoError(t, set.Set(flags.MnemonicFileFlag.Name, cfg.mnemonicFilePath))
|
|
||||||
assert.NoError(t, set.Set(flags.NumAccountsFlag.Name, strconv.Itoa(int(cfg.numAccounts))))
|
|
||||||
return cli.NewContext(&app, set, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecoverDerivedWallet(t *testing.T) {
|
|
||||||
cfg := setupRecoverCfg(t)
|
|
||||||
cfg.numAccounts = 4
|
|
||||||
cliCtx := createRecoverCliCtx(t, cfg)
|
|
||||||
require.NoError(t, RecoverWalletCli(cliCtx))
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
w, err := wallet.OpenWallet(cliCtx.Context, &wallet.Config{
|
|
||||||
WalletDir: cfg.walletDir,
|
|
||||||
WalletPassword: password,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
|
|
||||||
require.NoError(t, err)
|
|
||||||
derivedKM, ok := km.(*derived.Keymanager)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("not a derived keymanager")
|
|
||||||
}
|
|
||||||
names, err := derivedKM.ValidatingAccountNames(ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
require.Equal(t, len(names), int(cfg.numAccounts))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRecoverDerivedWallet_OneAccount is a test for regression in cases where the number of accounts recovered is 1
|
|
||||||
func TestRecoverDerivedWallet_OneAccount(t *testing.T) {
|
|
||||||
cfg := setupRecoverCfg(t)
|
|
||||||
cfg.numAccounts = 1
|
|
||||||
cliCtx := createRecoverCliCtx(t, cfg)
|
|
||||||
require.NoError(t, RecoverWalletCli(cliCtx))
|
|
||||||
|
|
||||||
_, err := wallet.OpenWallet(cliCtx.Context, &wallet.Config{
|
|
||||||
WalletDir: cfg.walletDir,
|
|
||||||
WalletPassword: password,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecoverDerivedWallet_AlreadyExists(t *testing.T) {
|
|
||||||
cfg := setupRecoverCfg(t)
|
|
||||||
cfg.numAccounts = 4
|
|
||||||
cliCtx := createRecoverCliCtx(t, cfg)
|
|
||||||
require.NoError(t, RecoverWalletCli(cliCtx))
|
|
||||||
|
|
||||||
// Trying to recover an HD wallet into a directory that already exists should give an error
|
|
||||||
require.ErrorContains(t, "a wallet already exists at this location", RecoverWalletCli(cliCtx))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateMnemonic(t *testing.T) {
|
func TestValidateMnemonic(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -187,13 +187,18 @@ func (s *Server) RecoverWallet(ctx context.Context, req *pb.RecoverWalletRequest
|
|||||||
return nil, status.Error(codes.InvalidArgument, "password did not pass validation")
|
return nil, status.Error(codes.InvalidArgument, "password did not pass validation")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := accounts.RecoverWallet(ctx, &accounts.RecoverWalletConfig{
|
opts := []accounts.Option{
|
||||||
WalletDir: walletDir,
|
accounts.WithWalletDir(walletDir),
|
||||||
WalletPassword: walletPassword,
|
accounts.WithWalletPassword(walletPassword),
|
||||||
Mnemonic: mnemonic,
|
accounts.WithMnemonic(mnemonic),
|
||||||
NumAccounts: numAccounts,
|
accounts.WithMnemonic25thWord(req.Mnemonic25ThWord),
|
||||||
Mnemonic25thWord: req.Mnemonic25ThWord,
|
accounts.WithNumAccounts(numAccounts),
|
||||||
}); err != nil {
|
}
|
||||||
|
acc, err := accounts.NewCLIManager(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := acc.WalletRecover(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.initializeWallet(ctx, &wallet.Config{
|
if err := s.initializeWallet(ctx, &wallet.Config{
|
||||||
|
|||||||
Reference in New Issue
Block a user