github.com/carter-ya/go-ethereum@v0.0.0-20230628080049-d2309be3983b/tests/fuzzers/stacktrie/trie_fuzzer.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 stacktrie 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "hash" 25 "io" 26 "sort" 27 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/trie" 30 "golang.org/x/crypto/sha3" 31 ) 32 33 type fuzzer struct { 34 input io.Reader 35 exhausted bool 36 debugging bool 37 } 38 39 func (f *fuzzer) read(size int) []byte { 40 out := make([]byte, size) 41 if _, err := f.input.Read(out); err != nil { 42 f.exhausted = true 43 } 44 return out 45 } 46 47 func (f *fuzzer) readSlice(min, max int) []byte { 48 var a uint16 49 binary.Read(f.input, binary.LittleEndian, &a) 50 size := min + int(a)%(max-min) 51 out := make([]byte, size) 52 if _, err := f.input.Read(out); err != nil { 53 f.exhausted = true 54 } 55 return out 56 } 57 58 // spongeDb is a dummy db backend which accumulates writes in a sponge 59 type spongeDb struct { 60 sponge hash.Hash 61 debug bool 62 } 63 64 func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") } 65 func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") } 66 func (s *spongeDb) Delete(key []byte) error { panic("implement me") } 67 func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} } 68 func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} } 69 func (s *spongeDb) NewSnapshot() (ethdb.Snapshot, error) { panic("implement me") } 70 func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") } 71 func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } 72 func (s *spongeDb) Close() error { return nil } 73 74 func (s *spongeDb) Put(key []byte, value []byte) error { 75 if s.debug { 76 fmt.Printf("db.Put %x : %x\n", key, value) 77 } 78 s.sponge.Write(key) 79 s.sponge.Write(value) 80 return nil 81 } 82 func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } 83 84 // spongeBatch is a dummy batch which immediately writes to the underlying spongedb 85 type spongeBatch struct { 86 db *spongeDb 87 } 88 89 func (b *spongeBatch) Put(key, value []byte) error { 90 b.db.Put(key, value) 91 return nil 92 } 93 func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } 94 func (b *spongeBatch) ValueSize() int { return 100 } 95 func (b *spongeBatch) Write() error { return nil } 96 func (b *spongeBatch) Reset() {} 97 func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil } 98 99 type kv struct { 100 k, v []byte 101 } 102 type kvs []kv 103 104 func (k kvs) Len() int { 105 return len(k) 106 } 107 108 func (k kvs) Less(i, j int) bool { 109 return bytes.Compare(k[i].k, k[j].k) < 0 110 } 111 112 func (k kvs) Swap(i, j int) { 113 k[j], k[i] = k[i], k[j] 114 } 115 116 // The function must return 117 // 118 // - 1 if the fuzzer should increase priority of the 119 // given input during subsequent fuzzing (for example, the input is lexically 120 // correct and was parsed successfully); 121 // - -1 if the input must not be added to corpus even if gives new coverage; and 122 // - 0 otherwise 123 // 124 // other values are reserved for future use. 125 func Fuzz(data []byte) int { 126 f := fuzzer{ 127 input: bytes.NewReader(data), 128 exhausted: false, 129 } 130 return f.fuzz() 131 } 132 133 func Debug(data []byte) int { 134 f := fuzzer{ 135 input: bytes.NewReader(data), 136 exhausted: false, 137 debugging: true, 138 } 139 return f.fuzz() 140 } 141 142 func (f *fuzzer) fuzz() int { 143 // This spongeDb is used to check the sequence of disk-db-writes 144 var ( 145 spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()} 146 dbA = trie.NewDatabase(spongeA) 147 trieA = trie.NewEmpty(dbA) 148 spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()} 149 trieB = trie.NewStackTrie(spongeB) 150 vals kvs 151 useful bool 152 maxElements = 10000 153 // operate on unique keys only 154 keys = make(map[string]struct{}) 155 ) 156 // Fill the trie with elements 157 for i := 0; !f.exhausted && i < maxElements; i++ { 158 k := f.read(32) 159 v := f.readSlice(1, 500) 160 if f.exhausted { 161 // If it was exhausted while reading, the value may be all zeroes, 162 // thus 'deletion' which is not supported on stacktrie 163 break 164 } 165 if _, present := keys[string(k)]; present { 166 // This key is a duplicate, ignore it 167 continue 168 } 169 keys[string(k)] = struct{}{} 170 vals = append(vals, kv{k: k, v: v}) 171 trieA.Update(k, v) 172 useful = true 173 } 174 if !useful { 175 return 0 176 } 177 // Flush trie -> database 178 rootA, nodes, err := trieA.Commit(false) 179 if err != nil { 180 panic(err) 181 } 182 if nodes != nil { 183 dbA.Update(trie.NewWithNodeSet(nodes)) 184 } 185 // Flush memdb -> disk (sponge) 186 dbA.Commit(rootA, false, nil) 187 188 // Stacktrie requires sorted insertion 189 sort.Sort(vals) 190 for _, kv := range vals { 191 if f.debugging { 192 fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v) 193 } 194 trieB.Update(kv.k, kv.v) 195 } 196 rootB := trieB.Hash() 197 if _, err := trieB.Commit(); err != nil { 198 panic(err) 199 } 200 if rootA != rootB { 201 panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB)) 202 } 203 sumA := spongeA.sponge.Sum(nil) 204 sumB := spongeB.sponge.Sum(nil) 205 if !bytes.Equal(sumA, sumB) { 206 panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB)) 207 } 208 return 1 209 }