Skip to content

Commit c9c4cd9

Browse files
Create Validator Accounts V2 Send Deposit (#7080)
* add in deposit logic * create the deposit functionality * import formatting and create deposit config * proceed with retrieving user input for calling the deposit func * actually send our the deposits * better handling and comments * programmatically send all * lint * gaz * add progress bar * deposit test * better error handling * Update validator/accounts/v2/accounts_deposit.go Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com> * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit * Merge refs/heads/master into send-deposit
1 parent 880298d commit c9c4cd9

File tree

8 files changed

+634
-0
lines changed

8 files changed

+634
-0
lines changed

validator/accounts/v2/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ go_library(
77
"accounts_backup.go",
88
"accounts_create.go",
99
"accounts_delete.go",
10+
"accounts_deposit.go",
1011
"accounts_exit.go",
1112
"accounts_helper.go",
1213
"accounts_import.go",
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"time"
9+
10+
"github.com/manifoldco/promptui"
11+
"github.com/pkg/errors"
12+
"github.com/prysmaticlabs/prysm/shared/bls"
13+
"github.com/prysmaticlabs/prysm/shared/fileutil"
14+
"github.com/prysmaticlabs/prysm/shared/params"
15+
"github.com/prysmaticlabs/prysm/shared/promptutil"
16+
"github.com/prysmaticlabs/prysm/validator/flags"
17+
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
18+
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
19+
"github.com/urfave/cli/v2"
20+
)
21+
22+
// SendDeposit transaction for user specified accounts via an interactive
23+
// CLI process or via command-line flags.
24+
func SendDeposit(cliCtx *cli.Context) error {
25+
// Read the wallet from the specified path.
26+
wallet, err := OpenWallet(cliCtx)
27+
if errors.Is(err, ErrNoWalletFound) {
28+
return errors.Wrap(err, "no wallet found at path, create a new wallet with wallet-v2 create")
29+
} else if err != nil {
30+
return errors.Wrap(err, "could not open wallet")
31+
}
32+
keymanager, err := wallet.InitializeKeymanager(
33+
cliCtx,
34+
true, /* skip mnemonic confirm */
35+
)
36+
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
37+
return errors.New("wrong wallet password entered")
38+
}
39+
if err != nil {
40+
return errors.Wrap(err, "could not initialize keymanager")
41+
}
42+
switch wallet.KeymanagerKind() {
43+
case v2keymanager.Derived:
44+
km, ok := keymanager.(*derived.Keymanager)
45+
if !ok {
46+
return errors.New("could not assert keymanager interface to concrete type")
47+
}
48+
depositConfig, err := createDepositConfig(cliCtx, km)
49+
if err != nil {
50+
return errors.Wrap(err, "could not initialize deposit config")
51+
}
52+
if err := km.SendDepositTx(depositConfig); err != nil {
53+
return err
54+
}
55+
default:
56+
return errors.New("only Prysm HD wallets support sending deposits at the moment")
57+
}
58+
return nil
59+
}
60+
61+
func createDepositConfig(cliCtx *cli.Context, km *derived.Keymanager) (*derived.SendDepositConfig, error) {
62+
pubKeysBytes, err := km.FetchValidatingPublicKeys(context.Background())
63+
if err != nil {
64+
return nil, errors.Wrap(err, "could not fetch validating public keys")
65+
}
66+
pubKeys := make([]bls.PublicKey, len(pubKeysBytes))
67+
for i, pk := range pubKeysBytes {
68+
pubKeys[i], err = bls.PublicKeyFromBytes(pk[:])
69+
if err != nil {
70+
return nil, errors.Wrap(err, "could not parse BLS public key")
71+
}
72+
}
73+
// Allow the user to interactively select the accounts to backup or optionally
74+
// provide them via cli flags as a string of comma-separated, hex strings. If the user has
75+
// selected to deposit all accounts, we skip this part.
76+
if !cliCtx.IsSet(flags.DepositPublicKeysFlag.Name) {
77+
pubKeys, err = filterPublicKeysFromUserInput(
78+
cliCtx,
79+
flags.DepositPublicKeysFlag,
80+
pubKeysBytes,
81+
selectAccountsDepositPromptText,
82+
)
83+
if err != nil {
84+
return nil, errors.Wrap(err, "could not filter validating public keys for deposit")
85+
}
86+
}
87+
88+
web3Provider := cliCtx.String(flags.HTTPWeb3ProviderFlag.Name)
89+
// Enter the web3provider information.
90+
if web3Provider == "" {
91+
web3Provider, err = promptutil.DefaultAndValidatePrompt(
92+
"Enter the HTTP address of your eth1 endpoint for the Goerli testnet",
93+
cliCtx.String(flags.HTTPWeb3ProviderFlag.Name),
94+
func(input string) error {
95+
return nil
96+
},
97+
)
98+
if err != nil {
99+
return nil, errors.Wrap(err, "could not validate web3 provider endpoint")
100+
}
101+
}
102+
depositDelaySeconds := cliCtx.Int(flags.DepositDelaySecondsFlag.Name)
103+
config := &derived.SendDepositConfig{
104+
DepositContractAddress: cliCtx.String(flags.DepositContractAddressFlag.Name),
105+
DepositDelaySeconds: time.Duration(depositDelaySeconds) * time.Second,
106+
DepositPublicKeys: pubKeys,
107+
Web3Provider: web3Provider,
108+
}
109+
110+
if !cliCtx.Bool(flags.SkipDepositConfirmationFlag.Name) {
111+
confirmDepositPrompt := "You are about to send %d ETH into contract address %s for %d eth2 validator accounts. " +
112+
"Are you sure you want to do this? Enter the words 'yes I do' to continue"
113+
gweiPerEth := params.BeaconConfig().GweiPerEth
114+
ethDepositTotal := uint64(len(pubKeys)) * params.BeaconConfig().MaxEffectiveBalance / gweiPerEth
115+
if _, err := promptutil.ValidatePrompt(
116+
os.Stdin,
117+
fmt.Sprintf(confirmDepositPrompt, ethDepositTotal, config.DepositContractAddress, len(pubKeys)),
118+
func(input string) error {
119+
if input != "yes I do" {
120+
return errors.New("please enter 'yes I do' or exit")
121+
}
122+
return nil
123+
},
124+
); err != nil {
125+
return nil, errors.Wrap(err, "could not confirm deposit acknowledgement")
126+
}
127+
}
128+
129+
// If the user passes any of the specified flags, we read them and return the
130+
// config struct directly, bypassing any CLI input.
131+
hasPrivateKey := cliCtx.IsSet(flags.Eth1PrivateKeyFileFlag.Name)
132+
hasEth1Keystore := cliCtx.IsSet(flags.Eth1KeystoreUTCPathFlag.Name)
133+
if hasPrivateKey || hasEth1Keystore {
134+
if hasPrivateKey {
135+
fileBytes, err := fileutil.ReadFileAsBytes(cliCtx.String(flags.Eth1PrivateKeyFileFlag.Name))
136+
if err != nil {
137+
return nil, err
138+
}
139+
config.Eth1PrivateKey = strings.TrimRight(string(fileBytes), "\r\n")
140+
}
141+
return config, nil
142+
}
143+
144+
usePrivateKeyPrompt := "Inputting an eth1 private key hex string directly"
145+
useEth1KeystorePrompt := "Using an encrypted eth1 keystore UTC file"
146+
eth1Prompt := promptui.Select{
147+
Label: "Select how you wish to sign your eth1 transaction",
148+
Items: []string{
149+
usePrivateKeyPrompt,
150+
useEth1KeystorePrompt,
151+
},
152+
}
153+
_, selection, err := eth1Prompt.Run()
154+
if err != nil {
155+
return nil, err
156+
}
157+
// If the user wants to proceed by inputting their private key directly, ask for it securely.
158+
if selection == usePrivateKeyPrompt {
159+
eth1PrivateKeyString, err := promptutil.PasswordPrompt(
160+
"Enter the hex string value of your eth1 private key",
161+
promptutil.NotEmpty,
162+
)
163+
if err != nil {
164+
return nil, errors.Wrap(err, "could not read eth1 private key string")
165+
}
166+
config.Eth1PrivateKey = strings.TrimRight(eth1PrivateKeyString, "\r\n")
167+
} else if selection == useEth1KeystorePrompt {
168+
// Otherwise, ask the user for paths to their keystore UTC file and its password.
169+
eth1KeystoreUTCFile, err := promptutil.DefaultAndValidatePrompt(
170+
"Enter the file path for your encrypted, eth1 keystore-utc file",
171+
cliCtx.String(flags.Eth1KeystoreUTCPathFlag.Name),
172+
func(input string) error {
173+
return nil
174+
},
175+
)
176+
if err != nil {
177+
return nil, errors.Wrap(err, "could not read eth1 keystore UTC path")
178+
}
179+
eth1KeystorePasswordFile, err := inputWeakPassword(
180+
cliCtx,
181+
flags.Eth1KeystorePasswordFileFlag,
182+
"Enter the file path a .txt file containing your eth1 keystore password",
183+
)
184+
if err != nil {
185+
return nil, errors.Wrap(err, "could not read eth1 keystore password file path")
186+
}
187+
config.Eth1KeystoreUTCFile = eth1KeystoreUTCFile
188+
config.Eth1KeystorePasswordFile = eth1KeystorePasswordFile
189+
}
190+
return config, nil
191+
}

