1// Copyright 2015 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 17package core 18 19import ( 20 "crypto/ecdsa" 21 "io/ioutil" 22 "math/big" 23 "os" 24 "testing" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/math" 28 "github.com/ethereum/go-ethereum/consensus/ethash" 29 "github.com/ethereum/go-ethereum/core/rawdb" 30 "github.com/ethereum/go-ethereum/core/types" 31 "github.com/ethereum/go-ethereum/core/vm" 32 "github.com/ethereum/go-ethereum/crypto" 33 "github.com/ethereum/go-ethereum/ethdb" 34 "github.com/ethereum/go-ethereum/params" 35) 36 37func BenchmarkInsertChain_empty_memdb(b *testing.B) { 38 benchInsertChain(b, false, nil) 39} 40func BenchmarkInsertChain_empty_diskdb(b *testing.B) { 41 benchInsertChain(b, true, nil) 42} 43func BenchmarkInsertChain_valueTx_memdb(b *testing.B) { 44 benchInsertChain(b, false, genValueTx(0)) 45} 46func BenchmarkInsertChain_valueTx_diskdb(b *testing.B) { 47 benchInsertChain(b, true, genValueTx(0)) 48} 49func BenchmarkInsertChain_valueTx_100kB_memdb(b *testing.B) { 50 benchInsertChain(b, false, genValueTx(100*1024)) 51} 52func BenchmarkInsertChain_valueTx_100kB_diskdb(b *testing.B) { 53 benchInsertChain(b, true, genValueTx(100*1024)) 54} 55func BenchmarkInsertChain_uncles_memdb(b *testing.B) { 56 benchInsertChain(b, false, genUncles) 57} 58func BenchmarkInsertChain_uncles_diskdb(b *testing.B) { 59 benchInsertChain(b, true, genUncles) 60} 61func BenchmarkInsertChain_ring200_memdb(b *testing.B) { 62 benchInsertChain(b, false, genTxRing(200)) 63} 64func BenchmarkInsertChain_ring200_diskdb(b *testing.B) { 65 benchInsertChain(b, true, genTxRing(200)) 66} 67func BenchmarkInsertChain_ring1000_memdb(b *testing.B) { 68 benchInsertChain(b, false, genTxRing(1000)) 69} 70func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) { 71 benchInsertChain(b, true, genTxRing(1000)) 72} 73 74var ( 75 // This is the content of the genesis block used by the benchmarks. 76 benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 77 benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey) 78 benchRootFunds = math.BigPow(2, 200) 79) 80 81// genValueTx returns a block generator that includes a single 82// value-transfer transaction with n bytes of extra data in each 83// block. 84func genValueTx(nbytes int) func(int, *BlockGen) { 85 return func(i int, gen *BlockGen) { 86 toaddr := common.Address{} 87 data := make([]byte, nbytes) 88 gas, _ := IntrinsicGas(data, nil, false, false, false) 89 signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) 90 gasPrice := big.NewInt(0) 91 if gen.header.BaseFee != nil { 92 gasPrice = gen.header.BaseFee 93 } 94 tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{ 95 Nonce: gen.TxNonce(benchRootAddr), 96 To: &toaddr, 97 Value: big.NewInt(1), 98 Gas: gas, 99 Data: data, 100 GasPrice: gasPrice, 101 }) 102 gen.AddTx(tx) 103 } 104} 105 106var ( 107 ringKeys = make([]*ecdsa.PrivateKey, 1000) 108 ringAddrs = make([]common.Address, len(ringKeys)) 109) 110 111func init() { 112 ringKeys[0] = benchRootKey 113 ringAddrs[0] = benchRootAddr 114 for i := 1; i < len(ringKeys); i++ { 115 ringKeys[i], _ = crypto.GenerateKey() 116 ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey) 117 } 118} 119 120// genTxRing returns a block generator that sends ether in a ring 121// among n accounts. This is creates n entries in the state database 122// and fills the blocks with many small transactions. 123func genTxRing(naccounts int) func(int, *BlockGen) { 124 from := 0 125 availableFunds := new(big.Int).Set(benchRootFunds) 126 return func(i int, gen *BlockGen) { 127 block := gen.PrevBlock(i - 1) 128 gas := block.GasLimit() 129 gasPrice := big.NewInt(0) 130 if gen.header.BaseFee != nil { 131 gasPrice = gen.header.BaseFee 132 } 133 signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) 134 for { 135 gas -= params.TxGas 136 if gas < params.TxGas { 137 break 138 } 139 to := (from + 1) % naccounts 140 burn := new(big.Int).SetUint64(params.TxGas) 141 burn.Mul(burn, gen.header.BaseFee) 142 availableFunds.Sub(availableFunds, burn) 143 if availableFunds.Cmp(big.NewInt(1)) < 0 { 144 panic("not enough funds") 145 } 146 tx, err := types.SignNewTx(ringKeys[from], signer, 147 &types.LegacyTx{ 148 Nonce: gen.TxNonce(ringAddrs[from]), 149 To: &ringAddrs[to], 150 Value: availableFunds, 151 Gas: params.TxGas, 152 GasPrice: gasPrice, 153 }) 154 if err != nil { 155 panic(err) 156 } 157 gen.AddTx(tx) 158 from = to 159 } 160 } 161} 162 163// genUncles generates blocks with two uncle headers. 164func genUncles(i int, gen *BlockGen) { 165 if i >= 6 { 166 b2 := gen.PrevBlock(i - 6).Header() 167 b2.Extra = []byte("foo") 168 gen.AddUncle(b2) 169 b3 := gen.PrevBlock(i - 6).Header() 170 b3.Extra = []byte("bar") 171 gen.AddUncle(b3) 172 } 173} 174 175func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { 176 // Create the database in memory or in a temporary directory. 177 var db ethdb.Database 178 if !disk { 179 db = rawdb.NewMemoryDatabase() 180 } else { 181 dir, err := ioutil.TempDir("", "eth-core-bench") 182 if err != nil { 183 b.Fatalf("cannot create temporary directory: %v", err) 184 } 185 defer os.RemoveAll(dir) 186 db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false) 187 if err != nil { 188 b.Fatalf("cannot create temporary database: %v", err) 189 } 190 defer db.Close() 191 } 192 193 // Generate a chain of b.N blocks using the supplied block 194 // generator function. 195 gspec := Genesis{ 196 Config: params.TestChainConfig, 197 Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, 198 } 199 genesis := gspec.MustCommit(db) 200 chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, b.N, gen) 201 202 // Time the insertion of the new chain. 203 // State and blocks are stored in the same DB. 204 chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) 205 defer chainman.Stop() 206 b.ReportAllocs() 207 b.ResetTimer() 208 if i, err := chainman.InsertChain(chain); err != nil { 209 b.Fatalf("insert error (block %d): %v\n", i, err) 210 } 211} 212 213func BenchmarkChainRead_header_10k(b *testing.B) { 214 benchReadChain(b, false, 10000) 215} 216func BenchmarkChainRead_full_10k(b *testing.B) { 217 benchReadChain(b, true, 10000) 218} 219func BenchmarkChainRead_header_100k(b *testing.B) { 220 benchReadChain(b, false, 100000) 221} 222func BenchmarkChainRead_full_100k(b *testing.B) { 223 benchReadChain(b, true, 100000) 224} 225func BenchmarkChainRead_header_500k(b *testing.B) { 226 benchReadChain(b, false, 500000) 227} 228func BenchmarkChainRead_full_500k(b *testing.B) { 229 benchReadChain(b, true, 500000) 230} 231func BenchmarkChainWrite_header_10k(b *testing.B) { 232 benchWriteChain(b, false, 10000) 233} 234func BenchmarkChainWrite_full_10k(b *testing.B) { 235 benchWriteChain(b, true, 10000) 236} 237func BenchmarkChainWrite_header_100k(b *testing.B) { 238 benchWriteChain(b, false, 100000) 239} 240func BenchmarkChainWrite_full_100k(b *testing.B) { 241 benchWriteChain(b, true, 100000) 242} 243func BenchmarkChainWrite_header_500k(b *testing.B) { 244 benchWriteChain(b, false, 500000) 245} 246func BenchmarkChainWrite_full_500k(b *testing.B) { 247 benchWriteChain(b, true, 500000) 248} 249 250// makeChainForBench writes a given number of headers or empty blocks/receipts 251// into a database. 252func makeChainForBench(db ethdb.Database, full bool, count uint64) { 253 var hash common.Hash 254 for n := uint64(0); n < count; n++ { 255 header := &types.Header{ 256 Coinbase: common.Address{}, 257 Number: big.NewInt(int64(n)), 258 ParentHash: hash, 259 Difficulty: big.NewInt(1), 260 UncleHash: types.EmptyUncleHash, 261 TxHash: types.EmptyRootHash, 262 ReceiptHash: types.EmptyRootHash, 263 } 264 hash = header.Hash() 265 266 rawdb.WriteHeader(db, header) 267 rawdb.WriteCanonicalHash(db, hash, n) 268 rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1))) 269 270 if full || n == 0 { 271 block := types.NewBlockWithHeader(header) 272 rawdb.WriteBody(db, hash, n, block.Body()) 273 rawdb.WriteReceipts(db, hash, n, nil) 274 rawdb.WriteHeadBlockHash(db, hash) 275 } 276 } 277} 278 279func benchWriteChain(b *testing.B, full bool, count uint64) { 280 for i := 0; i < b.N; i++ { 281 dir, err := ioutil.TempDir("", "eth-chain-bench") 282 if err != nil { 283 b.Fatalf("cannot create temporary directory: %v", err) 284 } 285 db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) 286 if err != nil { 287 b.Fatalf("error opening database at %v: %v", dir, err) 288 } 289 makeChainForBench(db, full, count) 290 db.Close() 291 os.RemoveAll(dir) 292 } 293} 294 295func benchReadChain(b *testing.B, full bool, count uint64) { 296 dir, err := ioutil.TempDir("", "eth-chain-bench") 297 if err != nil { 298 b.Fatalf("cannot create temporary directory: %v", err) 299 } 300 defer os.RemoveAll(dir) 301 302 db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) 303 if err != nil { 304 b.Fatalf("error opening database at %v: %v", dir, err) 305 } 306 makeChainForBench(db, full, count) 307 db.Close() 308 cacheConfig := *defaultCacheConfig 309 cacheConfig.TrieDirtyDisabled = true 310 311 b.ReportAllocs() 312 b.ResetTimer() 313 314 for i := 0; i < b.N; i++ { 315 db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) 316 if err != nil { 317 b.Fatalf("error opening database at %v: %v", dir, err) 318 } 319 chain, err := NewBlockChain(db, &cacheConfig, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) 320 if err != nil { 321 b.Fatalf("error creating chain: %v", err) 322 } 323 324 for n := uint64(0); n < count; n++ { 325 header := chain.GetHeaderByNumber(n) 326 if full { 327 hash := header.Hash() 328 rawdb.ReadBody(db, hash, n) 329 rawdb.ReadReceipts(db, hash, n, chain.Config()) 330 } 331 } 332 chain.Stop() 333 db.Close() 334 } 335} 336