// Copyright 2018 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 based on the Clique consensus engine. package main import ( "bytes" "crypto/ecdsa" "math/big" "math/rand" "os" "os/signal" "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/core" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "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" ) func main() { log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, 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() } sealers := make([]*ecdsa.PrivateKey, 4) for i := 0; i < len(sealers); i++ { sealers[i], _ = crypto.GenerateKey() } // Create a Clique network based off of the Sepolia config genesis := makeGenesis(faucets, sealers) // Handle interrupts. interruptCh := make(chan os.Signal, 5) signal.Notify(interruptCh, os.Interrupt) var ( stacks []*node.Node nodes []*eth.Ethereum enodes []*enode.Node ) for _, sealer := range sealers { // Start the node and wait until it's up stack, ethBackend, err := makeSealer(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 stacks = append(stacks, stack) nodes = append(nodes, ethBackend) enodes = append(enodes, stack.Server().Self()) // Inject the signer key and start sealing with it ks := keystore.NewKeyStore(stack.KeyStoreDir(), keystore.LightScryptN, keystore.LightScryptP) signer, err := ks.ImportECDSA(sealer, "") if err != nil { panic(err) } if err := ks.Unlock(signer, ""); err != nil { panic(err) } stack.AccountManager().AddBackend(ks) } // Iterate over all the nodes and start signing on them time.Sleep(3 * time.Second) for _, node := range nodes { if err := node.StartMining(); err != nil { panic(err) } } time.Sleep(3 * time.Second) // Start injecting transactions from the faucet like crazy nonces := make([]uint64, len(faucets)) for { // Stop when interrupted. select { case <-interruptCh: for _, node := range stacks { node.Close() } return default: } // Pick a random signer node index := rand.Intn(len(faucets)) backend := nodes[index%len(nodes)] // Create a self transaction and inject into the pool tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index]) if err != nil { panic(err) } if err := backend.TxPool().Add([]*types.Transaction{tx}, true, false); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated if pend, _ := backend.TxPool().Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } } // makeGenesis creates a custom Clique genesis block based on some pre-defined // signer and faucet accounts. func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core.Genesis { // Create a Clique network based off of the Rinkeby config genesis := core.DefaultGenesisBlock() genesis.GasLimit = 25000000 genesis.Config.ChainID = big.NewInt(18) genesis.Config.Clique.Period = 1 genesis.Alloc = types.GenesisAlloc{} for _, faucet := range faucets { genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = types.Account{ Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), } } // Sort the signers and embed into the extra-data section signers := make([]common.Address, len(sealers)) for i, sealer := range sealers { signers[i] = crypto.PubkeyToAddress(sealer.PublicKey) } for i := 0; i < len(signers); i++ { for j := i + 1; j < len(signers); j++ { if bytes.Compare(signers[i][:], signers[j][:]) > 0 { signers[i], signers[j] = signers[j], signers[i] } } } genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65) for i, signer := range signers { copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:]) } // Return the genesis block for initialization return genesis } func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { // Define the basic configurations for the Ethereum node datadir, _ := os.MkdirTemp("", "") config := &node.Config{ Name: "geth", Version: params.Version, DataDir: datadir, P2P: p2p.Config{ ListenAddr: "0.0.0.0:0", NoDiscovery: true, MaxPeers: 25, }, } // Start the node and configure a full Ethereum node on it stack, err := node.New(config) if err != nil { return nil, nil, err } // Create and register the backend ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, DatabaseCache: 256, DatabaseHandles: 256, TxPool: legacypool.DefaultConfig, GPO: ethconfig.Defaults.GPO, Miner: miner.Config{ 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 }