validator/accounts/v2/cmd_accounts.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,30 @@ this command outputs a deposit data string which is required to become a validat
137137
return nil
138138
},
139139
},
140+
{
141+
Name: "deposit",
142+
Description: "Submits a deposit to the eth2 deposit contract for a validator key by connecting " +
143+
"to an eth1 endpoint to submit a transaction. Requires signing the transaction with an eth1 private key",
144+
Flags: []cli.Flag{
145+
flags.WalletDirFlag,
146+
flags.WalletPasswordFileFlag,
147+
flags.HTTPWeb3ProviderFlag,
148+
flags.Eth1KeystoreUTCPathFlag,
149+
flags.Eth1KeystorePasswordFileFlag,
150+
flags.Eth1PrivateKeyFileFlag,
151+
flags.DepositDelaySecondsFlag,
152+
flags.DepositContractAddressFlag,
153+
flags.DepositPublicKeysFlag,
154+
flags.SkipDepositConfirmationFlag,
155+
flags.DepositAllAccountsFlag,
156+
},
157+
Action: func(cliCtx *cli.Context) error {
158+
featureconfig.ConfigureValidator(cliCtx)
159+
if err := SendDeposit(cliCtx); err != nil {
160+
log.Fatalf("Could not send validator deposit(s): %v", err)
161+
}
162+
return nil
163+
},
164+
},
140165
},
141166
}

