Implement Wallet Config and HasWallet Endpoints (#7341)

* fix the wallet config implementation

* implemented wallet config endpoint and tests

* better error messages

* fix nil pointer

* more nil issues fix

* gazelle

* fix interceptor

* revise

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
Raul Jordan
2020-09-24 15:37:59 -05:00
committed by GitHub
parent 90978cd22e
commit 5821454ac7
9 changed files with 783 additions and 521 deletions

View File

@@ -77,6 +77,7 @@ go_test(
"//validator/db/testing: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_gogo_protobuf//types:go_default_library",
"@com_github_google_uuid//:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",

View File

@@ -56,13 +56,16 @@ func (s *Server) Signup(ctx context.Context, req *pb.AuthRequest) (*pb.AuthRespo
// Login to authenticate with the validator RPC API using a password.
func (s *Server) Login(ctx context.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) {
hashedPassword, err := fileutil.ReadFileAsBytes(filepath.Join(defaultWalletPath, wallet.HashedPasswordFileName))
if err != nil {
return nil, status.Error(codes.Internal, "Could not retrieve hashed password from disk")
}
// Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.Password)); err != nil {
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
hashedPasswordPath := filepath.Join(defaultWalletPath, wallet.HashedPasswordFileName)
if fileutil.FileExists(hashedPasswordPath) {
hashedPassword, err := fileutil.ReadFileAsBytes(hashedPasswordPath)
if err != nil {
return nil, status.Error(codes.Internal, "Could not retrieve hashed password from disk")
}
// Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.Password)); err != nil {
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
}
}
if err := s.initializeWallet(ctx, &wallet.Config{
WalletDir: defaultWalletPath,

View File

@@ -19,7 +19,7 @@ var (
noAuthPaths = map[string]bool{
"/ethereum.validator.accounts.v2.Auth/Signup": true,
"/ethereum.validator.accounts.v2.Auth/Login": true,
"/ethereum.validator.accounts.v2.Wallet/WalletConfig": true,
"/ethereum.validator.accounts.v2.Wallet/HasWallet": true,
"/ethereum.validator.accounts.v2.Wallet/GenerateMnemonic": true,
}
authLock sync.RWMutex

View File

@@ -3,6 +3,7 @@ package rpc
import (
"context"
"encoding/json"
"io/ioutil"
"path/filepath"
ptypes "github.com/gogo/protobuf/types"
@@ -22,11 +23,28 @@ import (
var defaultWalletPath = filepath.Join(flags.DefaultValidatorDir(), flags.WalletDefaultDirName)
// HasWallet checks if a user has created a wallet before as well as whether or not
// they have used the web UI before to set a wallet password.
func (s *Server) HasWallet(ctx context.Context, _ *ptypes.Empty) (*pb.HasWalletResponse, error) {
err := wallet.Exists(defaultWalletPath)
if err != nil && errors.Is(err, wallet.ErrNoWalletFound) {
return &pb.HasWalletResponse{
WalletExists: false,
}, nil
}
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if wallet exists: %v", err)
}
return &pb.HasWalletResponse{
WalletExists: true,
}, nil
}
// CreateWallet via an API request, allowing a user to save a new
// derived, direct, or remote wallet.
func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest) (*pb.WalletResponse, error) {
switch req.Keymanager {
case pb.CreateWalletRequest_DIRECT:
case pb.KeymanagerKind_DIRECT:
// Needs to unmarshal the keystores from the requests.
if req.KeystoresImported == nil || len(req.KeystoresImported) < 1 {
return nil, status.Error(codes.InvalidArgument, "No keystores included for import")
@@ -69,7 +87,7 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
return &pb.WalletResponse{
WalletPath: defaultWalletPath,
}, nil
case pb.CreateWalletRequest_DERIVED:
case pb.KeymanagerKind_DERIVED:
if req.NumAccounts < 1 {
return nil, status.Error(codes.InvalidArgument, "Must create at least 1 validator account")
}
@@ -95,7 +113,7 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
return &pb.WalletResponse{
WalletPath: defaultWalletPath,
}, nil
case pb.CreateWalletRequest_REMOTE:
case pb.KeymanagerKind_REMOTE:
return nil, status.Error(codes.Unimplemented, "Remote keymanager not yet supported")
default:
return nil, status.Errorf(codes.InvalidArgument, "Keymanager type %T not yet supported", req.Keymanager)
@@ -117,9 +135,35 @@ func (s *Server) WalletConfig(ctx context.Context, _ *ptypes.Empty) (*pb.WalletR
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if wallet exists: %v", err)
}
if s.wallet == nil || s.keymanager == nil {
// If no wallet is found, we simply return an empty response.
return &pb.WalletResponse{}, nil
}
var keymanagerKind pb.KeymanagerKind
switch s.wallet.KeymanagerKind() {
case v2keymanager.Derived:
keymanagerKind = pb.KeymanagerKind_DERIVED
case v2keymanager.Direct:
keymanagerKind = pb.KeymanagerKind_DIRECT
case v2keymanager.Remote:
keymanagerKind = pb.KeymanagerKind_REMOTE
}
f, err := s.wallet.ReadKeymanagerConfigFromDisk(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not read keymanager config from disk: %v", err)
}
encoded, err := ioutil.ReadAll(f)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not parse keymanager config: %v", err)
}
var config map[string]string
if err := json.Unmarshal(encoded, &config); err != nil {
return nil, status.Errorf(codes.Internal, "Could not JSON unmarshal keymanager config: %v", err)
}
return &pb.WalletResponse{
WalletPath: defaultWalletPath,
KeymanagerConfig: nil, // Fill in by reading from disk.
KeymanagerKind: keymanagerKind,
KeymanagerConfig: config,
}, nil
}

View File

@@ -16,6 +16,7 @@ import (
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/validator/accounts/v2/wallet"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
)
@@ -29,7 +30,7 @@ func TestServer_CreateWallet_Direct(t *testing.T) {
}
req := &pb.CreateWalletRequest{
WalletPath: localWalletDir,
Keymanager: pb.CreateWalletRequest_DIRECT,
Keymanager: pb.KeymanagerKind_DIRECT,
WalletPassword: strongPass,
KeystoresPassword: strongPass,
}
@@ -75,7 +76,7 @@ func TestServer_CreateWallet_Derived(t *testing.T) {
}
req := &pb.CreateWalletRequest{
WalletPath: localWalletDir,
Keymanager: pb.CreateWalletRequest_DERIVED,
Keymanager: pb.KeymanagerKind_DERIVED,
WalletPassword: strongPass,
NumAccounts: 0,
}
@@ -110,7 +111,7 @@ func TestServer_WalletConfig(t *testing.T) {
walletInitializedFeed: new(event.Feed),
}
// We attempt to create the wallet.
_, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
w, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Direct,
@@ -119,10 +120,22 @@ func TestServer_WalletConfig(t *testing.T) {
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
s.wallet = w
s.keymanager = km
resp, err := s.WalletConfig(ctx, &ptypes.Empty{})
require.NoError(t, err)
expectedConfig := direct.DefaultKeymanagerOpts()
enc, err := json.Marshal(expectedConfig)
var jsonMap map[string]string
require.NoError(t, json.Unmarshal(enc, &jsonMap))
assert.DeepEqual(t, resp, &pb.WalletResponse{
WalletPath: localWalletDir,
WalletPath: localWalletDir,
KeymanagerKind: pb.KeymanagerKind_DIRECT,
KeymanagerConfig: jsonMap,
})
}
@@ -222,3 +235,31 @@ func TestServer_ChangePassword_DerivedKeymanager(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, w.Password(), newPassword)
}
func TestServer_HasWallet(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
ss := &Server{}
resp, err := ss.HasWallet(ctx, &ptypes.Empty{})
require.NoError(t, err)
assert.DeepEqual(t, &pb.HasWalletResponse{
WalletExists: false,
}, resp)
// We attempt to create the wallet.
_, err = v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Direct,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
resp, err = ss.HasWallet(ctx, &ptypes.Empty{})
require.NoError(t, err)
assert.DeepEqual(t, &pb.HasWalletResponse{
WalletExists: true,
}, resp)
}