github.com/prysmaticlabs/prysm@v1.4.4/endtoend/components/validator.go (about)

     1  package components
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math/big"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"strings"
    13  
    14  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    15  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    16  	"github.com/ethereum/go-ethereum/accounts/keystore"
    17  	"github.com/ethereum/go-ethereum/ethclient"
    18  	"github.com/ethereum/go-ethereum/rpc"
    19  	"github.com/pkg/errors"
    20  	"github.com/prysmaticlabs/prysm/cmd/validator/flags"
    21  	contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
    22  	"github.com/prysmaticlabs/prysm/endtoend/helpers"
    23  	e2e "github.com/prysmaticlabs/prysm/endtoend/params"
    24  	e2etypes "github.com/prysmaticlabs/prysm/endtoend/types"
    25  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    26  	cmdshared "github.com/prysmaticlabs/prysm/shared/cmd"
    27  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    28  	"github.com/prysmaticlabs/prysm/shared/params"
    29  	"github.com/prysmaticlabs/prysm/shared/testutil"
    30  )
    31  
    32  const depositGasLimit = 4000000
    33  
    34  var _ e2etypes.ComponentRunner = (*ValidatorNode)(nil)
    35  var _ e2etypes.ComponentRunner = (*ValidatorNodeSet)(nil)
    36  
    37  // ValidatorNodeSet represents set of validator nodes.
    38  type ValidatorNodeSet struct {
    39  	e2etypes.ComponentRunner
    40  	config  *e2etypes.E2EConfig
    41  	started chan struct{}
    42  }
    43  
    44  // NewValidatorNodeSet creates and returns a set of validator nodes.
    45  func NewValidatorNodeSet(config *e2etypes.E2EConfig) *ValidatorNodeSet {
    46  	return &ValidatorNodeSet{
    47  		config:  config,
    48  		started: make(chan struct{}, 1),
    49  	}
    50  }
    51  
    52  // Start starts the configured amount of validators, also sending and mining their deposits.
    53  func (s *ValidatorNodeSet) Start(ctx context.Context) error {
    54  	// Always using genesis count since using anything else would be difficult to test for.
    55  	validatorNum := int(params.BeaconConfig().MinGenesisActiveValidatorCount)
    56  	beaconNodeNum := e2e.TestParams.BeaconNodeCount
    57  	if validatorNum%beaconNodeNum != 0 {
    58  		return errors.New("validator count is not easily divisible by beacon node count")
    59  	}
    60  	validatorsPerNode := validatorNum / beaconNodeNum
    61  
    62  	// Create validator nodes.
    63  	nodes := make([]e2etypes.ComponentRunner, beaconNodeNum)
    64  	for i := 0; i < beaconNodeNum; i++ {
    65  		nodes[i] = NewValidatorNode(s.config, validatorsPerNode, i, validatorsPerNode*i)
    66  	}
    67  
    68  	// Wait for all nodes to finish their job (blocking).
    69  	// Once nodes are ready passed in handler function will be called.
    70  	return helpers.WaitOnNodes(ctx, nodes, func() {
    71  		// All nodes stated, close channel, so that all services waiting on a set, can proceed.
    72  		close(s.started)
    73  	})
    74  }
    75  
    76  // Started checks whether validator node set is started and all nodes are ready to be queried.
    77  func (s *ValidatorNodeSet) Started() <-chan struct{} {
    78  	return s.started
    79  }
    80  
    81  // ValidatorNode represents a validator node.
    82  type ValidatorNode struct {
    83  	e2etypes.ComponentRunner
    84  	config       *e2etypes.E2EConfig
    85  	started      chan struct{}
    86  	validatorNum int
    87  	index        int
    88  	offset       int
    89  }
    90  
    91  // NewValidatorNode creates and returns a validator node.
    92  func NewValidatorNode(config *e2etypes.E2EConfig, validatorNum, index, offset int) *ValidatorNode {
    93  	return &ValidatorNode{
    94  		config:       config,
    95  		validatorNum: validatorNum,
    96  		index:        index,
    97  		offset:       offset,
    98  		started:      make(chan struct{}, 1),
    99  	}
   100  }
   101  
   102  // Start starts a validator client.
   103  func (v *ValidatorNode) Start(ctx context.Context) error {
   104  	var pkg, target string
   105  	if v.config.UsePrysmShValidator {
   106  		pkg = ""
   107  		target = "prysm_sh"
   108  	} else {
   109  		pkg = "cmd/validator"
   110  		target = "validator"
   111  	}
   112  	binaryPath, found := bazel.FindBinary(pkg, target)
   113  	if !found {
   114  		return errors.New("validator binary not found")
   115  	}
   116  
   117  	config, validatorNum, index, offset := v.config, v.validatorNum, v.index, v.offset
   118  	beaconRPCPort := e2e.TestParams.BeaconNodeRPCPort + index
   119  	if beaconRPCPort >= e2e.TestParams.BeaconNodeRPCPort+e2e.TestParams.BeaconNodeCount {
   120  		// Point any extra validator clients to a node we know is running.
   121  		beaconRPCPort = e2e.TestParams.BeaconNodeRPCPort
   122  	}
   123  
   124  	file, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, fmt.Sprintf(e2e.ValidatorLogFileName, index))
   125  	if err != nil {
   126  		return err
   127  	}
   128  	gFile, err := helpers.GraffitiYamlFile(e2e.TestParams.TestPath)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	args := []string{
   133  		fmt.Sprintf("--%s=%s/eth2-val-%d", cmdshared.DataDirFlag.Name, e2e.TestParams.TestPath, index),
   134  		fmt.Sprintf("--%s=%s", cmdshared.LogFileName.Name, file.Name()),
   135  		fmt.Sprintf("--%s=%s", flags.GraffitiFileFlag.Name, gFile),
   136  		fmt.Sprintf("--%s=%d", flags.InteropNumValidators.Name, validatorNum),
   137  		fmt.Sprintf("--%s=%d", flags.InteropStartIndex.Name, offset),
   138  		fmt.Sprintf("--%s=%d", flags.MonitoringPortFlag.Name, e2e.TestParams.ValidatorMetricsPort+index),
   139  		fmt.Sprintf("--%s=%d", flags.GRPCGatewayPort.Name, e2e.TestParams.ValidatorGatewayPort+index),
   140  		fmt.Sprintf("--%s=localhost:%d", flags.BeaconRPCProviderFlag.Name, beaconRPCPort),
   141  		fmt.Sprintf("--%s=%s", flags.GrpcHeadersFlag.Name, "dummy=value,foo=bar"), // Sending random headers shouldn't break anything.
   142  		fmt.Sprintf("--%s=%s", cmdshared.VerbosityFlag.Name, "debug"),
   143  		"--" + cmdshared.ForceClearDB.Name,
   144  		"--" + cmdshared.E2EConfigFlag.Name,
   145  		"--" + cmdshared.AcceptTosFlag.Name,
   146  	}
   147  	// Only apply e2e flags to the current branch. New flags may not exist in previous release.
   148  	if !v.config.UsePrysmShValidator {
   149  		args = append(args, featureconfig.E2EValidatorFlags...)
   150  	}
   151  	args = append(args, config.ValidatorFlags...)
   152  
   153  	if v.config.UsePrysmShValidator {
   154  		args = append([]string{"validator"}, args...)
   155  		log.Warning("Using latest release validator via prysm.sh")
   156  	}
   157  
   158  	cmd := exec.CommandContext(ctx, binaryPath, args...)
   159  
   160  	// Write stdout and stderr to log files.
   161  	stdout, err := os.Create(path.Join(e2e.TestParams.LogPath, fmt.Sprintf("validator_%d_stdout.log", index)))
   162  	if err != nil {
   163  		return err
   164  	}
   165  	stderr, err := os.Create(path.Join(e2e.TestParams.LogPath, fmt.Sprintf("validator_%d_stderr.log", index)))
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer func() {
   170  		if err := stdout.Close(); err != nil {
   171  			log.WithError(err).Error("Failed to close stdout file")
   172  		}
   173  		if err := stderr.Close(); err != nil {
   174  			log.WithError(err).Error("Failed to close stderr file")
   175  		}
   176  	}()
   177  	cmd.Stdout = stdout
   178  	cmd.Stderr = stderr
   179  
   180  	log.Infof("Starting validator client %d with flags: %s %s", index, binaryPath, strings.Join(args, " "))
   181  	if err = cmd.Start(); err != nil {
   182  		return err
   183  	}
   184  
   185  	// Mark node as ready.
   186  	close(v.started)
   187  
   188  	return cmd.Wait()
   189  }
   190  
   191  // Started checks whether validator node is started and ready to be queried.
   192  func (v *ValidatorNode) Started() <-chan struct{} {
   193  	return v.started
   194  }
   195  
   196  // SendAndMineDeposits sends the requested amount of deposits and mines the chain after to ensure the deposits are seen.
   197  func SendAndMineDeposits(keystorePath string, validatorNum, offset int, partial bool) error {
   198  	client, err := rpc.DialHTTP(fmt.Sprintf("http://127.0.0.1:%d", e2e.TestParams.Eth1RPCPort))
   199  	if err != nil {
   200  		return err
   201  	}
   202  	defer client.Close()
   203  	web3 := ethclient.NewClient(client)
   204  
   205  	keystoreBytes, err := ioutil.ReadFile(keystorePath)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	if err = sendDeposits(web3, keystoreBytes, validatorNum, offset, partial); err != nil {
   210  		return err
   211  	}
   212  	mineKey, err := keystore.DecryptKey(keystoreBytes, "" /*password*/)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	if err = mineBlocks(web3, mineKey, params.BeaconConfig().Eth1FollowDistance); err != nil {
   217  		return fmt.Errorf("failed to mine blocks %w", err)
   218  	}
   219  	return nil
   220  }
   221  
   222  // sendDeposits uses the passed in web3 and keystore bytes to send the requested deposits.
   223  func sendDeposits(web3 *ethclient.Client, keystoreBytes []byte, num, offset int, partial bool) error {
   224  	txOps, err := bind.NewTransactorWithChainID(bytes.NewReader(keystoreBytes), "" /*password*/, big.NewInt(1337))
   225  	if err != nil {
   226  		return err
   227  	}
   228  	txOps.GasLimit = depositGasLimit
   229  	txOps.Context = context.Background()
   230  	nonce, err := web3.PendingNonceAt(context.Background(), txOps.From)
   231  	if err != nil {
   232  		return err
   233  	}
   234  	txOps.Nonce = big.NewInt(int64(nonce))
   235  
   236  	contract, err := contracts.NewDepositContract(e2e.TestParams.ContractAddress, web3)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	balances := make([]uint64, num+offset)
   242  	for i := 0; i < len(balances); i++ {
   243  		if i < len(balances)/2 && partial {
   244  			balances[i] = params.BeaconConfig().MaxEffectiveBalance / 2
   245  		} else {
   246  			balances[i] = params.BeaconConfig().MaxEffectiveBalance
   247  		}
   248  	}
   249  	deposits, trie, err := testutil.DepositsWithBalance(balances)
   250  	if err != nil {
   251  		return err
   252  	}
   253  	allDeposits := deposits
   254  	allRoots := trie.Items()
   255  	allBalances := balances
   256  	if partial {
   257  		deposits2, trie2, err := testutil.DepositsWithBalance(balances)
   258  		if err != nil {
   259  			return err
   260  		}
   261  		allDeposits = append(deposits, deposits2[:len(balances)/2]...)
   262  		allRoots = append(trie.Items(), trie2.Items()[:len(balances)/2]...)
   263  		allBalances = append(balances, balances[:len(balances)/2]...)
   264  	}
   265  	for index, dd := range allDeposits {
   266  		if index < offset {
   267  			continue
   268  		}
   269  		depositInGwei := big.NewInt(int64(allBalances[index]))
   270  		txOps.Value = depositInGwei.Mul(depositInGwei, big.NewInt(int64(params.BeaconConfig().GweiPerEth)))
   271  		_, err = contract.Deposit(txOps, dd.Data.PublicKey, dd.Data.WithdrawalCredentials, dd.Data.Signature, bytesutil.ToBytes32(allRoots[index]))
   272  		if err != nil {
   273  			return errors.Wrap(err, "unable to send transaction to contract")
   274  		}
   275  		txOps.Nonce = txOps.Nonce.Add(txOps.Nonce, big.NewInt(1))
   276  	}
   277  	return nil
   278  }