validator/accounts/v2/prompt.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
walletDirPromptText = "Enter a wallet directory"
2222
selectAccountsDeletePromptText = "Select the account(s) you would like to delete"
2323
selectAccountsBackupPromptText = "Select the account(s) you wish to backup"
24+
selectAccountsDepositPromptText = "Select the validating public keys you wish to submit deposits for"
2425
selectAccountsVoluntaryExitPromptText = "Select the account(s) on which you wish to perform a voluntary exit"
2526
)
2627

validator/flags/flags.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,60 @@ var (
261261
Usage: "Kind of keymanager, either direct, derived, or remote, specified during wallet creation",
262262
Value: "",
263263
}
264+
// Eth1KeystoreUTCPathFlag defines the path to an eth1 utc keystore containing eth1 private keys.
265+
Eth1KeystoreUTCPathFlag = &cli.StringFlag{
266+
Name: "eth1-keystore-utc-path",
267+
Usage: "Path to an eth1 utc keystore containing eth1 private keys",
268+
Value: "",
269+
}
270+
// Eth1KeystorePasswordFileFlag to unlock an eth1 keystores.
271+
Eth1KeystorePasswordFileFlag = &cli.StringFlag{
272+
Name: "eth1-keystore-password-file",
273+
Value: "",
274+
Usage: "Password file for unlock account",
275+
}
276+
// HTTPWeb3ProviderFlag provides an HTTP access endpoint to an ETH 1.0 RPC.
277+
HTTPWeb3ProviderFlag = &cli.StringFlag{
278+
Name: "http-web3provider",
279+
Usage: "An eth1 web3 provider string http endpoint",
280+
Value: "https://goerli.prylabs.net",
281+
}
282+
// Eth1PrivateKeyFileFlag containing a hex string for sending deposit transactions from eth1.
283+
Eth1PrivateKeyFileFlag = &cli.StringFlag{
284+
Name: "eth1-private-key-file",
285+
Usage: "File containing a private key for sending deposit transactions from eth1",
286+
Value: "",
287+
}
288+
// DepositDelaySecondsFlag to delay sending deposit transactions by a fixed interval.
289+
DepositDelaySecondsFlag = &cli.Int64Flag{
290+
Name: "deposit-delay-seconds",
291+
Usage: "The time delay between sending the deposits to the contract (in seconds)",
292+
Value: 5,
293+
}
294+
// DepositContractAddressFlag for the validator deposit contract on eth1.
295+
DepositContractAddressFlag = &cli.StringFlag{
296+
Name: "deposit-contract",
297+
Usage: "Address of the deposit contract",
298+
Value: "0x07b39F4fDE4A38bACe212b546dAc87C58DfE3fDC", // Medalla deposit contract address.
299+
}
300+
// DepositPublicKeysFlag for validating public keys a user wishes to deposit for.
301+
DepositPublicKeysFlag = &cli.StringFlag{
302+
Name: "deposit-public-keys",
303+
Usage: "Comma-separated list of validating public key hex strings to specify which validator accounts to deposit",
304+
Value: "",
305+
}
306+
// SkipDepositConfirmationFlag skips the y/n confirmation prompt for sending a deposit to the deposit contract.
307+
SkipDepositConfirmationFlag = &cli.BoolFlag{
308+
Name: "skip-deposit-confirmation",
309+
Usage: "Skips the y/n confirmation prompt for sending a deposit to the deposit contract",
310+
Value: false,
311+
}
312+
// DepositAllAccountsFlag is an easy way for a user to send deposit transactions for all accounts in their wallet.
313+
DepositAllAccountsFlag = &cli.BoolFlag{
314+
Name: "deposit-all-accounts",
315+
Usage: "Sends a 32 ETH deposit for each of a user's validator accounts in their wallet",
316+
Value: false,
317+
}
264318
)
265319

