go-ethereum/miner/stress/beacon/main.go
Marius van der Wijden 3038e480f5
all: core rework for the merge transition (#23761)
* all: work for eth1/2 transtition

* consensus/beacon, eth: change beacon difficulty to 0

* eth: updates

* all: add terminalBlockDifficulty config, fix rebasing issues

* eth: implemented merge interop spec

* internal/ethapi: update to v1.0.0.alpha.2

                                                                 This commit updates the code to the new spec, moving payloadId into
                                                                 it's own object. It also fixes an issue with finalizing an empty blockhash.
                                                                 It also properly sets the basefee

* all: sync polishes, other fixes + refactors

* core, eth: correct semantics for LeavePoW, EnterPoS

* core: fixed rebasing artifacts

* core: light: performance improvements

* core: use keyed field (f)

* core: eth: fix compilation issues + tests

* eth/catalyst: dbetter error codes

* all: move Merger to consensus/, remove reliance on it in bc

* all: renamed EnterPoS and LeavePoW to ReachTDD and FinalizePoS

* core: make mergelogs a function

* core: use InsertChain instead of InsertBlock

* les: drop merger from lightchain object

* consensus: add merger

* core: recoverAncestors in catalyst mode

* core: fix nitpick

* all: removed merger from beacon, use TTD, nitpicks

* consensus: eth: add docstring, removed unnecessary code duplication

* consensus/beacon: better comment

* all: easy to fix nitpicks by karalabe

* consensus/beacon: verify known headers to be sure

* core: comments

* core: eth: don't drop peers who advertise blocks, nitpicks

* core: never add beacon blocks to the future queue

* core: fixed nitpicks

* consensus/beacon: simplify IsTTDReached check

* consensus/beacon: correct IsTTDReached check

Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
Co-authored-by: Péter Szilágyi <peterke@gmail.com>
2021-11-26 13:23:02 +02:00

508 lines
14 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
// This file contains a miner stress test for the eth1/2 transition
package main
import (
"crypto/ecdsa"
"errors"
"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/common/hexutil"
"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/catalyst"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/les"
"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"
)
type nodetype int
const (
legacyMiningNode nodetype = iota
legacyNormalNode
eth2MiningNode
eth2NormalNode
eth2LightClient
)
func (typ nodetype) String() string {
switch typ {
case legacyMiningNode:
return "legacyMiningNode"
case legacyNormalNode:
return "legacyNormalNode"
case eth2MiningNode:
return "eth2MiningNode"
case eth2NormalNode:
return "eth2NormalNode"
case eth2LightClient:
return "eth2LightClient"
default:
return "undefined"
}
}
var (
// transitionDifficulty is the target total difficulty for transition
transitionDifficulty = new(big.Int).Mul(big.NewInt(20), params.MinimumDifficulty)
// blockInterval is the time interval for creating a new eth2 block
blockInterval = time.Second * 3
blockIntervalInt = 3
// finalizationDist is the block distance for finalizing block
finalizationDist = 10
)
type ethNode struct {
typ nodetype
api *catalyst.ConsensusAPI
ethBackend *eth.Ethereum
lesBackend *les.LightEthereum
stack *node.Node
enode *enode.Node
}
func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode {
var (
err error
api *catalyst.ConsensusAPI
stack *node.Node
ethBackend *eth.Ethereum
lesBackend *les.LightEthereum
)
// Start the node and wait until it's up
if typ == eth2LightClient {
stack, lesBackend, api, err = makeLightNode(genesis)
} else {
stack, ethBackend, api, err = makeFullNode(genesis)
}
if err != nil {
panic(err)
}
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)
}
enode := stack.Server().Self()
// Inject the signer key and start sealing with it
stack.AccountManager().AddBackend(keystore.NewPlaintextKeyStore("beacon-stress"))
store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
if _, err := store.NewAccount(""); err != nil {
panic(err)
}
return &ethNode{
typ: typ,
api: api,
ethBackend: ethBackend,
lesBackend: lesBackend,
stack: stack,
enode: enode,
}
}
func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*catalyst.ExecutableData, error) {
if n.typ != eth2MiningNode {
return nil, errors.New("invalid node type")
}
payload, err := n.api.PreparePayload(catalyst.AssembleBlockParams{
ParentHash: parentHash,
Timestamp: uint64(time.Now().Unix()),
})
if err != nil {
return nil, err
}
return n.api.GetPayload(hexutil.Uint64(payload.PayloadID))
}
func (n *ethNode) insertBlock(eb catalyst.ExecutableData) error {
if !eth2types(n.typ) {
return errors.New("invalid node type")
}
newResp, err := n.api.ExecutePayload(eb)
if err != nil {
return err
} else if newResp.Status != "VALID" {
return errors.New("failed to insert block")
}
return nil
}
func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed catalyst.ExecutableData) error {
if !eth2types(n.typ) {
return errors.New("invalid node type")
}
if err := n.insertBlock(ed); err != nil {
return err
}
block, err := catalyst.ExecutableDataToBlock(ed)
if err != nil {
return err
}
if err := n.api.ConsensusValidated(catalyst.ConsensusValidatedParams{BlockHash: block.Hash(), Status: "VALID"}); err != nil {
return err
}
return nil
}
type nodeManager struct {
genesis *core.Genesis
genesisBlock *types.Block
nodes []*ethNode
enodes []*enode.Node
close chan struct{}
}
func newNodeManager(genesis *core.Genesis) *nodeManager {
return &nodeManager{
close: make(chan struct{}),
genesis: genesis,
genesisBlock: genesis.ToBlock(nil),
}
}
func (mgr *nodeManager) createNode(typ nodetype) {
node := newNode(typ, mgr.genesis, mgr.enodes)
mgr.nodes = append(mgr.nodes, node)
mgr.enodes = append(mgr.enodes, node.enode)
}
func (mgr *nodeManager) getNodes(typ nodetype) []*ethNode {
var ret []*ethNode
for _, node := range mgr.nodes {
if node.typ == typ {
ret = append(ret, node)
}
}
return ret
}
func (mgr *nodeManager) startMining() {
for _, node := range append(mgr.getNodes(eth2MiningNode), mgr.getNodes(legacyMiningNode)...) {
if err := node.ethBackend.StartMining(1); err != nil {
panic(err)
}
}
}
func (mgr *nodeManager) shutdown() {
close(mgr.close)
for _, node := range mgr.nodes {
node.stack.Close()
}
}
func (mgr *nodeManager) run() {
if len(mgr.nodes) == 0 {
return
}
chain := mgr.nodes[0].ethBackend.BlockChain()
sink := make(chan core.ChainHeadEvent, 1024)
sub := chain.SubscribeChainHeadEvent(sink)
defer sub.Unsubscribe()
var (
transitioned bool
parentBlock *types.Block
waitFinalise []*types.Block
)
timer := time.NewTimer(0)
defer timer.Stop()
<-timer.C // discard the initial tick
// Handle the by default transition.
if transitionDifficulty.Sign() == 0 {
transitioned = true
parentBlock = mgr.genesisBlock
timer.Reset(blockInterval)
log.Info("Enable the transition by default")
}
// Handle the block finalization.
checkFinalise := func() {
if parentBlock == nil {
return
}
if len(waitFinalise) == 0 {
return
}
oldest := waitFinalise[0]
if oldest.NumberU64() > parentBlock.NumberU64() {
return
}
distance := parentBlock.NumberU64() - oldest.NumberU64()
if int(distance) < finalizationDist {
return
}
nodes := mgr.getNodes(eth2MiningNode)
nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
for _, node := range append(nodes) {
node.api.ConsensusValidated(catalyst.ConsensusValidatedParams{BlockHash: oldest.Hash(), Status: catalyst.VALID.Status})
}
log.Info("Finalised eth2 block", "number", oldest.NumberU64(), "hash", oldest.Hash())
waitFinalise = waitFinalise[1:]
}
for {
checkFinalise()
select {
case <-mgr.close:
return
case ev := <-sink:
if transitioned {
continue
}
td := chain.GetTd(ev.Block.Hash(), ev.Block.NumberU64())
if td.Cmp(transitionDifficulty) < 0 {
continue
}
transitioned, parentBlock = true, ev.Block
timer.Reset(blockInterval)
log.Info("Transition difficulty reached", "td", td, "target", transitionDifficulty, "number", ev.Block.NumberU64(), "hash", ev.Block.Hash())
case <-timer.C:
producers := mgr.getNodes(eth2MiningNode)
if len(producers) == 0 {
continue
}
hash, timestamp := parentBlock.Hash(), parentBlock.Time()
if parentBlock.NumberU64() == 0 {
timestamp = uint64(time.Now().Unix()) - uint64(blockIntervalInt)
}
ed, err := producers[0].assembleBlock(hash, timestamp)
if err != nil {
log.Error("Failed to assemble the block", "err", err)
continue
}
block, _ := catalyst.ExecutableDataToBlock(*ed)
nodes := mgr.getNodes(eth2MiningNode)
nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
for _, node := range nodes {
if err := node.insertBlockAndSetHead(parentBlock.Header(), *ed); err != nil {
log.Error("Failed to insert block", "type", node.typ, "err", err)
}
}
log.Info("Create and insert eth2 block", "number", ed.Number)
parentBlock = block
waitFinalise = append(waitFinalise, block)
timer.Reset(blockInterval)
}
}
}
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, 16)
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)
manager := newNodeManager(genesis)
defer manager.shutdown()
manager.createNode(eth2NormalNode)
manager.createNode(eth2MiningNode)
manager.createNode(legacyMiningNode)
manager.createNode(legacyNormalNode)
manager.createNode(eth2LightClient)
// Iterate over all the nodes and start mining
time.Sleep(3 * time.Second)
if transitionDifficulty.Sign() != 0 {
manager.startMining()
}
go manager.run()
// Start injecting transactions from the faucets like crazy
time.Sleep(3 * time.Second)
nonces := make([]uint64, len(faucets))
for {
// Pick a random mining node
nodes := manager.getNodes(eth2MiningNode)
index := rand.Intn(len(faucets))
node := 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+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index])
if err != nil {
panic(err)
}
if err := node.ethBackend.TxPool().AddLocal(tx); err != nil {
panic(err)
}
nonces[index]++
// Wait if we're too saturated
if pend, _ := node.ethBackend.TxPool().Stats(); pend > 2048 {
time.Sleep(100 * time.Millisecond)
}
}
}
// 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.Difficulty = params.MinimumDifficulty
genesis.GasLimit = 25000000
genesis.Config.ChainID = big.NewInt(18)
genesis.Config.EIP150Hash = common.Hash{}
genesis.BaseFee = big.NewInt(params.InitialBaseFee)
genesis.Config.TerminalTotalDifficulty = transitionDifficulty
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),
}
}
return genesis
}
func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *catalyst.ConsensusAPI, 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, nil, err
}
econfig := &ethconfig.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: 10 * time.Second, // Disable the recommit
},
LightServ: 100,
LightPeers: 10,
LightNoSyncServe: true,
}
ethBackend, err := eth.New(stack, econfig)
if err != nil {
return nil, nil, nil, err
}
_, err = les.NewLesServer(stack, ethBackend, econfig)
if err != nil {
log.Crit("Failed to create the LES server", "err", err)
}
err = stack.Start()
return stack, ethBackend, catalyst.NewConsensusAPI(ethBackend, nil), err
}
func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *catalyst.ConsensusAPI, 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, nil, err
}
lesBackend, err := les.New(stack, &ethconfig.Config{
Genesis: genesis,
NetworkId: genesis.Config.ChainID.Uint64(),
SyncMode: downloader.LightSync,
DatabaseCache: 256,
DatabaseHandles: 256,
TxPool: core.DefaultTxPoolConfig,
GPO: ethconfig.Defaults.GPO,
Ethash: ethconfig.Defaults.Ethash,
LightPeers: 10,
})
if err != nil {
return nil, nil, nil, err
}
err = stack.Start()
return stack, lesBackend, catalyst.NewConsensusAPI(nil, lesBackend), err
}
func eth2types(typ nodetype) bool {
if typ == eth2LightClient || typ == eth2NormalNode || typ == eth2MiningNode {
return true
}
return false
}