prysm-pulse/validator/keymanager/v2/derived/deposit.go

169 lines
5.7 KiB
Go
Raw Normal View History

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