aboutsummaryrefslogblamecommitdiffstats
path: root/trie/trie_test.go
blob: 121ba24c1e1d2d912f712dc8c05692d4f022c85f (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"
    "os"
    "testing"

    "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()

    ClearGlobalCache()

    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"))
    ClearGlobalCache()

    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 paranoiaCheck(t1 *Trie) (bool, *Trie) {
    t2 := new(Trie)
    it := NewIterator(t1)
    for it.Next() {
        t2.Update(it.Key, it.Value)
    }
    return t2.Hash() == t1.Hash(), t2
}

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

    vals := []struct{ k, v string }{
        {"do", "verb"},
        {"ether", "wookiedoo"},
        {"horse", "stallion"},
        {"shaman", "horse"},
        {"doge", "coin"},
        {"ether", ""},
        {"dog", "puppy"},
        {"shaman", ""},
        {"somethingveryoddindeedthis is", "myothernodedata"},
    }
    for _, val := range vals {
        updateString(trie, val.k, val.v)
    }
    trie.Commit()

    ok, t2 := paranoiaCheck(trie)
    if !ok {
        t.Errorf("trie paranoia check failed %x %x", trie.Hash(), t2.Hash())
    }
}

// Not an actual test
func TestOutput(t *testing.T) {
    t.Skip()

    base := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    trie := newEmpty()
    for i := 0; i < 50; i++ {
        updateString(trie, fmt.Sprintf("%s%d", base, i), "valueeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
    }
    fmt.Println("############################## FULL ################################")
    fmt.Println(trie.root)

    trie.Commit()
    fmt.Println("############################## SMALL ################################")
    trie2, _ := New(trie.Hash(), trie.db)
    getString(trie2, base+"20")
    fmt.Println(trie2.root)
}

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 kv struct {
    k, v []byte
    t    bool
}

func TestLargeData(t *testing.T) {
    trie := newEmpty()
    vals := make(map[string]*kv)

    for i := byte(0); i < 255; i++ {
        value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
        value2 := &kv{common.LeftPadBytes([]byte{10, i}, 32), []byte{i}, false}
        trie.Update(value.k, value.v)
        trie.Update(value2.k, value2.v)
        vals[string(value.k)] = value
        vals[string(value2.k)] = value2
    }

    it := NewIterator(trie)
    for it.Next() {
        vals[string(it.Key)].t = true
    }

    var untouched []*kv
    for _, value := range vals {
        if !value.t {
            untouched = append(untouched, value)
        }
    }

    if len(untouched) > 0 {
        t.Errorf("Missed %d nodes", len(untouched))
        for _, value := range untouched {
            t.Error(value)
        }
    }
}

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 {
        dir, tmpdb := tempDB()
        defer os.RemoveAll(dir)
        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)
    }
}

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