Skip to content

Commit

Permalink
Include Deposit Data JSON in Wallet Create RPC Response (#7444)
Browse files Browse the repository at this point in the history
* return deposit data for hd wallet create

* test added for deposit data json

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
rauljordan and prylabs-bulldozer[bot] authored Oct 7, 2020
1 parent 29137f7 commit 23bce8d
Show file tree
Hide file tree
Showing 9 changed files with 725 additions and 341 deletions.
560 changes: 408 additions & 152 deletions proto/validator/accounts/v2/web_api.pb.go

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion proto/validator/accounts/v2/web_api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ service Wallet {
get: "/v2/validator/wallet/exists"
};
}
rpc CreateWallet(CreateWalletRequest) returns (WalletResponse) {
rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) {
option (google.api.http) = {
post: "/v2/validator/wallet/create",
body: "*"
Expand Down Expand Up @@ -130,6 +130,11 @@ message CreateWalletRequest {
string remote_ca_crt_path = 11;
}

message CreateWalletResponse {
WalletResponse wallet = 1;
DepositDataResponse accounts_created = 2;
}

message EditWalletConfigRequest {
string remote_addr = 1;
string remote_crt_path = 2;
Expand Down
337 changes: 194 additions & 143 deletions proto/validator/accounts/v2_gateway/web_api.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions validator/accounts/v2/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ go_library(
"//validator:__subpackages__",
],
deps = [
"//proto/validator/accounts/v2:go_default_library",
"//shared/bls:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/cmd:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/fileutil:go_default_library",
"//shared/params:go_default_library",
"//shared/petnames:go_default_library",
"//shared/promptutil:go_default_library",
"//validator/accounts/v2/prompt:go_default_library",
Expand Down
33 changes: 33 additions & 0 deletions validator/accounts/v2/accounts_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"strings"

"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/validator/accounts/v2/wallet"
"github.com/prysmaticlabs/prysm/validator/flags"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
Expand Down Expand Up @@ -89,3 +92,33 @@ func CreateAccount(ctx context.Context, cfg *CreateAccountConfig) error {
}
return nil
}

// DepositDataJSON creates a raw map to match the deposit_data.json file format
// from the official eth2.0-deposit-cli https://github.com/ethereum/eth2.0-deposit-cli.
// The reason we utilize this map is to ensure we match the format of
// the eth2 deposit cli, which utilizes snake case and hex strings to represent binary data.
// Our gRPC gateway instead uses camel case and base64, which is why we use this workaround.
func DepositDataJSON(depositData *ethpb.Deposit_Data) (map[string]string, error) {
depositMessage := &pb.DepositMessage{
Pubkey: depositData.PublicKey,
WithdrawalCredentials: depositData.WithdrawalCredentials,
Amount: depositData.Amount,
}
depositMessageRoot, err := depositMessage.HashTreeRoot()
if err != nil {
return nil, err
}
depositDataRoot, err := depositData.HashTreeRoot()
if err != nil {
return nil, err
}
data := make(map[string]string)
data["pubkey"] = fmt.Sprintf("%x", depositData.PublicKey)
data["withdrawal_credentials"] = fmt.Sprintf("%x", depositData.WithdrawalCredentials)
data["amount"] = fmt.Sprintf("%d", depositData.Amount)
data["signature"] = fmt.Sprintf("%x", depositData.Signature)
data["deposit_message_root"] = fmt.Sprintf("%x", depositMessageRoot)
data["deposit_data_root"] = fmt.Sprintf("%x", depositDataRoot)
data["fork_version"] = fmt.Sprintf("%x", params.BeaconConfig().GenesisForkVersion)
return data, nil
}
32 changes: 32 additions & 0 deletions validator/accounts/v2/accounts_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package v2

import (
"context"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"testing"

ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/promptutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
Expand Down Expand Up @@ -140,3 +144,31 @@ func Test_KeysConsistency_Direct(t *testing.T) {
require.NoError(t, err)
assert.LogsContain(t, logHook, "Successfully created new validator account")
}

func TestDepositDataJSON(t *testing.T) {
// Use a real deposit data JSON fixture generated by the eth2 deposit cli
fixture := make(map[string]string)
fixture["pubkey"] = "a611f309b4a24853e0b04bd70e35fbac887e099b9f81c2fac2bb2cde9f6f58bd37d947be552ec515b1f45d406f61de27"
fixture["withdrawal_credentials"] = "003561705197f621bfaa59add59ee066e6f2fe356201d00c610ed5d6cd7fcb83"
fixture["amount"] = "32000000000"
fixture["signature"] = "b0a27f2e7684fc1aa6403e2e76dcbcf29568ba02e9076e61b4c926bccec25ec636a1fdc8d08457cf23a1715ea9ee4fe20b030820e2fcf6dee07a3ce5e6ec65a824027f4cb01c143db74b34f5ca54f7e011d84fe89ce55b0e75f39003e2c9afe9"
fixture["deposit_message_root"] = "12c267fdc80fb07b47770f8fcf5e25ed2280df391d7de224cc6486e925b7d7f9"
fixture["deposit_data_root"] = "3b3c62bcff04d0249209c79a76cea98520932609986c11cb4ff62a4f54b76548"
fixture["fork_version"] = fmt.Sprintf("%x", params.BeaconConfig().GenesisForkVersion)

pubKey, err := hex.DecodeString(fixture["pubkey"])
require.NoError(t, err)
credentials, err := hex.DecodeString(fixture["withdrawal_credentials"])
require.NoError(t, err)
sig, err := hex.DecodeString(fixture["signature"])
require.NoError(t, err)
depositData := &ethpb.Deposit_Data{
PublicKey: pubKey,
WithdrawalCredentials: credentials,
Amount: 32000000000,
Signature: sig,
}
got, err := DepositDataJSON(depositData)
require.NoError(t, err)
assert.DeepEqual(t, fixture, got)
}
36 changes: 21 additions & 15 deletions validator/accounts/v2/wallet_recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/promptutil"
"github.com/prysmaticlabs/prysm/validator/accounts/v2/prompt"
"github.com/prysmaticlabs/prysm/validator/accounts/v2/wallet"
Expand Down Expand Up @@ -57,7 +58,7 @@ func RecoverWalletCli(cliCtx *cli.Context) error {
if err != nil {
return errors.Wrap(err, "could not get number of accounts to recover")
}
w, err := RecoverWallet(cliCtx.Context, &RecoverWalletConfig{
w, _, err := RecoverWallet(cliCtx.Context, &RecoverWalletConfig{
WalletDir: walletDir,
WalletPassword: walletPassword,
Mnemonic: mnemonic,
Expand All @@ -78,14 +79,14 @@ func RecoverWalletCli(cliCtx *cli.Context) error {
}

// RecoverWallet uses a menmonic seed phrase to recover a wallet into the path provided.
func RecoverWallet(ctx context.Context, cfg *RecoverWalletConfig) (*wallet.Wallet, error) {
func RecoverWallet(ctx context.Context, cfg *RecoverWalletConfig) (*wallet.Wallet, []*ethpb.Deposit_Data, error) {
// Ensure that the wallet directory does not contain a wallet already
dirExists, err := wallet.Exists(cfg.WalletDir)
if err != nil {
return nil, err
return nil, nil, err
}
if dirExists {
return nil, errors.New("a wallet already exists at this location. Please input an" +
return nil, nil, errors.New("a wallet already exists at this location. Please input an" +
" alternative location for the new wallet or remove the current wallet")
}
w := wallet.New(&wallet.Config{
Expand All @@ -95,41 +96,46 @@ func RecoverWallet(ctx context.Context, cfg *RecoverWalletConfig) (*wallet.Walle
})
keymanagerConfig, err := derived.MarshalOptionsFile(ctx, derived.DefaultKeymanagerOpts())
if err != nil {
return nil, errors.Wrap(err, "could not marshal keymanager config file")
return nil, nil, errors.Wrap(err, "could not marshal keymanager config file")
}
if err := w.SaveWallet(); err != nil {
return nil, errors.Wrap(err, "could not save wallet to disk")
return nil, nil, errors.Wrap(err, "could not save wallet to disk")
}
if err := w.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
return nil, errors.Wrap(err, "could not write keymanager config to disk")
return nil, nil, errors.Wrap(err, "could not write keymanager config to disk")
}
km, err := derived.KeymanagerForPhrase(ctx, &derived.SetupConfig{
Opts: derived.DefaultKeymanagerOpts(),
Wallet: w,
Mnemonic: cfg.Mnemonic,
})
if err != nil {
return nil, errors.Wrap(err, "could not make keymanager for given phrase")
return nil, nil, errors.Wrap(err, "could not make keymanager for given phrase")
}
if err := km.WriteEncryptedSeedToWallet(ctx, cfg.Mnemonic); err != nil {
return nil, err
return nil, nil, err
}
depositDataList := make([]*ethpb.Deposit_Data, cfg.NumAccounts)
if cfg.NumAccounts == 1 {
if _, _, err := km.CreateAccount(ctx); err != nil {
return nil, errors.Wrap(err, "could not create account in wallet")
_, depositData, err := km.CreateAccount(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "could not create account in wallet")
}
return w, nil
depositDataList[0] = depositData
return w, nil, nil
}
for i := int64(0); i < cfg.NumAccounts; i++ {
if _, _, err := km.CreateAccount(ctx); err != nil {
return nil, errors.Wrap(err, "could not create account in wallet")
_, depositData, err := km.CreateAccount(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "could not create account in wallet")
}
depositDataList[i] = depositData
}
log.WithField("wallet-path", w.AccountsDir()).Infof(
"Successfully recovered HD wallet with %d accounts. Please use accounts-v2 list to view details for your accounts",
cfg.NumAccounts,
)
return w, nil
return w, depositDataList, nil
}

func inputMnemonic(cliCtx *cli.Context) (string, error) {
Expand Down
27 changes: 3 additions & 24 deletions validator/rpc/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/pagination"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/petnames"
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
Expand Down Expand Up @@ -218,34 +217,14 @@ func (s *Server) DeleteAccounts(

func createAccountWithDepositData(ctx context.Context, km accountCreator) (*pb.DepositDataResponse_DepositData, error) {
// Create a new validator account using the specified keymanager.
pubKey, depositData, err := km.CreateAccount(ctx)
_, depositData, err := km.CreateAccount(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not create account in wallet")
}
depositMessage := &pb.DepositMessage{
Pubkey: pubKey,
WithdrawalCredentials: depositData.WithdrawalCredentials,
Amount: depositData.Amount,
}
depositMessageRoot, err := depositMessage.HashTreeRoot()
data, err := v2.DepositDataJSON(depositData)
if err != nil {
return nil, err
}
depositDataRoot, err := depositData.HashTreeRoot()
if err != nil {
return nil, err
return nil, errors.Wrap(err, "could not create deposit data JSON")
}
// The reason we utilize this map is to ensure we match the format of
// the eth2 deposit cli, which utilizes snake case and hex strings to represent binary data.
// Our gRPC gateway instead uses camel case and base64, which is why we use this workaround.
data := make(map[string]string)
data["pubkey"] = fmt.Sprintf("%x", pubKey)
data["withdrawal_credentials"] = fmt.Sprintf("%x", depositData.WithdrawalCredentials)
data["amount"] = fmt.Sprintf("%d", depositData.Amount)
data["signature"] = fmt.Sprintf("%x", depositData.Signature)
data["deposit_message_root"] = fmt.Sprintf("%x", depositMessageRoot)
data["deposit_data_root"] = fmt.Sprintf("%x", depositDataRoot)
data["fork_version"] = fmt.Sprintf("%x", params.BeaconConfig().GenesisForkVersion)
return &pb.DepositDataResponse_DepositData{
Data: data,
}, nil
Expand Down
32 changes: 26 additions & 6 deletions validator/rpc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (s *Server) HasWallet(ctx context.Context, _ *ptypes.Empty) (*pb.HasWalletR

// 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) {
func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest) (*pb.CreateWalletResponse, error) {
// Currently defaultWalletPath is used as the wallet directory and req's WalletPath is ignored for simplicity
exists, err := wallet.Exists(defaultWalletPath)
if err != nil {
Expand Down Expand Up @@ -99,8 +99,11 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
}); err != nil {
return nil, err
}
return &pb.WalletResponse{
WalletPath: defaultWalletPath,
return &pb.CreateWalletResponse{
Wallet: &pb.WalletResponse{
WalletPath: defaultWalletPath,
KeymanagerKind: pb.KeymanagerKind_DIRECT,
},
}, nil
case pb.KeymanagerKind_DERIVED:
if req.NumAccounts < 1 {
Expand All @@ -109,7 +112,7 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
if req.Mnemonic == "" {
return nil, status.Error(codes.InvalidArgument, "Must include mnemonic in request")
}
_, err := v2.RecoverWallet(ctx, &v2.RecoverWalletConfig{
_, depositData, err := v2.RecoverWallet(ctx, &v2.RecoverWalletConfig{
WalletDir: defaultWalletPath,
WalletPassword: req.WalletPassword,
Mnemonic: req.Mnemonic,
Expand All @@ -125,8 +128,25 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
}); err != nil {
return nil, err
}
return &pb.WalletResponse{
WalletPath: defaultWalletPath,

depositDataList := make([]*pb.DepositDataResponse_DepositData, len(depositData))
for i, item := range depositData {
data, err := v2.DepositDataJSON(item)
if err != nil {
return nil, err
}
depositDataList[i] = &pb.DepositDataResponse_DepositData{
Data: data,
}
}
return &pb.CreateWalletResponse{
Wallet: &pb.WalletResponse{
WalletPath: defaultWalletPath,
KeymanagerKind: pb.KeymanagerKind_DERIVED,
},
AccountsCreated: &pb.DepositDataResponse{
DepositDataList: depositDataList,
},
}, nil
case pb.KeymanagerKind_REMOTE:
return nil, status.Error(codes.Unimplemented, "Remote keymanager not yet supported")
Expand Down

0 comments on commit 23bce8d

Please sign in to comment.