github.com/ethereum/go-ethereum@v1.16.1/core/bench_test.go (about)

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