Integrate Accounts v2 Keymanager Into Validator Client (#6489)

* add in configs
* ask for enable accounts v2
* begin integration of v2 keymanager
* refactor wallet opening
* include significant refactoring of how opening a wallet works, making it easy to include at runtime
* ensure build with keymanager v2
* further improving runtime integration
* default pass paths
* finally running v2 at runtime
* import spacing
* Merge branch 'master' into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* confs
* rem e2e val flag
* Merge branch 'master' into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
* Merge refs/heads/master into v2-accounts-feature
This commit is contained in:
Raul Jordan
2020-07-08 00:01:09 -05:00
committed by GitHub
parent fe13f1f856
commit fd9003f822
17 changed files with 327 additions and 151 deletions

View File

@@ -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.
InitSyncVerbose bool // InitSyncVerbose logs every processed block during initial syncing.
// DisableForkChoice disables using LMD-GHOST fork choice to update
@@ -264,6 +265,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

View File

@@ -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.",
@@ -562,6 +566,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.

View File

@@ -3,7 +3,6 @@ package v2
import (
"context"
"fmt"
"os"
"path"
"unicode"
@@ -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"
)
@@ -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 {
@@ -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)
@@ -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",
@@ -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",

View File

@@ -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 {
@@ -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
}
@@ -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
@@ -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
}

View File

@@ -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",
@@ -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",

View File

@@ -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")
}
@@ -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
}

View File

