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