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