github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/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/ethereum/go-ethereum/accounts/keystore"
    31  	"github.com/ethereum/go-ethereum/common"
    32  	"github.com/ethereum/go-ethereum/common/fdlimit"
    33  	"github.com/ethereum/go-ethereum/consensus/ethash"
    34  	"github.com/ethereum/go-ethereum/core"
    35  	"github.com/ethereum/go-ethereum/core/beacon"
    36  	"github.com/ethereum/go-ethereum/core/types"
    37  	"github.com/ethereum/go-ethereum/crypto"
    38  	"github.com/ethereum/go-ethereum/eth"
    39  	ethcatalyst "github.com/ethereum/go-ethereum/eth/catalyst"
    40  	"github.com/ethereum/go-ethereum/eth/downloader"
    41  	"github.com/ethereum/go-ethereum/eth/ethconfig"
    42  	"github.com/ethereum/go-ethereum/les"
    43  	lescatalyst "github.com/ethereum/go-ethereum/les/catalyst"
    44  	"github.com/ethereum/go-ethereum/log"
    45  	"github.com/ethereum/go-ethereum/miner"
    46  	"github.com/ethereum/go-ethereum/node"
    47  	"github.com/ethereum/go-ethereum/p2p"
    48  	"github.com/ethereum/go-ethereum/p2p/enode"
    49  	"github.com/ethereum/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  	blockInterval    = time.Second * 3
    85  	blockIntervalInt = 3
    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  	return n.api.GetPayloadV1(*payload.PayloadID)
   168  }
   169  
   170  func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error {
   171  	if !eth2types(n.typ) {
   172  		return errors.New("invalid node type")
   173  	}
   174  	switch n.typ {
   175  	case eth2NormalNode, eth2MiningNode:
   176  		newResp, err := n.api.NewPayloadV1(eb)
   177  		if err != nil {
   178  			return err
   179  		} else if newResp.Status != "VALID" {
   180  			return errors.New("failed to insert block")
   181  		}
   182  		return nil
   183  	case eth2LightClient:
   184  		newResp, err := n.lapi.ExecutePayloadV1(eb)
   185  		if err != nil {
   186  			return err
   187  		} else if newResp.Status != "VALID" {
   188  			return errors.New("failed to insert block")
   189  		}
   190  		return nil
   191  	default:
   192  		return errors.New("undefined node")
   193  	}
   194  }
   195  
   196  func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed beacon.ExecutableDataV1) error {
   197  	if !eth2types(n.typ) {
   198  		return errors.New("invalid node type")
   199  	}
   200  	if err := n.insertBlock(ed); err != nil {
   201  		return err
   202  	}
   203  	block, err := beacon.ExecutableDataToBlock(ed)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	fcState := beacon.ForkchoiceStateV1{
   208  		HeadBlockHash:      block.ParentHash(),
   209  		SafeBlockHash:      common.Hash{},
   210  		FinalizedBlockHash: common.Hash{},
   211  	}
   212  	switch n.typ {
   213  	case eth2NormalNode, eth2MiningNode:
   214  		if _, err := n.api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   215  			return err
   216  		}
   217  		return nil
   218  	case eth2LightClient:
   219  		if _, err := n.lapi.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   220  			return err
   221  		}
   222  		return nil
   223  	default:
   224  		return errors.New("undefined node")
   225  	}
   226  }
   227  
   228  type nodeManager struct {
   229  	genesis      *core.Genesis
   230  	genesisBlock *types.Block
   231  	nodes        []*ethNode
   232  	enodes       []*enode.Node
   233  	close        chan struct{}
   234  }
   235  
   236  func newNodeManager(genesis *core.Genesis) *nodeManager {
   237  	return &nodeManager{
   238  		close:        make(chan struct{}),
   239  		genesis:      genesis,
   240  		genesisBlock: genesis.ToBlock(nil),
   241  	}
   242  }
   243  
   244  func (mgr *nodeManager) createNode(typ nodetype) {
   245  	node := newNode(typ, mgr.genesis, mgr.enodes)
   246  	mgr.nodes = append(mgr.nodes, node)
   247  	mgr.enodes = append(mgr.enodes, node.enode)
   248  }
   249  
   250  func (mgr *nodeManager) getNodes(typ nodetype) []*ethNode {
   251  	var ret []*ethNode
   252  	for _, node := range mgr.nodes {
   253  		if node.typ == typ {
   254  			ret = append(ret, node)
   255  		}
   256  	}
   257  	return ret
   258  }
   259  
   260  func (mgr *nodeManager) startMining() {
   261  	for _, node := range append(mgr.getNodes(eth2MiningNode), mgr.getNodes(legacyMiningNode)...) {
   262  		if err := node.ethBackend.StartMining(1); err != nil {
   263  			panic(err)
   264  		}
   265  	}
   266  }
   267  
   268  func (mgr *nodeManager) shutdown() {
   269  	close(mgr.close)
   270  	for _, node := range mgr.nodes {
   271  		node.stack.Close()
   272  	}
   273  }
   274  
   275  func (mgr *nodeManager) run() {
   276  	if len(mgr.nodes) == 0 {
   277  		return
   278  	}
   279  	chain := mgr.nodes[0].ethBackend.BlockChain()
   280  	sink := make(chan core.ChainHeadEvent, 1024)
   281  	sub := chain.SubscribeChainHeadEvent(sink)
   282  	defer sub.Unsubscribe()
   283  
   284  	var (
   285  		transitioned bool
   286  		parentBlock  *types.Block
   287  		waitFinalise []*types.Block
   288  	)
   289  	timer := time.NewTimer(0)
   290  	defer timer.Stop()
   291  	<-timer.C // discard the initial tick
   292  
   293  	// Handle the by default transition.
   294  	if transitionDifficulty.Sign() == 0 {
   295  		transitioned = true
   296  		parentBlock = mgr.genesisBlock
   297  		timer.Reset(blockInterval)
   298  		log.Info("Enable the transition by default")
   299  	}
   300  
   301  	// Handle the block finalization.
   302  	checkFinalise := func() {
   303  		if parentBlock == nil {
   304  			return
   305  		}
   306  		if len(waitFinalise) == 0 {
   307  			return
   308  		}
   309  		oldest := waitFinalise[0]
   310  		if oldest.NumberU64() > parentBlock.NumberU64() {
   311  			return
   312  		}
   313  		distance := parentBlock.NumberU64() - oldest.NumberU64()
   314  		if int(distance) < finalizationDist {
   315  			return
   316  		}
   317  		nodes := mgr.getNodes(eth2MiningNode)
   318  		nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
   319  		nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
   320  		for _, node := range append(nodes) {
   321  			fcState := beacon.ForkchoiceStateV1{
   322  				HeadBlockHash:      oldest.Hash(),
   323  				SafeBlockHash:      common.Hash{},
   324  				FinalizedBlockHash: oldest.Hash(),
   325  			}
   326  			// TODO(rjl493456442) finalization doesn't work properly, FIX IT
   327  			_ = fcState
   328  			_ = node
   329  			//node.api.ForkchoiceUpdatedV1(fcState, nil)
   330  		}
   331  		log.Info("Finalised eth2 block", "number", oldest.NumberU64(), "hash", oldest.Hash())
   332  		waitFinalise = waitFinalise[1:]
   333  	}
   334  
   335  	for {
   336  		checkFinalise()
   337  		select {
   338  		case <-mgr.close:
   339  			return
   340  
   341  		case ev := <-sink:
   342  			if transitioned {
   343  				continue
   344  			}
   345  			td := chain.GetTd(ev.Block.Hash(), ev.Block.NumberU64())
   346  			if td.Cmp(transitionDifficulty) < 0 {
   347  				continue
   348  			}
   349  			transitioned, parentBlock = true, ev.Block
   350  			timer.Reset(blockInterval)
   351  			log.Info("Transition difficulty reached", "td", td, "target", transitionDifficulty, "number", ev.Block.NumberU64(), "hash", ev.Block.Hash())
   352  
   353  		case <-timer.C:
   354  			producers := mgr.getNodes(eth2MiningNode)
   355  			if len(producers) == 0 {
   356  				continue
   357  			}
   358  			hash, timestamp := parentBlock.Hash(), parentBlock.Time()
   359  			if parentBlock.NumberU64() == 0 {
   360  				timestamp = uint64(time.Now().Unix()) - uint64(blockIntervalInt)
   361  			}
   362  			ed, err := producers[0].assembleBlock(hash, timestamp)
   363  			if err != nil {
   364  				log.Error("Failed to assemble the block", "err", err)
   365  				continue
   366  			}
   367  			block, _ := beacon.ExecutableDataToBlock(*ed)
   368  
   369  			nodes := mgr.getNodes(eth2MiningNode)
   370  			nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
   371  			nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
   372  			for _, node := range nodes {
   373  				if err := node.insertBlockAndSetHead(parentBlock.Header(), *ed); err != nil {
   374  					log.Error("Failed to insert block", "type", node.typ, "err", err)
   375  				}
   376  			}
   377  			log.Info("Create and insert eth2 block", "number", ed.Number)
   378  			parentBlock = block
   379  			waitFinalise = append(waitFinalise, block)
   380  			timer.Reset(blockInterval)
   381  		}
   382  	}
   383  }
   384  
   385  func main() {
   386  	log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
   387  	fdlimit.Raise(2048)
   388  
   389  	// Generate a batch of accounts to seal and fund with
   390  	faucets := make([]*ecdsa.PrivateKey, 16)
   391  	for i := 0; i < len(faucets); i++ {
   392  		faucets[i], _ = crypto.GenerateKey()
   393  	}
   394  	// Pre-generate the ethash mining DAG so we don't race
   395  	ethash.MakeDataset(1, filepath.Join(os.Getenv("HOME"), ".ethash"))
   396  
   397  	// Create an Ethash network based off of the Ropsten config
   398  	genesis := makeGenesis(faucets)
   399  	manager := newNodeManager(genesis)
   400  	defer manager.shutdown()
   401  
   402  	manager.createNode(eth2NormalNode)
   403  	manager.createNode(eth2MiningNode)
   404  	manager.createNode(legacyMiningNode)
   405  	manager.createNode(legacyNormalNode)
   406  	manager.createNode(eth2LightClient)
   407  
   408  	// Iterate over all the nodes and start mining
   409  	time.Sleep(3 * time.Second)
   410  	if transitionDifficulty.Sign() != 0 {
   411  		manager.startMining()
   412  	}
   413  	go manager.run()
   414  
   415  	// Start injecting transactions from the faucets like crazy
   416  	time.Sleep(3 * time.Second)
   417  	nonces := make([]uint64, len(faucets))
   418  	for {
   419  		// Pick a random mining node
   420  		nodes := manager.getNodes(eth2MiningNode)
   421  
   422  		index := rand.Intn(len(faucets))
   423  		node := nodes[index%len(nodes)]
   424  
   425  		// Create a self transaction and inject into the pool
   426  		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])
   427  		if err != nil {
   428  			panic(err)
   429  		}
   430  		if err := node.ethBackend.TxPool().AddLocal(tx); err != nil {
   431  			panic(err)
   432  		}
   433  		nonces[index]++
   434  
   435  		// Wait if we're too saturated
   436  		if pend, _ := node.ethBackend.TxPool().Stats(); pend > 2048 {
   437  			time.Sleep(100 * time.Millisecond)
   438  		}
   439  	}
   440  }
   441  
   442  // makeGenesis creates a custom Ethash genesis block based on some pre-defined
   443  // faucet accounts.
   444  func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis {
   445  	genesis := core.DefaultRopstenGenesisBlock()
   446  	genesis.Difficulty = params.MinimumDifficulty
   447  	genesis.GasLimit = 25000000
   448  
   449  	genesis.BaseFee = big.NewInt(params.InitialBaseFee)
   450  	genesis.Config = params.AllEthashProtocolChanges
   451  	genesis.Config.TerminalTotalDifficulty = transitionDifficulty
   452  
   453  	genesis.Alloc = core.GenesisAlloc{}
   454  	for _, faucet := range faucets {
   455  		genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = core.GenesisAccount{
   456  			Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil),
   457  		}
   458  	}
   459  	return genesis
   460  }
   461  
   462  func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *ethcatalyst.ConsensusAPI, error) {
   463  	// Define the basic configurations for the Ethereum node
   464  	datadir, _ := ioutil.TempDir("", "")
   465  
   466  	config := &node.Config{
   467  		Name:    "geth",
   468  		Version: params.Version,
   469  		DataDir: datadir,
   470  		P2P: p2p.Config{
   471  			ListenAddr:  "0.0.0.0:0",
   472  			NoDiscovery: true,
   473  			MaxPeers:    25,
   474  		},
   475  		UseLightweightKDF: true,
   476  	}
   477  	// Create the node and configure a full Ethereum node on it
   478  	stack, err := node.New(config)
   479  	if err != nil {
   480  		return nil, nil, nil, err
   481  	}
   482  	econfig := &ethconfig.Config{
   483  		Genesis:         genesis,
   484  		NetworkId:       genesis.Config.ChainID.Uint64(),
   485  		SyncMode:        downloader.FullSync,
   486  		DatabaseCache:   256,
   487  		DatabaseHandles: 256,
   488  		TxPool:          core.DefaultTxPoolConfig,
   489  		GPO:             ethconfig.Defaults.GPO,
   490  		Ethash:          ethconfig.Defaults.Ethash,
   491  		Miner: miner.Config{
   492  			GasFloor: genesis.GasLimit * 9 / 10,
   493  			GasCeil:  genesis.GasLimit * 11 / 10,
   494  			GasPrice: big.NewInt(1),
   495  			Recommit: 10 * time.Second, // Disable the recommit
   496  		},
   497  		LightServ:        100,
   498  		LightPeers:       10,
   499  		LightNoSyncServe: true,
   500  	}
   501  	ethBackend, err := eth.New(stack, econfig)
   502  	if err != nil {
   503  		return nil, nil, nil, err
   504  	}
   505  	_, err = les.NewLesServer(stack, ethBackend, econfig)
   506  	if err != nil {
   507  		log.Crit("Failed to create the LES server", "err", err)
   508  	}
   509  	err = stack.Start()
   510  	return stack, ethBackend, ethcatalyst.NewConsensusAPI(ethBackend), err
   511  }
   512  
   513  func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *lescatalyst.ConsensusAPI, error) {
   514  	// Define the basic configurations for the Ethereum node
   515  	datadir, _ := ioutil.TempDir("", "")
   516  
   517  	config := &node.Config{
   518  		Name:    "geth",
   519  		Version: params.Version,
   520  		DataDir: datadir,
   521  		P2P: p2p.Config{
   522  			ListenAddr:  "0.0.0.0:0",
   523  			NoDiscovery: true,
   524  			MaxPeers:    25,
   525  		},
   526  		UseLightweightKDF: true,
   527  	}
   528  	// Create the node and configure a full Ethereum node on it
   529  	stack, err := node.New(config)
   530  	if err != nil {
   531  		return nil, nil, nil, err
   532  	}
   533  	lesBackend, err := les.New(stack, &ethconfig.Config{
   534  		Genesis:         genesis,
   535  		NetworkId:       genesis.Config.ChainID.Uint64(),
   536  		SyncMode:        downloader.LightSync,
   537  		DatabaseCache:   256,
   538  		DatabaseHandles: 256,
   539  		TxPool:          core.DefaultTxPoolConfig,
   540  		GPO:             ethconfig.Defaults.GPO,
   541  		Ethash:          ethconfig.Defaults.Ethash,
   542  		LightPeers:      10,
   543  	})
   544  	if err != nil {
   545  		return nil, nil, nil, err
   546  	}
   547  	err = stack.Start()
   548  	return stack, lesBackend, lescatalyst.NewConsensusAPI(lesBackend), err
   549  }
   550  
   551  func eth2types(typ nodetype) bool {
   552  	if typ == eth2LightClient || typ == eth2NormalNode || typ == eth2MiningNode {
   553  		return true
   554  	}
   555  	return false
   556  }