Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Accounts v2 Keymanager Into Validator Client #6489

Merged
merged 24 commits into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9f69f35
add in configs
rauljordan Jul 4, 2020
d8eeb08
ask for enable accounts v2
rauljordan Jul 6, 2020
e6438ea
begin integration of v2 keymanager
rauljordan Jul 6, 2020
7bc4260
refactor wallet opening
rauljordan Jul 6, 2020
871a21f
include significant refactoring of how opening a wallet works, making…
rauljordan Jul 6, 2020
637460a
ensure build with keymanager v2
rauljordan Jul 6, 2020
d09c0b4
further improving runtime integration
rauljordan Jul 6, 2020
a30513a
default pass paths
rauljordan Jul 6, 2020
c5c4a0f
finally running v2 at runtime
rauljordan Jul 6, 2020
397ef9d
import spacing
rauljordan Jul 6, 2020
480310b
Merge branch 'master' into v2-accounts-feature
rauljordan Jul 6, 2020
ad458c4
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 6, 2020
9faa5c9
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 6, 2020
f7997f7
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 7, 2020
9054b01
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 7, 2020
973910e
confs
rauljordan Jul 7, 2020
4403cc5
rem e2e val flag
rauljordan Jul 7, 2020
53f0bab
Merge branch 'master' into v2-accounts-feature
rauljordan Jul 7, 2020
ec671ed
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 7, 2020
a78a9de
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 8, 2020
9d2262e
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 8, 2020
a39e12a
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 8, 2020
b0b3391
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 8, 2020
e2df2ce
Merge refs/heads/master into v2-accounts-feature
prylabs-bulldozer[bot] Jul 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions shared/featureconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type Flags struct {
NewStateMgmt bool // NewStateMgmt enables the new state mgmt service.
WaitForSynced bool // WaitForSynced uses WaitForSynced in validator startup to ensure it can communicate with the beacon node as soon as possible.
ReduceAttesterStateCopy bool // ReduceAttesterStateCopy reduces head state copies for attester rpc.
EnableAccountsV2 bool // EnableAccountsV2 for Prysm validator clients.
BatchBlockVerify bool // BatchBlockVerify performs batched verification of block batches that we receive when syncing.
// DisableForkChoice disables using LMD-GHOST fork choice to update
// the head of the chain based on attestations and instead accepts any valid received block
Expand Down Expand Up @@ -259,6 +260,10 @@ func ConfigureValidator(ctx *cli.Context) {
log.Warn("Enabled validator slashing protection.")
cfg.LocalProtection = true
}
if ctx.Bool(enableAccountsV2.Name) {
log.Warn("Enabling v2 of Prysm validator accounts")
cfg.EnableAccountsV2 = true
}
if ctx.Bool(enableExternalSlasherProtectionFlag.Name) {
log.Warn("Enabled validator attestation and block slashing protection using an external slasher.")
cfg.SlasherProtection = true
Expand Down
5 changes: 5 additions & 0 deletions shared/featureconfig/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ var (
Name: "altona",
Usage: "This defines the flag through which we can run on the Altona Multiclient Testnet",
}
enableAccountsV2 = &cli.BoolFlag{
Name: "enable-accounts-v2",
Usage: "Enables usage of v2 for Prysm validator accounts",
}
batchBlockVerify = &cli.BoolFlag{
Name: "batch-block-verify",
Usage: "When enabled we will perform full signature verification of blocks in batches instead of singularly.",
Expand Down Expand Up @@ -558,6 +562,7 @@ var ValidatorFlags = append(deprecatedFlags, []cli.Flag{
disableDomainDataCacheFlag,
waitForSyncedFlag,
altonaTestnet,
enableAccountsV2,
}...)

// SlasherFlags contains a list of all the feature flags that apply to the slasher client.
Expand Down
99 changes: 12 additions & 87 deletions validator/accounts/v2/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package v2
import (
"context"
"fmt"
"os"
"path"
"unicode"

Expand All @@ -12,7 +11,6 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/validator/flags"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -52,20 +50,12 @@ func NewAccount(cliCtx *cli.Context) error {
// based on specified options.
var wallet *Wallet
var isNewWallet bool
ok, err := hasWalletDir(walletDir)
if err != nil {
log.Fatalf("Could not check if wallet exists at %s: %v", walletDir, err)
}
if ok {
// Read the wallet from the specified path.
wallet, err = OpenWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDirPath,
WalletDir: walletDir,
})
if err != nil {
log.Fatalf("Could not read wallet at specified path %s: %v", walletDir, err)
}
} else {
// Read the wallet from the specified path.
wallet, err = OpenWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDirPath,
WalletDir: walletDir,
})
if err == ErrNoWalletFound {
// Determine the desired keymanager kind for the wallet from user input.
keymanagerKind, err := inputKeymanagerKind(cliCtx)
if err != nil {
Expand All @@ -82,14 +72,16 @@ func NewAccount(cliCtx *cli.Context) error {
log.Fatalf("Could not create wallet at specified path %s: %v", walletDir, err)
}
isNewWallet = true
} else if err != nil {
log.Fatalf("Could not read wallet at specified path %s: %v", walletDir, err)
}

// We initialize a new keymanager depending on the user's selected keymanager kind.
var keymanager v2keymanager.IKeymanager
if isNewWallet {
keymanager, err = initializeNewKeymanager(ctx, wallet)
keymanager, err = wallet.CreateKeymanager(ctx)
} else {
keymanager, err = initializeExistingKeymanager(ctx, wallet)
keymanager, err = wallet.ExistingKeyManager(ctx)
}
if err != nil {
log.Fatalf("Could not initialize keymanager: %v", err)
Expand All @@ -108,77 +100,10 @@ func NewAccount(cliCtx *cli.Context) error {
return nil
}

// Initializes a keymanager. If a config file exists in the wallet, it
// reads the config file and initializes the keymanager that way. Otherwise,
// writes a new configuration file to the wallet and returns the initialized
// keymanager for use.
func initializeNewKeymanager(ctx context.Context, wallet *Wallet) (v2keymanager.IKeymanager, error) {
var keymanager v2keymanager.IKeymanager
var err error
switch wallet.KeymanagerKind() {
case v2keymanager.Direct:
keymanager, err = direct.NewKeymanager(ctx, wallet, direct.DefaultConfig())
if err != nil {
return nil, errors.Wrap(err, "could not read keymanager")
}
case v2keymanager.Derived:
return nil, errors.New("derived keymanager is unimplemented, work in progress")
case v2keymanager.Remote:
return nil, errors.New("remote keymanager is unimplemented, work in progress")
default:
return nil, errors.New("keymanager type must be specified")
}
keymanagerConfig, err := keymanager.MarshalConfigFile(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not marshal keymanager config file")
}
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
return nil, errors.Wrap(err, "could not write keymanager config file to disk")
}
return keymanager, nil
}

func initializeExistingKeymanager(
ctx context.Context, wallet *Wallet,
) (v2keymanager.IKeymanager, error) {
var keymanager v2keymanager.IKeymanager
switch wallet.KeymanagerKind() {
case v2keymanager.Direct:
configFile, err := wallet.ReadKeymanagerConfigFromDisk(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not read keymanager config")
}
cfg, err := direct.UnmarshalConfigFile(configFile)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
}
keymanager, err = direct.NewKeymanager(ctx, wallet, cfg)
if err != nil {
return nil, errors.Wrap(err, "could not initialize keymanager")
}
case v2keymanager.Derived:
return nil, errors.New("derived keymanager is unimplemented, work in progress")
case v2keymanager.Remote:
return nil, errors.New("remote keymanager is unimplemented, work in progress")
default:
return nil, errors.New("keymanager kind must be specified")
}
return keymanager, nil
}

// Check if a user has an existing wallet at the specified path.
func hasWalletDir(walletPath string) (bool, error) {
_, err := os.Stat(walletPath)
if os.IsNotExist(err) {
return false, nil
}
return true, err
}

func inputWalletDir(cliCtx *cli.Context) (string, error) {
walletDir := cliCtx.String(flags.WalletDirFlag.Name)
if walletDir == flags.DefaultValidatorDir() {
walletDir = path.Join(walletDir, walletDefaultDirName)
walletDir = path.Join(walletDir, WalletDefaultDirName)
}
prompt := promptui.Prompt{
Label: "Enter a wallet directory",
Expand Down Expand Up @@ -244,7 +169,7 @@ func inputAccountPassword(_ *cli.Context) (string, error) {
func inputPasswordsDirectory(cliCtx *cli.Context) string {
passwordsDir := cliCtx.String(flags.WalletPasswordsDirFlag.Name)
if passwordsDir == flags.DefaultValidatorDir() {
passwordsDir = path.Join(passwordsDir, walletDefaultDirName, passwordsDefaultDirName)
passwordsDir = path.Join(passwordsDir, PasswordsDefaultDirName)
}
prompt := promptui.Prompt{
Label: "Passwords directory",
Expand Down
126 changes: 119 additions & 7 deletions validator/accounts/v2/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,27 @@ import (
petname "github.com/dustinkirkland/golang-petname"
"github.com/pkg/errors"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
"github.com/sirupsen/logrus"
)

const (
// WalletDefaultDirName for accounts-v2.
WalletDefaultDirName = ".prysm-wallet-v2"
// PasswordsDefaultDirName where account passwords are stored.
PasswordsDefaultDirName = ".prysm-wallet-v2-passwords"
keymanagerConfigFileName = "keymanageropts.json"
walletDefaultDirName = ".prysm-wallet-v2"
passwordsDefaultDirName = ".passwords"
passwordFileSuffix = ".pass"
numAccountWords = 3 // Number of words in account human-readable names.
accountFilePermissions = os.O_CREATE | os.O_RDWR
directoryPermissions = os.ModePerm
)

var (
// ErrNoWalletFound signifies there is no data at the given wallet path.
ErrNoWalletFound = errors.New("no wallet found at path")
)

// WalletConfig for a wallet struct, containing important information
// such as the passwords directory, the wallet's directory, and keymanager.
type WalletConfig struct {
Expand Down Expand Up @@ -67,13 +75,46 @@ func CreateWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
return w, nil
}

// OpenWallet instantiates a wallet from a specified path.
// OpenWallet instantiates a wallet from a specified path. It checks the
// type of keymanager associated with the wallet by reading files in the wallet
// path, if applicable. If a wallet does not exist, returns an appropriate error.
func OpenWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
ok, err := hasDir(cfg.WalletDir)
if err != nil {
return nil, errors.Wrapf(err, "could not check if wallet exists at %s", cfg.WalletDir)
}
if !ok {
return nil, ErrNoWalletFound
}
walletPath := path.Join(cfg.WalletDir, cfg.KeymanagerKind.String())
walletDir, err := os.Open(cfg.WalletDir)
if err != nil {
return nil, err
}
defer func() {
if err := walletDir.Close(); err != nil {
log.WithField(
"path", walletPath,
).Errorf("Could not close wallet directory: %v", err)
}
}()
// Retrieve the type of keymanager the wallet uses by looking at
// directories in its directory path.
list, err := walletDir.Readdirnames(0) // 0 to read all files and folders.
if err != nil {
return nil, errors.Wrapf(err, "could not read files in directory: %s", walletPath)
}
if len(list) != 1 {
return nil, fmt.Errorf("expected a single directory in the wallet path: %s", walletPath)
}
keymanagerKind, err := v2keymanager.ParseKind(list[0])
if err != nil {
return nil, errors.Wrap(err, "could not parse keymanager kind from wallet path")
}
return &Wallet{
accountsPath: walletPath,
passwordsDir: cfg.PasswordsDir,
keymanagerKind: cfg.KeymanagerKind,
keymanagerKind: keymanagerKind,
}, nil
}

Expand Down Expand Up @@ -115,7 +156,77 @@ func (w *Wallet) AccountNames() ([]string, error) {
if err != nil {
return nil, errors.Wrapf(err, "could not read files in directory: %s", w.accountsPath)
}
return list, err
accountNames := make([]string, 0)
for _, item := range list {
ok, err := hasDir(path.Join(w.accountsPath, item))
if err != nil {
return nil, errors.Wrapf(err, "could not parse directory: %v", err)
}
if ok {
accountNames = append(accountNames, item)
}
}
return accountNames, err
}

// ExistingKeyManager reads a keymanager config from disk at the wallet path,
// unmarshals it based on the wallet's keymanager kind, and returns its value.
func (w *Wallet) ExistingKeyManager(
ctx context.Context,
) (v2keymanager.IKeymanager, error) {
var keymanager v2keymanager.IKeymanager
switch w.KeymanagerKind() {
case v2keymanager.Direct:
configFile, err := w.ReadKeymanagerConfigFromDisk(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not read keymanager config")
}
cfg, err := direct.UnmarshalConfigFile(configFile)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
}
keymanager, err = direct.NewKeymanager(ctx, w, cfg)
if err != nil {
return nil, errors.Wrap(err, "could not initialize keymanager")
}
case v2keymanager.Derived:
return nil, errors.New("derived keymanager is unimplemented, work in progress")
case v2keymanager.Remote:
return nil, errors.New("remote keymanager is unimplemented, work in progress")
default:
return nil, errors.New("keymanager kind must be specified")
}
return keymanager, nil
}

// CreateKeymanager determines if a config file exists in the wallet, it
// reads the config file and initializes the keymanager that way. Otherwise,
// writes a new configuration file to the wallet and returns the initialized
// keymanager for use.
func (w *Wallet) CreateKeymanager(ctx context.Context) (v2keymanager.IKeymanager, error) {
var keymanager v2keymanager.IKeymanager
var err error
switch w.KeymanagerKind() {
case v2keymanager.Direct:
keymanager, err = direct.NewKeymanager(ctx, w, direct.DefaultConfig())
if err != nil {
return nil, errors.Wrap(err, "could not read keymanager")
}
case v2keymanager.Derived:
return nil, errors.New("derived keymanager is unimplemented, work in progress")
case v2keymanager.Remote:
return nil, errors.New("remote keymanager is unimplemented, work in progress")
default:
return nil, errors.New("keymanager type must be specified")
}
keymanagerConfig, err := keymanager.MarshalConfigFile(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not marshal keymanager config file")
}
if err := w.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
return nil, errors.Wrap(err, "could not write keymanager config file to disk")
}
return keymanager, nil
}

// WriteAccountToDisk creates an account directory under a unique namespace
Expand Down Expand Up @@ -297,10 +408,11 @@ func fileExists(filename string) bool {
return !info.IsDir()
}

// Checks if a directory indeed exists at the specified path.
func hasDir(dirPath string) (bool, error) {
_, err := os.Stat(dirPath)
info, err := os.Stat(dirPath)
if os.IsNotExist(err) {
return false, nil
}
return true, err
return info.IsDir(), err
}
2 changes: 2 additions & 0 deletions validator/client/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/state/stateutil:go_default_library",
"//proto/slashing:go_default_library",
"//proto/validator/accounts/v2:go_default_library",
"//shared/blockutil:go_default_library",
"//shared/bls:go_default_library",
"//shared/bytesutil:go_default_library",
Expand All @@ -33,6 +34,7 @@ go_library(
"//validator/db:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/keymanager/v1:go_default_library",
"//validator/keymanager/v2:go_default_library",
"//validator/slashing-protection:go_default_library",
"@com_github_dgraph_io_ristretto//:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
Expand Down
4 changes: 2 additions & 2 deletions validator/client/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (v *validator) signSlot(ctx context.Context, pubKey [48]byte, slot uint64)
return nil, err
}

sig, err := v.signObject(pubKey, slot, domain.SignatureDomain)
sig, err := v.signObject(ctx, pubKey, slot, domain.SignatureDomain)
if err != nil {
return nil, errors.Wrap(err, "Failed to sign slot")
}
Expand Down Expand Up @@ -142,7 +142,7 @@ func (v *validator) aggregateAndProofSig(ctx context.Context, pubKey [48]byte, a
if err != nil {
return nil, err
}
sig, err := v.signObject(pubKey, agg, d.SignatureDomain)
sig, err := v.signObject(ctx, pubKey, agg, d.SignatureDomain)
if err != nil {
return nil, err
}
Expand Down
Loading