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