github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/trie/stacktrie_fuzzer_test.go (about)

     1  // Copyright 2020 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 trie
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"slices"
    24  	"testing"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    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/trie/trienode"
    31  )
    32  
    33  func FuzzStackTrie(f *testing.F) {
    34  	f.Fuzz(func(t *testing.T, data []byte) {
    35  		fuzz(data, false)
    36  	})
    37  }
    38  
    39  func fuzz(data []byte, debugging bool) {
    40  	// This spongeDb is used to check the sequence of disk-db-writes
    41  	var (
    42  		input   = bytes.NewReader(data)
    43  		spongeA = &spongeDb{sponge: crypto.NewKeccakState()}
    44  		dbA     = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme)
    45  		trieA   = NewEmpty(dbA)
    46  		spongeB = &spongeDb{sponge: crypto.NewKeccakState()}
    47  		dbB     = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme)
    48  		trieB   = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
    49  			rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
    50  		})
    51  		vals        []*kv
    52  		maxElements = 10000
    53  		// operate on unique keys only
    54  		keys = make(map[string]struct{})
    55  	)
    56  	// Fill the trie with elements
    57  	for i := 0; input.Len() > 0 && i < maxElements; i++ {
    58  		k := make([]byte, 32)
    59  		input.Read(k)
    60  		var a uint16
    61  		binary.Read(input, binary.LittleEndian, &a)
    62  		a = 1 + a%100
    63  		v := make([]byte, a)
    64  		input.Read(v)
    65  		if input.Len() == 0 {
    66  			// If it was exhausted while reading, the value may be all zeroes,
    67  			// thus 'deletion' which is not supported on stacktrie
    68  			break
    69  		}
    70  		if _, present := keys[string(k)]; present {
    71  			// This key is a duplicate, ignore it
    72  			continue
    73  		}
    74  		keys[string(k)] = struct{}{}
    75  		vals = append(vals, &kv{k: k, v: v})
    76  		trieA.MustUpdate(k, v)
    77  	}
    78  	if len(vals) == 0 {
    79  		return
    80  	}
    81  	// Flush trie -> database
    82  	rootA, nodes, err := trieA.Commit(false)
    83  	if err != nil {
    84  		panic(err)
    85  	}
    86  	if nodes != nil {
    87  		dbA.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
    88  	}
    89  	// Flush memdb -> disk (sponge)
    90  	dbA.Commit(rootA)
    91  
    92  	// Stacktrie requires sorted insertion
    93  	slices.SortFunc(vals, (*kv).cmp)
    94  
    95  	for _, kv := range vals {
    96  		if debugging {
    97  			fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v)
    98  		}
    99  		trieB.Update(kv.k, kv.v)
   100  	}
   101  	rootB := trieB.Hash()
   102  	if rootA != rootB {
   103  		panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
   104  	}
   105  	sumA := spongeA.sponge.Sum(nil)
   106  	sumB := spongeB.sponge.Sum(nil)
   107  	if !bytes.Equal(sumA, sumB) {
   108  		panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
   109  	}
   110  
   111  	// Ensure all the nodes are persisted correctly
   112  	var (
   113  		nodeset = make(map[string][]byte) // path -> blob
   114  		trieC   = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
   115  			if crypto.Keccak256Hash(blob) != hash {
   116  				panic("invalid node blob")
   117  			}
   118  			nodeset[string(path)] = common.CopyBytes(blob)
   119  		})
   120  		checked int
   121  	)
   122  	for _, kv := range vals {
   123  		trieC.Update(kv.k, kv.v)
   124  	}
   125  	rootC := trieC.Hash()
   126  	if rootA != rootC {
   127  		panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC))
   128  	}
   129  	trieA, _ = New(TrieID(rootA), dbA)
   130  	iterA := trieA.MustNodeIterator(nil)
   131  	for iterA.Next(true) {
   132  		if iterA.Hash() == (common.Hash{}) {
   133  			if _, present := nodeset[string(iterA.Path())]; present {
   134  				panic("unexpected tiny node")
   135  			}
   136  			continue
   137  		}
   138  		nodeBlob, present := nodeset[string(iterA.Path())]
   139  		if !present {
   140  			panic("missing node")
   141  		}
   142  		if !bytes.Equal(nodeBlob, iterA.NodeBlob()) {
   143  			panic("node blob is not matched")
   144  		}
   145  		checked += 1
   146  	}
   147  	if checked != len(nodeset) {
   148  		panic("node number is not matched")
   149  	}
   150  }