266320
// Deprecated flags list.

validator/keymanager/v2/derived/BUILD.bazel

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go_library(
55
name = "go_default_library",
66
srcs = [
77
"backup.go",
8+
"deposit.go",
89
"derived.go",
910
"mnemonic.go",
1011
],
@@ -14,19 +15,29 @@ go_library(
1415
"//validator:__subpackages__",
1516
],
1617
deps = [
18+
"//contracts/deposit-contract:go_default_library",
1719
"//proto/validator/accounts/v2:go_default_library",
1820
"//shared/bls:go_default_library",
1921
"//shared/bytesutil:go_default_library",
2022
"//shared/depositutil:go_default_library",
2123
"//shared/fileutil:go_default_library",
24+
"//shared/params:go_default_library",
2225
"//shared/petnames:go_default_library",
2326
"//shared/promptutil:go_default_library",
2427
"//shared/rand:go_default_library",
2528
"//validator/accounts/v2/iface:go_default_library",
2629
"//validator/flags:go_default_library",
2730
"//validator/keymanager/v2:go_default_library",
31+
"@com_github_ethereum_go_ethereum//accounts/abi/bind:go_default_library",
32+
"@com_github_ethereum_go_ethereum//accounts/keystore:go_default_library",
33+
"@com_github_ethereum_go_ethereum//common:go_default_library",
34+
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
35+
"@com_github_ethereum_go_ethereum//ethclient:go_default_library",
36+
"@com_github_ethereum_go_ethereum//rpc:go_default_library",
2837
"@com_github_google_uuid//:go_default_library",
38+
"@com_github_k0kubun_go_ansi//:go_default_library",
2939
"@com_github_pkg_errors//:go_default_library",
40+
"@com_github_schollz_progressbar_v3//:go_default_library",
3041
"@com_github_sirupsen_logrus//:go_default_library",
3142
"@com_github_tyler_smith_go_bip39//:go_default_library",
3243
"@com_github_urfave_cli_v2//:go_default_library",
@@ -39,19 +50,30 @@ go_test(
3950
name = "go_default_test",
4051
srcs = [
4152
"backup_test.go",
53+
"deposit_test.go",
4254
"derived_test.go",
4355
"mnemonic_test.go",
4456
],
4557
embed = [":go_default_library"],
4658
deps = [
59+
"//contracts/deposit-contract:go_default_library",
4760
"//proto/validator/accounts/v2:go_default_library",
4861
"//shared/bls:go_default_library",
4962
"//shared/bytesutil:go_default_library",
63+
"//shared/interop:go_default_library",
64+
"//shared/params:go_default_library",
5065
"//shared/rand:go_default_library",
66+
"//shared/testutil:go_default_library",
5167
"//shared/testutil/assert:go_default_library",
5268
"//shared/testutil/require:go_default_library",
69+
"//shared/trieutil:go_default_library",
5370
"//validator/accounts/v2/testing:go_default_library",
71+
"@com_github_ethereum_go_ethereum//:go_default_library",
72+
"@com_github_ethereum_go_ethereum//common:go_default_library",
5473
"@com_github_google_uuid//:go_default_library",
74+
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
75+
"@com_github_prysmaticlabs_go_ssz//:go_default_library",
76+
"@com_github_sirupsen_logrus//:go_default_library",
5577
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
5678
"@com_github_tyler_smith_go_bip39//:go_default_library",
5779
"@com_github_wealdtech_go_eth2_util//:go_default_library",

0 commit comments

Comments
 (0)