@@ -10,8 +10,10 @@ import (
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/roughtime"
@@ -175,10 +177,17 @@ func (v *validator) signAtt(ctx context.Context, pubKey [48]byte, data *ethpb.At
}
var sig bls.Signature
if protectingKeymanager, supported := v.keyManager.(keymanager.ProtectingKeyManager); supported {
sig, err = protectingKeymanager.SignAttestation(pubKey, bytesutil.ToBytes32(domain.SignatureDomain), data)
if featureconfig.Get().EnableAccountsV2 {
sig, err = v.keyManagerV2.Sign(ctx, &validatorpb.SignRequest{
PublicKey: pubKey[:],
Data: root[:],
})
} else {
sig, err = v.keyManager.Sign(pubKey, root)
if protectingKeymanager, supported := v.keyManager.(keymanager.ProtectingKeyManager); supported {
sig, err = protectingKeymanager.SignAttestation(pubKey, bytesutil.ToBytes32(domain.SignatureDomain), data)
} else {
sig, err = v.keyManager.Sign(pubKey, root)
}
}
if err != nil {
return nil, err

View File

@@ -8,6 +8,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/sirupsen/logrus"
)
@@ -140,7 +141,13 @@ func (v *validator) LogValidatorGainsAndLosses(ctx context.Context, slot uint64)
return nil
}
pks, err := v.keyManager.FetchValidatingKeys()
var pks [][48]byte
var err error
if featureconfig.Get().EnableAccountsV2 {
pks, err = v.keyManagerV2.FetchValidatingPublicKeys(ctx)
} else {
pks, err = v.keyManager.FetchValidatingKeys()
}
if err != nil {
return err
}

View File

@@ -9,8 +9,10 @@ import (
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil"
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
km "github.com/prysmaticlabs/prysm/validator/keymanager/v1"
"github.com/sirupsen/logrus"
@@ -124,7 +126,7 @@ func (v *validator) signRandaoReveal(ctx context.Context, pubKey [48]byte, epoch
return nil, errors.Wrap(err, "could not get domain data")
}
randaoReveal, err := v.signObject(pubKey, epoch, domain.SignatureDomain)
randaoReveal, err := v.signObject(ctx, pubKey, epoch, domain.SignatureDomain)
if err != nil {
return nil, errors.Wrap(err, "could not sign reveal")
}
@@ -138,6 +140,21 @@ func (v *validator) signBlock(ctx context.Context, pubKey [48]byte, epoch uint64
return nil, errors.Wrap(err, "could not get domain data")
}
var sig bls.Signature
if featureconfig.Get().EnableAccountsV2 {
blockRoot, err := helpers.ComputeSigningRoot(b, domain.SignatureDomain)
if err != nil {
return nil, errors.Wrap(err, "could not get signing root")
}
sig, err = v.keyManagerV2.Sign(ctx, &validatorpb.SignRequest{
PublicKey: pubKey[:],
Data: blockRoot[:],
})
if err != nil {
return nil, errors.Wrap(err, "could not sign block proposal")
}
return sig.Marshal(), nil
}
if protectingKeymanager, supported := v.keyManager.(km.ProtectingKeyManager); supported {
bodyRoot, err := stateutil.BlockBodyRoot(b.Body)
if err != nil {

View File

@@ -14,12 +14,15 @@ import (
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/go-ssz"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/validator/db/kv"
keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v1"
v2 "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
slashingprotection "github.com/prysmaticlabs/prysm/validator/slashing-protection"
"github.com/sirupsen/logrus"
"go.opencensus.io/plugin/ocgrpc"
@@ -42,9 +45,11 @@ type ValidatorService struct {
withCert string
dataDir string
keyManager keymanager.KeyManager
keyManagerV2 v2.IKeymanager
logValidatorBalances bool
emitAccountMetrics bool
maxCallRecvMsgSize int
validatingPubKeys [][48]byte
grpcRetries uint
grpcHeaders []string
protector slashingprotection.Protector
@@ -56,7 +61,9 @@ type Config struct {
DataDir string
CertFlag string
GraffitiFlag string
ValidatingPubKeys [][48]byte
KeyManager keymanager.KeyManager
KeyManagerV2 v2.IKeymanager
LogValidatorBalances bool
EmitAccountMetrics bool
GrpcMaxCallRecvMsgSizeFlag int
@@ -77,6 +84,8 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e
dataDir: cfg.DataDir,
graffiti: []byte(cfg.GraffitiFlag),
keyManager: cfg.KeyManager,
keyManagerV2: cfg.KeyManagerV2,
validatingPubKeys: cfg.ValidatingPubKeys,
logValidatorBalances: cfg.LogValidatorBalances,
emitAccountMetrics: cfg.EmitAccountMetrics,
maxCallRecvMsgSize: cfg.GrpcMaxCallRecvMsgSizeFlag,
@@ -113,13 +122,7 @@ func (v *ValidatorService) Start() {
log.Info("Established secure gRPC connection")
}
pubkeys, err := v.keyManager.FetchValidatingKeys()
if err != nil {
log.Errorf("Could not get validating keys: %v", err)
return
}
valDB, err := kv.NewKVStore(v.dataDir, pubkeys)
valDB, err := kv.NewKVStore(v.dataDir, v.validatingPubKeys)
if err != nil {
log.Errorf("Could not initialize db: %v", err)
return
@@ -147,6 +150,7 @@ func (v *ValidatorService) Start() {
beaconClient: ethpb.NewBeaconChainClient(v.conn),
node: ethpb.NewNodeClient(v.conn),
keyManager: v.keyManager,
keyManagerV2: v.keyManagerV2,
graffiti: v.graffiti,
logValidatorBalances: v.logValidatorBalances,
emitAccountMetrics: v.emitAccountMetrics,
@@ -182,7 +186,22 @@ func (v *ValidatorService) Status() error {
}
// signObject signs a generic object, with protection if available.
func (v *validator) signObject(pubKey [48]byte, object interface{}, domain []byte) (bls.Signature, error) {
func (v *validator) signObject(
ctx context.Context,
pubKey [48]byte,
object interface{},
domain []byte,
) (bls.Signature, error) {
if featureconfig.Get().EnableAccountsV2 {
root, err := helpers.ComputeSigningRoot(object, domain)
if err != nil {
return nil, err
}
return v.keyManagerV2.Sign(ctx, &validatorpb.SignRequest{
PublicKey: pubKey[:],
Data: root[:],
})
}
if protectingKeymanager, supported := v.keyManager.(keymanager.ProtectingKeyManager); supported {
root, err := ssz.HashTreeRoot(object)
if err != nil {

View File

@@ -28,6 +28,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/slotutil"
vdb "github.com/prysmaticlabs/prysm/validator/db"
keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v1"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
slashingprotection "github.com/prysmaticlabs/prysm/validator/slashing-protection"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
@@ -52,6 +53,7 @@ type validator struct {
graffiti []byte
node ethpb.NodeClient
keyManager keymanager.KeyManager
keyManagerV2 v2keymanager.IKeymanager
startBalances map[[48]byte]uint64
prevBalance map[[48]byte]uint64
voteStats voteStats
@@ -180,7 +182,14 @@ func (v *validator) WaitForSynced(ctx context.Context) error {
func (v *validator) WaitForActivation(ctx context.Context) error {
ctx, span := trace.StartSpan(ctx, "validator.WaitForActivation")
defer span.End()
validatingKeys, err := v.keyManager.FetchValidatingKeys()
var validatingKeys [][48]byte
var err error
if featureconfig.Get().EnableAccountsV2 {
validatingKeys, err = v.keyManagerV2.FetchValidatingPublicKeys(ctx)
} else {
validatingKeys, err = v.keyManager.FetchValidatingKeys()
}
if err != nil {
return errors.Wrap(err, "could not fetch validating keys")
}
@@ -314,7 +323,13 @@ func (v *validator) UpdateDuties(ctx context.Context, slot uint64) error {
ctx, span := trace.StartSpan(ctx, "validator.UpdateAssignments")
defer span.End()
validatingKeys, err := v.keyManager.FetchValidatingKeys()
var validatingKeys [][48]byte
var err error
if featureconfig.Get().EnableAccountsV2 {
validatingKeys, err = v.keyManagerV2.FetchValidatingPublicKeys(ctx)
} else {
validatingKeys, err = v.keyManager.FetchValidatingKeys()
}
if err != nil {
return err
}

View File

@@ -47,7 +47,7 @@ func NewKeystore(input string) (KeyManager, string, error) {
if opts.Path == "" {
opts.Path = v1.DefaultValidatorDir()
}
log.WithField("keystorePath", opts.Path).Info("Checking validator keys")
log.WithField("keystorePath", opts.Path).Debug("Checking validator keys")
exists, err := v1.Exists(opts.Path, true /* assertNonEmpty */)
if err != nil {

View File

@@ -46,3 +46,17 @@ func (k Kind) String() string {
return fmt.Sprintf("%d", int(k))
}
}
// ParseKind from a raw string, returning a keymanager kind.
func ParseKind(k string) (Kind, error) {
switch k {
case "direct":
return Direct, nil
case "derived":
return Derived, nil
case "remote":
return Remote, nil
default:
return 0, fmt.Errorf("%s is not an allowed keymanager", k)
}
}

View File

@@ -70,6 +70,8 @@ var appFlags = []cli.Flag{
flags.MonitoringPortFlag,
flags.SlasherRPCProviderFlag,
flags.SlasherCertFlag,
flags.WalletPasswordsDirFlag,
flags.WalletDirFlag,
cmd.MinimalConfigFlag,
cmd.E2EConfigFlag,
cmd.VerbosityFlag,
@@ -174,7 +176,10 @@ contract in order to activate the validator client`,
var err error
var pubKeys [][]byte
if cliCtx.String(flags.KeyManager.Name) != "" {
pubKeysBytes48, success := node.ExtractPublicKeysFromKeyManager(cliCtx)
pubKeysBytes48, success := node.ExtractPublicKeysFromKeymanager(
cliCtx,
nil, /* nil v2 keymanager */
)
pubKeys, err = bytesutil.FromBytes48Array(pubKeysBytes48), success
} else {
keystorePath, passphrase, err := v1.HandleEmptyKeystoreFlags(cliCtx, false /*confirmPassword*/)

View File

@@ -27,10 +27,12 @@ go_library(
"//shared/prometheus:go_default_library",
"//shared/tracing:go_default_library",
"//shared/version:go_default_library",
"//validator/accounts/v2:go_default_library",
"//validator/client:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/flags:go_default_library",
"//validator/keymanager/v1:go_default_library",
"//validator/keymanager/v2:go_default_library",
"//validator/slashing-protection:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",

View File

@@ -9,14 +9,12 @@ import (
"io/ioutil"
"os"
"os/signal"
"path"
"strings"
"sync"
"syscall"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"github.com/prysmaticlabs/prysm/shared"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/debug"
@@ -25,18 +23,21 @@ import (
"github.com/prysmaticlabs/prysm/shared/prometheus"
"github.com/prysmaticlabs/prysm/shared/tracing"
"github.com/prysmaticlabs/prysm/shared/version"
accountsv2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/validator/client"
"github.com/prysmaticlabs/prysm/validator/db/kv"
"github.com/prysmaticlabs/prysm/validator/flags"
keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v1"
v1 "github.com/prysmaticlabs/prysm/validator/keymanager/v1"
v2 "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
slashing_protection "github.com/prysmaticlabs/prysm/validator/slashing-protection"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var log = logrus.WithField("prefix", "node")
// ValidatorClient defines an instance of a sharding validator that manages
// the entire lifecycle of services attached to it participating in
// Ethereum Serenity.
// ValidatorClient defines an instance of an eth2 validator that manages
// the entire lifecycle of services attached to it participating in eth2.
type ValidatorClient struct {
cliCtx *cli.Context
services *shared.ServiceRegistry // Lifecycle and service store.
@@ -44,7 +45,7 @@ type ValidatorClient struct {
stop chan struct{} // Channel to wait for termination notifications.
}
// NewValidatorClient creates a new, Ethereum Serenity validator client.
// NewValidatorClient creates a new, Prysm validator client.
func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
if err := tracing.Setup(
"validator", // service name
@@ -77,23 +78,48 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
cmd.ConfigureValidator(cliCtx)
featureconfig.ConfigureValidator(cliCtx)
keyManager, err := selectKeyManager(cliCtx)
keyManagerV1, err := selectV1Keymanager(cliCtx)
if err != nil {
return nil, err
}
pubKeys, err := keyManager.FetchValidatingKeys()
var keyManagerV2 v2.IKeymanager
if featureconfig.Get().EnableAccountsV2 {
walletDir := cliCtx.String(flags.WalletDirFlag.Name)
if walletDir == flags.DefaultValidatorDir() {
walletDir = path.Join(walletDir, accountsv2.WalletDefaultDirName)
}
passwordsDir := cliCtx.String(flags.WalletPasswordsDirFlag.Name)
if passwordsDir == flags.DefaultValidatorDir() {
passwordsDir = path.Join(passwordsDir, accountsv2.PasswordsDefaultDirName)
}
// Read the wallet from the specified path.
wallet, err := accountsv2.OpenWallet(context.Background(), &accountsv2.WalletConfig{
PasswordsDir: passwordsDir,
WalletDir: walletDir,
})
if err == accountsv2.ErrNoWalletFound {
log.Fatal("No wallet found at path, please create a new wallet using `validator accounts-v2 new`")
}
if err != nil {
log.Fatalf("Could not open wallet: %v", err)
}
keyManagerV2, err = wallet.ExistingKeyManager(context.Background())
if err != nil {
log.Fatalf("Could not read existing keymanager for wallet: %v", err)
}
}
pubKeys, err := ExtractPublicKeysFromKeymanager(cliCtx, keyManagerV2)
if err != nil {
log.WithError(err).Error("Failed to obtain public keys for validation")
return nil, err
}
if len(pubKeys) == 0 {
log.Warn("No keys found; nothing to validate")
} else {
if len(pubKeys) == 0 {
log.Warn("No keys found; nothing to validate")
} else {
log.WithField("validators", len(pubKeys)).Debug("Found validator keys")
for _, key := range pubKeys {
log.WithField("pubKey", fmt.Sprintf("%#x", key)).Info("Validating for public key")
}
log.WithField("validators", len(pubKeys)).Debug("Found validator keys")
for _, key := range pubKeys {
log.WithField("pubKey", fmt.Sprintf("%#x", key)).Info("Validating for public key")
}
}
@@ -101,10 +127,6 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
forceClearFlag := cliCtx.Bool(cmd.ForceClearDB.Name)
dataDir := cliCtx.String(cmd.DataDirFlag.Name)
if clearFlag || forceClearFlag {
pubkeys, err := keyManager.FetchValidatingKeys()
if err != nil {
return nil, err
}
if dataDir == "" {
dataDir = cmd.DefaultDataDir()
if dataDir == "" {
@@ -115,7 +137,7 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
}
}
if err := clearDB(dataDir, pubkeys, forceClearFlag); err != nil {
if err := clearDB(dataDir, pubKeys, forceClearFlag); err != nil {
return nil, err
}
}
@@ -129,7 +151,7 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
return nil, err
}
}
if err := ValidatorClient.registerClientService(keyManager); err != nil {
if err := ValidatorClient.registerClientService(keyManagerV1, keyManagerV2, pubKeys); err != nil {
return nil, err
}
@@ -176,7 +198,7 @@ func (s *ValidatorClient) Close() {
defer s.lock.Unlock()
s.services.StopAll()
log.Info("Stopping sharding validator")
log.Info("Stopping Prysm validator")
close(s.stop)
}
@@ -190,7 +212,11 @@ func (s *ValidatorClient) registerPrometheusService() error {
return s.services.RegisterService(service)
}
func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager) error {
func (s *ValidatorClient) registerClientService(
keyManager v1.KeyManager,
keyManagerV2 v2.IKeymanager,
validatingPubKeys [][48]byte,
) error {
endpoint := s.cliCtx.String(flags.BeaconRPCProviderFlag.Name)
dataDir := s.cliCtx.String(cmd.DataDirFlag.Name)
logValidatorBalances := !s.cliCtx.Bool(flags.DisablePenaltyRewardLogFlag.Name)
@@ -208,10 +234,12 @@ func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager
Endpoint: endpoint,
DataDir: dataDir,
KeyManager: keyManager,
KeyManagerV2: keyManagerV2,
LogValidatorBalances: logValidatorBalances,
EmitAccountMetrics: emitAccountMetrics,
CertFlag: cert,
GraffitiFlag: graffiti,
ValidatingPubKeys: validatingPubKeys,
GrpcMaxCallRecvMsgSizeFlag: maxCallRecvMsgSize,
GrpcRetriesFlag: grpcRetries,
GrpcHeadersFlag: s.cliCtx.String(flags.GrpcHeadersFlag.Name),
@@ -245,8 +273,8 @@ func (s *ValidatorClient) registerSlasherClientService() error {
return s.services.RegisterService(sp)
}
// selectKeyManager selects the key manager depending on the options provided by the user.
func selectKeyManager(ctx *cli.Context) (keymanager.KeyManager, error) {
// Selects the key manager depending on the options provided by the user.
func selectV1Keymanager(ctx *cli.Context) (v1.KeyManager, error) {
manager := strings.ToLower(ctx.String(flags.KeyManager.Name))
opts := ctx.String(flags.KeyManagerOpts.Name)
if opts == "" {
@@ -286,20 +314,20 @@ func selectKeyManager(ctx *cli.Context) (keymanager.KeyManager, error) {
}
}
var km keymanager.KeyManager
var km v1.KeyManager
var help string
var err error
switch manager {
case "interop":
km, help, err = keymanager.NewInterop(opts)
km, help, err = v1.NewInterop(opts)
case "unencrypted":
km, help, err = keymanager.NewUnencrypted(opts)
km, help, err = v1.NewUnencrypted(opts)
case "keystore":
km, help, err = keymanager.NewKeystore(opts)
km, help, err = v1.NewKeystore(opts)
case "wallet":
km, help, err = keymanager.NewWallet(opts)
km, help, err = v1.NewWallet(opts)
case "remote":
km, help, err = keymanager.NewRemoteWallet(opts)
km, help, err = v1.NewRemoteWallet(opts)
default:
return nil, fmt.Errorf("unknown keymanager %q", manager)
}
@@ -342,9 +370,18 @@ func clearDB(dataDir string, pubkeys [][48]byte, force bool) error {
return nil
}
// ExtractPublicKeysFromKeyManager extracts only the public keys from the specified key manager.
func ExtractPublicKeysFromKeyManager(ctx *cli.Context) ([][48]byte, error) {
km, err := selectKeyManager(ctx)
// ExtractPublicKeysFromKeymanager extracts only the public keys from the specified key manager.
func ExtractPublicKeysFromKeymanager(cliCtx *cli.Context, keyManagerV2 v2.IKeymanager) ([][48]byte, error) {
var pubKeys [][48]byte
var err error
if featureconfig.Get().EnableAccountsV2 {
pubKeys, err = keyManagerV2.FetchValidatingPublicKeys(context.Background())
if err != nil {
return nil, errors.Wrap(err, "failed to obtain public keys for validation")
}
return pubKeys, nil
}
km, err := selectV1Keymanager(cliCtx)
if err != nil {
return nil, err
}

View File

@@ -94,6 +94,8 @@ var appHelpFlagGroups = []flagGroup{
flags.SourceDirectory,
flags.TargetDirectory,
flags.DisableAccountMetricsFlag,
flags.WalletDirFlag,
flags.WalletPasswordsDirFlag,
},
},
{