github.com/ethereum/go-ethereum@v1.16.1/eth/catalyst/simulated_beacon_test.go (about) 1 // Copyright 2023 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 package catalyst 18 19 import ( 20 "context" 21 "fmt" 22 "math/big" 23 "testing" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/core" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/ethereum/go-ethereum/eth" 31 "github.com/ethereum/go-ethereum/eth/ethconfig" 32 "github.com/ethereum/go-ethereum/miner" 33 "github.com/ethereum/go-ethereum/node" 34 "github.com/ethereum/go-ethereum/p2p" 35 "github.com/ethereum/go-ethereum/params" 36 ) 37 38 func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis, period uint64) (*node.Node, *eth.Ethereum, *SimulatedBeacon) { 39 t.Helper() 40 41 n, err := node.New(&node.Config{ 42 P2P: p2p.Config{ 43 ListenAddr: "127.0.0.1:0", 44 NoDiscovery: true, 45 MaxPeers: 0, 46 }, 47 }) 48 if err != nil { 49 t.Fatal("can't create node:", err) 50 } 51 52 ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: ethconfig.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: miner.DefaultConfig} 53 ethservice, err := eth.New(n, ethcfg) 54 if err != nil { 55 t.Fatal("can't create eth service:", err) 56 } 57 58 simBeacon, err := NewSimulatedBeacon(period, common.Address{}, ethservice) 59 if err != nil { 60 t.Fatal("can't create simulated beacon:", err) 61 } 62 63 n.RegisterLifecycle(simBeacon) 64 65 if err := n.Start(); err != nil { 66 t.Fatal("can't start node:", err) 67 } 68 69 ethservice.SetSynced() 70 return n, ethservice, simBeacon 71 } 72 73 // send 20 transactions, >10 withdrawals and ensure they are included in order 74 // send enough transactions to fill multiple blocks 75 func TestSimulatedBeaconSendWithdrawals(t *testing.T) { 76 var withdrawals []types.Withdrawal 77 txs := make(map[common.Hash]*types.Transaction) 78 79 var ( 80 // testKey is a private key to use for funding a tester account. 81 testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 82 83 // testAddr is the Ethereum address of the tester account. 84 testAddr = crypto.PubkeyToAddress(testKey.PublicKey) 85 ) 86 87 // short period (1 second) for testing purposes 88 var gasLimit uint64 = 10_000_000 89 genesis := core.DeveloperGenesisBlock(gasLimit, &testAddr) 90 node, ethService, mock := startSimulatedBeaconEthService(t, genesis, 1) 91 _ = mock 92 defer node.Close() 93 94 chainHeadCh := make(chan core.ChainHeadEvent, 10) 95 subscription := ethService.BlockChain().SubscribeChainHeadEvent(chainHeadCh) 96 defer subscription.Unsubscribe() 97 98 // generate some withdrawals 99 for i := 0; i < 20; i++ { 100 withdrawals = append(withdrawals, types.Withdrawal{Index: uint64(i)}) 101 if err := mock.withdrawals.add(&withdrawals[i]); err != nil { 102 t.Fatal("addWithdrawal failed", err) 103 } 104 } 105 106 // generate a bunch of transactions 107 signer := types.NewEIP155Signer(ethService.BlockChain().Config().ChainID) 108 for i := 0; i < 20; i++ { 109 tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) 110 if err != nil { 111 t.Fatalf("error signing transaction, err=%v", err) 112 } 113 txs[tx.Hash()] = tx 114 115 if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil { 116 t.Fatal("SendTx failed", err) 117 } 118 } 119 120 includedTxs := make(map[common.Hash]struct{}) 121 var includedWithdrawals []uint64 122 123 timer := time.NewTimer(30 * time.Second) 124 for { 125 select { 126 case ev := <-chainHeadCh: 127 block := ethService.BlockChain().GetBlock(ev.Header.Hash(), ev.Header.Number.Uint64()) 128 for _, includedTx := range block.Transactions() { 129 includedTxs[includedTx.Hash()] = struct{}{} 130 } 131 for _, includedWithdrawal := range block.Withdrawals() { 132 includedWithdrawals = append(includedWithdrawals, includedWithdrawal.Index) 133 } 134 // ensure all withdrawals/txs included. this will at least take two blocks b/c number of withdrawals > 10 135 if len(includedTxs) == len(txs) && len(includedWithdrawals) == len(withdrawals) { 136 return 137 } 138 case <-timer.C: 139 t.Fatal("timed out without including all withdrawals/txs") 140 } 141 } 142 } 143 144 // Tests that zero-period dev mode can handle a lot of simultaneous 145 // transactions/withdrawals 146 func TestOnDemandSpam(t *testing.T) { 147 // This test is flaky, due to various causes, and the root cause is synchronicity. 148 // We have optimistic timeouts here and there in the simulated becaon and the worker. 149 // This test typically fails on 32-bit windows appveyor. 150 t.Skip("flaky test") 151 var ( 152 withdrawals []types.Withdrawal 153 txCount = 20000 154 wxCount = 20 155 testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 156 testAddr = crypto.PubkeyToAddress(testKey.PublicKey) 157 gasLimit uint64 = 10_000_000 158 genesis = core.DeveloperGenesisBlock(gasLimit, &testAddr) 159 node, eth, mock = startSimulatedBeaconEthService(t, genesis, 0) 160 _ = newSimulatedBeaconAPI(mock) 161 signer = types.LatestSigner(eth.BlockChain().Config()) 162 chainHeadCh = make(chan core.ChainHeadEvent, 100) 163 sub = eth.BlockChain().SubscribeChainHeadEvent(chainHeadCh) 164 ) 165 defer node.Close() 166 defer sub.Unsubscribe() 167 168 // generate some withdrawals 169 for i := 0; i < wxCount; i++ { 170 withdrawals = append(withdrawals, types.Withdrawal{Index: uint64(i)}) 171 if err := mock.withdrawals.add(&withdrawals[i]); err != nil { 172 t.Fatal("addWithdrawal failed", err) 173 } 174 } 175 176 // generate a bunch of transactions 177 go func() { 178 for i := 0; i < txCount; i++ { 179 tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{byte(i), byte(1)}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), signer, testKey) 180 if err != nil { 181 panic(fmt.Sprintf("error signing transaction: %v", err)) 182 } 183 if err := eth.TxPool().Add([]*types.Transaction{tx}, false)[0]; err != nil { 184 panic(fmt.Sprintf("error adding txs to pool: %v", err)) 185 } 186 } 187 }() 188 var ( 189 includedTxs int 190 includedWxs int 191 abort = time.NewTimer(10 * time.Second) 192 ) 193 defer abort.Stop() 194 for { 195 select { 196 case ev := <-chainHeadCh: 197 block := eth.BlockChain().GetBlock(ev.Header.Hash(), ev.Header.Number.Uint64()) 198 includedTxs += len(block.Transactions()) 199 includedWxs += len(block.Withdrawals()) 200 // ensure all withdrawals/txs included. this will take two blocks b/c number of withdrawals > 10 201 if includedTxs == txCount && includedWxs == wxCount { 202 return 203 } 204 abort.Reset(10 * time.Second) 205 case <-abort.C: 206 t.Fatalf("timed out without including all withdrawals/txs: have txs %d, want %d, have wxs %d, want %d", 207 includedTxs, txCount, includedWxs, wxCount) 208 } 209 } 210 }