aboutsummaryrefslogblamecommitdiffstats
path: root/trie/trie_test.go
blob: 14ac5a6669c61f4c8b46a4ecffc355a2ca55a453 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                         
                                                
  
                                                                                  



                                                                              
                                                                             
                                                                 
                                                               


                                                                           
                                                                                  
 
            

        
               
                         
             
                   
                   
            
                 
                 
                       
 
                                         
                                                
                                               

 


                                         

 




                                         

 
                                  
                     
                          

                                    

                                                        

 
                             
                     





                                                                     





                                                                                          
                                                  
                                                             


         






                                                                        






























                                                                                                     































                                                              
                               
                          
 


                                                 
 
                                                                                                   
                           
                        

                                                    
 

                                                                                     
 





                                                                                                  

                                                    
 
 
                            









                                                              
 



                                                                
 



                              
         

 
                               
                          








                                        
         

                                  
                                                        
                        
                                                 
                 
         
 
                           

                                                                                                   
                                                         
         
 
 
                                    
                          
 








                                        
         
                                  
                                                

         
                           

                                                                                                   
                                                         
         

 
                                    
                          





                                        
                                 
                                                                     
         
                                  
                                                
         


                                                 
         
 














                                                                                

                                                                       
 

                                                   


                                       





                                                                        
         

                                                 
         
                                              


                                                                       
 




                                                                

 











































                                                                                                     





                                                     







                       
                             















































                                                                         
 













































                                                                                                          
                                           
                                                                                       




                   


                                                                                                   

                              

                                        
                       





















                                                                                            

                                    







                                                             

 









                                                                            
                         
                   
                                    
                                                   














                                                           






                                                   


                                                          
                          








                                                  
                          











                                             
                                  



                                                                               
                                                    




                                                                              











                                             
// 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 <http://www.gnu.org/licenses/>.

package trie

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io/ioutil"
    "math/rand"
    "os"
    "reflect"
    "testing"
    "testing/quick"

    "github.com/davecgh/go-spew/spew"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethdb"
)

func init() {
    spew.Config.Indent = "    "
    spew.Config.DisableMethods = true
}

// Used for testing
func newEmpty() *Trie {
    db, _ := ethdb.NewMemDatabase()
    trie, _ := New(common.Hash{}, db)
    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 := common.FromHex("0x823140710bf13990e4500136726d8b55")
    trie.Update(key, value)
    value = trie.Get(key)
}

func TestMissingRoot(t *testing.T) {
    db, _ := ethdb.NewMemDatabase()
    trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), db)
    if trie != nil {
        t.Error("New returned non-nil trie for invalid root")
    }
    if _, ok := err.(*MissingNodeError); !ok {
        t.Errorf("New returned wrong error: %v", err)
    }
}

func TestMissingNode(t *testing.T) {
    db, _ := ethdb.NewMemDatabase()
    trie, _ := New(common.Hash{}, db)
    updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
    updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
    root, _ := trie.Commit()

    trie, _ = New(root, db)
    _, err := trie.TryGet([]byte("120000"))
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }

    trie, _ = New(root, db)
    _, err = trie.TryGet([]byte("120099"))
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }

    trie, _ = New(root, db)
    _, err = trie.TryGet([]byte("123456"))
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }

    trie, _ = New(root, db)
    err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv"))
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }

    trie, _ = New(root, db)
    err = trie.TryDelete([]byte("123456"))
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }

    db.Delete(common.FromHex("e1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9"))

    trie, _ = New(root, db)
    _, err = trie.TryGet([]byte("120000"))
    if _, ok := err.(*MissingNodeError); !ok {
        t.Errorf("Wrong error: %v", err)
    }

    trie, _ = New(root, db)
    _, err = trie.TryGet([]byte("120099"))
    if _, ok := err.(*MissingNodeError); !ok {
        t.Errorf("Wrong error: %v", err)
    }

    trie, _ = New(root, db)
    _, err = trie.TryGet([]byte("123456"))
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }

    trie, _ = New(root, db)
    err = trie.TryUpdate([]byte("120099"), []byte("zxcv"))
    if _, ok := err.(*MissingNodeError); !ok {
        t.Errorf("Wrong error: %v", err)
    }

    trie, _ = New(root, db)
    err = trie.TryDelete([]byte("123456"))
    if _, ok := err.(*MissingNodeError); !ok {
        t.Errorf("Wrong error: %v", err)
    }
}

