github.com/ethereum/go-ethereum@v1.14.3/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/rand" 26 "reflect" 27 "strings" 28 "testing" 29 "testing/quick" 30 31 "github.com/ethereum/go-ethereum/common" 32 "github.com/ethereum/go-ethereum/core/rawdb" 33 "github.com/ethereum/go-ethereum/core/state/snapshot" 34 "github.com/ethereum/go-ethereum/core/tracing" 35 "github.com/ethereum/go-ethereum/core/types" 36 "github.com/ethereum/go-ethereum/crypto" 37 "github.com/ethereum/go-ethereum/rlp" 38 "github.com/ethereum/go-ethereum/trie" 39 "github.com/ethereum/go-ethereum/trie/triestate" 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])) 73 }, 74 args: make([]int64, 1), 75 }, 76 { 77 name: "SetState", 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 accountList []map[common.Address][]byte 182 storageList []map[common.Address]map[common.Hash][]byte 183 onCommit = func(states *triestate.Set) { 184 accountList = append(accountList, copySet(states.Accounts)) 185 storageList = append(storageList, copy2DSet(states.Storages)) 186 } 187 disk = rawdb.NewMemoryDatabase() 188 tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults}) 189 sdb = NewDatabaseWithNodeDB(disk, tdb) 190 byzantium = rand.Intn(2) == 0 191 ) 192 defer disk.Close() 193 defer tdb.Close() 194 195 var snaps *snapshot.Tree 196 if rand.Intn(3) == 0 { 197 snaps, _ = snapshot.New(snapshot.Config{ 198 CacheSize: 1, 199 Recovery: false, 200 NoBuild: false, 201 AsyncBuild: false, 202 }, disk, tdb, types.EmptyRootHash) 203 } 204 for i, actions := range test.actions { 205 root := types.EmptyRootHash 206 if i != 0 { 207 root = roots[len(roots)-1] 208 } 209 state, err := New(root, sdb, snaps) 210 if err != nil { 211 panic(err) 212 } 213 state.onCommit = onCommit 214 215 for i, action := range actions { 216 if i%test.chunk == 0 && i != 0 { 217 if byzantium { 218 state.Finalise(true) // call finalise at the transaction boundary 219 } else { 220 state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary 221 } 222 } 223 action.fn(action, state) 224 } 225 if byzantium { 226 state.Finalise(true) // call finalise at the transaction boundary 227 } else { 228 state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary 229 } 230 nroot, err := state.Commit(0, true) // call commit at the block boundary 231 if err != nil { 232 panic(err) 233 } 234 if nroot == root { 235 return true // filter out non-change state transition 236 } 237 roots = append(roots, nroot) 238 } 239 for i := 0; i < len(test.actions); i++ { 240 root := types.EmptyRootHash 241 if i != 0 { 242 root = roots[i-1] 243 } 244 test.err = test.verify(root, roots[i], tdb, accountList[i], storageList[i]) 245 if test.err != nil { 246 return false 247 } 248 } 249 return true 250 } 251 252 // verifyAccountCreation this function is called once the state diff says that 253 // specific account was not present. A serial of checks will be performed to 254 // ensure the state diff is correct, includes: 255 // 256 // - the account was indeed not present in trie 257 // - the account is present in new trie, nil->nil is regarded as invalid 258 // - the slots transition is correct 259 func (test *stateTest) verifyAccountCreation(next common.Hash, db *triedb.Database, otr, ntr *trie.Trie, addr common.Address, slots map[common.Hash][]byte) error { 260 // Verify account change 261 addrHash := crypto.Keccak256Hash(addr.Bytes()) 262 oBlob, err := otr.Get(addrHash.Bytes()) 263 if err != nil { 264 return err 265 } 266 nBlob, err := ntr.Get(addrHash.Bytes()) 267 if err != nil { 268 return err 269 } 270 if len(oBlob) != 0 { 271 return fmt.Errorf("unexpected account in old trie, %x", addrHash) 272 } 273 if len(nBlob) == 0 { 274 return fmt.Errorf("missing account in new trie, %x", addrHash) 275 } 276 277 // Verify storage changes 278 var nAcct types.StateAccount 279 if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { 280 return err 281 } 282 // Account has no slot, empty slot set is expected 283 if nAcct.Root == types.EmptyRootHash { 284 if len(slots) != 0 { 285 return fmt.Errorf("unexpected slot changes %x", addrHash) 286 } 287 return nil 288 } 289 // Account has slots, ensure all new slots are contained 290 st, err := trie.New(trie.StorageTrieID(next, addrHash, nAcct.Root), db) 291 if err != nil { 292 return err 293 } 294 for key, val := range slots { 295 st.Update(key.Bytes(), val) 296 } 297 if st.Hash() != types.EmptyRootHash { 298 return errors.New("invalid slot changes") 299 } 300 return nil 301 } 302 303 // verifyAccountUpdate this function is called once the state diff says that 304 // specific account was present. A serial of checks will be performed to 305 // ensure the state diff is correct, includes: 306 // 307 // - the account was indeed present in trie 308 // - the account in old trie matches the provided value 309 // - the slots transition is correct 310 func (test *stateTest) verifyAccountUpdate(next common.Hash, db *triedb.Database, otr, ntr *trie.Trie, addr common.Address, origin []byte, slots map[common.Hash][]byte) error { 311 // Verify account change 312 addrHash := crypto.Keccak256Hash(addr.Bytes()) 313 oBlob, err := otr.Get(addrHash.Bytes()) 314 if err != nil { 315 return err 316 } 317 nBlob, err := ntr.Get(addrHash.Bytes()) 318 if err != nil { 319 return err 320 } 321 if len(oBlob) == 0 { 322 return fmt.Errorf("missing account in old trie, %x", addrHash) 323 } 324 full, err := types.FullAccountRLP(origin) 325 if err != nil { 326 return err 327 } 328 if !bytes.Equal(full, oBlob) { 329 return fmt.Errorf("account value is not matched, %x", addrHash) 330 } 331 332 // Decode accounts 333 var ( 334 oAcct types.StateAccount 335 nAcct types.StateAccount 336 nRoot common.Hash 337 ) 338 if err := rlp.DecodeBytes(oBlob, &oAcct); err != nil { 339 return err 340 } 341 if len(nBlob) == 0 { 342 nRoot = types.EmptyRootHash 343 } else { 344 if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { 345 return err 346 } 347 nRoot = nAcct.Root 348 } 349 350 // Verify storage 351 st, err := trie.New(trie.StorageTrieID(next, addrHash, nRoot), db) 352 if err != nil { 353 return err 354 } 355 for key, val := range slots { 356 st.Update(key.Bytes(), val) 357 } 358 if st.Hash() != oAcct.Root { 359 return errors.New("invalid slot changes") 360 } 361 return nil 362 } 363 364 func (test *stateTest) verify(root common.Hash, next common.Hash, db *triedb.Database, accountsOrigin map[common.Address][]byte, storagesOrigin map[common.Address]map[common.Hash][]byte) error { 365 otr, err := trie.New(trie.StateTrieID(root), db) 366 if err != nil { 367 return err 368 } 369 ntr, err := trie.New(trie.StateTrieID(next), db) 370 if err != nil { 371 return err 372 } 373 for addr, account := range accountsOrigin { 374 var err error 375 if len(account) == 0 { 376 err = test.verifyAccountCreation(next, db, otr, ntr, addr, storagesOrigin[addr]) 377 } else { 378 err = test.verifyAccountUpdate(next, db, otr, ntr, addr, accountsOrigin[addr], storagesOrigin[addr]) 379 } 380 if err != nil { 381 return err 382 } 383 } 384 return nil 385 } 386 387 func TestStateChanges(t *testing.T) { 388 config := &quick.Config{MaxCount: 1000} 389 err := quick.Check((*stateTest).run, config) 390 if cerr, ok := err.(*quick.CheckError); ok { 391 test := cerr.In[0].(*stateTest) 392 t.Errorf("%v:\n%s", test.err, test) 393 } else if err != nil { 394 t.Error(err) 395 } 396 }