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