// Copyright 2015 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 . package core import ( "context" "crypto/ecdsa" "io/ioutil" "math/big" "os" "testing" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/common/math" "github.com/ledgerwatch/turbo-geth/consensus/ethash" "github.com/ledgerwatch/turbo-geth/core/rawdb" "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/core/vm" "github.com/ledgerwatch/turbo-geth/crypto" "github.com/ledgerwatch/turbo-geth/ethdb" "github.com/ledgerwatch/turbo-geth/params" ) func BenchmarkInsertChain_empty_memdb(b *testing.B) { benchInsertChain(b, false, nil) } func BenchmarkInsertChain_empty_diskdb(b *testing.B) { benchInsertChain(b, true, nil) } func BenchmarkInsertChain_valueTx_memdb(b *testing.B) { benchInsertChain(b, false, genValueTx(0)) } func BenchmarkInsertChain_valueTx_diskdb(b *testing.B) { benchInsertChain(b, true, genValueTx(0)) } func BenchmarkInsertChain_valueTx_100kB_memdb(b *testing.B) { benchInsertChain(b, false, genValueTx(100*1024)) } func BenchmarkInsertChain_valueTx_100kB_diskdb(b *testing.B) { benchInsertChain(b, true, genValueTx(100*1024)) } func BenchmarkInsertChain_uncles_memdb(b *testing.B) { benchInsertChain(b, false, genUncles) } func BenchmarkInsertChain_uncles_diskdb(b *testing.B) { benchInsertChain(b, true, genUncles) } func BenchmarkInsertChain_ring200_memdb(b *testing.B) { benchInsertChain(b, false, genTxRing(200)) } func BenchmarkInsertChain_ring200_diskdb(b *testing.B) { benchInsertChain(b, true, genTxRing(200)) } func BenchmarkInsertChain_ring1000_memdb(b *testing.B) { benchInsertChain(b, false, genTxRing(1000)) } func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) { benchInsertChain(b, true, genTxRing(1000)) } var ( // This is the content of the genesis block used by the benchmarks. benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey) benchRootFunds = math.BigPow(2, 100) ) // genValueTx returns a block generator that includes a single // value-transfer transaction with n bytes of extra data in each // block. func genValueTx(nbytes int) func(int, *BlockGen) { return func(i int, gen *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) gas, _ := IntrinsicGas(data, false, false, false) tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey) gen.AddTx(tx) } } var ( ringKeys = make([]*ecdsa.PrivateKey, 1000) ringAddrs = make([]common.Address, len(ringKeys)) ) func init() { ringKeys[0] = benchRootKey ringAddrs[0] = benchRootAddr for i := 1; i < len(ringKeys); i++ { ringKeys[i], _ = crypto.GenerateKey() ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey) } } // genTxRing returns a block generator that sends ether in a ring // among n accounts. This is creates n entries in the state database // and fills the blocks with many small transactions. func genTxRing(naccounts int) func(int, *BlockGen) { from := 0 return func(i int, gen *BlockGen) { block := gen.PrevBlock(i - 1) gas := CalcGasLimit(block, block.GasLimit(), block.GasLimit()) for { gas -= params.TxGas if gas < params.TxGas { break } to := (from + 1) % naccounts tx := types.NewTransaction( gen.TxNonce(ringAddrs[from]), ringAddrs[to], benchRootFunds, params.TxGas, nil, nil, ) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, ringKeys[from]) gen.AddTx(tx) from = to } } } // genUncles generates blocks with two uncle headers. func genUncles(i int, gen *BlockGen) { if i >= 6 { b2 := gen.PrevBlock(i - 6).Header() b2.Extra = []byte("foo") gen.AddUncle(b2) b3 := gen.PrevBlock(i - 6).Header() b3.Extra = []byte("bar") gen.AddUncle(b3) } } func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Create the database in memory or in a temporary directory. var db ethdb.Database if !disk { db = ethdb.NewMemDatabase() } else { dir, err := ioutil.TempDir("", "eth-core-bench") if err != nil { b.Fatalf("cannot create temporary directory: %v", err) } defer os.RemoveAll(dir) db, err = ethdb.NewBoltDatabase(dir) if err != nil { b.Fatalf("cannot create temporary database: %v", err) } defer db.Close() } // Generate a chain of b.N blocks using the supplied block // generator function. gspec := Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, } genesis := gspec.MustCommit(db) chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) ctx := chainman.WithContext(context.Background(), big.NewInt(genesis.Number().Int64()+1)) defer chainman.Stop() chain, _ := GenerateChain(ctx, gspec.Config, genesis, ethash.NewFaker(), db, b.N, gen) // Time the insertion of the new chain. // State and blocks are stored in the same DB. b.ReportAllocs() b.ResetTimer() if i, err := chainman.InsertChain(context.Background(), chain); err != nil { b.Fatalf("insert error (block %d): %v\n", i, err) } } func BenchmarkChainRead_header_10k(b *testing.B) { benchReadChain(b, false, 10000) } func BenchmarkChainRead_full_10k(b *testing.B) { benchReadChain(b, true, 10000) } func BenchmarkChainRead_header_100k(b *testing.B) { benchReadChain(b, false, 100000) } func BenchmarkChainRead_full_100k(b *testing.B) { benchReadChain(b, true, 100000) } func BenchmarkChainRead_header_500k(b *testing.B) { benchReadChain(b, false, 500000) } func BenchmarkChainRead_full_500k(b *testing.B) { benchReadChain(b, true, 500000) } func BenchmarkChainWrite_header_10k(b *testing.B) { benchWriteChain(b, false, 10000) } func BenchmarkChainWrite_full_10k(b *testing.B) { benchWriteChain(b, true, 10000) } func BenchmarkChainWrite_header_100k(b *testing.B) { benchWriteChain(b, false, 100000) } func BenchmarkChainWrite_full_100k(b *testing.B) { benchWriteChain(b, true, 100000) } func BenchmarkChainWrite_header_500k(b *testing.B) { benchWriteChain(b, false, 500000) } func BenchmarkChainWrite_full_500k(b *testing.B) { benchWriteChain(b, true, 500000) } // makeChainForBench writes a given number of headers or empty blocks/receipts // into a database. func makeChainForBench(db ethdb.Database, full bool, count uint64) { var hash common.Hash for n := uint64(0); n < count; n++ { header := &types.Header{ Coinbase: common.Address{}, Number: big.NewInt(int64(n)), ParentHash: hash, Difficulty: big.NewInt(1), UncleHash: types.EmptyUncleHash, TxHash: types.EmptyRootHash, ReceiptHash: types.EmptyRootHash, } hash = header.Hash() ctx := context.Background() rawdb.WriteHeader(ctx, db, header) rawdb.WriteCanonicalHash(db, hash, n) rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1))) if full || n == 0 { block := types.NewBlockWithHeader(header) rawdb.WriteBody(ctx, db, hash, n, block.Body()) rawdb.WriteReceipts(db, hash, n, nil) } } } func benchWriteChain(b *testing.B, full bool, count uint64) { for i := 0; i < b.N; i++ { dir, err := ioutil.TempDir("", "eth-chain-bench") if err != nil { b.Fatalf("cannot create temporary directory: %v", err) } db, err := ethdb.NewBoltDatabase(dir) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } makeChainForBench(db, full, count) db.Close() os.RemoveAll(dir) } } func benchReadChain(b *testing.B, full bool, count uint64) { dir, err := ioutil.TempDir("", "eth-chain-bench") if err != nil { b.Fatalf("cannot create temporary directory: %v", err) } defer os.RemoveAll(dir) db, err := ethdb.NewBoltDatabase(dir) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } makeChainForBench(db, full, count) db.Close() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { db, err := ethdb.NewBoltDatabase(dir) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } chain, err := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil) if err != nil { b.Fatalf("error creating chain: %v", err) } for n := uint64(0); n < count; n++ { header := chain.GetHeaderByNumber(n) if full { hash := header.Hash() rawdb.ReadBody(db, hash, n) rawdb.ReadReceipts(db, hash, n, chain.Config()) } } chain.Stop() db.Close() } }