mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Prepare Validator Client for Web Usage (#7158)
* prepare validator for web usage * wallet exists func in rpc server * only initialize wallet if feed exists and wallet also exists * enabling web via a feed * wallet creation works but needs to be able to recover from mnemonic via api * ensure logic works * Merge branch 'master' into validator-web * fix up tests * test file * wallet rpc tests * add all tests to create wallet * Merge branch 'master' into validator-web * imports * Merge branch 'validator-web' of github.com:prysmaticlabs/prysm into validator-web * Merge refs/heads/master into validator-web * Merge refs/heads/master into validator-web
This commit is contained in:
@@ -8,24 +8,32 @@ go_library(
|
||||
"health.go",
|
||||
"intercepter.go",
|
||||
"server.go",
|
||||
"wallet.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/rpc",
|
||||
visibility = ["//validator:__subpackages__"],
|
||||
deps = [
|
||||
"//proto/validator/accounts/v2:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/event:go_default_library",
|
||||
"//shared/promptutil:go_default_library",
|
||||
"//shared/rand:go_default_library",
|
||||
"//shared/roughtime:go_default_library",
|
||||
"//shared/traceutil:go_default_library",
|
||||
"//validator/accounts/v2:go_default_library",
|
||||
"//validator/client:go_default_library",
|
||||
"//validator/db:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/keymanager/v2: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",
|
||||
"@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library",
|
||||
"@com_github_grpc_ecosystem_go_grpc_middleware//tracing/opentracing:go_default_library",
|
||||
"@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@io_opencensus_go//plugin/ocgrpc:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
@@ -44,16 +52,25 @@ go_test(
|
||||
"health_test.go",
|
||||
"intercepter_test.go",
|
||||
"server_test.go",
|
||||
"wallet_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//proto/validator/accounts/v2:go_default_library",
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/event:go_default_library",
|
||||
"//shared/testutil:go_default_library",
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"//validator/accounts/v2:go_default_library",
|
||||
"//validator/client:go_default_library",
|
||||
"//validator/db/testing:go_default_library",
|
||||
"//validator/keymanager/v2: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",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
],
|
||||
|
||||
@@ -5,12 +5,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/roughtime"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/roughtime"
|
||||
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,6 +49,12 @@ func (s *Server) Signup(ctx context.Context, req *pb.AuthRequest) (*pb.AuthRespo
|
||||
if err := s.valDB.SaveHashedPasswordForAPI(ctx, hashedPassword); err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not save hashed password to database")
|
||||
}
|
||||
if err := s.initializeWallet(ctx, &v2.WalletConfig{
|
||||
WalletDir: defaultWalletPath,
|
||||
WalletPassword: req.Password,
|
||||
}); err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not initialize wallet")
|
||||
}
|
||||
return s.sendAuthResponse()
|
||||
}
|
||||
|
||||
@@ -60,6 +69,12 @@ func (s *Server) Login(ctx context.Context, req *pb.AuthRequest) (*pb.AuthRespon
|
||||
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.Password)); err != nil {
|
||||
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
|
||||
}
|
||||
if err := s.initializeWallet(ctx, &v2.WalletConfig{
|
||||
WalletDir: defaultWalletPath,
|
||||
WalletPassword: req.Password,
|
||||
}); err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not initialize wallet")
|
||||
}
|
||||
return s.sendAuthResponse()
|
||||
}
|
||||
|
||||
@@ -91,3 +106,26 @@ func (s *Server) createTokenString() (string, uint64, error) {
|
||||
}
|
||||
return tokenString, uint64(expirationTime.Unix()), nil
|
||||
}
|
||||
|
||||
// Initialize a wallet and send it over a global feed.
|
||||
func (s *Server) initializeWallet(ctx context.Context, cfg *v2.WalletConfig) error {
|
||||
// We first ensure the user has a wallet.
|
||||
if err := v2.WalletExists(cfg.WalletDir); err != nil {
|
||||
if errors.Is(err, v2.ErrNoWalletFound) {
|
||||
return v2.ErrNoWalletFound
|
||||
}
|
||||
return errors.Wrap(err, "could not check if wallet exists")
|
||||
}
|
||||
// We fire an event with the opened wallet over
|
||||
// a global feed signifying wallet initialization.
|
||||
wallet, err := v2.OpenWallet(ctx, &v2.WalletConfig{
|
||||
WalletDir: cfg.WalletDir,
|
||||
WalletPassword: cfg.WalletPassword,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not open wallet")
|
||||
}
|
||||
s.walletInitialized = true
|
||||
s.walletInitializedFeed.Send(wallet)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,14 +2,34 @@ package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/event"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
|
||||
dbtest "github.com/prysmaticlabs/prysm/validator/db/testing"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
)
|
||||
|
||||
func setupWalletDir(t testing.TB) string {
|
||||
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
require.NoError(t, err, "Could not generate random file path")
|
||||
walletDir := filepath.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "wallet")
|
||||
require.NoError(t, os.RemoveAll(walletDir), "Failed to remove directory")
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.RemoveAll(walletDir), "Failed to remove directory")
|
||||
})
|
||||
return walletDir
|
||||
}
|
||||
|
||||
func TestServer_Signup_PasswordAlreadyExists(t *testing.T) {
|
||||
valDB := dbtest.SetupDB(t, [][48]byte{})
|
||||
ctx := context.Background()
|
||||
@@ -33,17 +53,32 @@ func TestServer_Signup_PasswordAlreadyExists(t *testing.T) {
|
||||
func TestServer_SignupAndLogin_RoundTrip(t *testing.T) {
|
||||
valDB := dbtest.SetupDB(t, [][48]byte{})
|
||||
ctx := context.Background()
|
||||
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
strongPass := "29384283xasjasd32%%&*@*#*"
|
||||
// We attempt to create the wallet.
|
||||
_, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
|
||||
WalletCfg: &v2.WalletConfig{
|
||||
WalletDir: defaultWalletPath,
|
||||
KeymanagerKind: v2keymanager.Direct,
|
||||
WalletPassword: strongPass,
|
||||
},
|
||||
SkipMnemonicConfirm: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ss := &Server{
|
||||
valDB: valDB,
|
||||
valDB: valDB,
|
||||
walletInitializedFeed: new(event.Feed),
|
||||
}
|
||||
weakPass := "password"
|
||||
_, err := ss.Signup(ctx, &pb.AuthRequest{
|
||||
_, err = ss.Signup(ctx, &pb.AuthRequest{
|
||||
Password: weakPass,
|
||||
})
|
||||
require.ErrorContains(t, "Could not validate password input", err)
|
||||
|
||||
// We assert we are able to signup with a strong password.
|
||||
strongPass := "29384283xasjasd32%%&*@*#*"
|
||||
_, err = ss.Signup(ctx, &pb.AuthRequest{
|
||||
Password: strongPass,
|
||||
})
|
||||
|
||||
@@ -56,11 +56,15 @@ func (g *Gateway) Start() {
|
||||
),
|
||||
)
|
||||
opts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
if err := pb.RegisterAuthHandlerFromEndpoint(ctx, gwmux, g.remoteAddr, opts); err != nil {
|
||||
log.Fatalf("Could not register API handler with grpc endpoint: %v", err)
|
||||
handlers := []func(context.Context, *gwruntime.ServeMux, string, []grpc.DialOption) error{
|
||||
pb.RegisterAuthHandlerFromEndpoint,
|
||||
pb.RegisterWalletHandlerFromEndpoint,
|
||||
pb.RegisterHealthHandlerFromEndpoint,
|
||||
}
|
||||
if err := pb.RegisterHealthHandlerFromEndpoint(ctx, gwmux, g.remoteAddr, opts); err != nil {
|
||||
log.Fatalf("Could not register API handler with grpc endpoint: %v", err)
|
||||
for _, h := range handlers {
|
||||
if err := h(ctx, gwmux, g.remoteAddr, opts); err != nil {
|
||||
log.Fatalf("Could not register API handler with grpc endpoint: %v", err)
|
||||
}
|
||||
}
|
||||
g.mux.Handle("/", g.corsMiddleware(gwmux))
|
||||
g.server = &http.Server{
|
||||
|
||||
@@ -12,6 +12,9 @@ import (
|
||||
|
||||
// ListBalances lists the validator balances.
|
||||
func (s *Server) ListBalances(ctx context.Context, req *pb.AccountRequest) (*pb.ListBalancesResponse, error) {
|
||||
if !s.walletInitialized {
|
||||
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
|
||||
}
|
||||
filteredIndices := s.filteredIndices(ctx, req)
|
||||
returnedKeys := make([][]byte, 0, len(filteredIndices))
|
||||
returnedIndices := make([]uint64, 0, len(filteredIndices))
|
||||
@@ -42,6 +45,9 @@ func (s *Server) ListBalances(ctx context.Context, req *pb.AccountRequest) (*pb.
|
||||
|
||||
// ListStatuses lists the validator current statuses.
|
||||
func (s *Server) ListStatuses(ctx context.Context, req *pb.AccountRequest) (*pb.ListStatusesResponse, error) {
|
||||
if !s.walletInitialized {
|
||||
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
|
||||
}
|
||||
filteredIndices := s.filteredIndices(ctx, req)
|
||||
returnedKeys := make([][]byte, 0, len(filteredIndices))
|
||||
returnedIndices := make([]uint64, 0, len(filteredIndices))
|
||||
@@ -72,6 +78,9 @@ func (s *Server) ListStatuses(ctx context.Context, req *pb.AccountRequest) (*pb.
|
||||
|
||||
// ListPerformance lists the validator current performances.
|
||||
func (s *Server) ListPerformance(ctx context.Context, req *pb.AccountRequest) (*pb.ListPerformanceResponse, error) {
|
||||
if !s.walletInitialized {
|
||||
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
|
||||
}
|
||||
return nil, status.Error(codes.Unimplemented, "Unimplemented")
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestServer_ListBalancesHappy(t *testing.T) {
|
||||
fv := setupFakeClient()
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{Validator: fv})
|
||||
require.NoError(t, err)
|
||||
s := &Server{validatorService: vs}
|
||||
s := &Server{validatorService: vs, walletInitialized: true}
|
||||
req := &pb.AccountRequest{
|
||||
PublicKeys: [][]byte{{'a'}, {'b'}, {'c'}},
|
||||
Indices: []uint64{4, 5, 6},
|
||||
@@ -43,7 +43,7 @@ func TestServer_ListBalancesOverlaps(t *testing.T) {
|
||||
fv := setupFakeClient()
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{Validator: fv})
|
||||
require.NoError(t, err)
|
||||
s := &Server{validatorService: vs}
|
||||
s := &Server{validatorService: vs, walletInitialized: true}
|
||||
req := &pb.AccountRequest{
|
||||
PublicKeys: [][]byte{{'a'}, {'b'}, {'c'}},
|
||||
Indices: []uint64{1, 2, 4},
|
||||
@@ -68,7 +68,7 @@ func TestServer_ListBalancesMissing(t *testing.T) {
|
||||
fv := setupFakeClient()
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{Validator: fv})
|
||||
require.NoError(t, err)
|
||||
s := &Server{validatorService: vs}
|
||||
s := &Server{validatorService: vs, walletInitialized: true}
|
||||
req := &pb.AccountRequest{
|
||||
PublicKeys: [][]byte{{'a'}, {'x'}, {'y'}},
|
||||
Indices: []uint64{1, 200, 400},
|
||||
@@ -90,7 +90,7 @@ func TestServer_ListStatusesHappy(t *testing.T) {
|
||||
fv := setupFakeClient()
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{Validator: fv})
|
||||
require.NoError(t, err)
|
||||
s := &Server{validatorService: vs}
|
||||
s := &Server{validatorService: vs, walletInitialized: true}
|
||||
req := &pb.AccountRequest{
|
||||
PublicKeys: [][]byte{{'a'}, {'b'}, {'c'}},
|
||||
Indices: []uint64{4, 5, 6},
|
||||
@@ -117,7 +117,7 @@ func TestServer_ListStatusesOverlaps(t *testing.T) {
|
||||
fv := setupFakeClient()
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{Validator: fv})
|
||||
require.NoError(t, err)
|
||||
s := &Server{validatorService: vs}
|
||||
s := &Server{validatorService: vs, walletInitialized: true}
|
||||
req := &pb.AccountRequest{
|
||||
PublicKeys: [][]byte{{'a'}, {'b'}, {'c'}},
|
||||
Indices: []uint64{1, 2, 4},
|
||||
@@ -142,7 +142,7 @@ func TestServer_ListStatusesMissing(t *testing.T) {
|
||||
fv := setupFakeClient()
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{Validator: fv})
|
||||
require.NoError(t, err)
|
||||
s := &Server{validatorService: vs}
|
||||
s := &Server{validatorService: vs, walletInitialized: true}
|
||||
req := &pb.AccountRequest{
|
||||
PublicKeys: [][]byte{{'a'}, {'x'}, {'y'}},
|
||||
Indices: []uint64{1, 200, 400},
|
||||
|
||||
@@ -15,8 +15,11 @@ import (
|
||||
// authentication from our API.
|
||||
var (
|
||||
noAuthPaths = map[string]bool{
|
||||
"/ethereum.validator.accounts.v2.Auth/Signup": true,
|
||||
"/ethereum.validator.accounts.v2.Auth/Login": true,
|
||||
"/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/GenerateMnemonic": true,
|
||||
"/ethereum.validator.accounts.v2.Wallet/CreateWallet": true,
|
||||
}
|
||||
authLock sync.RWMutex
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/event"
|
||||
"github.com/prysmaticlabs/prysm/shared/rand"
|
||||
"github.com/prysmaticlabs/prysm/shared/traceutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/client"
|
||||
@@ -29,42 +30,50 @@ func init() {
|
||||
|
||||
// Config options for the gRPC server.
|
||||
type Config struct {
|
||||
Host string
|
||||
Port string
|
||||
CertFlag string
|
||||
KeyFlag string
|
||||
ValDB db.Database
|
||||
ValidatorService *client.ValidatorService
|
||||
Host string
|
||||
Port string
|
||||
CertFlag string
|
||||
KeyFlag string
|
||||
ValDB db.Database
|
||||
ValidatorService *client.ValidatorService
|
||||
WalletInitializedFeed *event.Feed
|
||||
WalletDir string
|
||||
}
|
||||
|
||||
// Server defining a gRPC server for the remote signer API.
|
||||
type Server struct {
|
||||
valDB db.Database
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
host string
|
||||
port string
|
||||
listener net.Listener
|
||||
withCert string
|
||||
withKey string
|
||||
credentialError error
|
||||
grpcServer *grpc.Server
|
||||
jwtKey []byte
|
||||
validatorService *client.ValidatorService
|
||||
valDB db.Database
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
host string
|
||||
port string
|
||||
listener net.Listener
|
||||
withCert string
|
||||
withKey string
|
||||
credentialError error
|
||||
grpcServer *grpc.Server
|
||||
jwtKey []byte
|
||||
validatorService *client.ValidatorService
|
||||
walletDir string
|
||||
walletInitializedFeed *event.Feed
|
||||
walletInitialized bool
|
||||
}
|
||||
|
||||
// NewServer instantiates a new gRPC server.
|
||||
func NewServer(ctx context.Context, cfg *Config) *Server {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &Server{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
host: cfg.Host,
|
||||
port: cfg.Port,
|
||||
withCert: cfg.CertFlag,
|
||||
withKey: cfg.KeyFlag,
|
||||
valDB: cfg.ValDB,
|
||||
validatorService: cfg.ValidatorService,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
host: cfg.Host,
|
||||
port: cfg.Port,
|
||||
withCert: cfg.CertFlag,
|
||||
withKey: cfg.KeyFlag,
|
||||
valDB: cfg.ValDB,
|
||||
validatorService: cfg.ValidatorService,
|
||||
walletDir: cfg.WalletDir,
|
||||
walletInitializedFeed: cfg.WalletInitializedFeed,
|
||||
walletInitialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +131,7 @@ func (s *Server) Start() {
|
||||
// Register services available for the gRPC server.
|
||||
reflection.Register(s.grpcServer)
|
||||
pb.RegisterAuthServer(s.grpcServer, s)
|
||||
pb.RegisterWalletServer(s.grpcServer, s)
|
||||
pb.RegisterHealthServer(s.grpcServer, s)
|
||||
|
||||
go func() {
|
||||
|
||||
126
validator/rpc/wallet.go
Normal file
126
validator/rpc/wallet.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
|
||||
ptypes "github.com/gogo/protobuf/types"
|
||||
"github.com/pkg/errors"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/rand"
|
||||
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var defaultWalletPath = filepath.Join(flags.DefaultValidatorDir(), flags.WalletDefaultDirName)
|
||||
|
||||
// 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:
|
||||
// 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")
|
||||
}
|
||||
keystores := make([]*v2keymanager.Keystore, len(req.KeystoresImported))
|
||||
for i := 0; i < len(req.KeystoresImported); i++ {
|
||||
encoded := req.KeystoresImported[i]
|
||||
keystore := &v2keymanager.Keystore{}
|
||||
if err := json.Unmarshal([]byte(encoded), &keystore); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Not a valid EIP-2335 keystore JSON file: %v", err)
|
||||
}
|
||||
keystores[i] = keystore
|
||||
}
|
||||
wallet, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
|
||||
WalletCfg: &v2.WalletConfig{
|
||||
WalletDir: defaultWalletPath,
|
||||
KeymanagerKind: v2keymanager.Direct,
|
||||
WalletPassword: req.WalletPassword,
|
||||
},
|
||||
SkipMnemonicConfirm: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Import the uploaded accounts.
|
||||
if err := v2.ImportAccounts(ctx, &v2.ImportAccountsConfig{
|
||||
Wallet: wallet,
|
||||
Keystores: keystores,
|
||||
AccountPassword: req.KeystoresPassword,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.WalletResponse{
|
||||
WalletPath: defaultWalletPath,
|
||||
}, nil
|
||||
case pb.CreateWalletRequest_DERIVED:
|
||||
if req.NumAccounts < 1 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Must create at least 1 validator account")
|
||||
}
|
||||
if req.Mnemonic == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Must include mnemonic in request")
|
||||
}
|
||||
_, err := v2.RecoverWallet(ctx, &v2.RecoverWalletConfig{
|
||||
WalletDir: defaultWalletPath,
|
||||
WalletPassword: req.WalletPassword,
|
||||
Mnemonic: req.Mnemonic,
|
||||
NumAccounts: int64(req.NumAccounts),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.WalletResponse{
|
||||
WalletPath: defaultWalletPath,
|
||||
}, nil
|
||||
case pb.CreateWalletRequest_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)
|
||||
}
|
||||
}
|
||||
|
||||
// EditConfig allows the user to edit their wallet's keymanageropts.
|
||||
func (s *Server) EditConfig(ctx context.Context, req *pb.EditWalletConfigRequest) (*pb.WalletResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Unimplemented")
|
||||
}
|
||||
|
||||
// WalletConfig returns the wallet's configuration. If no wallet exists, we return an empty response.
|
||||
func (s *Server) WalletConfig(ctx context.Context, _ *ptypes.Empty) (*pb.WalletResponse, error) {
|
||||
err := v2.WalletExists(defaultWalletPath)
|
||||
if err != nil && errors.Is(err, v2.ErrNoWalletFound) {
|
||||
// If no wallet is found, we simply return an empty response.
|
||||
return &pb.WalletResponse{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if wallet exists: %v", err)
|
||||
}
|
||||
return &pb.WalletResponse{
|
||||
WalletPath: defaultWalletPath,
|
||||
KeymanagerConfig: nil, // Fill in by reading from disk.
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateMnemonic creates a new, random bip39 mnemonic phrase.
|
||||
func (s *Server) GenerateMnemonic(ctx context.Context, _ *ptypes.Empty) (*pb.GenerateMnemonicResponse, error) {
|
||||
mnemonicRandomness := make([]byte, 32)
|
||||
if _, err := rand.NewGenerator().Read(mnemonicRandomness); err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.FailedPrecondition,
|
||||
"Could not initialize mnemonic source of randomness: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
mnemonic, err := bip39.NewMnemonic(mnemonicRandomness)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not generate wallet seed: %v", err)
|
||||
}
|
||||
return &pb.GenerateMnemonicResponse{
|
||||
Mnemonic: mnemonic,
|
||||
}, nil
|
||||
}
|
||||
119
validator/rpc/wallet_test.go
Normal file
119
validator/rpc/wallet_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
ptypes "github.com/gogo/protobuf/types"
|
||||
"github.com/google/uuid"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"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"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestServer_CreateWallet_Direct(t *testing.T) {
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
ctx := context.Background()
|
||||
strongPass := "29384283xasjasd32%%&*@*#*"
|
||||
s := &Server{}
|
||||
req := &pb.CreateWalletRequest{
|
||||
WalletPath: localWalletDir,
|
||||
Keymanager: pb.CreateWalletRequest_DIRECT,
|
||||
WalletPassword: strongPass,
|
||||
KeystoresPassword: strongPass,
|
||||
}
|
||||
_, err := s.CreateWallet(ctx, req)
|
||||
require.ErrorContains(t, "No keystores included for import", err)
|
||||
|
||||
req.KeystoresImported = []string{"badjson"}
|
||||
_, err = s.CreateWallet(ctx, req)
|
||||
require.ErrorContains(t, "Not a valid EIP-2335 keystore", err)
|
||||
|
||||
encryptor := keystorev4.New()
|
||||
keystores := make([]string, 3)
|
||||
for i := 0; i < len(keystores); i++ {
|
||||
privKey := bls.RandKey()
|
||||
pubKey := fmt.Sprintf("%x", privKey.PublicKey().Marshal())
|
||||
id, err := uuid.NewRandom()
|
||||
require.NoError(t, err)
|
||||
cryptoFields, err := encryptor.Encrypt(privKey.Marshal(), strongPass)
|
||||
require.NoError(t, err)
|
||||
item := &v2keymanager.Keystore{
|
||||
Crypto: cryptoFields,
|
||||
ID: id.String(),
|
||||
Version: encryptor.Version(),
|
||||
Pubkey: pubKey,
|
||||
Name: encryptor.Name(),
|
||||
}
|
||||
encodedFile, err := json.MarshalIndent(item, "", "\t")
|
||||
require.NoError(t, err)
|
||||
keystores[i] = string(encodedFile)
|
||||
}
|
||||
req.KeystoresImported = keystores
|
||||
_, err = s.CreateWallet(ctx, req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_CreateWallet_Derived(t *testing.T) {
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
ctx := context.Background()
|
||||
strongPass := "29384283xasjasd32%%&*@*#*"
|
||||
s := &Server{}
|
||||
req := &pb.CreateWalletRequest{
|
||||
WalletPath: localWalletDir,
|
||||
Keymanager: pb.CreateWalletRequest_DERIVED,
|
||||
WalletPassword: strongPass,
|
||||
NumAccounts: 0,
|
||||
}
|
||||
_, err := s.CreateWallet(ctx, req)
|
||||
require.ErrorContains(t, "Must create at least 1 validator account", err)
|
||||
|
||||
req.NumAccounts = 2
|
||||
_, err = s.CreateWallet(ctx, req)
|
||||
require.ErrorContains(t, "Must include mnemonic", err)
|
||||
|
||||
mnemonicResp, err := s.GenerateMnemonic(ctx, &ptypes.Empty{})
|
||||
require.NoError(t, err)
|
||||
req.Mnemonic = mnemonicResp.Mnemonic
|
||||
|
||||
_, err = s.CreateWallet(ctx, req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_WalletConfig_NoWalletFound(t *testing.T) {
|
||||
s := &Server{}
|
||||
resp, err := s.WalletConfig(context.Background(), &ptypes.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, resp, &pb.WalletResponse{})
|
||||
}
|
||||
|
||||
func TestServer_WalletConfig(t *testing.T) {
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
ctx := context.Background()
|
||||
strongPass := "29384283xasjasd32%%&*@*#*"
|
||||
s := &Server{}
|
||||
// We attempt to create the wallet.
|
||||
_, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
|
||||
WalletCfg: &v2.WalletConfig{
|
||||
WalletDir: defaultWalletPath,
|
||||
KeymanagerKind: v2keymanager.Direct,
|
||||
WalletPassword: strongPass,
|
||||
},
|
||||
SkipMnemonicConfirm: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
resp, err := s.WalletConfig(ctx, &ptypes.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, resp, &pb.WalletResponse{
|
||||
WalletPath: localWalletDir,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user