Refactor validator accounts import to remove cli context dependency (#10890)

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
Mike Neuder
2022-06-16 10:14:03 -04:00
committed by GitHub
parent 4de92bafc4
commit a80c15f3a9
13 changed files with 454 additions and 342 deletions

View File

@@ -7,6 +7,7 @@ go_library(
"backup.go",
"delete.go",
"exit.go",
"import.go",
"list.go",
"wallet_utils.go",
],
@@ -36,6 +37,7 @@ go_test(
"backup_test.go",
"delete_test.go",
"exit_test.go",
"import_test.go",
],
embed = [":go_default_library"],
deps = [

View File

@@ -7,7 +7,6 @@ import (
"github.com/prysmaticlabs/prysm/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/config/features"
"github.com/prysmaticlabs/prysm/runtime/tos"
"github.com/prysmaticlabs/prysm/validator/accounts"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
@@ -139,13 +138,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.ImportAccountsCli(cliCtx); err != nil {
return features.ConfigureValidator(cliCtx)
},
Action: func(cliCtx *cli.Context) error {
if err := accountsImport(cliCtx); err != nil {
log.Fatalf("Could not import accounts: %v", err)
}
return nil

View File

@@ -181,7 +181,7 @@ func TestBackupAccounts_Noninteractive_Imported(t *testing.T) {
// We attempt to import accounts we wrote to the keys directory
// into our newly created wallet.
require.NoError(t, accounts.ImportAccountsCli(cliCtx))
require.NoError(t, accountsImport(cliCtx))
// Next, we attempt to backup the accounts.
require.NoError(t, accountsBackup(cliCtx))

View File

@@ -83,6 +83,7 @@ type testWalletConfig struct {
deletePublicKeys string
keysDir string
backupDir string
passwordsDir string
walletDir string
}
@@ -169,7 +170,7 @@ func TestDeleteAccounts_Noninteractive(t *testing.T) {
require.NoError(t, err)
// We attempt to import accounts.
require.NoError(t, accounts.ImportAccountsCli(cliCtx))
require.NoError(t, accountsImport(cliCtx))
// We attempt to delete the accounts specified.
require.NoError(t, accountsDelete(cliCtx))

View File

@@ -75,7 +75,7 @@ func TestExitAccountsCli_OK(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, accounts.ImportAccountsCli(cliCtx))
require.NoError(t, accountsImport(cliCtx))
_, keymanager, err := walletWithKeymanager(cliCtx)
require.NoError(t, err)
@@ -175,7 +175,7 @@ func TestExitAccountsCli_OK_AllPublicKeys(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, accounts.ImportAccountsCli(cliCtx))
require.NoError(t, accountsImport(cliCtx))
_, keymanager, err := walletWithKeymanager(cliCtx)
require.NoError(t, err)

View File

@@ -0,0 +1,113 @@
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/userprompt"
"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/validator/client"
"github.com/prysmaticlabs/prysm/validator/keymanager"
"github.com/urfave/cli/v2"
)
func accountsImport(c *cli.Context) error {
w, err := walletImport(c)
if err != nil {
return errors.Wrap(err, "could not initialize wallet")
}
km, err := w.InitializeKeymanager(c.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
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),
}
opts = append(opts, accounts.WithImportPrivateKeys(c.IsSet(flags.ImportPrivateKeyFileFlag.Name)))
opts = append(opts, accounts.WithPrivateKeyFile(c.String(flags.ImportPrivateKeyFileFlag.Name)))
opts = append(opts, accounts.WithReadPasswordFile(c.IsSet(flags.AccountPasswordFileFlag.Name)))
opts = append(opts, accounts.WithPasswordFilePath(c.String(flags.AccountPasswordFileFlag.Name)))
keysDir, err := userprompt.InputDirectory(c, userprompt.ImportKeysDirPromptText, flags.KeysDirFlag)
if err != nil {
return errors.Wrap(err, "could not parse keys directory")
}
opts = append(opts, accounts.WithKeysDir(keysDir))
acc, err := accounts.NewCLIManager(opts...)
if err != nil {
return err
}
return acc.Import(c.Context)
}
func walletImport(c *cli.Context) (*wallet.Wallet, error) {
return wallet.OpenWalletOrElseCli(c, func(cliCtx *cli.Context) (*wallet.Wallet, error) {
walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag)
if err != nil {
return nil, err
}
exists, err := wallet.Exists(walletDir)
if err != nil {
return nil, errors.Wrap(err, wallet.CheckExistsErrMsg)
}
if exists {
isValid, err := wallet.IsValid(walletDir)
if err != nil {
return nil, errors.Wrap(err, wallet.CheckValidityErrMsg)
}
if !isValid {
return nil, errors.New(wallet.InvalidWalletErrMsg)
}
walletPassword, err := wallet.InputPassword(
cliCtx,
flags.WalletPasswordFileFlag,
wallet.PasswordPromptText,
false, /* Do not confirm password */
wallet.ValidateExistingPass,
)
if err != nil {
return nil, err
}
return wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: walletPassword,
})
}
cfg, err := accounts.ExtractWalletCreationConfigFromCli(cliCtx, keymanager.Local)
if err != nil {
return nil, err
}
w := wallet.New(&wallet.Config{
KeymanagerKind: cfg.WalletCfg.KeymanagerKind,
WalletDir: cfg.WalletCfg.WalletDir,
WalletPassword: cfg.WalletCfg.WalletPassword,
})
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(
"Successfully created new wallet",
)
return w, nil
})
}

View File

@@ -0,0 +1,262 @@
package accounts
import (
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/uuid"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/prysmaticlabs/prysm/testing/assert"
"github.com/prysmaticlabs/prysm/testing/require"
"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/prysmaticlabs/prysm/validator/keymanager/local"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
)
func TestImport_Noninteractive(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keysDir,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
keymanager, err := local.NewKeymanager(
cliCtx.Context,
&local.SetupConfig{
Wallet: w,
ListenForChanges: false,
},
)
require.NoError(t, err)
// Make sure there are no accounts at the start.
accounts, err := keymanager.ValidatingAccountNames()
require.NoError(t, err)
assert.Equal(t, len(accounts), 0)
// Create 2 keys.
createKeystore(t, keysDir)
time.Sleep(time.Second)
createKeystore(t, keysDir)
require.NoError(t, accountsImport(cliCtx))
w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
assert.Equal(t, 2, len(keys))
}
// TestImport_DuplicateKeys is a regression test that ensures correction function if duplicate keys are being imported
func TestImport_DuplicateKeys(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keysDir,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
// Create a key and then copy it to create a duplicate
_, keystorePath := createKeystore(t, keysDir)
time.Sleep(time.Second)
input, err := os.ReadFile(keystorePath)
require.NoError(t, err)
keystorePath2 := filepath.Join(keysDir, "copyOfKeystore.json")
err = os.WriteFile(keystorePath2, input, os.ModePerm)
require.NoError(t, err)
require.NoError(t, accountsImport(cliCtx))
_, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
// There should only be 1 account as the duplicate keystore was ignored
assert.Equal(t, 1, len(keys))
}
func TestImport_Noninteractive_RandomName(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keysDir,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
keymanager, err := local.NewKeymanager(
cliCtx.Context,
&local.SetupConfig{
Wallet: w,
ListenForChanges: false,
},
)
require.NoError(t, err)
// Make sure there are no accounts at the start.
accounts, err := keymanager.ValidatingAccountNames()
require.NoError(t, err)
assert.Equal(t, len(accounts), 0)
// Create 2 keys.
createRandomNameKeystore(t, keysDir)
time.Sleep(time.Second)
createRandomNameKeystore(t, keysDir)
require.NoError(t, accountsImport(cliCtx))
w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
assert.Equal(t, 2, len(keys))
}
// Returns the fullPath to the newly created keystore file.
func createRandomNameKeystore(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
random, err := rand.Int(rand.Reader, big.NewInt(1000000))
require.NoError(t, err)
fullPath := filepath.Join(path, fmt.Sprintf("test-%d-keystore", random.Int64()))
require.NoError(t, os.WriteFile(fullPath, encoded, os.ModePerm))
return keystoreFile, fullPath
}
func TestImport_Noninteractive_Filepath(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
_, keystorePath := createKeystore(t, keysDir)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keystorePath,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := accounts.CreateWalletWithKeymanager(cliCtx.Context, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
keymanager, err := local.NewKeymanager(
cliCtx.Context,
&local.SetupConfig{
Wallet: w,
ListenForChanges: false,
},
)
require.NoError(t, err)
// Make sure there are no accounts at the start.
accounts, err := keymanager.ValidatingAccountNames()
require.NoError(t, err)
assert.Equal(t, len(accounts), 0)
require.NoError(t, accountsImport(cliCtx))
w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
assert.Equal(t, 1, len(keys))
}

View File

@@ -14,17 +14,13 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/io/file"
"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"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
)
@@ -81,93 +77,31 @@ type ImportAccountsConfig struct {
// ImportAccountsCli can import external, EIP-2335 compliant keystore.json files as
// new accounts into the Prysm validator wallet. This uses the CLI to extract
// values necessary to run the function.
func ImportAccountsCli(cliCtx *cli.Context) error {
w, err := wallet.OpenWalletOrElseCli(cliCtx, func(cliCtx *cli.Context) (*wallet.Wallet, error) {
walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag)
if err != nil {
return nil, err
}
exists, err := wallet.Exists(walletDir)
if err != nil {
return nil, errors.Wrap(err, wallet.CheckExistsErrMsg)
}
if exists {
isValid, err := wallet.IsValid(walletDir)
if err != nil {
return nil, errors.Wrap(err, wallet.CheckValidityErrMsg)
}
if !isValid {
return nil, errors.New(wallet.InvalidWalletErrMsg)
}
walletPassword, err := wallet.InputPassword(
cliCtx,
flags.WalletPasswordFileFlag,
wallet.PasswordPromptText,
false, /* Do not confirm password */
wallet.ValidateExistingPass,
)
if err != nil {
return nil, err
}
return wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: walletPassword,
})
}
cfg, err := extractWalletCreationConfigFromCli(cliCtx, keymanager.Local)
if err != nil {
return nil, err
}
w := wallet.New(&wallet.Config{
KeymanagerKind: cfg.WalletCfg.KeymanagerKind,
WalletDir: cfg.WalletCfg.WalletDir,
WalletPassword: cfg.WalletCfg.WalletPassword,
})
if err = createLocalKeymanagerWallet(cliCtx.Context, w); err != nil {
return nil, errors.Wrap(err, "could not create keymanager")
}
log.WithField("wallet-path", cfg.WalletCfg.WalletDir).Info(
"Successfully created new wallet",
)
return w, nil
})
if err != nil {
return errors.Wrap(err, "could not initialize wallet")
}
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
if err != nil {
return err
}
k, ok := km.(keymanager.Importer)
func (acm *AccountsCLIManager) Import(ctx context.Context) error {
k, ok := acm.keymanager.(keymanager.Importer)
if !ok {
return errors.New("keymanager cannot import keystores")
}
// Check if the user wishes to import a one-off, private key directly
// as an account into the Prysm validator.
if cliCtx.IsSet(flags.ImportPrivateKeyFileFlag.Name) {
return importPrivateKeyAsAccount(cliCtx, w, k)
if acm.importPrivateKeys {
return importPrivateKeyAsAccount(ctx, acm.wallet, k, acm.privateKeyFile)
}
keysDir, err := userprompt.InputDirectory(cliCtx, userprompt.ImportKeysDirPromptText, flags.KeysDirFlag)
if err != nil {
return errors.Wrap(err, "could not parse keys directory")
}
// Consider that the keysDir might be a path to a specific file and handle accordingly.
isDir, err := file.HasDir(keysDir)
isDir, err := file.HasDir(acm.keysDir)
if err != nil {
return errors.Wrap(err, "could not determine if path is a directory")
}
keystoresImported := make([]*keymanager.Keystore, 0)
if isDir {
files, err := os.ReadDir(keysDir)
files, err := os.ReadDir(acm.keysDir)
if err != nil {
return errors.Wrap(err, "could not read dir")
}
if len(files) == 0 {
return fmt.Errorf("directory %s has no files, cannot import from it", keysDir)
return fmt.Errorf("directory %s has no files, cannot import from it", acm.keysDir)
}
filesInDir := make([]string, 0)
for i := 0; i < len(files); i++ {
@@ -180,7 +114,7 @@ func ImportAccountsCli(cliCtx *cli.Context) error {
// specify this value in their filename.
sort.Sort(byDerivationPath(filesInDir))
for _, name := range filesInDir {
keystore, err := readKeystoreFile(cliCtx.Context, filepath.Join(keysDir, name))
keystore, err := readKeystoreFile(ctx, filepath.Join(acm.keysDir, name))
if err != nil && strings.Contains(err.Error(), "could not decode keystore json") {
continue
} else if err != nil {
@@ -189,7 +123,7 @@ func ImportAccountsCli(cliCtx *cli.Context) error {
keystoresImported = append(keystoresImported, keystore)
}
} else {
keystore, err := readKeystoreFile(cliCtx.Context, keysDir)
keystore, err := readKeystoreFile(ctx, acm.keysDir)
if err != nil {
return errors.Wrap(err, "could not import keystore")
}
@@ -197,9 +131,8 @@ func ImportAccountsCli(cliCtx *cli.Context) error {
}
var accountsPassword string
if cliCtx.IsSet(flags.AccountPasswordFileFlag.Name) {
passwordFilePath := cliCtx.String(flags.AccountPasswordFileFlag.Name)
data, err := os.ReadFile(passwordFilePath) // #nosec G304
if acm.readPasswordFile {
data, err := os.ReadFile(acm.passwordFilePath) // #nosec G304
if err != nil {
return err
}
@@ -213,7 +146,7 @@ func ImportAccountsCli(cliCtx *cli.Context) error {
}
}
fmt.Println("Importing accounts, this may take a while...")
statuses, err := ImportAccounts(cliCtx.Context, &ImportAccountsConfig{
statuses, err := ImportAccounts(ctx, &ImportAccountsConfig{
Importer: k,
Keystores: keystoresImported,
AccountPassword: accountsPassword,
@@ -265,8 +198,7 @@ func ImportAccounts(ctx context.Context, cfg *ImportAccountsConfig) ([]*ethpbser
// Imports a one-off file containing a private key as a hex string into
// the Prysm validator's accounts.
func importPrivateKeyAsAccount(cliCtx *cli.Context, wallet *wallet.Wallet, importer keymanager.Importer) error {
privKeyFile := cliCtx.String(flags.ImportPrivateKeyFileFlag.Name)
func importPrivateKeyAsAccount(ctx context.Context, wallet *wallet.Wallet, importer keymanager.Importer, privKeyFile string) error {
fullPath, err := file.ExpandPath(privKeyFile)
if err != nil {
return errors.Wrapf(err, "could not expand file path for %s", privKeyFile)
@@ -297,7 +229,7 @@ func importPrivateKeyAsAccount(cliCtx *cli.Context, wallet *wallet.Wallet, impor
return errors.Wrap(err, "could not encrypt private key into a keystore file")
}
statuses, err := ImportAccounts(
cliCtx.Context,
ctx,
&ImportAccountsConfig{
Importer: importer,
AccountPassword: wallet.Password(),

View File

@@ -2,15 +2,12 @@ package accounts
import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"os"
"path/filepath"
"sort"
"testing"
"time"
"github.com/google/uuid"
"github.com/prysmaticlabs/prysm/config/params"
@@ -27,111 +24,6 @@ import (
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
)
func TestImport_Noninteractive(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keysDir,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
keymanager, err := local.NewKeymanager(
cliCtx.Context,
&local.SetupConfig{
Wallet: w,
ListenForChanges: false,
},
)
require.NoError(t, err)
// Make sure there are no accounts at the start.
accounts, err := keymanager.ValidatingAccountNames()
require.NoError(t, err)
assert.Equal(t, len(accounts), 0)
// Create 2 keys.
createKeystore(t, keysDir)
time.Sleep(time.Second)
createKeystore(t, keysDir)
require.NoError(t, ImportAccountsCli(cliCtx))
w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
assert.Equal(t, 2, len(keys))
}
// TestImport_DuplicateKeys is a regression test that ensures correction function if duplicate keys are being imported
func TestImport_DuplicateKeys(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keysDir,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
// Create a key and then copy it to create a duplicate
_, keystorePath := createKeystore(t, keysDir)
time.Sleep(time.Second)
input, err := os.ReadFile(keystorePath)
require.NoError(t, err)
keystorePath2 := filepath.Join(keysDir, "copyOfKeystore.json")
err = os.WriteFile(keystorePath2, input, os.ModePerm)
require.NoError(t, err)
require.NoError(t, ImportAccountsCli(cliCtx))
_, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
// There should only be 1 account as the duplicate keystore was ignored
assert.Equal(t, 1, len(keys))
}
func TestImportAccounts_NoPassword(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
@@ -166,115 +58,6 @@ func TestImportAccounts_NoPassword(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 1, len(resp))
require.Equal(t, resp[0].Status, ethpbservice.ImportedKeystoreStatus_ERROR)
}
func TestImport_Noninteractive_RandomName(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keysDir,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
keymanager, err := local.NewKeymanager(
cliCtx.Context,
&local.SetupConfig{
Wallet: w,
ListenForChanges: false,
},
)
require.NoError(t, err)
// Make sure there are no accounts at the start.
accounts, err := keymanager.ValidatingAccountNames()
require.NoError(t, err)
assert.Equal(t, len(accounts), 0)
// Create 2 keys.
createRandomNameKeystore(t, keysDir)
time.Sleep(time.Second)
createRandomNameKeystore(t, keysDir)
require.NoError(t, ImportAccountsCli(cliCtx))
w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
assert.Equal(t, 2, len(keys))
}
func TestImport_Noninteractive_Filepath(t *testing.T) {
local.ResetCaches()
walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
keysDir := filepath.Join(t.TempDir(), "keysDir")
require.NoError(t, os.MkdirAll(keysDir, os.ModePerm))
_, keystorePath := createKeystore(t, keysDir)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keysDir: keystorePath,
keymanagerKind: keymanager.Local,
walletPasswordFile: passwordFilePath,
accountPasswordFile: passwordFilePath,
})
w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Local,
WalletPassword: password,
},
})
require.NoError(t, err)
keymanager, err := local.NewKeymanager(
cliCtx.Context,
&local.SetupConfig{
Wallet: w,
ListenForChanges: false,
},
)
require.NoError(t, err)
// Make sure there are no accounts at the start.
accounts, err := keymanager.ValidatingAccountNames()
require.NoError(t, err)
assert.Equal(t, len(accounts), 0)
require.NoError(t, ImportAccountsCli(cliCtx))
w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
WalletPassword: password,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
require.NoError(t, err)
assert.Equal(t, 1, len(keys))
}
func TestImport_SortByDerivationPath(t *testing.T) {
@@ -378,7 +161,7 @@ func Test_importPrivateKeyAsAccount(t *testing.T) {
},
)
require.NoError(t, err)
assert.NoError(t, importPrivateKeyAsAccount(cliCtx, wallet, keymanager))
assert.NoError(t, importPrivateKeyAsAccount(cliCtx.Context, wallet, keymanager, privKeyFileName))
// We re-instantiate the keymanager and check we now have 1 public key.
keymanager, err = local.NewKeymanager(
@@ -419,29 +202,3 @@ func createKeystore(t *testing.T, path string) (*keymanager.Keystore, string) {
require.NoError(t, os.WriteFile(fullPath, encoded, os.ModePerm))
return keystoreFile, fullPath
}
// Returns the fullPath to the newly created keystore file.
func createRandomNameKeystore(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
random, err := rand.Int(rand.Reader, big.NewInt(1000000))
require.NoError(t, err)
fullPath := filepath.Join(path, fmt.Sprintf("test-%d-keystore", random.Int64()))
require.NoError(t, os.WriteFile(fullPath, encoded, os.ModePerm))
return keystoreFile, fullPath
}

View File

@@ -32,10 +32,15 @@ type AccountsCLIManager struct {
showPrivateKeys bool
listValidatorIndices bool
deletePublicKeys bool
importPrivateKeys bool
readPasswordFile bool
dialOpts []grpc.DialOption
grpcHeaders []string
beaconRPCProvider string
walletKeyCount int
privateKeyFile string
passwordFilePath string
keysDir string
backupsDir string
backupsPassword string
filteredPubKeys []bls.PublicKey

View File

@@ -90,6 +90,46 @@ func WithDeletePublicKeys(deletePublicKeys bool) Option {
}
}
// WithReadPasswordFile indicates whether to read the password from a file.
func WithReadPasswordFile(readPasswordFile bool) Option {
return func(acc *AccountsCLIManager) error {
acc.readPasswordFile = readPasswordFile
return nil
}
}
// WithImportPrivateKeys indicates whether to import private keys as accounts.
func WithImportPrivateKeys(importPrivateKeys bool) Option {
return func(acc *AccountsCLIManager) error {
acc.importPrivateKeys = importPrivateKeys
return nil
}
}
// WithPrivateKeyFile specifies the private key path.
func WithPrivateKeyFile(privateKeyFile string) Option {
return func(acc *AccountsCLIManager) error {
acc.privateKeyFile = privateKeyFile
return nil
}
}
// WithKeysDir specifies the directory keys are read from.
func WithKeysDir(keysDir string) Option {
return func(acc *AccountsCLIManager) error {
acc.keysDir = keysDir
return nil
}
}
// WithPasswordFilePath specifies where the password is stored.
func WithPasswordFilePath(passwordFilePath string) Option {
return func(acc *AccountsCLIManager) error {
acc.passwordFilePath = passwordFilePath
return nil
}
}
// WithBackupDir specifies the directory backups are written to.
func WithBackupsDir(backupsDir string) Option {
return func(acc *AccountsCLIManager) error {

View File

@@ -40,7 +40,7 @@ func CreateAndSaveWalletCli(cliCtx *cli.Context) (*wallet.Wallet, error) {
if err != nil {
return nil, err
}
createWalletConfig, err := extractWalletCreationConfigFromCli(cliCtx, keymanagerKind)
createWalletConfig, err := ExtractWalletCreationConfigFromCli(cliCtx, keymanagerKind)
if err != nil {
return nil, err
}
@@ -72,7 +72,7 @@ func CreateWalletWithKeymanager(ctx context.Context, cfg *CreateWalletConfig) (*
var err error
switch w.KeymanagerKind() {
case keymanager.Local:
if err = createLocalKeymanagerWallet(ctx, w); err != nil {
if err = CreateLocalKeymanagerWallet(ctx, w); err != nil {
return nil, errors.Wrap(err, "could not initialize wallet")
}
// TODO(#9883) - Remove this when we have a better way to handle this. should be safe to use for now.
@@ -131,7 +131,8 @@ func extractKeymanagerKindFromCli(cliCtx *cli.Context) (keymanager.Kind, error)
return inputKeymanagerKind(cliCtx)
}
func extractWalletCreationConfigFromCli(cliCtx *cli.Context, keymanagerKind keymanager.Kind) (*CreateWalletConfig, error) {
// 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
@@ -204,7 +205,7 @@ func extractWalletCreationConfigFromCli(cliCtx *cli.Context, keymanagerKind keym
return createWalletConfig, nil
}
func createLocalKeymanagerWallet(_ context.Context, wallet *wallet.Wallet) error {
func CreateLocalKeymanagerWallet(_ context.Context, wallet *wallet.Wallet) error {
if wallet == nil {
return errors.New("nil wallet")
}

View File

@@ -119,7 +119,7 @@ func TestCreateOrOpenWallet(t *testing.T) {
walletPasswordFile: walletPasswordFile,
})
createLocalWallet := func(cliCtx *cli.Context) (*wallet.Wallet, error) {
cfg, err := extractWalletCreationConfigFromCli(cliCtx, keymanager.Local)
cfg, err := ExtractWalletCreationConfigFromCli(cliCtx, keymanager.Local)
if err != nil {
return nil, err
}
@@ -128,7 +128,7 @@ func TestCreateOrOpenWallet(t *testing.T) {
WalletDir: cfg.WalletCfg.WalletDir,
WalletPassword: cfg.WalletCfg.WalletPassword,
})
if err = createLocalKeymanagerWallet(cliCtx.Context, w); err != nil {
if err = CreateLocalKeymanagerWallet(cliCtx.Context, w); err != nil {
return nil, errors.Wrap(err, "could not create keymanager")
}
log.WithField("wallet-path", cfg.WalletCfg.WalletDir).Info(