mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
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
This commit is contained in:
@@ -7,6 +7,7 @@ go_library(
|
||||
"accounts_backup.go",
|
||||
"accounts_create.go",
|
||||
"accounts_delete.go",
|
||||
"accounts_deposit.go",
|
||||
"accounts_exit.go",
|
||||
"accounts_helper.go",
|
||||
"accounts_import.go",
|
||||
|
||||
191
validator/accounts/v2/accounts_deposit.go
Normal file
191
validator/accounts/v2/accounts_deposit.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// SendDeposit transaction for user specified accounts via an interactive
|
||||
// CLI process or via command-line flags.
|
||||
func SendDeposit(cliCtx *cli.Context) error {
|
||||
// Read the wallet from the specified path.
|
||||
wallet, err := OpenWallet(cliCtx)
|
||||
if errors.Is(err, ErrNoWalletFound) {
|
||||
return errors.Wrap(err, "no wallet found at path, create a new wallet with wallet-v2 create")
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "could not open wallet")
|
||||
}
|
||||
keymanager, err := wallet.InitializeKeymanager(
|
||||
cliCtx,
|
||||
true, /* skip mnemonic confirm */
|
||||
)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
return errors.New("wrong wallet password entered")
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize keymanager")
|
||||
}
|
||||
switch wallet.KeymanagerKind() {
|
||||
case v2keymanager.Derived:
|
||||
km, ok := keymanager.(*derived.Keymanager)
|
||||
if !ok {
|
||||
return errors.New("could not assert keymanager interface to concrete type")
|
||||
}
|
||||
depositConfig, err := createDepositConfig(cliCtx, km)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize deposit config")
|
||||
}
|
||||
if err := km.SendDepositTx(depositConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("only Prysm HD wallets support sending deposits at the moment")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDepositConfig(cliCtx *cli.Context, km *derived.Keymanager) (*derived.SendDepositConfig, error) {
|
||||
pubKeysBytes, err := km.FetchValidatingPublicKeys(context.Background())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not fetch validating public keys")
|
||||
}
|
||||
pubKeys := make([]bls.PublicKey, len(pubKeysBytes))
|
||||
for i, pk := range pubKeysBytes {
|
||||
pubKeys[i], err = bls.PublicKeyFromBytes(pk[:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse BLS public key")
|
||||
}
|
||||
}
|
||||
// Allow the user to interactively select the accounts to backup or optionally
|
||||
// provide them via cli flags as a string of comma-separated, hex strings. If the user has
|
||||
// selected to deposit all accounts, we skip this part.
|
||||
if !cliCtx.IsSet(flags.DepositPublicKeysFlag.Name) {
|
||||
pubKeys, err = filterPublicKeysFromUserInput(
|
||||
cliCtx,
|
||||
flags.DepositPublicKeysFlag,
|
||||
pubKeysBytes,
|
||||
selectAccountsDepositPromptText,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not filter validating public keys for deposit")
|
||||
}
|
||||
}
|
||||
|
||||
web3Provider := cliCtx.String(flags.HTTPWeb3ProviderFlag.Name)
|
||||
// Enter the web3provider information.
|
||||
if web3Provider == "" {
|
||||
web3Provider, err = promptutil.DefaultAndValidatePrompt(
|
||||
"Enter the HTTP address of your eth1 endpoint for the Goerli testnet",
|
||||
cliCtx.String(flags.HTTPWeb3ProviderFlag.Name),
|
||||
func(input string) error {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not validate web3 provider endpoint")
|
||||
}
|
||||
}
|
||||
depositDelaySeconds := cliCtx.Int(flags.DepositDelaySecondsFlag.Name)
|
||||
config := &derived.SendDepositConfig{
|
||||
DepositContractAddress: cliCtx.String(flags.DepositContractAddressFlag.Name),
|
||||
DepositDelaySeconds: time.Duration(depositDelaySeconds) * time.Second,
|
||||
DepositPublicKeys: pubKeys,
|
||||
Web3Provider: web3Provider,
|
||||
}
|
||||
|
||||
if !cliCtx.Bool(flags.SkipDepositConfirmationFlag.Name) {
|
||||
confirmDepositPrompt := "You are about to send %d ETH into contract address %s for %d eth2 validator accounts. " +
|
||||
"Are you sure you want to do this? Enter the words 'yes I do' to continue"
|
||||
gweiPerEth := params.BeaconConfig().GweiPerEth
|
||||
ethDepositTotal := uint64(len(pubKeys)) * params.BeaconConfig().MaxEffectiveBalance / gweiPerEth
|
||||
if _, err := promptutil.ValidatePrompt(
|
||||
os.Stdin,
|
||||
fmt.Sprintf(confirmDepositPrompt, ethDepositTotal, config.DepositContractAddress, len(pubKeys)),
|
||||
func(input string) error {
|
||||
if input != "yes I do" {
|
||||
return errors.New("please enter 'yes I do' or exit")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
return nil, errors.Wrap(err, "could not confirm deposit acknowledgement")
|
||||
}
|
||||
}
|
||||
|
||||
// If the user passes any of the specified flags, we read them and return the
|
||||
// config struct directly, bypassing any CLI input.
|
||||
hasPrivateKey := cliCtx.IsSet(flags.Eth1PrivateKeyFileFlag.Name)
|
||||
hasEth1Keystore := cliCtx.IsSet(flags.Eth1KeystoreUTCPathFlag.Name)
|
||||
if hasPrivateKey || hasEth1Keystore {
|
||||
if hasPrivateKey {
|
||||
fileBytes, err := fileutil.ReadFileAsBytes(cliCtx.String(flags.Eth1PrivateKeyFileFlag.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Eth1PrivateKey = strings.TrimRight(string(fileBytes), "\r\n")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
usePrivateKeyPrompt := "Inputting an eth1 private key hex string directly"
|
||||
useEth1KeystorePrompt := "Using an encrypted eth1 keystore UTC file"
|
||||
eth1Prompt := promptui.Select{
|
||||
Label: "Select how you wish to sign your eth1 transaction",
|
||||
Items: []string{
|
||||
usePrivateKeyPrompt,
|
||||
useEth1KeystorePrompt,
|
||||
},
|
||||
}
|
||||
_, selection, err := eth1Prompt.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If the user wants to proceed by inputting their private key directly, ask for it securely.
|
||||
if selection == usePrivateKeyPrompt {
|
||||
eth1PrivateKeyString, err := promptutil.PasswordPrompt(
|
||||
"Enter the hex string value of your eth1 private key",
|
||||
promptutil.NotEmpty,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read eth1 private key string")
|
||||
}
|
||||
config.Eth1PrivateKey = strings.TrimRight(eth1PrivateKeyString, "\r\n")
|
||||
} else if selection == useEth1KeystorePrompt {
|
||||
// Otherwise, ask the user for paths to their keystore UTC file and its password.
|
||||
eth1KeystoreUTCFile, err := promptutil.DefaultAndValidatePrompt(
|
||||
"Enter the file path for your encrypted, eth1 keystore-utc file",
|
||||
cliCtx.String(flags.Eth1KeystoreUTCPathFlag.Name),
|
||||
func(input string) error {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read eth1 keystore UTC path")
|
||||
}
|
||||
eth1KeystorePasswordFile, err := inputWeakPassword(
|
||||
cliCtx,
|
||||
flags.Eth1KeystorePasswordFileFlag,
|
||||
"Enter the file path a .txt file containing your eth1 keystore password",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read eth1 keystore password file path")
|
||||
}
|
||||
config.Eth1KeystoreUTCFile = eth1KeystoreUTCFile
|
||||
config.Eth1KeystorePasswordFile = eth1KeystorePasswordFile
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -137,5 +137,30 @@ this command outputs a deposit data string which is required to become a validat
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "deposit",
|
||||
Description: "Submits a deposit to the eth2 deposit contract for a validator key by connecting " +
|
||||
"to an eth1 endpoint to submit a transaction. Requires signing the transaction with an eth1 private key",
|
||||
Flags: []cli.Flag{
|
||||
flags.WalletDirFlag,
|
||||
flags.WalletPasswordFileFlag,
|
||||
flags.HTTPWeb3ProviderFlag,
|
||||
flags.Eth1KeystoreUTCPathFlag,
|
||||
flags.Eth1KeystorePasswordFileFlag,
|
||||
flags.Eth1PrivateKeyFileFlag,
|
||||
flags.DepositDelaySecondsFlag,
|
||||
flags.DepositContractAddressFlag,
|
||||
flags.DepositPublicKeysFlag,
|
||||
flags.SkipDepositConfirmationFlag,
|
||||
flags.DepositAllAccountsFlag,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
featureconfig.ConfigureValidator(cliCtx)
|
||||
if err := SendDeposit(cliCtx); err != nil {
|
||||
log.Fatalf("Could not send validator deposit(s): %v", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
walletDirPromptText = "Enter a wallet directory"
|
||||
selectAccountsDeletePromptText = "Select the account(s) you would like to delete"
|
||||
selectAccountsBackupPromptText = "Select the account(s) you wish to backup"
|
||||
selectAccountsDepositPromptText = "Select the validating public keys you wish to submit deposits for"
|
||||
selectAccountsVoluntaryExitPromptText = "Select the account(s) on which you wish to perform a voluntary exit"
|
||||
)
|
||||
|
||||
|
||||
@@ -261,6 +261,60 @@ var (
|
||||
Usage: "Kind of keymanager, either direct, derived, or remote, specified during wallet creation",
|
||||
Value: "",
|
||||
}
|
||||
// Eth1KeystoreUTCPathFlag defines the path to an eth1 utc keystore containing eth1 private keys.
|
||||
Eth1KeystoreUTCPathFlag = &cli.StringFlag{
|
||||
Name: "eth1-keystore-utc-path",
|
||||
Usage: "Path to an eth1 utc keystore containing eth1 private keys",
|
||||
Value: "",
|
||||
}
|
||||
// Eth1KeystorePasswordFileFlag to unlock an eth1 keystores.
|
||||
Eth1KeystorePasswordFileFlag = &cli.StringFlag{
|
||||
Name: "eth1-keystore-password-file",
|
||||
Value: "",
|
||||
Usage: "Password file for unlock account",
|
||||
}
|
||||
// HTTPWeb3ProviderFlag provides an HTTP access endpoint to an ETH 1.0 RPC.
|
||||
HTTPWeb3ProviderFlag = &cli.StringFlag{
|
||||
Name: "http-web3provider",
|
||||
Usage: "An eth1 web3 provider string http endpoint",
|
||||
Value: "https://goerli.prylabs.net",
|
||||
}
|
||||
// Eth1PrivateKeyFileFlag containing a hex string for sending deposit transactions from eth1.
|
||||
Eth1PrivateKeyFileFlag = &cli.StringFlag{
|
||||
Name: "eth1-private-key-file",
|
||||
Usage: "File containing a private key for sending deposit transactions from eth1",
|
||||
Value: "",
|
||||
}
|
||||
// DepositDelaySecondsFlag to delay sending deposit transactions by a fixed interval.
|
||||
DepositDelaySecondsFlag = &cli.Int64Flag{
|
||||
Name: "deposit-delay-seconds",
|
||||
Usage: "The time delay between sending the deposits to the contract (in seconds)",
|
||||
Value: 5,
|
||||
}
|
||||
// DepositContractAddressFlag for the validator deposit contract on eth1.
|
||||
DepositContractAddressFlag = &cli.StringFlag{
|
||||
Name: "deposit-contract",
|
||||
Usage: "Address of the deposit contract",
|
||||
Value: "0x07b39F4fDE4A38bACe212b546dAc87C58DfE3fDC", // Medalla deposit contract address.
|
||||
}
|
||||
// DepositPublicKeysFlag for validating public keys a user wishes to deposit for.
|
||||
DepositPublicKeysFlag = &cli.StringFlag{
|
||||
Name: "deposit-public-keys",
|
||||
Usage: "Comma-separated list of validating public key hex strings to specify which validator accounts to deposit",
|
||||
Value: "",
|
||||
}
|
||||
// SkipDepositConfirmationFlag skips the y/n confirmation prompt for sending a deposit to the deposit contract.
|
||||
SkipDepositConfirmationFlag = &cli.BoolFlag{
|
||||
Name: "skip-deposit-confirmation",
|
||||
Usage: "Skips the y/n confirmation prompt for sending a deposit to the deposit contract",
|
||||
Value: false,
|
||||
}
|
||||
// DepositAllAccountsFlag is an easy way for a user to send deposit transactions for all accounts in their wallet.
|
||||
DepositAllAccountsFlag = &cli.BoolFlag{
|
||||
Name: "deposit-all-accounts",
|
||||
Usage: "Sends a 32 ETH deposit for each of a user's validator accounts in their wallet",
|
||||
Value: false,
|
||||
}
|
||||
)
|
||||
|
||||
// Deprecated flags list.
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"backup.go",
|
||||
"deposit.go",
|
||||
"derived.go",
|
||||
"mnemonic.go",
|
||||
],
|
||||
@@ -14,19 +15,29 @@ go_library(
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//contracts/deposit-contract:go_default_library",
|
||||
"//proto/validator/accounts/v2:go_default_library",
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/depositutil:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//shared/promptutil:go_default_library",
|
||||
"//shared/rand:go_default_library",
|
||||
"//validator/accounts/v2/iface:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//accounts/abi/bind:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//accounts/keystore:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//ethclient:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//rpc:go_default_library",
|
||||
"@com_github_google_uuid//:go_default_library",
|
||||
"@com_github_k0kubun_go_ansi//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_schollz_progressbar_v3//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
@@ -39,19 +50,30 @@ go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"backup_test.go",
|
||||
"deposit_test.go",
|
||||
"derived_test.go",
|
||||
"mnemonic_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//contracts/deposit-contract:go_default_library",
|
||||
"//proto/validator/accounts/v2:go_default_library",
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/interop:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/rand:go_default_library",
|
||||
"//shared/testutil:go_default_library",
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"//shared/trieutil:go_default_library",
|
||||
"//validator/accounts/v2/testing:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_google_uuid//:go_default_library",
|
||||
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_ssz//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_util//:go_default_library",
|
||||
|
||||
168
validator/keymanager/v2/derived/deposit.go
Normal file
168
validator/keymanager/v2/derived/deposit.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package derived
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/k0kubun/go-ansi"
|
||||
"github.com/pkg/errors"
|
||||
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/depositutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
)
|
||||
|
||||
// SendDepositConfig contains all the required information for
|
||||
// the derived keymanager to submit a 32 ETH deposit from the user's
|
||||
// eth1 wallet to an eth1 RPC endpoint.
|
||||
type SendDepositConfig struct {
|
||||
DepositContractAddress string
|
||||
DepositDelaySeconds time.Duration
|
||||
DepositPublicKeys []bls.PublicKey
|
||||
Eth1KeystoreUTCFile string
|
||||
Eth1KeystorePasswordFile string
|
||||
Eth1PrivateKey string
|
||||
Web3Provider string
|
||||
}
|
||||
|
||||
// SendDepositTx to the validator deposit contract on the eth1 chain
|
||||
// using a defined configuration by first unlocking the user's eth1 wallet,
|
||||
// then generating the deposit data for a desired validator account, finally
|
||||
// submitting the transaction via an eth1 web3 endpoint.
|
||||
func (dr *Keymanager) SendDepositTx(conf *SendDepositConfig) error {
|
||||
var txOps *bind.TransactOpts
|
||||
rpcClient, err := rpc.Dial(conf.Web3Provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ethclient.NewClient(rpcClient)
|
||||
depositAmountInGwei := params.BeaconConfig().MaxEffectiveBalance
|
||||
|
||||
if conf.Eth1PrivateKey != "" {
|
||||
// User inputs private key, sign tx with private key
|
||||
privKey, err := crypto.HexToECDSA(conf.Eth1PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txOps = bind.NewKeyedTransactor(privKey)
|
||||
txOps.Value = new(big.Int).Mul(big.NewInt(int64(depositAmountInGwei)), big.NewInt(1e9))
|
||||
} else {
|
||||
// User inputs keystore json file, sign tx with keystore json
|
||||
password, err := fileutil.ReadFileAsBytes(conf.Eth1KeystorePasswordFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// #nosec - Inclusion of file via variable is OK for this tool.
|
||||
keyJSON, err := fileutil.ReadFileAsBytes(conf.Eth1KeystoreUTCFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKey, err := keystore.DecryptKey(keyJSON, strings.TrimRight(string(password), "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txOps = bind.NewKeyedTransactor(privKey.PrivateKey)
|
||||
txOps.Value = new(big.Int).Mul(big.NewInt(int64(depositAmountInGwei)), big.NewInt(1e9))
|
||||
txOps.GasLimit = 500000
|
||||
}
|
||||
|
||||
depositContract, err := contracts.NewDepositContract(common.HexToAddress(conf.DepositContractAddress), client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wantedPubKeys := make(map[[48]byte]bool, len(conf.DepositPublicKeys))
|
||||
for _, pk := range conf.DepositPublicKeys {
|
||||
wantedPubKeys[bytesutil.ToBytes48(pk.Marshal())] = true
|
||||
}
|
||||
bar := initializeProgressBar(int(dr.seedCfg.NextAccount), "Sending deposit transactions...")
|
||||
for i := uint64(0); i < dr.seedCfg.NextAccount; i++ {
|
||||
validatingKeyPath := fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i)
|
||||
validatingKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, validatingKeyPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read validating key for account %d", i)
|
||||
}
|
||||
if ok := wantedPubKeys[bytesutil.ToBytes48(validatingKey.PublicKey().Marshal())]; !ok {
|
||||
continue
|
||||
}
|
||||
withdrawalKeyPath := fmt.Sprintf(WithdrawalKeyDerivationPathTemplate, i)
|
||||
withdrawalKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, withdrawalKeyPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read withdrawal key for account %d", i)
|
||||
}
|
||||
validatingKeyBLS, err := bls.SecretKeyFromBytes(validatingKey.Marshal())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
withdrawalKeyBLS, err := bls.SecretKeyFromBytes(withdrawalKey.Marshal())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, depositRoot, err := depositutil.DepositInput(validatingKeyBLS, withdrawalKeyBLS, depositAmountInGwei)
|
||||
if err != nil {
|
||||
log.Errorf("Could not generate deposit input data: %v", err)
|
||||
continue
|
||||
}
|
||||
tx, err := depositContract.Deposit(
|
||||
txOps,
|
||||
data.PublicKey,
|
||||
data.WithdrawalCredentials,
|
||||
data.Signature,
|
||||
depositRoot,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to send transaction to contract: %v", err)
|
||||
continue
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"Transaction Hash": fmt.Sprintf("%#x", tx.Hash()),
|
||||
}).Infof(
|
||||
"Deposit %d sent to contract address %v for validator with a public key %#x",
|
||||
i,
|
||||
conf.DepositContractAddress,
|
||||
validatingKey.PublicKey().Marshal(),
|
||||
)
|
||||
log.Infof(
|
||||
"You can monitor your transaction on Etherscan here https://goerli.etherscan.io/tx/0x%x",
|
||||
tx.Hash(),
|
||||
)
|
||||
log.Infof("Waiting for a short delay of %v seconds...", conf.DepositDelaySeconds)
|
||||
if err := bar.Add(1); err != nil {
|
||||
log.Errorf("Could not increase progress bar percentage: %v", err)
|
||||
}
|
||||
time.Sleep(conf.DepositDelaySeconds)
|
||||
}
|
||||
log.Infof("Successfully sent all validator deposits!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeProgressBar(numItems int, msg string) *progressbar.ProgressBar {
|
||||
return progressbar.NewOptions(
|
||||
numItems,
|
||||
progressbar.OptionFullWidth(),
|
||||
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
|
||||
progressbar.OptionEnableColorCodes(true),
|
||||
progressbar.OptionSetTheme(progressbar.Theme{
|
||||
Saucer: "[green]=[reset]",
|
||||
SaucerHead: "[green]>[reset]",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "[",
|
||||
BarEnd: "]",
|
||||
}),
|
||||
progressbar.OptionOnCompletion(func() { fmt.Println() }),
|
||||
progressbar.OptionSetDescription(msg),
|
||||
)
|
||||
}
|
||||
172
validator/keymanager/v2/derived/deposit_test.go
Normal file
172
validator/keymanager/v2/derived/deposit_test.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package derived
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
|
||||
"github.com/prysmaticlabs/prysm/shared/interop"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/trieutil"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
func sendDepositsTest(
|
||||
t *testing.T,
|
||||
testAcc *contracts.TestAccount,
|
||||
numberOfDeposits, numberOfValidators uint64,
|
||||
) []*ethpb.Deposit {
|
||||
deposits := make([]*ethpb.Deposit, 0, numberOfValidators)
|
||||
depositDelay := int64(1)
|
||||
depositContractAddrStr := testAcc.ContractAddr.Hex()
|
||||
|
||||
privKeys, pubKeys, err := interop.DeterministicallyGenerateKeys(0, numberOfValidators)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to generate keys: %v", err)
|
||||
}
|
||||
|
||||
depositData, depositDataRoots, err := interop.DepositDataFromKeys(privKeys, pubKeys)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to generate deposit data from keys: %v", err)
|
||||
}
|
||||
|
||||
for i, data := range depositData {
|
||||
dataRoot := [32]byte{}
|
||||
copy(dataRoot[:], depositDataRoots[i])
|
||||
|
||||
pubKey := pubKeys[i]
|
||||
|
||||
deposits = append(deposits, ðpb.Deposit{
|
||||
Data: data,
|
||||
})
|
||||
|
||||
for j := uint64(0); j < numberOfDeposits; j++ {
|
||||
tx, err := testAcc.Contract.Deposit(testAcc.TxOpts, data.PublicKey, data.WithdrawalCredentials, data.Signature, dataRoot)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send transaction to contract: %v", err)
|
||||
}
|
||||
|
||||
testAcc.Backend.Commit()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"Transaction Hash": fmt.Sprintf("%#x", tx.Hash()),
|
||||
}).Infof("Deposit %d sent to contract address %v for validator with a public key %#x", j, depositContractAddrStr, pubKey.Marshal())
|
||||
|
||||
time.Sleep(time.Duration(depositDelay) * time.Second)
|
||||
}
|
||||
}
|
||||
return deposits
|
||||
}
|
||||
|
||||
func TestSendDepositTx(t *testing.T) {
|
||||
testutil.ResetCache()
|
||||
testAcc, err := contracts.Setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to set up simulated backend %v", err)
|
||||
}
|
||||
|
||||
testAcc.Backend.Commit()
|
||||
|
||||
testAcc.TxOpts.Value = contracts.Amount32Eth()
|
||||
testAcc.TxOpts.GasLimit = 1000000
|
||||
|
||||
numberOfValidators := uint64(2)
|
||||
numberOfDeposits := uint64(5)
|
||||
deposits := sendDepositsTest(t, testAcc, numberOfDeposits, numberOfValidators)
|
||||
|
||||
query := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{
|
||||
testAcc.ContractAddr,
|
||||
},
|
||||
}
|
||||
|
||||
logs, err := testAcc.Backend.FilterLogs(context.Background(), query)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to retrieve logs %v", err)
|
||||
}
|
||||
if len(logs) == 0 {
|
||||
t.Fatal("no logs")
|
||||
}
|
||||
|
||||
if len(logs) != int(numberOfDeposits*numberOfValidators) {
|
||||
t.Fatal("No sufficient number of logs")
|
||||
}
|
||||
|
||||
j := 0
|
||||
for i, log := range logs {
|
||||
loggedPubkey, withCreds, _, loggedSig, index, err := contracts.UnpackDepositLogData(log.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to unpack logs %v", err)
|
||||
}
|
||||
|
||||
if binary.LittleEndian.Uint64(index) != uint64(i) {
|
||||
t.Errorf("Retrieved merkle tree index is incorrect %d", index)
|
||||
}
|
||||
|
||||
if !bytes.Equal(loggedPubkey, deposits[j].Data.PublicKey) {
|
||||
t.Errorf("Pubkey is not the same as the data that was put in %v, i: %d", loggedPubkey, i)
|
||||
}
|
||||
|
||||
if !bytes.Equal(loggedSig, deposits[j].Data.Signature) {
|
||||
t.Errorf("Proof of Possession is not the same as the data that was put in %v, i: %d", loggedSig, i)
|
||||
}
|
||||
|
||||
if !bytes.Equal(withCreds, deposits[j].Data.WithdrawalCredentials) {
|
||||
t.Errorf("Withdrawal Credentials is not the same as the data that was put in %v, i: %d", withCreds, i)
|
||||
}
|
||||
|
||||
if i == int(numberOfDeposits)-1 {
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
encodedDeposits := make([][]byte, numberOfValidators*numberOfDeposits)
|
||||
for i := 0; i < int(numberOfValidators); i++ {
|
||||
hashedDeposit, err := ssz.HashTreeRoot(deposits[i].Data)
|
||||
if err != nil {
|
||||
t.Fatalf("could not tree hash deposit data: %v", err)
|
||||
}
|
||||
for j := 0; j < int(numberOfDeposits); j++ {
|
||||
encodedDeposits[i*int(numberOfDeposits)+j] = hashedDeposit[:]
|
||||
}
|
||||
}
|
||||
|
||||
depositTrie, err := trieutil.GenerateTrieFromItems(encodedDeposits, int(params.BeaconConfig().DepositContractTreeDepth))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not generate trie: %v", err)
|
||||
}
|
||||
|
||||
root := depositTrie.Root()
|
||||
|
||||
for i, encodedDeposit := range encodedDeposits {
|
||||
proof, err := depositTrie.MerkleProof(i)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not generate proof: %v", err)
|
||||
}
|
||||
if ok := trieutil.VerifyMerkleBranch(
|
||||
root[:],
|
||||
encodedDeposit,
|
||||
i,
|
||||
proof,
|
||||
); !ok {
|
||||
t.Fatalf(
|
||||
"Unable verify deposit merkle branch of deposit root for root: %#x, encodedDeposit: %#x, i : %d",
|
||||
root[:], encodedDeposit, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user