From 71ff65b188f7a0b9fc3963ac312c4372f44854a8 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 4 Jun 2021 15:32:35 +0800 Subject: [PATCH] miner/stress: add stress test for eip 1559 (#22930) * miner/stress/1559: add 1559 stress tests * miner/stress: add 1559 stress test --- miner/stress/1559/main.go | 256 +++++++++++++++++++++ miner/stress/clique/{clique.go => main.go} | 0 2 files changed, 256 insertions(+) create mode 100644 miner/stress/1559/main.go rename miner/stress/clique/{clique.go => main.go} (100%) diff --git a/miner/stress/1559/main.go b/miner/stress/1559/main.go new file mode 100644 index 000000000..b93cef8a1 --- /dev/null +++ b/miner/stress/1559/main.go @@ -0,0 +1,256 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains a miner stress test for eip 1559. +package main + +import ( + "crypto/ecdsa" + "io/ioutil" + "math/big" + "math/rand" + "os" + "path/filepath" + "time" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/fdlimit" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" +) + +var ( + londonBlock = big.NewInt(30) // Predefined london fork block for activating eip 1559. +) + +func main() { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + fdlimit.Raise(2048) + + // Generate a batch of accounts to seal and fund with + faucets := make([]*ecdsa.PrivateKey, 128) + for i := 0; i < len(faucets); i++ { + faucets[i], _ = crypto.GenerateKey() + } + // Pre-generate the ethash mining DAG so we don't race + ethash.MakeDataset(1, filepath.Join(os.Getenv("HOME"), ".ethash")) + + // Create an Ethash network based off of the Ropsten config + genesis := makeGenesis(faucets) + + var ( + nodes []*eth.Ethereum + enodes []*enode.Node + ) + for i := 0; i < 4; i++ { + // Start the node and wait until it's up + stack, ethBackend, err := makeMiner(genesis) + if err != nil { + panic(err) + } + defer stack.Close() + + for stack.Server().NodeInfo().Ports.Listener == 0 { + time.Sleep(250 * time.Millisecond) + } + // Connect the node to all the previous ones + for _, n := range enodes { + stack.Server().AddPeer(n) + } + // Start tracking the node and its enode + nodes = append(nodes, ethBackend) + enodes = append(enodes, stack.Server().Self()) + + // Inject the signer key and start sealing with it + store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + if _, err := store.NewAccount(""); err != nil { + panic(err) + } + } + + // Iterate over all the nodes and start mining + time.Sleep(3 * time.Second) + for _, node := range nodes { + if err := node.StartMining(1); err != nil { + panic(err) + } + } + time.Sleep(3 * time.Second) + + // Start injecting transactions from the faucets like crazy + var ( + nonces = make([]uint64, len(faucets)) + + // The signer activates the 1559 features even before the fork, + // so the new 1559 txs can be created with this signer. + signer = types.LatestSignerForChainID(genesis.Config.ChainID) + ) + for { + // Pick a random mining node + index := rand.Intn(len(faucets)) + backend := nodes[index%len(nodes)] + + headHeader := backend.BlockChain().CurrentHeader() + baseFee := headHeader.BaseFee + + // Create a self transaction and inject into the pool. The legacy + // and 1559 transactions can all be created by random even if the + // fork is not happened. + tx := makeTransaction(nonces[index], faucets[index], signer, baseFee) + if err := backend.TxPool().AddLocal(tx); err != nil { + continue + } + nonces[index]++ + + // Wait if we're too saturated + if pend, _ := backend.TxPool().Stats(); pend > 4192 { + time.Sleep(100 * time.Millisecond) + } + + // Wait if the basefee is raised too fast + if baseFee != nil && baseFee.Cmp(new(big.Int).Mul(big.NewInt(100), big.NewInt(params.GWei))) > 0 { + time.Sleep(500 * time.Millisecond) + } + } +} + +func makeTransaction(nonce uint64, privKey *ecdsa.PrivateKey, signer types.Signer, baseFee *big.Int) *types.Transaction { + // Generate legacy transaction + if rand.Intn(2) == 0 { + tx, err := types.SignTx(types.NewTransaction(nonce, crypto.PubkeyToAddress(privKey.PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), signer, privKey) + if err != nil { + panic(err) + } + return tx + } + // Generate eip 1559 transaction + recipient := crypto.PubkeyToAddress(privKey.PublicKey) + + // Feecap and feetip are limited to 32 bytes. Offer a sightly + // larger buffer for creating both valid and invalid transactions. + var buf = make([]byte, 32+5) + rand.Read(buf) + tip := new(big.Int).SetBytes(buf) + + // If the given base fee is nil(the 1559 is still not available), + // generate a fake base fee in order to create 1559 tx forcibly. + if baseFee == nil { + baseFee = new(big.Int).SetInt64(int64(rand.Int31())) + } + // Generate the feecap, 75% valid feecap and 25% unguaranted. + var feeCap *big.Int + if rand.Intn(4) == 0 { + rand.Read(buf) + feeCap = new(big.Int).SetBytes(buf) + } else { + feeCap = new(big.Int).Add(baseFee, tip) + } + return types.MustSignNewTx(privKey, signer, &types.DynamicFeeTx{ + ChainID: signer.ChainID(), + Nonce: nonce, + Tip: tip, + FeeCap: feeCap, + Gas: 21000, + To: &recipient, + Value: big.NewInt(100), + Data: nil, + AccessList: nil, + }) +} + +// makeGenesis creates a custom Ethash genesis block based on some pre-defined +// faucet accounts. +func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { + genesis := core.DefaultRopstenGenesisBlock() + + genesis.Config = params.AllEthashProtocolChanges + genesis.Config.LondonBlock = londonBlock + genesis.Difficulty = params.MinimumDifficulty + + // Small gaslimit for easier basefee moving testing. + genesis.GasLimit = 8_000_000 + + genesis.Config.ChainID = big.NewInt(18) + genesis.Config.EIP150Hash = common.Hash{} + + genesis.Alloc = core.GenesisAlloc{} + for _, faucet := range faucets { + genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = core.GenesisAccount{ + Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), + } + } + if londonBlock.Sign() == 0 { + log.Info("Enabled the eip 1559 by default") + } else { + log.Info("Registered the london fork", "number", londonBlock) + } + return genesis +} + +func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { + // Define the basic configurations for the Ethereum node + datadir, _ := ioutil.TempDir("", "") + + config := &node.Config{ + Name: "geth", + Version: params.Version, + DataDir: datadir, + P2P: p2p.Config{ + ListenAddr: "0.0.0.0:0", + NoDiscovery: true, + MaxPeers: 25, + }, + UseLightweightKDF: true, + } + // Create the node and configure a full Ethereum node on it + stack, err := node.New(config) + if err != nil { + return nil, nil, err + } + ethBackend, err := eth.New(stack, ðconfig.Config{ + Genesis: genesis, + NetworkId: genesis.Config.ChainID.Uint64(), + SyncMode: downloader.FullSync, + DatabaseCache: 256, + DatabaseHandles: 256, + TxPool: core.DefaultTxPoolConfig, + GPO: ethconfig.Defaults.GPO, + Ethash: ethconfig.Defaults.Ethash, + Miner: miner.Config{ + GasFloor: genesis.GasLimit * 9 / 10, + GasCeil: genesis.GasLimit * 11 / 10, + GasPrice: big.NewInt(1), + Recommit: time.Second, + }, + }) + if err != nil { + return nil, nil, err + } + err = stack.Start() + return stack, ethBackend, err +} diff --git a/miner/stress/clique/clique.go b/miner/stress/clique/main.go similarity index 100% rename from miner/stress/clique/clique.go rename to miner/stress/clique/main.go