github.com/tmoore22/go-ethereum@v1.10.22-0.20220814113424-76f4d8bc4994/eth/downloader/testchain_test.go (about) 1 // Copyright 2018 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 downloader 18 19 import ( 20 "fmt" 21 "math/big" 22 "sync" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/consensus/ethash" 27 "github.com/ethereum/go-ethereum/core" 28 "github.com/ethereum/go-ethereum/core/rawdb" 29 "github.com/ethereum/go-ethereum/core/types" 30 "github.com/ethereum/go-ethereum/core/vm" 31 "github.com/ethereum/go-ethereum/crypto" 32 "github.com/ethereum/go-ethereum/params" 33 ) 34 35 // Test chain parameters. 36 var ( 37 testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 38 testAddress = crypto.PubkeyToAddress(testKey.PublicKey) 39 testDB = rawdb.NewMemoryDatabase() 40 41 testGspec = core.Genesis{ 42 Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, 43 BaseFee: big.NewInt(params.InitialBaseFee), 44 } 45 testGenesis = testGspec.MustCommit(testDB) 46 ) 47 48 // The common prefix of all test chains: 49 var testChainBase *testChain 50 51 // Different forks on top of the base chain: 52 var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain 53 54 var pregenerated bool 55 56 func init() { 57 // Reduce some of the parameters to make the tester faster 58 fullMaxForkAncestry = 10000 59 lightMaxForkAncestry = 10000 60 blockCacheMaxItems = 1024 61 fsHeaderSafetyNet = 256 62 fsHeaderContCheck = 500 * time.Millisecond 63 64 testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) 65 66 var forkLen = int(fullMaxForkAncestry + 50) 67 var wg sync.WaitGroup 68 69 // Generate the test chains to seed the peers with 70 wg.Add(3) 71 go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() 72 go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }() 73 go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }() 74 wg.Wait() 75 76 // Generate the test peers used by the tests to avoid overloading during testing. 77 // These seemingly random chains are used in various downloader tests. We're just 78 // pre-generating them here. 79 chains := []*testChain{ 80 testChainBase, 81 testChainForkLightA, 82 testChainForkLightB, 83 testChainForkHeavy, 84 testChainBase.shorten(1), 85 testChainBase.shorten(blockCacheMaxItems - 15), 86 testChainBase.shorten((blockCacheMaxItems - 15) / 2), 87 testChainBase.shorten(blockCacheMaxItems - 15 - 5), 88 testChainBase.shorten(MaxHeaderFetch), 89 testChainBase.shorten(800), 90 testChainBase.shorten(800 / 2), 91 testChainBase.shorten(800 / 3), 92 testChainBase.shorten(800 / 4), 93 testChainBase.shorten(800 / 5), 94 testChainBase.shorten(800 / 6), 95 testChainBase.shorten(800 / 7), 96 testChainBase.shorten(800 / 8), 97 testChainBase.shorten(3*fsHeaderSafetyNet + 256 + fsMinFullBlocks), 98 testChainBase.shorten(fsMinFullBlocks + 256 - 1), 99 testChainForkLightA.shorten(len(testChainBase.blocks) + 80), 100 testChainForkLightB.shorten(len(testChainBase.blocks) + 81), 101 testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch), 102 testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch), 103 testChainForkHeavy.shorten(len(testChainBase.blocks) + 79), 104 } 105 wg.Add(len(chains)) 106 for _, chain := range chains { 107 go func(blocks []*types.Block) { 108 newTestBlockchain(blocks) 109 wg.Done() 110 }(chain.blocks[1:]) 111 } 112 wg.Wait() 113 114 // Mark the chains pregenerated. Generating a new one will lead to a panic. 115 pregenerated = true 116 } 117 118 type testChain struct { 119 blocks []*types.Block 120 } 121 122 // newTestChain creates a blockchain of the given length. 123 func newTestChain(length int, genesis *types.Block) *testChain { 124 tc := &testChain{ 125 blocks: []*types.Block{genesis}, 126 } 127 tc.generate(length-1, 0, genesis, false) 128 return tc 129 } 130 131 // makeFork creates a fork on top of the test chain. 132 func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { 133 fork := tc.copy(len(tc.blocks) + length) 134 fork.generate(length, seed, tc.blocks[len(tc.blocks)-1], heavy) 135 return fork 136 } 137 138 // shorten creates a copy of the chain with the given length. It panics if the 139 // length is longer than the number of available blocks. 140 func (tc *testChain) shorten(length int) *testChain { 141 if length > len(tc.blocks) { 142 panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, len(tc.blocks))) 143 } 144 return tc.copy(length) 145 } 146 147 func (tc *testChain) copy(newlen int) *testChain { 148 if newlen > len(tc.blocks) { 149 newlen = len(tc.blocks) 150 } 151 cpy := &testChain{ 152 blocks: append([]*types.Block{}, tc.blocks[:newlen]...), 153 } 154 return cpy 155 } 156 157 // generate creates a chain of n blocks starting at and including parent. 158 // the returned hash chain is ordered head->parent. In addition, every 22th block 159 // contains a transaction and every 5th an uncle to allow testing correct block 160 // reassembly. 161 func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) { 162 blocks, _ := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { 163 block.SetCoinbase(common.Address{seed}) 164 // If a heavy chain is requested, delay blocks to raise difficulty 165 if heavy { 166 block.OffsetTime(-9) 167 } 168 // Include transactions to the miner to make blocks more interesting. 169 if parent == tc.blocks[0] && i%22 == 0 { 170 signer := types.MakeSigner(params.TestChainConfig, block.Number()) 171 tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) 172 if err != nil { 173 panic(err) 174 } 175 block.AddTx(tx) 176 } 177 // if the block number is a multiple of 5, add a bonus uncle to the block 178 if i > 0 && i%5 == 0 { 179 block.AddUncle(&types.Header{ 180 ParentHash: block.PrevBlock(i - 2).Hash(), 181 Number: big.NewInt(block.Number().Int64() - 1), 182 }) 183 } 184 }) 185 tc.blocks = append(tc.blocks, blocks...) 186 } 187 188 var ( 189 testBlockchains = make(map[common.Hash]*testBlockchain) 190 testBlockchainsLock sync.Mutex 191 ) 192 193 type testBlockchain struct { 194 chain *core.BlockChain 195 gen sync.Once 196 } 197 198 // newTestBlockchain creates a blockchain database built by running the given blocks, 199 // either actually running them, or reusing a previously created one. The returned 200 // chains are *shared*, so *do not* mutate them. 201 func newTestBlockchain(blocks []*types.Block) *core.BlockChain { 202 // Retrieve an existing database, or create a new one 203 head := testGenesis.Hash() 204 if len(blocks) > 0 { 205 head = blocks[len(blocks)-1].Hash() 206 } 207 testBlockchainsLock.Lock() 208 if _, ok := testBlockchains[head]; !ok { 209 testBlockchains[head] = new(testBlockchain) 210 } 211 tbc := testBlockchains[head] 212 testBlockchainsLock.Unlock() 213 214 // Ensure that the database is generated 215 tbc.gen.Do(func() { 216 if pregenerated { 217 panic("Requested chain generation outside of init") 218 } 219 db := rawdb.NewMemoryDatabase() 220 testGspec.MustCommit(db) 221 222 chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) 223 if err != nil { 224 panic(err) 225 } 226 if n, err := chain.InsertChain(blocks); err != nil { 227 panic(fmt.Sprintf("block %d: %v", n, err)) 228 } 229 tbc.chain = chain 230 }) 231 return tbc.chain 232 }