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