// Copyright 2014 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 trie import ( "bytes" "encoding/binary" "fmt" "io/ioutil" "math/big" "math/rand" "os" "reflect" "testing" "testing/quick" "github.com/davecgh/go-spew/spew" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/core/types/accounts" "github.com/ledgerwatch/turbo-geth/crypto" "github.com/ledgerwatch/turbo-geth/ethdb" "github.com/ledgerwatch/turbo-geth/rlp" ) func init() { spew.Config.Indent = " " spew.Config.DisableMethods = false } // Used for testing func newEmpty() *Trie { trie := New(common.Hash{}) return trie } func TestEmptyTrie(t *testing.T) { var trie Trie res := trie.Hash() exp := EmptyRoot if res != common.Hash(exp) { t.Errorf("expected %x got %x", exp, res) } } func TestNull(t *testing.T) { var trie Trie key := make([]byte, 32) value := []byte("test") trie.Update(key, value, 0) v, _ := trie.Get(key) if !bytes.Equal(v, value) { t.Fatal("wrong value") } } func TestInsert(t *testing.T) { t.Skip("we don't support different key length") trie := newEmpty() updateString(trie, "doe", "reindeer") updateString(trie, "dog", "puppy") updateString(trie, "dogglesworth", "cat") fmt.Printf("\n\n%s\n\n", trie.root.fstring("")) exp := common.HexToHash("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3") root := trie.Hash() if root != exp { t.Errorf("exp %x got %x", exp, root) } trie = newEmpty() updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") root = trie.Hash() if root != exp { t.Errorf("exp %x got %x", exp, root) } } func TestGet(t *testing.T) { t.Skip("different length of key is not supported") trie := newEmpty() updateString(trie, "doe", "reindeer") updateString(trie, "dog", "puppy") updateString(trie, "dogglesworth", "cat") for i := 0; i < 2; i++ { res := getString(trie, "dog") if !bytes.Equal(res, []byte("puppy")) { t.Errorf("expected puppy got %x", res) } unknown := getString(trie, "unknown") if unknown != nil { t.Errorf("expected nil got %x", unknown) } if i == 1 { return } } } func TestDelete(t *testing.T) { t.Skip("should be restored. skipped for turbo-geth") trie := newEmpty() vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, {"horse", "stallion"}, {"shaman", "horse"}, {"doge", "coin"}, {"ether", ""}, {"dog", "puppy"}, {"shaman", ""}, } for _, val := range vals { if val.v != "" { updateString(trie, val.k, val.v) } else { deleteString(trie, val.k) } } hash := trie.Hash() exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") if hash != exp { t.Errorf("expected %x got %x", exp, hash) } } func TestEmptyValues(t *testing.T) { t.Skip("should be restored. skipped for turbo-geth") trie := newEmpty() vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, {"horse", "stallion"}, {"shaman", "horse"}, {"doge", "coin"}, {"ether", ""}, {"dog", "puppy"}, {"shaman", ""}, } for _, val := range vals { updateString(trie, val.k, val.v) } hash := trie.Hash() exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") if hash != exp { t.Errorf("expected %x got %x", exp, hash) } } func TestReplication(t *testing.T) { t.Skip("should be restored. skipped for turbo-geth") trie := newEmpty() vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, {"horse", "stallion"}, {"shaman", "horse"}, {"doge", "coin"}, {"dog", "puppy"}, {"somethingveryoddindeedthis is", "myothernodedata"}, } for _, val := range vals { updateString(trie, val.k, val.v) } exp := trie.Hash() // create a new trie on top of the database and check that lookups work. trie2 := New(exp) for _, kv := range vals { if string(getString(trie2, kv.k)) != kv.v { t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) } } hash := trie2.Hash() if hash != exp { t.Errorf("root failure. expected %x got %x", exp, hash) } // perform some insertions on the new trie. vals2 := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, {"horse", "stallion"}, // {"shaman", "horse"}, // {"doge", "coin"}, // {"ether", ""}, // {"dog", "puppy"}, // {"somethingveryoddindeedthis is", "myothernodedata"}, // {"shaman", ""}, } for _, val := range vals2 { updateString(trie2, val.k, val.v) } if hash := trie2.Hash(); hash != exp { t.Errorf("root failure. expected %x got %x", exp, hash) } } func TestLargeValue(t *testing.T) { trie := newEmpty() trie.Update([]byte("key1"), []byte{99, 99, 99, 99}, 0) trie.Update([]byte("key2"), bytes.Repeat([]byte{1}, 32), 0) trie.Hash() } type countingDB struct { ethdb.Database gets map[string]int } func (db *countingDB) Get(bucket []byte, key []byte) ([]byte, error) { db.gets[string(key)]++ return db.Database.Get(bucket, key) } // randTest performs random trie operations. // Instances of this test are created by Generate. type randTest []randTestStep type randTestStep struct { op int key []byte // for opUpdate, opDelete, opGet value []byte // for opUpdate err error // for debugging } const ( opUpdate = iota opDelete opGet opCommit opHash opReset opItercheckhash opCheckCacheInvariant opMax // boundary value, not an actual op ) func (randTest) Generate(r *rand.Rand, size int) reflect.Value { var allKeys [][]byte genKey := func() []byte { if len(allKeys) < 2 || r.Intn(100) < 10 { // new key key := make([]byte, r.Intn(50)) r.Read(key) allKeys = append(allKeys, key) return key } // use existing key return allKeys[r.Intn(len(allKeys))] } var steps randTest for i := 0; i < size; i++ { step := randTestStep{op: r.Intn(opMax)} switch step.op { case opUpdate: step.key = genKey() step.value = make([]byte, 8) binary.BigEndian.PutUint64(step.value, uint64(i)) case opGet, opDelete: step.key = genKey() } steps = append(steps, step) } return reflect.ValueOf(steps) } func runRandTest(rt randTest) bool { tr := New(common.Hash{}) values := make(map[string]string) // tracks content of the trie for i, step := range rt { switch step.op { case opUpdate: tr.Update(step.key, step.value, 0) values[string(step.key)] = string(step.value) case opDelete: tr.Delete(step.key, 0) delete(values, string(step.key)) case opGet: v, _ := tr.Get(step.key) want := values[string(step.key)] if string(v) != want { rt[i].err = fmt.Errorf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want) } case opCommit: case opHash: tr.Hash() case opReset: hash := tr.Hash() newtr := New(hash) tr = newtr case opItercheckhash: // FIXME: restore for turbo-geth /* checktr := New(common.Hash{}) it := NewIterator(tr.NodeIterator(nil)) for it.Next() { checktr.Update(it.Key, it.Value) } if tr.Hash() != checktr.Hash() { rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash") } */ case opCheckCacheInvariant: } // Abort the test on error. if rt[i].err != nil { return false } } return true } func TestRandom(t *testing.T) { t.Skip("should be restored. skipped for turbo-geth") if err := quick.Check(runRandTest, nil); err != nil { if cerr, ok := err.(*quick.CheckError); ok { t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) } t.Fatal(err) } } func BenchmarkGet(b *testing.B) { benchGet(b, false) } func BenchmarkGetDB(b *testing.B) { benchGet(b, true) } func BenchmarkUpdateBE(b *testing.B) { benchUpdate(b, binary.BigEndian) } func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) } const benchElemCount = 20000 func benchGet(b *testing.B, commit bool) { trie := new(Trie) var tmpdir string var tmpdb ethdb.Database if commit { tmpdir, tmpdb = tempDB() trie = New(common.Hash{}) } k := make([]byte, 32) for i := 0; i < benchElemCount; i++ { binary.LittleEndian.PutUint64(k, uint64(i)) trie.Update(k, k, 0) } binary.LittleEndian.PutUint64(k, benchElemCount/2) b.ResetTimer() for i := 0; i < b.N; i++ { trie.Get(k) } b.StopTimer() if commit { tmpdb.Close() os.RemoveAll(tmpdir) } } func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { trie := newEmpty() k := make([]byte, 32) for i := 0; i < b.N; i++ { e.PutUint64(k, uint64(i)) trie.Update(k, k, 0) } return trie } // Benchmarks the trie hashing. Since the trie caches the result of any operation, // we cannot use b.N as the number of hashing rouns, since all rounds apart from // the first one will be NOOP. As such, we'll use b.N as the number of account to // insert into the trie before measuring the hashing. func BenchmarkHash(b *testing.B) { // Make the random benchmark deterministic random := rand.New(rand.NewSource(0)) // Create a realistic account trie to hash addresses := make([][20]byte, b.N) for i := 0; i < len(addresses); i++ { for j := 0; j < len(addresses[i]); j++ { addresses[i][j] = byte(random.Intn(256)) } } accounts := make([][]byte, len(addresses)) for i := 0; i < len(accounts); i++ { var ( nonce = uint64(random.Int63()) balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) root = EmptyRoot code = crypto.Keccak256(nil) ) accounts[i], _ = rlp.EncodeToBytes([]interface{}{nonce, balance, root, code}) } // Insert the accounts into the trie and hash it trie := newEmpty() for i := 0; i < len(addresses); i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i], 0) } b.ResetTimer() b.ReportAllocs() trie.Hash() } func tempDB() (string, ethdb.Database) { dir, err := ioutil.TempDir("", "trie-bench") if err != nil { panic(fmt.Sprintf("can't create temporary directory: %v", err)) } diskdb, err := ethdb.NewBoltDatabase(dir) if err != nil { panic(fmt.Sprintf("can't create temporary database: %v", err)) } return dir, diskdb } func getString(trie *Trie, k string) []byte { v, _ := trie.Get([]byte(k)) return v } func updateString(trie *Trie, k, v string) { trie.Update([]byte(k), []byte(v), 0) } func deleteString(trie *Trie, k string) { trie.Delete([]byte(k), 0) } func TestDeepHash(t *testing.T) { acc := accounts.NewAccount() prefix := "prefix" var testdata = [][]struct { key string value string }{ {{"key1", "value1"}}, {{"key1", "value1"}, {"key2", "value2"}}, {{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}}, {{"key1", "value1"}, {"key2", "value2"}, {"\xffek3", "value3"}}, } for i, keyVals := range testdata { fmt.Println("Test", i) trie := New(common.Hash{}) for _, keyVal := range keyVals { trie.Update([]byte(keyVal.key), []byte(keyVal.value), 0) } trie.PrintTrie() hash1 := trie.Hash() prefixTrie := New(common.Hash{}) prefixTrie.UpdateAccount([]byte(prefix), &acc) for _, keyVal := range keyVals { // Add a prefix to every key prefixTrie.Update([]byte(prefix+keyVal.key), []byte(keyVal.value), 0) } got2, hash2 := prefixTrie.DeepHash([]byte(prefix)) if !got2 { t.Errorf("Expected DeepHash returning true, got false, testcase %d", i) } if hash1 != hash2 { t.Errorf("DeepHash mistmatch: %x, expected %x, testcase %d", hash2, hash1, i) } } }