func TestInsert(t *testing.T) {
    trie := newEmpty()

    updateString(trie, "doe", "reindeer")
    updateString(trie, "dog", "puppy")
    updateString(trie, "dogglesworth", "cat")

    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, err := trie.Commit()
    if err != nil {
        t.Fatalf("commit error: %v", err)
    }
    if root != exp {
        t.Errorf("exp %x got %x", exp, root)
    }
}

func TestGet(t *testing.T) {
    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
        }
        trie.Commit()
    }
}

func TestDelete(t *testing.T) {
    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) {
    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) {
    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, err := trie.Commit()
    if err != nil {
        t.Fatalf("commit error: %v", err)
    }

    // create a new trie on top of the database and check that lookups work.
    trie2, err := New(exp, trie.db)
    if err != nil {
        t.Fatalf("can't recreate trie at %x: %v", exp, err)
    }
    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, err := trie2.Commit()
    if err != nil {
        t.Fatalf("commit error: %v", err)
    }
    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})
    trie.Update([]byte("key2"), bytes.Repeat([]byte{1}, 32))
    trie.Hash()
}

type countingDB struct {
    Database
    gets map[string]int
}

func (db *countingDB) Get(key []byte) ([]byte, error) {
    db.gets[string(key)]++
    return db.Database.Get(key)
}

// TestCacheUnload checks that decoded nodes are unloaded after a
// certain number of commit operations.
func TestCacheUnload(t *testing.T) {
    // Create test trie with two branches.
    trie := newEmpty()
    key1 := "---------------------------------"
    key2 := "---some other branch"
    updateString(trie, key1, "this is the branch of key1.")
    updateString(trie, key2, "this is the branch of key2.")
    root, _ := trie.Commit()

    // Commit the trie repeatedly and access key1.
    // The branch containing it is loaded from DB exactly two times:
    // in the 0th and 6th iteration.
    db := &countingDB{Database: trie.db, gets: make(map[string]int)}
    trie, _ = New(root, db)
    trie.SetCacheLimit(5)
    for i := 0; i < 12; i++ {
        getString(trie, key1)
        trie.Commit()
    }

    // Check that it got loaded two times.
    for dbkey, count := range db.gets {
        if count != 2 {
            t.Errorf("db key %x loaded %d times, want %d times", []byte(dbkey), count, 2)
        }
    }
}

// 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
}

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))
            randRead(r, 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)
}

// rand.Rand provides a Read method in Go 1.7 and later, but
// we can't use it yet.
func randRead(r *rand.Rand, b []byte) {
    pos := 0
    val := 0
    for n := 0; n < len(b); n++ {
        if pos == 0 {
            val = r.Int()
            pos = 7
        }
        b[n] = byte(val)
        val >>= 8
        pos--
    }
}

