github.com/theQRL/go-zond@v0.2.1/core/state/statedb_fuzz_test.go (about) 1 // Copyright 2023 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 state 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "math" 25 "math/big" 26 "math/rand" 27 "reflect" 28 "strings" 29 "testing" 30 "testing/quick" 31 32 "github.com/theQRL/go-zond/common" 33 "github.com/theQRL/go-zond/core/rawdb" 34 "github.com/theQRL/go-zond/core/state/snapshot" 35 "github.com/theQRL/go-zond/core/types" 36 "github.com/theQRL/go-zond/crypto" 37 "github.com/theQRL/go-zond/rlp" 38 "github.com/theQRL/go-zond/trie" 39 "github.com/theQRL/go-zond/trie/triedb/pathdb" 40 "github.com/theQRL/go-zond/trie/triestate" 41 ) 42 43 // A stateTest checks that the state changes are correctly captured. Instances 44 // of this test with pseudorandom content are created by Generate. 45 // 46 // The test works as follows: 47 // 48 // A list of states are created by applying actions. The state changes between 49 // each state instance are tracked and be verified. 50 type stateTest struct { 51 addrs []common.Address // all account addresses 52 actions [][]testAction // modifications to the state, grouped by block 53 chunk int // The number of actions per chunk 54 err error // failure details are reported through this field 55 } 56 57 // newStateTestAction creates a random action that changes state. 58 func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction { 59 actions := []testAction{ 60 { 61 name: "SetBalance", 62 fn: func(a testAction, s *StateDB) { 63 s.SetBalance(addr, big.NewInt(a.args[0])) 64 }, 65 args: make([]int64, 1), 66 }, 67 { 68 name: "SetNonce", 69 fn: func(a testAction, s *StateDB) { 70 s.SetNonce(addr, uint64(a.args[0])) 71 }, 72 args: make([]int64, 1), 73 }, 74 { 75 name: "SetState", 76 fn: func(a testAction, s *StateDB) { 77 var key, val common.Hash 78 binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) 79 binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) 80 s.SetState(addr, key, val) 81 }, 82 args: make([]int64, 2), 83 }, 84 { 85 name: "SetCode", 86 fn: func(a testAction, s *StateDB) { 87 code := make([]byte, 16) 88 binary.BigEndian.PutUint64(code, uint64(a.args[0])) 89 binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) 90 s.SetCode(addr, code) 91 }, 92 args: make([]int64, 2), 93 }, 94 { 95 name: "CreateAccount", 96 fn: func(a testAction, s *StateDB) { 97 s.CreateAccount(addr) 98 }, 99 }, 100 } 101 var nonRandom = index != -1 102 if index == -1 { 103 index = r.Intn(len(actions)) 104 } 105 action := actions[index] 106 var names []string 107 if !action.noAddr { 108 names = append(names, addr.Hex()) 109 } 110 for i := range action.args { 111 if nonRandom { 112 action.args[i] = rand.Int63n(10000) + 1 // set balance to non-zero 113 } else { 114 action.args[i] = rand.Int63n(10000) 115 } 116 names = append(names, fmt.Sprint(action.args[i])) 117 } 118 action.name += " " + strings.Join(names, ", ") 119 return action 120 } 121 122 // Generate returns a new snapshot test of the given size. All randomness is 123 // derived from r. 124 func (*stateTest) Generate(r *rand.Rand, size int) reflect.Value { 125 addrs := make([]common.Address, 5) 126 for i := range addrs { 127 addrs[i][0] = byte(i) 128 } 129 actions := make([][]testAction, rand.Intn(5)+1) 130 131 addr, _ := common.NewAddressFromString("Z00000000000000000000000000000000deadbeef") 132 for i := 0; i < len(actions); i++ { 133 actions[i] = make([]testAction, size) 134 for j := range actions[i] { 135 if j == 0 { 136 // Always include a set balance action to make sure 137 // the state changes are not empty. 138 actions[i][j] = newStateTestAction(addr, r, 0) 139 continue 140 } 141 actions[i][j] = newStateTestAction(addrs[r.Intn(len(addrs))], r, -1) 142 } 143 } 144 chunk := int(math.Sqrt(float64(size))) 145 if size > 0 && chunk == 0 { 146 chunk = 1 147 } 148 return reflect.ValueOf(&stateTest{ 149 addrs: addrs, 150 actions: actions, 151 chunk: chunk, 152 }) 153 } 154 155 func (test *stateTest) String() string { 156 out := new(bytes.Buffer) 157 for i, actions := range test.actions { 158 fmt.Fprintf(out, "---- block %d ----\n", i) 159 for j, action := range actions { 160 if j%test.chunk == 0 { 161 fmt.Fprintf(out, "---- transaction %d ----\n", j/test.chunk) 162 } 163 fmt.Fprintf(out, "%4d: %s\n", j%test.chunk, action.name) 164 } 165 } 166 return out.String() 167 } 168 169 func (test *stateTest) run() bool { 170 var ( 171 roots []common.Hash 172 accountList []map[common.Address][]byte 173 storageList []map[common.Address]map[common.Hash][]byte 174 onCommit = func(states *triestate.Set) { 175 accountList = append(accountList, copySet(states.Accounts)) 176 storageList = append(storageList, copy2DSet(states.Storages)) 177 } 178 disk = rawdb.NewMemoryDatabase() 179 tdb = trie.NewDatabase(disk, &trie.Config{PathDB: pathdb.Defaults}) 180 sdb = NewDatabaseWithNodeDB(disk, tdb) 181 byzantium = rand.Intn(2) == 0 182 ) 183 defer disk.Close() 184 defer tdb.Close() 185 186 var snaps *snapshot.Tree 187 if rand.Intn(3) == 0 { 188 snaps, _ = snapshot.New(snapshot.Config{ 189 CacheSize: 1, 190 Recovery: false, 191 NoBuild: false, 192 AsyncBuild: false, 193 }, disk, tdb, types.EmptyRootHash) 194 } 195 for i, actions := range test.actions { 196 root := types.EmptyRootHash 197 if i != 0 { 198 root = roots[len(roots)-1] 199 } 200 state, err := New(root, sdb, snaps) 201 if err != nil { 202 panic(err) 203 } 204 state.onCommit = onCommit 205 206 for i, action := range actions { 207 if i%test.chunk == 0 && i != 0 { 208 if byzantium { 209 state.Finalise(true) // call finalise at the transaction boundary 210 } else { 211 state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary 212 } 213 } 214 action.fn(action, state) 215 } 216 if byzantium { 217 state.Finalise(true) // call finalise at the transaction boundary 218 } else { 219 state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary 220 } 221 nroot, err := state.Commit(0, true) // call commit at the block boundary 222 if err != nil { 223 panic(err) 224 } 225 if nroot == root { 226 return true // filter out non-change state transition 227 } 228 roots = append(roots, nroot) 229 } 230 for i := 0; i < len(test.actions); i++ { 231 root := types.EmptyRootHash 232 if i != 0 { 233 root = roots[i-1] 234 } 235 test.err = test.verify(root, roots[i], tdb, accountList[i], storageList[i]) 236 if test.err != nil { 237 return false 238 } 239 } 240 return true 241 } 242 243 // verifyAccountCreation this function is called once the state diff says that 244 // specific account was not present. A serial of checks will be performed to 245 // ensure the state diff is correct, includes: 246 // 247 // - the account was indeed not present in trie 248 // - the account is present in new trie, nil->nil is regarded as invalid 249 // - the slots transition is correct 250 func (test *stateTest) verifyAccountCreation(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, slots map[common.Hash][]byte) error { 251 // Verify account change 252 addrHash := crypto.Keccak256Hash(addr.Bytes()) 253 oBlob, err := otr.Get(addrHash.Bytes()) 254 if err != nil { 255 return err 256 } 257 nBlob, err := ntr.Get(addrHash.Bytes()) 258 if err != nil { 259 return err 260 } 261 if len(oBlob) != 0 { 262 return fmt.Errorf("unexpected account in old trie, %x", addrHash) 263 } 264 if len(nBlob) == 0 { 265 return fmt.Errorf("missing account in new trie, %x", addrHash) 266 } 267 268 // Verify storage changes 269 var nAcct types.StateAccount 270 if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { 271 return err 272 } 273 // Account has no slot, empty slot set is expected 274 if nAcct.Root == types.EmptyRootHash { 275 if len(slots) != 0 { 276 return fmt.Errorf("unexpected slot changes %x", addrHash) 277 } 278 return nil 279 } 280 // Account has slots, ensure all new slots are contained 281 st, err := trie.New(trie.StorageTrieID(next, addrHash, nAcct.Root), db) 282 if err != nil { 283 return err 284 } 285 for key, val := range slots { 286 st.Update(key.Bytes(), val) 287 } 288 if st.Hash() != types.EmptyRootHash { 289 return errors.New("invalid slot changes") 290 } 291 return nil 292 } 293 294 // verifyAccountUpdate this function is called once the state diff says that 295 // specific account was present. A serial of checks will be performed to 296 // ensure the state diff is correct, includes: 297 // 298 // - the account was indeed present in trie 299 // - the account in old trie matches the provided value 300 // - the slots transition is correct 301 func (test *stateTest) verifyAccountUpdate(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, origin []byte, slots map[common.Hash][]byte) error { 302 // Verify account change 303 addrHash := crypto.Keccak256Hash(addr.Bytes()) 304 oBlob, err := otr.Get(addrHash.Bytes()) 305 if err != nil { 306 return err 307 } 308 nBlob, err := ntr.Get(addrHash.Bytes()) 309 if err != nil { 310 return err 311 } 312 if len(oBlob) == 0 { 313 return fmt.Errorf("missing account in old trie, %x", addrHash) 314 } 315 full, err := types.FullAccountRLP(origin) 316 if err != nil { 317 return err 318 } 319 if !bytes.Equal(full, oBlob) { 320 return fmt.Errorf("account value is not matched, %x", addrHash) 321 } 322 323 // Decode accounts 324 var ( 325 oAcct types.StateAccount 326 nAcct types.StateAccount 327 nRoot common.Hash 328 ) 329 if err := rlp.DecodeBytes(oBlob, &oAcct); err != nil { 330 return err 331 } 332 if len(nBlob) == 0 { 333 nRoot = types.EmptyRootHash 334 } else { 335 if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { 336 return err 337 } 338 nRoot = nAcct.Root 339 } 340 341 // Verify storage 342 st, err := trie.New(trie.StorageTrieID(next, addrHash, nRoot), db) 343 if err != nil { 344 return err 345 } 346 for key, val := range slots { 347 st.Update(key.Bytes(), val) 348 } 349 if st.Hash() != oAcct.Root { 350 return errors.New("invalid slot changes") 351 } 352 return nil 353 } 354 355 func (test *stateTest) verify(root common.Hash, next common.Hash, db *trie.Database, accountsOrigin map[common.Address][]byte, storagesOrigin map[common.Address]map[common.Hash][]byte) error { 356 otr, err := trie.New(trie.StateTrieID(root), db) 357 if err != nil { 358 return err 359 } 360 ntr, err := trie.New(trie.StateTrieID(next), db) 361 if err != nil { 362 return err 363 } 364 for addr, account := range accountsOrigin { 365 var err error 366 if len(account) == 0 { 367 err = test.verifyAccountCreation(next, db, otr, ntr, addr, storagesOrigin[addr]) 368 } else { 369 err = test.verifyAccountUpdate(next, db, otr, ntr, addr, accountsOrigin[addr], storagesOrigin[addr]) 370 } 371 if err != nil { 372 return err 373 } 374 } 375 return nil 376 } 377 378 func TestStateChanges(t *testing.T) { 379 config := &quick.Config{MaxCount: 1000} 380 err := quick.Check((*stateTest).run, config) 381 if cerr, ok := err.(*quick.CheckError); ok { 382 test := cerr.In[0].(*stateTest) 383 t.Errorf("%v:\n%s", test.err, test) 384 } else if err != nil { 385 t.Error(err) 386 } 387 }