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:
Raul Jordan
2020-08-24 14:19:21 -05:00
committed by GitHub
parent 880298d844
commit c9c4cd9f87
8 changed files with 634 additions and 0 deletions

View File

@@ -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",

View 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
}

View File

@@ -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
},
},
},
}

View File

@@ -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"
)

View File

@@ -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.

View File

@@ -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",

View 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),
)
}

View 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, &ethpb.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)
}
}
}