mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-08 18:51:19 +00:00
5a66807989
* First take at updating everything to v5 * Patch gRPC gateway to use prysm v5 Fix patch * Update go ssz --------- Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
234 lines
8.7 KiB
Go
234 lines
8.7 KiB
Go
package eth1
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
"github.com/pkg/errors"
|
|
"github.com/prysmaticlabs/prysm/v5/config/params"
|
|
contracts "github.com/prysmaticlabs/prysm/v5/contracts/deposit"
|
|
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
|
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
|
e2e "github.com/prysmaticlabs/prysm/v5/testing/endtoend/params"
|
|
"github.com/prysmaticlabs/prysm/v5/testing/endtoend/types"
|
|
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var gweiPerEth = big.NewInt(int64(params.BeaconConfig().GweiPerEth))
|
|
|
|
func amtInGwei(deposit *eth.Deposit) *big.Int {
|
|
amt := big.NewInt(0).SetUint64(deposit.Data.Amount)
|
|
return amt.Mul(amt, gweiPerEth)
|
|
}
|
|
|
|
// computeDeposits uses the deterministic validator generator to generate deposits for `nvals` (number of validators).
|
|
// To control which validators should receive deposits, so that we can generate deposits at different stages of e2e,
|
|
// the `offset` parameter skips the first N validators in the deterministic list.
|
|
// In order to test the requirement that our deposit follower is able to handle multiple partial deposits,
|
|
// the `partial` flag specifies that half of the deposits should be broken up into 2 transactions.
|
|
func computeDeposits(offset, nvals int, partial bool) ([]*eth.Deposit, error) {
|
|
balances := make([]uint64, offset+nvals)
|
|
partialIndex := len(balances) // set beyond loop invariant so by default nothing gets partial
|
|
if partial {
|
|
// Validators in this range will get 2 half (MAX_EFFECTIVE_BALANCE/2) deposits.
|
|
// Upper half of the range of desired validator indices is used because these can be easily
|
|
// duplicated with a slice from this offset to the end.
|
|
partialIndex = offset + nvals/2
|
|
}
|
|
// Start setting values at `offset`. Lower elements will be discarded - setting them to zero
|
|
// ensures they don't slip through and add extra deposits.
|
|
for i := offset; i < len(balances); i++ {
|
|
if i >= partialIndex {
|
|
// these deposits will be duplicated, resulting in 2 deposits summing to MAX_EFFECTIVE_BALANCE
|
|
balances[i] = params.BeaconConfig().MaxEffectiveBalance / 2
|
|
} else {
|
|
balances[i] = params.BeaconConfig().MaxEffectiveBalance
|
|
}
|
|
}
|
|
deposits, _, err := util.DepositsWithBalance(balances)
|
|
if err != nil {
|
|
return []*eth.Deposit{}, err
|
|
}
|
|
|
|
// if partial = false, these will be a no-op (partialIndex == len(deposits)),
|
|
// otherwise it will duplicate the partial deposits.
|
|
deposits = append(deposits[offset:], deposits[partialIndex:]...)
|
|
|
|
return deposits, nil
|
|
}
|
|
|
|
type Depositor struct {
|
|
// The EmptyComponent type is embedded in the Depositor so that it satisfies the ComponentRunner interface.
|
|
// This allows other components or e2e set up code to block until its Start method has been called.
|
|
types.EmptyComponent
|
|
Key *keystore.Key
|
|
Client *ethclient.Client
|
|
ChainID *big.Int
|
|
NetworkId *big.Int
|
|
cd *contracts.DepositContract
|
|
sent *DepositHistory
|
|
}
|
|
|
|
var _ types.ComponentRunner = &Depositor{}
|
|
|
|
// History exposes the DepositHistory value for a Depositor.
|
|
func (d *Depositor) History() *DepositHistory {
|
|
if d.sent == nil {
|
|
d.sent = &DepositHistory{}
|
|
}
|
|
return d.sent
|
|
}
|
|
|
|
// DepositHistory is a type used by Depositor to keep track of batches of deposit for test assertion purposes.
|
|
type DepositHistory struct {
|
|
sync.RWMutex
|
|
byBatch map[types.DepositBatch][]*SentDeposit
|
|
deposits []*SentDeposit
|
|
}
|
|
|
|
func (h *DepositHistory) record(sd *SentDeposit) {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
h.deposits = append(h.deposits, sd)
|
|
h.updateByBatch(sd)
|
|
}
|
|
|
|
func (h *DepositHistory) updateByBatch(sd *SentDeposit) {
|
|
if h.byBatch == nil {
|
|
h.byBatch = make(map[types.DepositBatch][]*SentDeposit)
|
|
}
|
|
h.byBatch[sd.batch] = append(h.byBatch[sd.batch], sd)
|
|
}
|
|
|
|
// Balances sums, by validator, all deposit amounts that were sent as part of the given batch.
|
|
// This can be used in e2e evaluators to check that the results of deposit transactions are visible on chain.
|
|
func (h *DepositHistory) Balances(batch types.DepositBatch) map[[48]byte]uint64 {
|
|
balances := make(map[[48]byte]uint64)
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
for _, d := range h.byBatch[batch] {
|
|
k := bytesutil.ToBytes48(d.deposit.Data.PublicKey)
|
|
if _, ok := balances[k]; !ok {
|
|
balances[k] = d.deposit.Data.Amount
|
|
} else {
|
|
balances[k] += d.deposit.Data.Amount
|
|
}
|
|
}
|
|
return balances
|
|
}
|
|
|
|
// SentDeposit is the record of an individual deposit which has been successfully submitted as a transaction.
|
|
type SentDeposit struct {
|
|
root [32]byte
|
|
deposit *eth.Deposit
|
|
tx *gethtypes.Transaction
|
|
time time.Time
|
|
batch types.DepositBatch
|
|
}
|
|
|
|
// SendAndMine uses the deterministic validator generator to generate deposits for `nvals` (number of validators).
|
|
// To control which validators should receive deposits, so that we can generate deposits at different stages of e2e,
|
|
// the `offset` parameter skips the first N validators in the deterministic list.
|
|
// In order to test the requirement that our deposit follower is able to handle multiple partial deposits,
|
|
// the `partial` flag specifies that half of the deposits should be broken up into 2 transactions.
|
|
// Once the set of deposits has been generated, it submits a transaction for each deposit
|
|
// (using 2 transactions for partial deposits) and then uses WaitForBlocks (which spams the miner node with transactions
|
|
// to and from its own address) to advance the chain until it has moved forward ETH1_FOLLOW_DISTANCE blocks.
|
|
func (d *Depositor) SendAndMine(ctx context.Context, offset, nvals int, batch types.DepositBatch, partial bool) error {
|
|
balance, err := d.Client.BalanceAt(ctx, d.Key.Address, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.WithField("balance", balance.String()).WithField("account", d.Key.Address.Hex()).Info("SendAndMine balance check")
|
|
// This is the "Send" part of the function. Compute deposits for `nvals` validators,
|
|
// with half of those deposits being split over 2 transactions if the `partial` flag is true,
|
|
// and throwing away any validators before `offset`.
|
|
deposits, err := computeDeposits(offset, nvals, partial)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
txo, err := d.txops(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, dd := range deposits {
|
|
if err := d.SendDeposit(dd, txo, batch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// This is the "AndMine" part of the function. WaitForBlocks will spam transactions to/from the given key
|
|
// to advance the EL chain and until the chain has advanced the requested amount.
|
|
if err = WaitForBlocks(d.Client, d.Key, params.BeaconConfig().Eth1FollowDistance); err != nil {
|
|
return fmt.Errorf("failed to mine blocks %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SendDeposit sends a single deposit. A record of this deposit will be tracked for the life of the Depositor,
|
|
// allowing evaluators to use the deposit history to make assertions about those deposits.
|
|
func (d *Depositor) SendDeposit(dep *eth.Deposit, txo *bind.TransactOpts, batch types.DepositBatch) error {
|
|
contract, err := d.contractDepositor()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txo.Value = amtInGwei(dep)
|
|
root, err := dep.Data.HashTreeRoot()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sent := time.Now()
|
|
tx, err := contract.Deposit(txo, dep.Data.PublicKey, dep.Data.WithdrawalCredentials, dep.Data.Signature, root)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to send transaction to contract")
|
|
}
|
|
d.History().record(&SentDeposit{deposit: dep, time: sent, tx: tx, root: root, batch: batch})
|
|
txo.Nonce = txo.Nonce.Add(txo.Nonce, big.NewInt(1))
|
|
return nil
|
|
}
|
|
|
|
// txops is a little helper method that creates a TransactOpts (type encapsulating auth/meta data needed to make a tx).
|
|
func (d *Depositor) txops(ctx context.Context) (*bind.TransactOpts, error) {
|
|
txo, err := bind.NewKeyedTransactorWithChainID(d.Key.PrivateKey, d.NetworkId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
txo.Context = ctx
|
|
txo.GasLimit = e2e.DepositGasLimit
|
|
nonce, err := d.Client.PendingNonceAt(ctx, txo.From)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
txo.Nonce = big.NewInt(0).SetUint64(nonce)
|
|
return txo, nil
|
|
}
|
|
|
|
// contractDepositor is a little helper method that inits and caches a DepositContract value.
|
|
// DepositContract is a special-purpose client for calling the deposit contract.
|
|
func (d *Depositor) contractDepositor() (*contracts.DepositContract, error) {
|
|
if d.cd == nil {
|
|
addr := common.HexToAddress(params.BeaconConfig().DepositContractAddress)
|
|
contract, err := contracts.NewDepositContract(addr, d.Client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d.cd = contract
|
|
}
|
|
return d.cd, nil
|
|
}
|
|
|
|
func (d *Depositor) UnderlyingProcess() *os.Process {
|
|
return nil // No subprocess for this component.
|
|
}
|