Skip to content

Commit

Permalink
Implement Create and List Accounts Validator RPC (#7172)
Browse files Browse the repository at this point in the history
* impl of accounts RPCs
* create and list accounts impls
* impl create account and list accounts
* tests pass
* imports
* fix test
  • Loading branch information
rauljordan authored Sep 4, 2020
1 parent 366b98a commit 0961fef
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 29 deletions.
20 changes: 10 additions & 10 deletions validator/keymanager/v2/derived/derived.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,41 +262,41 @@ func (dr *Keymanager) ValidatingAccountNames(ctx context.Context) ([]string, err
// for hierarchical derivation of BLS secret keys and a common derivation path structure for
// persisting accounts to disk. Each account stores the generated keystore.json file.
// The entire derived wallet seed phrase can be recovered from a BIP-39 english mnemonic.
func (dr *Keymanager) CreateAccount(ctx context.Context, logAccountInfo bool) (string, error) {
func (dr *Keymanager) CreateAccount(ctx context.Context, logAccountInfo bool) ([]byte, error) {
withdrawalKeyPath := fmt.Sprintf(WithdrawalKeyDerivationPathTemplate, dr.seedCfg.NextAccount)
validatingKeyPath := fmt.Sprintf(ValidatingKeyDerivationPathTemplate, dr.seedCfg.NextAccount)
withdrawalKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, withdrawalKeyPath)
if err != nil {
return "", errors.Wrapf(err, "failed to create withdrawal key for account %d", dr.seedCfg.NextAccount)
return nil, errors.Wrapf(err, "failed to create withdrawal key for account %d", dr.seedCfg.NextAccount)
}
validatingKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, validatingKeyPath)
if err != nil {
return "", errors.Wrapf(err, "failed to create validating key for account %d", dr.seedCfg.NextAccount)
return nil, errors.Wrapf(err, "failed to create validating key for account %d", dr.seedCfg.NextAccount)
}

// Upon confirmation of the withdrawal key, proceed to display
// and write associated deposit data to disk.
blsValidatingKey, err := bls.SecretKeyFromBytes(validatingKey.Marshal())
if err != nil {
return "", err
return nil, err
}
blsWithdrawalKey, err := bls.SecretKeyFromBytes(withdrawalKey.Marshal())
if err != nil {
return "", err
return nil, err
}
// Upon confirmation of the withdrawal key, proceed to display
// and write associated deposit data to disk.
tx, data, err := depositutil.GenerateDepositTransaction(blsValidatingKey, blsWithdrawalKey)
if err != nil {
return "", errors.Wrap(err, "could not generate deposit transaction data")
return nil, errors.Wrap(err, "could not generate deposit transaction data")
}
domain, err := helpers.ComputeDomain(
params.BeaconConfig().DomainDeposit,
nil, /*forkVersion*/
nil, /*genesisValidatorsRoot*/
)
if err := depositutil.VerifyDepositSignature(data, domain); err != nil {
return "", errors.Wrap(err, "failed to verify deposit signature, please make sure your account was created properly")
return nil, errors.Wrap(err, "failed to verify deposit signature, please make sure your account was created properly")
}
// Log the deposit transaction data to the user.
fmt.Printf(`
Expand Down Expand Up @@ -325,12 +325,12 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, logAccountInfo bool) (s
dr.lock.Unlock()
encodedCfg, err := marshalEncryptedSeedFile(dr.seedCfg)
if err != nil {
return "", errors.Wrap(err, "could not marshal encrypted seed file")
return nil, errors.Wrap(err, "could not marshal encrypted seed file")
}
if err := dr.wallet.WriteEncryptedSeedToDisk(ctx, encodedCfg); err != nil {
return "", errors.Wrap(err, "could not write encrypted seed file to disk")
return nil, errors.Wrap(err, "could not write encrypted seed file to disk")
}
return fmt.Sprintf("%d", newAccountNumber), nil
return publicKey[:], nil
}

// Sign signs a message using a validator key.
Expand Down
9 changes: 3 additions & 6 deletions validator/keymanager/v2/derived/derived_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,8 @@ func TestDerivedKeymanager_CreateAccount(t *testing.T) {
}
require.NoError(t, dr.initializeKeysCachesFromSeed())
ctx := context.Background()
accountName, err := dr.CreateAccount(ctx, true /*logAccountInfo*/)
_, err := dr.CreateAccount(ctx, true /*logAccountInfo*/)
require.NoError(t, err)
assert.Equal(t, "0", accountName)

// Assert the new value for next account increased and also
// check the config file was updated on disk with this new value.
Expand Down Expand Up @@ -112,9 +111,8 @@ func TestDerivedKeymanager_FetchValidatingPublicKeys(t *testing.T) {
numAccounts := 20
wantedPublicKeys := make([][48]byte, numAccounts)
for i := 0; i < numAccounts; i++ {
accountName, err := dr.CreateAccount(ctx, false /*logAccountInfo*/)
_, err := dr.CreateAccount(ctx, false /*logAccountInfo*/)
require.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%d", i), accountName)
validatingKeyPath := fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i)
validatingKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, validatingKeyPath)
require.NoError(t, err)
Expand Down Expand Up @@ -152,9 +150,8 @@ func TestDerivedKeymanager_Sign(t *testing.T) {
numAccounts := 2
ctx := context.Background()
for i := 0; i < numAccounts; i++ {
accountName, err := dr.CreateAccount(ctx, false /*logAccountInfo*/)
_, err := dr.CreateAccount(ctx, false /*logAccountInfo*/)
require.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%d", i), accountName)
}
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions validator/keymanager/v2/direct/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ go_test(
"//shared/bls:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/event:go_default_library",
"//shared/petnames:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
"//validator/accounts/v2/testing:go_default_library",
Expand Down
16 changes: 8 additions & 8 deletions validator/keymanager/v2/direct/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,15 @@ func (dr *Keymanager) initializeKeysCachesFromKeystore() error {
// stores the generated keystore.json file in the wallet and additionally
// generates withdrawal credentials. At the end, it logs
// the raw deposit data hex string for users to copy.
func (dr *Keymanager) CreateAccount(ctx context.Context) (string, error) {
func (dr *Keymanager) CreateAccount(ctx context.Context) ([]byte, error) {
// Create a petname for an account from its public key and write its password to disk.
validatingKey := bls.RandKey()
accountName := petnames.DeterministicName(validatingKey.PublicKey().Marshal(), "-")
dr.accountsStore.PrivateKeys = append(dr.accountsStore.PrivateKeys, validatingKey.Marshal())
dr.accountsStore.PublicKeys = append(dr.accountsStore.PublicKeys, validatingKey.PublicKey().Marshal())
newStore, err := dr.createAccountsKeystore(ctx, dr.accountsStore.PrivateKeys, dr.accountsStore.PublicKeys)
if err != nil {
return "", errors.Wrap(err, "could not create accounts keystore")
return nil, errors.Wrap(err, "could not create accounts keystore")
}

// Generate a withdrawal key and confirm user
Expand All @@ -239,15 +239,15 @@ func (dr *Keymanager) CreateAccount(ctx context.Context) (string, error) {
// and write associated deposit data to disk.
tx, data, err := depositutil.GenerateDepositTransaction(validatingKey, withdrawalKey)
if err != nil {
return "", errors.Wrap(err, "could not generate deposit transaction data")
return nil, errors.Wrap(err, "could not generate deposit transaction data")
}
domain, err := helpers.ComputeDomain(
params.BeaconConfig().DomainDeposit,
nil, /*forkVersion*/
nil, /*genesisValidatorsRoot*/
)
if err := depositutil.VerifyDepositSignature(data, domain); err != nil {
return "", errors.Wrap(err, "failed to verify deposit signature, please make sure your account was created properly")
return nil, errors.Wrap(err, "failed to verify deposit signature, please make sure your account was created properly")
}

// Log the deposit transaction data to the user.
Expand All @@ -260,10 +260,10 @@ func (dr *Keymanager) CreateAccount(ctx context.Context) (string, error) {
// Write the encoded keystore.
encoded, err := json.MarshalIndent(newStore, "", "\t")
if err != nil {
return "", err
return nil, err
}
if err := dr.wallet.WriteFileAtPath(ctx, AccountsPath, accountsKeystoreFileName, encoded); err != nil {
return "", errors.Wrap(err, "could not write keystore file for accounts")
return nil, errors.Wrap(err, "could not write keystore file for accounts")
}

log.WithFields(logrus.Fields{
Expand All @@ -272,9 +272,9 @@ func (dr *Keymanager) CreateAccount(ctx context.Context) (string, error) {

err = dr.initializeKeysCachesFromKeystore()
if err != nil {
return "", errors.Wrap(err, "failed to initialize keys caches")
return nil, errors.Wrap(err, "failed to initialize keys caches")
}
return accountName, nil
return validatingKey.PublicKey().Marshal(), nil
}

// DeleteAccounts takes in public keys and removes the accounts entirely. This includes their disk keystore and cached keystore.
Expand Down
5 changes: 3 additions & 2 deletions validator/keymanager/v2/direct/direct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
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/petnames"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
mock "github.com/prysmaticlabs/prysm/validator/accounts/v2/testing"
Expand All @@ -30,7 +31,7 @@ func TestDirectKeymanager_CreateAccount(t *testing.T) {
accountsStore: &AccountStore{},
}
ctx := context.Background()
accountName, err := dr.CreateAccount(ctx)
createdPubKey, err := dr.CreateAccount(ctx)
require.NoError(t, err)

// Ensure the keystore file was written to the wallet
Expand Down Expand Up @@ -58,7 +59,7 @@ func TestDirectKeymanager_CreateAccount(t *testing.T) {
require.NoError(t, err)
pubKey := privKey.PublicKey().Marshal()
assert.DeepEqual(t, pubKey, store.PublicKeys[0])
require.LogsContain(t, hook, accountName)
require.LogsContain(t, hook, petnames.DeterministicName(createdPubKey, "-"))
require.LogsContain(t, hook, "Successfully created new validator account")
}

Expand Down
6 changes: 6 additions & 0 deletions validator/rpc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"accounts.go",
"auth.go",
"health.go",
"intercepter.go",
Expand All @@ -15,6 +16,7 @@ go_library(
deps = [
"//proto/validator/accounts/v2:go_default_library",
"//shared/event:go_default_library",
"//shared/petnames:go_default_library",
"//shared/promptutil:go_default_library",
"//shared/rand:go_default_library",
"//shared/roughtime:go_default_library",
Expand All @@ -24,6 +26,8 @@ go_library(
"//validator/db:go_default_library",
"//validator/flags:go_default_library",
"//validator/keymanager/v2:go_default_library",
"//validator/keymanager/v2/derived:go_default_library",
"//validator/keymanager/v2/direct:go_default_library",
"@com_github_dgrijalva_jwt_go//:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library",
Expand All @@ -47,6 +51,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"accounts_test.go",
"auth_test.go",
"health_test.go",
"intercepter_test.go",
Expand All @@ -65,6 +70,7 @@ go_test(
"//validator/client:go_default_library",
"//validator/db/testing:go_default_library",
"//validator/keymanager/v2:go_default_library",
"//validator/keymanager/v2/direct:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@com_github_google_uuid//:go_default_library",
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
Expand Down
79 changes: 79 additions & 0 deletions validator/rpc/accounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package rpc

import (
"context"
"fmt"

ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/petnames"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

// CreateAccount allows creation of a new account in a user's wallet via RPC.
func (s *Server) CreateAccount(ctx context.Context, _ *ptypes.Empty) (*pb.CreateAccountResponse, error) {
if !s.walletInitialized {
return nil, status.Error(codes.FailedPrecondition, "Wallet not yet initialized")
}
var pubKey []byte
var err error
switch s.wallet.KeymanagerKind() {
case v2keymanager.Remote:
return nil, status.Error(codes.InvalidArgument, "Cannot create account for remote keymanager")
case v2keymanager.Direct:
km, ok := s.keymanager.(*direct.Keymanager)
if !ok {
return nil, status.Error(codes.InvalidArgument, "Not a direct keymanager")
}
// Create a new validator account using the specified keymanager.
pubKey, err = km.CreateAccount(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not create account in wallet")
}
case v2keymanager.Derived:
km, ok := s.keymanager.(*derived.Keymanager)
if !ok {
return nil, status.Error(codes.InvalidArgument, "Not a derived keymanager")
}
pubKey, err = km.CreateAccount(ctx, false /*logAccountInfo*/)
if err != nil {
return nil, errors.Wrap(err, "could not create account in wallet")
}
}
return &pb.CreateAccountResponse{
Account: &pb.Account{
ValidatingPublicKey: pubKey,
AccountName: petnames.DeterministicName(pubKey, "-"),
},
}, nil
}

// ListAccounts allows retrieval of validating keys and their petnames
// for a user's wallet via RPC.
func (s *Server) ListAccounts(ctx context.Context, req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) {
if !s.walletInitialized {
return nil, status.Error(codes.FailedPrecondition, "Wallet not yet initialized")
}
keys, err := s.keymanager.FetchValidatingPublicKeys(ctx)
if err != nil {
return nil, err
}
accounts := make([]*pb.Account, len(keys))
for i := 0; i < len(keys); i++ {
accounts[i] = &pb.Account{
ValidatingPublicKey: keys[i][:],
AccountName: petnames.DeterministicName(keys[i][:], "-"),
}
if s.wallet.KeymanagerKind() == v2keymanager.Derived {
accounts[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
}
}
return &pb.ListAccountsResponse{
Accounts: accounts,
}, nil
}
78 changes: 78 additions & 0 deletions validator/rpc/accounts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package rpc

import (
"context"
"testing"

ptypes "github.com/gogo/protobuf/types"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
)

func TestServer_CreateAccount(t *testing.T) {
ctx := context.Background()
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
strongPass := "29384283xasjasd32%%&*@*#*"
// We attempt to create the wallet.
wallet, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &v2.WalletConfig{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Direct,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
km, err := wallet.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
s := &Server{
keymanager: km,
walletInitialized: true,
wallet: wallet,
}
resp, err := s.CreateAccount(ctx, &ptypes.Empty{})
require.NoError(t, err)
assert.NotNil(t, resp.Account.ValidatingPublicKey)
}

func TestServer_ListAccounts(t *testing.T) {
ctx := context.Background()
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
strongPass := "29384283xasjasd32%%&*@*#*"
// We attempt to create the wallet.
wallet, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &v2.WalletConfig{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Direct,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
km, err := wallet.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
s := &Server{
keymanager: km,
walletInitialized: true,
wallet: wallet,
}
numAccounts := 5
keys := make([][]byte, numAccounts)
for i := 0; i < numAccounts; i++ {
key, err := km.(*direct.Keymanager).CreateAccount(ctx)
require.NoError(t, err)
keys[i] = key
}
resp, err := s.ListAccounts(ctx, &pb.ListAccountsRequest{})
require.NoError(t, err)
require.Equal(t, len(resp.Accounts), numAccounts)
for i := 0; i < numAccounts; i++ {
assert.DeepEqual(t, resp.Accounts[i].ValidatingPublicKey, keys[i])
}
}
Loading

0 comments on commit 0961fef

Please sign in to comment.