func runRandTest(rt randTest) bool {
    db, _ := ethdb.NewMemDatabase()
    tr, _ := New(common.Hash{}, db)
    values := make(map[string]string) // tracks content of the trie

    for _, step := range rt {
        switch step.op {
        case opUpdate:
            tr.Update(step.key, step.value)
            values[string(step.key)] = string(step.value)
        case opDelete:
            tr.Delete(step.key)
            delete(values, string(step.key))
        case opGet:
            v := tr.Get(step.key)
            want := values[string(step.key)]
            if string(v) != want {
                fmt.Printf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want)
                return false
            }
        case opCommit:
            if _, err := tr.Commit(); err != nil {
                panic(err)
            }
        case opHash:
            tr.Hash()
        case opReset:
            hash, err := tr.Commit()
            if err != nil {
                panic(err)
            }
            newtr, err := New(hash, db)
            if err != nil {
                panic(err)
            }
            tr = newtr
        case opItercheckhash:
            checktr, _ := New(common.Hash{}, nil)
            it := tr.Iterator()
            for it.Next() {
                checktr.Update(it.Key, it.Value)
            }
            if tr.Hash() != checktr.Hash() {
                fmt.Println("hashes not equal")
                return false
            }
        case opCheckCacheInvariant:
            return checkCacheInvariant(tr.root, nil, tr.cachegen, false, 0)
        }
    }
    return true
}

func checkCacheInvariant(n, parent node, parentCachegen uint16, parentDirty bool, depth int) bool {
    var children []node
    var flag nodeFlag
    switch n := n.(type) {
    case *shortNode:
        flag = n.flags
        children = []node{n.Val}
    case *fullNode:
        flag = n.flags
        children = n.Children[:]
    default:
        return true
    }

    showerror := func() {
        fmt.Printf("at depth %d node %s", depth, spew.Sdump(n))
        fmt.Printf("parent: %s", spew.Sdump(parent))
    }
    if flag.gen > parentCachegen {
        fmt.Printf("cache invariant violation: %d > %d\n", flag.gen, parentCachegen)
        showerror()
        return false
    }
    if depth > 0 && !parentDirty && flag.dirty {
        fmt.Printf("cache invariant violation: child is dirty but parent isn't\n")
        showerror()
        return false
    }
    for _, child := range children {
        if !checkCacheInvariant(child, n, flag.gen, flag.dirty, depth+1) {
            return false
        }
    }
    return true
}

func TestRandom(t *testing.T) {
    if err := quick.Check(runRandTest, nil); err != nil {
        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) }
func BenchmarkHashBE(b *testing.B)   { benchHash(b, binary.BigEndian) }
func BenchmarkHashLE(b *testing.B)   { benchHash(b, binary.LittleEndian) }

const benchElemCount = 20000

func benchGet(b *testing.B, commit bool) {
    trie := new(Trie)
    if commit {
        _, tmpdb := tempDB()
        trie, _ = New(common.Hash{}, tmpdb)
    }
    k := make([]byte, 32)
    for i := 0; i < benchElemCount; i++ {
        binary.LittleEndian.PutUint64(k, uint64(i))
        trie.Update(k, k)
    }
    binary.LittleEndian.PutUint64(k, benchElemCount/2)
    if commit {
        trie.Commit()
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        trie.Get(k)
    }
    b.StopTimer()

    if commit {
        ldb := trie.db.(*ethdb.LDBDatabase)
        ldb.Close()
        os.RemoveAll(ldb.Path())
    }
}

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)
    }
    return trie
}

func benchHash(b *testing.B, e binary.ByteOrder) {
    trie := newEmpty()
    k := make([]byte, 32)
    for i := 0; i < benchElemCount; i++ {
        e.PutUint64(k, uint64(i))
        trie.Update(k, k)
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        trie.Hash()
    }
}

func tempDB() (string, Database) {
    dir, err := ioutil.TempDir("", "trie-bench")
    if err != nil {
        panic(fmt.Sprintf("can't create temporary directory: %v", err))
    }
    db, err := ethdb.NewLDBDatabase(dir, 256, 0)
    if err != nil {
        panic(fmt.Sprintf("can't create temporary database: %v", err))
    }
    return dir, db
}

func getString(trie *Trie, k string) []byte {
    return trie.Get([]byte(k))
}

func updateString(trie *Trie, k, v string) {
    trie.Update([]byte(k), []byte(v))
}

func deleteString(trie *Trie, k string) {
    trie.Delete([]byte(k))
}