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

     1  package components
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math/big"
    10  	"os"
    11  	"os/exec"
    12  	"path"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    17  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    18  	"github.com/ethereum/go-ethereum/accounts/keystore"
    19  	"github.com/ethereum/go-ethereum/core/types"
    20  	"github.com/ethereum/go-ethereum/ethclient"
    21  	"github.com/ethereum/go-ethereum/rpc"
    22  	contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
    23  	"github.com/prysmaticlabs/prysm/endtoend/helpers"
    24  	e2e "github.com/prysmaticlabs/prysm/endtoend/params"
    25  	e2etypes "github.com/prysmaticlabs/prysm/endtoend/types"
    26  	"github.com/prysmaticlabs/prysm/shared/params"
    27  )
    28  
    29  const timeGapPerTX = 100 * time.Millisecond
    30  const timeGapPerMiningTX = 250 * time.Millisecond
    31  
    32  var _ e2etypes.ComponentRunner = (*Eth1Node)(nil)
    33  
    34  // Eth1Node represents ETH1 node.
    35  type Eth1Node struct {
    36  	e2etypes.ComponentRunner
    37  	started      chan struct{}
    38  	keystorePath string
    39  }
    40  
    41  // NewEth1Node creates and returns ETH1 node.
    42  func NewEth1Node() *Eth1Node {
    43  	return &Eth1Node{
    44  		started: make(chan struct{}, 1),
    45  	}
    46  }
    47  
    48  // KeystorePath exposes node's keystore path.
    49  func (node *Eth1Node) KeystorePath() string {
    50  	return node.keystorePath
    51  }
    52  
    53  // Start starts an ETH1 local dev chain and deploys a deposit contract.
    54  func (node *Eth1Node) Start(ctx context.Context) error {
    55  	binaryPath, found := bazel.FindBinary("cmd/geth", "geth")
    56  	if !found {
    57  		return errors.New("go-ethereum binary not found")
    58  	}
    59  
    60  	eth1Path := path.Join(e2e.TestParams.TestPath, "eth1data/")
    61  	// Clear out ETH1 to prevent issues.
    62  	if _, err := os.Stat(eth1Path); !os.IsNotExist(err) {
    63  		if err = os.RemoveAll(eth1Path); err != nil {
    64  			return err
    65  		}
    66  	}
    67  
    68  	args := []string{
    69  		fmt.Sprintf("--datadir=%s", eth1Path),
    70  		fmt.Sprintf("--rpcport=%d", e2e.TestParams.Eth1RPCPort),
    71  		fmt.Sprintf("--ws.port=%d", e2e.TestParams.Eth1RPCPort+1),
    72  		"--rpc",
    73  		"--rpcaddr=127.0.0.1",
    74  		"--rpccorsdomain=\"*\"",
    75  		"--rpcvhosts=\"*\"",
    76  		"--rpc.allow-unprotected-txs",
    77  		"--ws",
    78  		"--ws.addr=127.0.0.1",
    79  		"--ws.origins=\"*\"",
    80  		"--dev",
    81  		"--dev.period=2",
    82  		"--ipcdisable",
    83  	}
    84  	cmd := exec.CommandContext(ctx, binaryPath, args...)
    85  	file, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, "eth1.log")
    86  	if err != nil {
    87  		return err
    88  	}
    89  	cmd.Stdout = file
    90  	cmd.Stderr = file
    91  	if err = cmd.Start(); err != nil {
    92  		return fmt.Errorf("failed to start eth1 chain: %w", err)
    93  	}
    94  
    95  	if err = helpers.WaitForTextInFile(file, "Commit new mining work"); err != nil {
    96  		return fmt.Errorf("mining log not found, this means the eth1 chain had issues starting: %w", err)
    97  	}
    98  
    99  	// Connect to the started geth dev chain.
   100  	client, err := rpc.DialHTTP(fmt.Sprintf("http://127.0.0.1:%d", e2e.TestParams.Eth1RPCPort))
   101  	if err != nil {
   102  		return fmt.Errorf("failed to connect to ipc: %w", err)
   103  	}
   104  	web3 := ethclient.NewClient(client)
   105  
   106  	// Access the dev account keystore to deploy the contract.
   107  	fileName, err := exec.Command("ls", path.Join(eth1Path, "keystore")).Output()
   108  	if err != nil {
   109  		return err
   110  	}
   111  	keystorePath := path.Join(eth1Path, fmt.Sprintf("keystore/%s", strings.TrimSpace(string(fileName))))
   112  	jsonBytes, err := ioutil.ReadFile(keystorePath)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	store, err := keystore.DecryptKey(jsonBytes, "" /*password*/)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	// Advancing the blocks eth1follow distance to prevent issues reading the chain.
   122  	if err = mineBlocks(web3, store, params.BeaconConfig().Eth1FollowDistance); err != nil {
   123  		return fmt.Errorf("unable to advance chain: %w", err)
   124  	}
   125  
   126  	txOpts, err := bind.NewTransactorWithChainID(bytes.NewReader(jsonBytes), "" /*password*/, big.NewInt(1337))
   127  	if err != nil {
   128  		return err
   129  	}
   130  	nonce, err := web3.PendingNonceAt(context.Background(), store.Address)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	txOpts.Nonce = big.NewInt(int64(nonce))
   135  	txOpts.Context = context.Background()
   136  	contractAddr, tx, _, err := contracts.DeployDepositContract(txOpts, web3, txOpts.From)
   137  	if err != nil {
   138  		return fmt.Errorf("failed to deploy deposit contract: %w", err)
   139  	}
   140  	e2e.TestParams.ContractAddress = contractAddr
   141  
   142  	// Wait for contract to mine.
   143  	for pending := true; pending; _, pending, err = web3.TransactionByHash(context.Background(), tx.Hash()) {
   144  		if err != nil {
   145  			return err
   146  		}
   147  		time.Sleep(timeGapPerTX)
   148  	}
   149  
   150  	// Advancing the blocks another eth1follow distance to prevent issues reading the chain.
   151  	if err = mineBlocks(web3, store, params.BeaconConfig().Eth1FollowDistance); err != nil {
   152  		return fmt.Errorf("unable to advance chain: %w", err)
   153  	}
   154  
   155  	// Save keystore path (used for saving and mining deposits).
   156  	node.keystorePath = keystorePath
   157  
   158  	// Mark node as ready.
   159  	close(node.started)
   160  
   161  	return cmd.Wait()
   162  }
   163  
   164  // Started checks whether ETH1 node is started and ready to be queried.
   165  func (node *Eth1Node) Started() <-chan struct{} {
   166  	return node.started
   167  }
   168  
   169  func mineBlocks(web3 *ethclient.Client, keystore *keystore.Key, blocksToMake uint64) error {
   170  	nonce, err := web3.PendingNonceAt(context.Background(), keystore.Address)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	chainID, err := web3.NetworkID(context.Background())
   175  	if err != nil {
   176  		return err
   177  	}
   178  	block, err := web3.BlockByNumber(context.Background(), nil)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	finishBlock := block.NumberU64() + blocksToMake
   183  
   184  	for block.NumberU64() <= finishBlock {
   185  		spamTX := types.NewTransaction(nonce, keystore.Address, big.NewInt(0), 21000, big.NewInt(1e6), []byte{})
   186  		signed, err := types.SignTx(spamTX, types.NewEIP155Signer(chainID), keystore.PrivateKey)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		if err = web3.SendTransaction(context.Background(), signed); err != nil {
   191  			return err
   192  		}
   193  		nonce++
   194  		time.Sleep(timeGapPerMiningTX)
   195  		block, err = web3.BlockByNumber(context.Background(), nil)
   196  		if err != nil {
   197  			return err
   198  		}
   199  	}
   200  	return nil
   201  }