github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/miner/stress/beacon/main.go (about)

     1  // Copyright 2021 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // This file contains a miner stress test for the eth1/2 transition
    18  package main
    19  
    20  import (
    21  	"crypto/ecdsa"
    22  	"errors"
    23  	"io/ioutil"
    24  	"math/big"
    25  	"math/rand"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"github.com/hardtosaygoodbye/go-ethereum/accounts/keystore"
    31  	"github.com/hardtosaygoodbye/go-ethereum/common"
    32  	"github.com/hardtosaygoodbye/go-ethereum/common/fdlimit"
    33  	"github.com/hardtosaygoodbye/go-ethereum/consensus/ethash"
    34  	"github.com/hardtosaygoodbye/go-ethereum/core"
    35  	"github.com/hardtosaygoodbye/go-ethereum/core/types"
    36  	"github.com/hardtosaygoodbye/go-ethereum/crypto"
    37  	"github.com/hardtosaygoodbye/go-ethereum/eth"
    38  	"github.com/hardtosaygoodbye/go-ethereum/eth/catalyst"
    39  	"github.com/hardtosaygoodbye/go-ethereum/eth/downloader"
    40  	"github.com/hardtosaygoodbye/go-ethereum/eth/ethconfig"
    41  	"github.com/hardtosaygoodbye/go-ethereum/les"
    42  	"github.com/hardtosaygoodbye/go-ethereum/log"
    43  	"github.com/hardtosaygoodbye/go-ethereum/miner"
    44  	"github.com/hardtosaygoodbye/go-ethereum/node"
    45  	"github.com/hardtosaygoodbye/go-ethereum/p2p"
    46  	"github.com/hardtosaygoodbye/go-ethereum/p2p/enode"
    47  	"github.com/hardtosaygoodbye/go-ethereum/params"
    48  )
    49  
    50  type nodetype int
    51  
    52  const (
    53  	legacyMiningNode nodetype = iota
    54  	legacyNormalNode
    55  	eth2MiningNode
    56  	eth2NormalNode
    57  	eth2LightClient
    58  )
    59  
    60  func (typ nodetype) String() string {
    61  	switch typ {
    62  	case legacyMiningNode:
    63  		return "legacyMiningNode"
    64  	case legacyNormalNode:
    65  		return "legacyNormalNode"
    66  	case eth2MiningNode:
    67  		return "eth2MiningNode"
    68  	case eth2NormalNode:
    69  		return "eth2NormalNode"
    70  	case eth2LightClient:
    71  		return "eth2LightClient"
    72  	default:
    73  		return "undefined"
    74  	}
    75  }
    76  
    77  var (
    78  	// transitionDifficulty is the target total difficulty for transition
    79  	transitionDifficulty = new(big.Int).Mul(big.NewInt(20), params.MinimumDifficulty)
    80  
    81  	// blockInterval is the time interval for creating a new eth2 block
    82  	blockInterval    = time.Second * 3
    83  	blockIntervalInt = 3
    84  
    85  	// finalizationDist is the block distance for finalizing block
    86  	finalizationDist = 10
    87  )
    88  
    89  type ethNode struct {
    90  	typ        nodetype
    91  	api        *catalyst.ConsensusAPI
    92  	ethBackend *eth.Ethereum
    93  	lesBackend *les.LightEthereum
    94  	stack      *node.Node
    95  	enode      *enode.Node
    96  }
    97  
    98  func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode {
    99  	var (
   100  		err        error
   101  		api        *catalyst.ConsensusAPI
   102  		stack      *node.Node
   103  		ethBackend *eth.Ethereum
   104  		lesBackend *les.LightEthereum
   105  	)
   106  	// Start the node and wait until it's up
   107  	if typ == eth2LightClient {
   108  		stack, lesBackend, api, err = makeLightNode(genesis)
   109  	} else {
   110  		stack, ethBackend, api, err = makeFullNode(genesis)
   111  	}
   112  	if err != nil {
   113  		panic(err)
   114  	}
   115  	for stack.Server().NodeInfo().Ports.Listener == 0 {
   116  		time.Sleep(250 * time.Millisecond)
   117  	}
   118  	// Connect the node to all the previous ones
   119  	for _, n := range enodes {
   120  		stack.Server().AddPeer(n)
   121  	}
   122  	enode := stack.Server().Self()
   123  
   124  	// Inject the signer key and start sealing with it
   125  	stack.AccountManager().AddBackend(keystore.NewPlaintextKeyStore("beacon-stress"))
   126  	store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   127  	if _, err := store.NewAccount(""); err != nil {
   128  		panic(err)
   129  	}
   130  	return &ethNode{
   131  		typ:        typ,
   132  		api:        api,
   133  		ethBackend: ethBackend,
   134  		lesBackend: lesBackend,
   135  		stack:      stack,
   136  		enode:      enode,
   137  	}
   138  }
   139  
   140  func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*catalyst.ExecutableDataV1, error) {
   141  	if n.typ != eth2MiningNode {
   142  		return nil, errors.New("invalid node type")
   143  	}
   144  	payloadAttribute := catalyst.PayloadAttributesV1{
   145  		Timestamp: uint64(time.Now().Unix()),
   146  	}
   147  	fcState := catalyst.ForkchoiceStateV1{
   148  		HeadBlockHash:      parentHash,
   149  		SafeBlockHash:      common.Hash{},
   150  		FinalizedBlockHash: common.Hash{},
   151  	}
   152  	payload, err := n.api.ForkchoiceUpdatedV1(fcState, &payloadAttribute)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	return n.api.GetPayloadV1(*payload.PayloadID)
   157  }
   158  
   159  func (n *ethNode) insertBlock(eb catalyst.ExecutableDataV1) error {
   160  	if !eth2types(n.typ) {
   161  		return errors.New("invalid node type")
   162  	}
   163  	newResp, err := n.api.ExecutePayloadV1(eb)
   164  	if err != nil {
   165  		return err
   166  	} else if newResp.Status != "VALID" {
   167  		return errors.New("failed to insert block")
   168  	}
   169  	return nil
   170  }
   171  
   172  func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed catalyst.ExecutableDataV1) error {
   173  	if !eth2types(n.typ) {
   174  		return errors.New("invalid node type")
   175  	}
   176  	if err := n.insertBlock(ed); err != nil {
   177  		return err
   178  	}
   179  	block, err := catalyst.ExecutableDataToBlock(ed)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	fcState := catalyst.ForkchoiceStateV1{
   184  		HeadBlockHash:      block.ParentHash(),
   185  		SafeBlockHash:      common.Hash{},
   186  		FinalizedBlockHash: common.Hash{},
   187  	}
   188  	if _, err := n.api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   189  		return err
   190  	}
   191  	return nil
   192  }
   193  
   194  type nodeManager struct {
   195  	genesis      *core.Genesis
   196  	genesisBlock *types.Block
   197  	nodes        []*ethNode
   198  	enodes       []*enode.Node
   199  	close        chan struct{}
   200  }
   201  
   202  func newNodeManager(genesis *core.Genesis) *nodeManager {
   203  	return &nodeManager{
   204  		close:        make(chan struct{}),
   205  		genesis:      genesis,
   206  		genesisBlock: genesis.ToBlock(nil),
   207  	}
   208  }
   209  
   210  func (mgr *nodeManager) createNode(typ nodetype) {
   211  	node := newNode(typ, mgr.genesis, mgr.enodes)
   212  	mgr.nodes = append(mgr.nodes, node)
   213  	mgr.enodes = append(mgr.enodes, node.enode)
   214  }
   215  
   216  func (mgr *nodeManager) getNodes(typ nodetype) []*ethNode {
   217  	var ret []*ethNode
   218  	for _, node := range mgr.nodes {
   219  		if node.typ == typ {
   220  			ret = append(ret, node)
   221  		}
   222  	}
   223  	return ret
   224  }
   225  
   226  func (mgr *nodeManager) startMining() {
   227  	for _, node := range append(mgr.getNodes(eth2MiningNode), mgr.getNodes(legacyMiningNode)...) {
   228  		if err := node.ethBackend.StartMining(1); err != nil {
   229  			panic(err)
   230  		}
   231  	}
   232  }
   233  
   234  func (mgr *nodeManager) shutdown() {
   235  	close(mgr.close)
   236  	for _, node := range mgr.nodes {
   237  		node.stack.Close()
   238  	}
   239  }
   240  
   241  func (mgr *nodeManager) run() {
   242  	if len(mgr.nodes) == 0 {
   243  		return
   244  	}
   245  	chain := mgr.nodes[0].ethBackend.BlockChain()
   246  	sink := make(chan core.ChainHeadEvent, 1024)
   247  	sub := chain.SubscribeChainHeadEvent(sink)
   248  	defer sub.Unsubscribe()
   249  
   250  	var (
   251  		transitioned bool
   252  		parentBlock  *types.Block
   253  		waitFinalise []*types.Block
   254  	)
   255  	timer := time.NewTimer(0)
   256  	defer timer.Stop()
   257  	<-timer.C // discard the initial tick
   258  
   259  	// Handle the by default transition.
   260  	if transitionDifficulty.Sign() == 0 {
   261  		transitioned = true
   262  		parentBlock = mgr.genesisBlock
   263  		timer.Reset(blockInterval)
   264  		log.Info("Enable the transition by default")
   265  	}
   266  
   267  	// Handle the block finalization.
   268  	checkFinalise := func() {
   269  		if parentBlock == nil {
   270  			return
   271  		}
   272  		if len(waitFinalise) == 0 {
   273  			return
   274  		}
   275  		oldest := waitFinalise[0]
   276  		if oldest.NumberU64() > parentBlock.NumberU64() {
   277  			return
   278  		}
   279  		distance := parentBlock.NumberU64() - oldest.NumberU64()
   280  		if int(distance) < finalizationDist {
   281  			return
   282  		}
   283  		nodes := mgr.getNodes(eth2MiningNode)
   284  		nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
   285  		nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
   286  		for _, node := range append(nodes) {
   287  			fcState := catalyst.ForkchoiceStateV1{
   288  				HeadBlockHash:      oldest.Hash(),
   289  				SafeBlockHash:      common.Hash{},
   290  				FinalizedBlockHash: common.Hash{},
   291  			}
   292  			node.api.ForkchoiceUpdatedV1(fcState, nil)
   293  		}
   294  		log.Info("Finalised eth2 block", "number", oldest.NumberU64(), "hash", oldest.Hash())
   295  		waitFinalise = waitFinalise[1:]
   296  	}
   297  
   298  	for {
   299  		checkFinalise()
   300  		select {
   301  		case <-mgr.close:
   302  			return
   303  
   304  		case ev := <-sink:
   305  			if transitioned {
   306  				continue
   307  			}
   308  			td := chain.GetTd(ev.Block.Hash(), ev.Block.NumberU64())
   309  			if td.Cmp(transitionDifficulty) < 0 {
   310  				continue
   311  			}
   312  			transitioned, parentBlock = true, ev.Block
   313  			timer.Reset(blockInterval)
   314  			log.Info("Transition difficulty reached", "td", td, "target", transitionDifficulty, "number", ev.Block.NumberU64(), "hash", ev.Block.Hash())
   315  
   316  		case <-timer.C:
   317  			producers := mgr.getNodes(eth2MiningNode)
   318  			if len(producers) == 0 {
   319  				continue
   320  			}
   321  			hash, timestamp := parentBlock.Hash(), parentBlock.Time()
   322  			if parentBlock.NumberU64() == 0 {
   323  				timestamp = uint64(time.Now().Unix()) - uint64(blockIntervalInt)
   324  			}
   325  			ed, err := producers[0].assembleBlock(hash, timestamp)
   326  			if err != nil {
   327  				log.Error("Failed to assemble the block", "err", err)
   328  				continue
   329  			}
   330  			block, _ := catalyst.ExecutableDataToBlock(*ed)
   331  
   332  			nodes := mgr.getNodes(eth2MiningNode)
   333  			nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
   334  			nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
   335  
   336  			for _, node := range nodes {
   337  				if err := node.insertBlockAndSetHead(parentBlock.Header(), *ed); err != nil {
   338  					log.Error("Failed to insert block", "type", node.typ, "err", err)
   339  				}
   340  			}
   341  			log.Info("Create and insert eth2 block", "number", ed.Number)
   342  			parentBlock = block
   343  			waitFinalise = append(waitFinalise, block)
   344  			timer.Reset(blockInterval)
   345  		}
   346  	}
   347  }
   348  
   349  func main() {
   350  	log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
   351  	fdlimit.Raise(2048)
   352  
   353  	// Generate a batch of accounts to seal and fund with
   354  	faucets := make([]*ecdsa.PrivateKey, 16)
   355  	for i := 0; i < len(faucets); i++ {
   356  		faucets[i], _ = crypto.GenerateKey()
   357  	}
   358  	// Pre-generate the ethash mining DAG so we don't race
   359  	ethash.MakeDataset(1, filepath.Join(os.Getenv("HOME"), ".ethash"))
   360  
   361  	// Create an Ethash network based off of the Ropsten config
   362  	genesis := makeGenesis(faucets)
   363  	manager := newNodeManager(genesis)
   364  	defer manager.shutdown()
   365  
   366  	manager.createNode(eth2NormalNode)
   367  	manager.createNode(eth2MiningNode)
   368  	manager.createNode(legacyMiningNode)
   369  	manager.createNode(legacyNormalNode)
   370  	manager.createNode(eth2LightClient)
   371  
   372  	// Iterate over all the nodes and start mining
   373  	time.Sleep(3 * time.Second)
   374  	if transitionDifficulty.Sign() != 0 {
   375  		manager.startMining()
   376  	}
   377  	go manager.run()
   378  
   379  	// Start injecting transactions from the faucets like crazy
   380  	time.Sleep(3 * time.Second)
   381  	nonces := make([]uint64, len(faucets))
   382  	for {
   383  		// Pick a random mining node
   384  		nodes := manager.getNodes(eth2MiningNode)
   385  
   386  		index := rand.Intn(len(faucets))
   387  		node := nodes[index%len(nodes)]
   388  
   389  		// Create a self transaction and inject into the pool
   390  		tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index])
   391  		if err != nil {
   392  			panic(err)
   393  		}
   394  		if err := node.ethBackend.TxPool().AddLocal(tx); err != nil {
   395  			panic(err)
   396  		}
   397  		nonces[index]++
   398  
   399  		// Wait if we're too saturated
   400  		if pend, _ := node.ethBackend.TxPool().Stats(); pend > 2048 {
   401  			time.Sleep(100 * time.Millisecond)
   402  		}
   403  	}
   404  }
   405  
   406  // makeGenesis creates a custom Ethash genesis block based on some pre-defined
   407  // faucet accounts.
   408  func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis {
   409  	genesis := core.DefaultRopstenGenesisBlock()
   410  	genesis.Difficulty = params.MinimumDifficulty
   411  	genesis.GasLimit = 25000000
   412  
   413  	genesis.Config.ChainID = big.NewInt(18)
   414  	genesis.Config.EIP150Hash = common.Hash{}
   415  	genesis.BaseFee = big.NewInt(params.InitialBaseFee)
   416  	genesis.Config.TerminalTotalDifficulty = transitionDifficulty
   417  
   418  	genesis.Alloc = core.GenesisAlloc{}
   419  	for _, faucet := range faucets {
   420  		genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = core.GenesisAccount{
   421  			Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil),
   422  		}
   423  	}
   424  	return genesis
   425  }
   426  
   427  func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *catalyst.ConsensusAPI, error) {
   428  	// Define the basic configurations for the Ethereum node
   429  	datadir, _ := ioutil.TempDir("", "")
   430  
   431  	config := &node.Config{
   432  		Name:    "geth",
   433  		Version: params.Version,
   434  		DataDir: datadir,
   435  		P2P: p2p.Config{
   436  			ListenAddr:  "0.0.0.0:0",
   437  			NoDiscovery: true,
   438  			MaxPeers:    25,
   439  		},
   440  		UseLightweightKDF: true,
   441  	}
   442  	// Create the node and configure a full Ethereum node on it
   443  	stack, err := node.New(config)
   444  	if err != nil {
   445  		return nil, nil, nil, err
   446  	}
   447  	econfig := &ethconfig.Config{
   448  		Genesis:         genesis,
   449  		NetworkId:       genesis.Config.ChainID.Uint64(),
   450  		SyncMode:        downloader.FullSync,
   451  		DatabaseCache:   256,
   452  		DatabaseHandles: 256,
   453  		TxPool:          core.DefaultTxPoolConfig,
   454  		GPO:             ethconfig.Defaults.GPO,
   455  		Ethash:          ethconfig.Defaults.Ethash,
   456  		Miner: miner.Config{
   457  			GasFloor: genesis.GasLimit * 9 / 10,
   458  			GasCeil:  genesis.GasLimit * 11 / 10,
   459  			GasPrice: big.NewInt(1),
   460  			Recommit: 10 * time.Second, // Disable the recommit
   461  		},
   462  		LightServ:        100,
   463  		LightPeers:       10,
   464  		LightNoSyncServe: true,
   465  	}
   466  	ethBackend, err := eth.New(stack, econfig)
   467  	if err != nil {
   468  		return nil, nil, nil, err
   469  	}
   470  	_, err = les.NewLesServer(stack, ethBackend, econfig)
   471  	if err != nil {
   472  		log.Crit("Failed to create the LES server", "err", err)
   473  	}
   474  	err = stack.Start()
   475  	return stack, ethBackend, catalyst.NewConsensusAPI(ethBackend, nil), err
   476  }
   477  
   478  func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *catalyst.ConsensusAPI, error) {
   479  	// Define the basic configurations for the Ethereum node
   480  	datadir, _ := ioutil.TempDir("", "")
   481  
   482  	config := &node.Config{
   483  		Name:    "geth",
   484  		Version: params.Version,
   485  		DataDir: datadir,
   486  		P2P: p2p.Config{
   487  			ListenAddr:  "0.0.0.0:0",
   488  			NoDiscovery: true,
   489  			MaxPeers:    25,
   490  		},
   491  		UseLightweightKDF: true,
   492  	}
   493  	// Create the node and configure a full Ethereum node on it
   494  	stack, err := node.New(config)
   495  	if err != nil {
   496  		return nil, nil, nil, err
   497  	}
   498  	lesBackend, err := les.New(stack, &ethconfig.Config{
   499  		Genesis:         genesis,
   500  		NetworkId:       genesis.Config.ChainID.Uint64(),
   501  		SyncMode:        downloader.LightSync,
   502  		DatabaseCache:   256,
   503  		DatabaseHandles: 256,
   504  		TxPool:          core.DefaultTxPoolConfig,
   505  		GPO:             ethconfig.Defaults.GPO,
   506  		Ethash:          ethconfig.Defaults.Ethash,
   507  		LightPeers:      10,
   508  	})
   509  	if err != nil {
   510  		return nil, nil, nil, err
   511  	}
   512  	err = stack.Start()
   513  	return stack, lesBackend, catalyst.NewConsensusAPI(nil, lesBackend), err
   514  }
   515  
   516  func eth2types(typ nodetype) bool {
   517  	if typ == eth2LightClient || typ == eth2NormalNode || typ == eth2MiningNode {
   518  		return true
   519  	}
   520  	return false
   521  }