// Copyright 2016 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package state import ( "bytes" "encoding/binary" "fmt" "math" "math/big" "math/rand" "reflect" "strings" "sync" "testing" "testing/quick" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/core/types" ) // Tests that updating a state trie does not leak any database writes prior to // actually committing the state. func TestUpdateLeaks(t *testing.T) { // Create an empty state database db := rawdb.NewMemoryDatabase() state, _ := New(common.Hash{}, NewDatabase(db)) // Update it with some accounts for i := byte(0); i < 255; i++ { addr := common.BytesToAddress([]byte{i}) state.AddBalance(addr, big.NewInt(int64(11*i))) state.SetNonce(addr, uint64(42*i)) if i%2 == 0 { state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) } if i%3 == 0 { state.SetCode(addr, []byte{i, i, i, i, i}) } } root := state.IntermediateRoot(false) if err := state.Database().TrieDB().Commit(root, false); err != nil { t.Errorf("can not commit trie %v to persistent database", root.Hex()) } // Ensure that no data was leaked into the database it := db.NewIterator() for it.Next() { t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value()) } it.Release() } // Tests that no intermediate state of an object is stored into the database, // only the one right before the commit. func TestIntermediateLeaks(t *testing.T) { // Create two state databases, one transitioning to the final state, the other final from the beginning transDb := rawdb.NewMemoryDatabase() finalDb := rawdb.NewMemoryDatabase() transState, _ := New(common.Hash{}, NewDatabase(transDb)) finalState, _ := New(common.Hash{}, NewDatabase(finalDb)) modify := func(state *DB, addr common.Address, i, tweak byte) { state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) state.SetNonce(addr, uint64(42*i+tweak)) if i%2 == 0 { state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{}) state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak}) } if i%3 == 0 { state.SetCode(addr, []byte{i, i, i, i, i, tweak}) } } // Modify the transient state. for i := byte(0); i < 255; i++ { modify(transState, common.Address{i}, i, 0) } // Write modifications to trie. transState.IntermediateRoot(false) // Overwrite all the data with new values in the transient database. for i := byte(0); i < 255; i++ { modify(transState, common.Address{i}, i, 99) modify(finalState, common.Address{i}, i, 99) } // Commit and cross check the databases. transRoot, err := transState.Commit(false) if err != nil { t.Fatalf("failed to commit transition state: %v", err) } if err = transState.Database().TrieDB().Commit(transRoot, false); err != nil { t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) } finalRoot, err := finalState.Commit(false) if err != nil { t.Fatalf("failed to commit final state: %v", err) } if err = finalState.Database().TrieDB().Commit(finalRoot, false); err != nil { t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) } it := finalDb.NewIterator() for it.Next() { key, fvalue := it.Key(), it.Value() tvalue, err := transDb.Get(key) if err != nil { t.Errorf("entry missing from the transition database: %x -> %x", key, fvalue) } if !bytes.Equal(fvalue, tvalue) { t.Errorf("the value associate key %x is mismatch,: %x in transition database ,%x in final database", key, tvalue, fvalue) } } it.Release() it = transDb.NewIterator() for it.Next() { key, tvalue := it.Key(), it.Value() fvalue, err := finalDb.Get(key) if err != nil { t.Errorf("extra entry in the transition database: %x -> %x", key, it.Value()) } if !bytes.Equal(fvalue, tvalue) { t.Errorf("the value associate key %x is mismatch,: %x in transition database ,%x in final database", key, tvalue, fvalue) } } } // TestCopy tests that copying a statedb object indeed makes the original and // the copy independent of each other. This test is a regression test against // https://github.com/ethereum/go-ethereum/pull/15549. func TestCopy(t *testing.T) { // Create a random state test to copy and modify "independently" orig, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) for i := byte(0); i < 255; i++ { obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) obj.AddBalance(big.NewInt(int64(i))) orig.updateStateObject(obj) } orig.Finalise(false) // Copy the state copy := orig.Copy() // Copy the copy state ccopy := copy.Copy() // modify all in memory for i := byte(0); i < 255; i++ { origObj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) origObj.AddBalance(big.NewInt(2 * int64(i))) copyObj.AddBalance(big.NewInt(3 * int64(i))) ccopyObj.AddBalance(big.NewInt(4 * int64(i))) orig.updateStateObject(origObj) copy.updateStateObject(copyObj) ccopy.updateStateObject(copyObj) } // Finalise the changes on all concurrently finalise := func(wg *sync.WaitGroup, db *DB) { defer wg.Done() db.Finalise(true) } var wg sync.WaitGroup wg.Add(3) go finalise(&wg, orig) go finalise(&wg, copy) go finalise(&wg, ccopy) wg.Wait() // Verify that the three states have been updated independently for i := byte(0); i < 255; i++ { origObj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) if want := big.NewInt(3 * int64(i)); origObj.Balance().Cmp(want) != 0 { t.Errorf("orig obj %d: balance mismatch: have %v, want %v", i, origObj.Balance(), want) } if want := big.NewInt(4 * int64(i)); copyObj.Balance().Cmp(want) != 0 { t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, copyObj.Balance(), want) } if want := big.NewInt(5 * int64(i)); ccopyObj.Balance().Cmp(want) != 0 { t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, ccopyObj.Balance(), want) } } } func TestSnapshotRandom(t *testing.T) { config := &quick.Config{MaxCount: 1000} err := quick.Check((*snapshotTest).run, config) if cerr, ok := err.(*quick.CheckError); ok { test := cerr.In[0].(*snapshotTest) t.Errorf("%v:\n%s", test.err, test) } else if err != nil { t.Error(err) } } // A snapshotTest checks that reverting StateDB snapshots properly undoes all changes // captured by the snapshot. Instances of this test with pseudorandom content are created // by Generate. // // The test works as follows: // // A new state is created and all actions are applied to it. Several snapshots are taken // in between actions. The test then reverts each snapshot. For each snapshot the actions // leading up to it are replayed on a fresh, empty state. The behaviour of all public // accessor methods on the reverted state must match the return value of the equivalent // methods on the replayed state. type snapshotTest struct { addrs []common.Address // all account addresses actions []testAction // modifications to the state snapshots []int // actions indexes at which snapshot is taken err error // failure details are reported through this field } type testAction struct { name string fn func(testAction, *DB) args []int64 noAddr bool } // newTestAction creates a random action that changes state. func newTestAction(addr common.Address, r *rand.Rand) testAction { actions := []testAction{ { name: "SetBalance", fn: func(a testAction, s *DB) { s.SetBalance(addr, big.NewInt(a.args[0])) }, args: make([]int64, 1), }, { name: "AddBalance", fn: func(a testAction, s *DB) { s.AddBalance(addr, big.NewInt(a.args[0])) }, args: make([]int64, 1), }, { name: "SetNonce", fn: func(a testAction, s *DB) { s.SetNonce(addr, uint64(a.args[0])) }, args: make([]int64, 1), }, { name: "SetState", fn: func(a testAction, s *DB) { var key, val common.Hash binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) s.SetState(addr, key, val) }, args: make([]int64, 2), }, { name: "SetCode", fn: func(a testAction, s *DB) { code := make([]byte, 16) binary.BigEndian.PutUint64(code, uint64(a.args[0])) binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) s.SetCode(addr, code) }, args: make([]int64, 2), }, { name: "CreateAccount", fn: func(a testAction, s *DB) { s.CreateAccount(addr) }, }, { name: "Suicide", fn: func(a testAction, s *DB) { s.Suicide(addr) }, }, { name: "AddRefund", fn: func(a testAction, s *DB) { s.AddRefund(uint64(a.args[0])) }, args: make([]int64, 1), noAddr: true, }, { name: "AddLog", fn: func(a testAction, s *DB) { data := make([]byte, 2) binary.BigEndian.PutUint16(data, uint16(a.args[0])) s.AddLog(&types.Log{Address: addr, Data: data}) }, args: make([]int64, 1), }, { name: "AddPreimage", fn: func(a testAction, s *DB) { preimage := []byte{1} hash := common.BytesToHash(preimage) s.AddPreimage(hash, preimage) }, args: make([]int64, 1), }, } action := actions[r.Intn(len(actions))] var nameargs []string if !action.noAddr { nameargs = append(nameargs, addr.Hex()) } for i := range action.args { action.args[i] = rand.Int63n(100) nameargs = append(nameargs, fmt.Sprint(action.args[i])) } action.name += strings.Join(nameargs, ", ") return action } // Generate returns a new snapshot test of the given size. All randomness is // derived from r. func (*snapshotTest) Generate(r *rand.Rand, size int) reflect.Value { // Generate random actions. addrs := make([]common.Address, 50) for i := range addrs { addrs[i][0] = byte(i) } actions := make([]testAction, size) for i := range actions { addr := addrs[r.Intn(len(addrs))] actions[i] = newTestAction(addr, r) } // Generate snapshot indexes. nsnapshots := int(math.Sqrt(float64(size))) if size > 0 && nsnapshots == 0 { nsnapshots = 1 } snapshots := make([]int, nsnapshots) snaplen := len(actions) / nsnapshots for i := range snapshots { // Try to place the snapshots some number of actions apart from each other. snapshots[i] = (i * snaplen) + r.Intn(snaplen) } return reflect.ValueOf(&snapshotTest{addrs, actions, snapshots, nil}) } func (test *snapshotTest) String() string { out := new(bytes.Buffer) sindex := 0 for i, action := range test.actions { if len(test.snapshots) > sindex && i == test.snapshots[sindex] { fmt.Fprintf(out, "---- snapshot %d ----\n", sindex) sindex++ } fmt.Fprintf(out, "%4d: %s\n", i, action.name) } return out.String() } func (test *snapshotTest) run() bool { // Run all actions and create snapshots. var ( state, _ = New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) snapshotRevs = make([]int, len(test.snapshots)) sindex = 0 ) for i, action := range test.actions { if len(test.snapshots) > sindex && i == test.snapshots[sindex] { snapshotRevs[sindex] = state.Snapshot() sindex++ } action.fn(action, state) } // Revert all snapshots in reverse order. Each revert must yield a state // that is equivalent to fresh state with all actions up the snapshot applied. for sindex--; sindex >= 0; sindex-- { checkstate, _ := New(common.Hash{}, state.Database()) for _, action := range test.actions[:test.snapshots[sindex]] { action.fn(action, checkstate) } state.RevertToSnapshot(snapshotRevs[sindex]) if err := test.checkEqual(state, checkstate); err != nil { test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err) return false } } return true } // checkEqual checks that methods of state and checkstate return the same values. func (test *snapshotTest) checkEqual(state, checkstate *DB) error { for _, addr := range test.addrs { var err error checkeq := func(op string, a, b interface{}) bool { if err == nil && !reflect.DeepEqual(a, b) { err = fmt.Errorf("got %s(%s) == %v, want %v", op, addr.Hex(), a, b) return false } return true } // Check basic accessor methods. checkeq("Exist", state.Exist(addr), checkstate.Exist(addr)) checkeq("HasSuicided", state.HasSuicided(addr), checkstate.HasSuicided(addr)) checkeq("GetBalance", state.GetBalance(addr), checkstate.GetBalance(addr)) checkeq("GetNonce", state.GetNonce(addr), checkstate.GetNonce(addr)) checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr)) checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr)) checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr)) // Check storage. if obj := state.getStateObject(addr); obj != nil { state.ForEachStorage(addr, func(key, value common.Hash) bool { return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) }) checkstate.ForEachStorage(addr, func(key, value common.Hash) bool { return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) }) } if err != nil { return err } } if state.GetRefund() != checkstate.GetRefund() { return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d", state.GetRefund(), checkstate.GetRefund()) } if !reflect.DeepEqual(state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) { return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v", state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) } return nil } func TestTouchDelete(t *testing.T) { s := newStateTest() s.state.GetOrNewStateObject(common.Address{}) root, _ := s.state.Commit(false) s.state.Reset(root) snapshot := s.state.Snapshot() s.state.AddBalance(common.Address{}, new(big.Int)) if len(s.state.journal.dirties) != 1 { t.Fatal("expected one dirty state object") } s.state.RevertToSnapshot(snapshot) if len(s.state.journal.dirties) != 0 { t.Fatal("expected no dirty state object") } } // TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. // See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 func TestCopyOfCopy(t *testing.T) { state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) addr := common.HexToAddress("aaaa") state.SetBalance(addr, big.NewInt(42)) if got := state.Copy().GetBalance(addr).Uint64(); got != 42 { t.Fatalf("1st copy fail, expected 42, got %v", got) } if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { t.Fatalf("2nd copy fail, expected 42, got %v", got) } } // Tests a regression where committing a copy lost some internal meta information, // leading to corrupted subsequent copies. // // See https://github.com/ethereum/go-ethereum/issues/20106. func TestCopyCommitCopy(t *testing.T) { state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) // Create an account and check if the retrieved balance is correct addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") skey := common.HexToHash("aaa") sval := common.HexToHash("bbb") state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetState(addr, skey, sval) // Change the storage trie if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) } if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) } if val := state.GetState(addr, skey); val != sval { t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } // Copy the non-committed state database and check pre/post commit balance copyOne := state.Copy() if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42) } if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("first copy pre-commit code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyOne.GetState(addr, skey); val != sval { t.Fatalf("first copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } copyOne.Commit(false) if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy post-commit balance mismatch: have %v, want %v", balance, 42) } if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("first copy post-commit code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyOne.GetState(addr, skey); val != sval { t.Fatalf("first copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyOne.GetCommittedState(addr, skey); val != sval { t.Fatalf("first copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) } // Copy the copy and check the balance once more copyTwo := copyOne.Copy() if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42) } if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("second copy code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyTwo.GetCommittedState(addr, skey); val != sval { t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) } } // Tests a regression where committing a copy lost some internal meta information, // leading to corrupted subsequent copies. // // See https://github.com/ethereum/go-ethereum/issues/20106. func TestCopyCopyCommitCopy(t *testing.T) { state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) // Create an account and check if the retrieved balance is correct addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") skey := common.HexToHash("aaa") sval := common.HexToHash("bbb") state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetState(addr, skey, sval) // Change the storage trie if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) } if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) } if val := state.GetState(addr, skey); val != sval { t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } // Copy the non-committed state database and check pre/post commit balance copyOne := state.Copy() if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42) } if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("first copy code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyOne.GetState(addr, skey); val != sval { t.Fatalf("first copy non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } // Copy the copy and check the balance once more copyTwo := copyOne.Copy() if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42) } if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("second copy pre-commit code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } copyTwo.Commit(false) if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy post-commit balance mismatch: have %v, want %v", balance, 42) } if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("second copy post-commit code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyTwo.GetCommittedState(addr, skey); val != sval { t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) } // Copy the copy-copy and check the balance once more copyThree := copyTwo.Copy() if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42) } if code := copyThree.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("third copy code mismatch: have %x, want %x", code, []byte("hello")) } if val := copyThree.GetState(addr, skey); val != sval { t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyThree.GetCommittedState(addr, skey); val != sval { t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval) } } // TestDeleteCreateRevert tests a weird state transition corner case that we hit // while changing the internals of statedb. The workflow is that a contract is // self destructed, then in a followup transaction (but same block) it's created // again and the transaction reverted. // // The original statedb implementation flushed dirty objects to the tries after // each transaction, so this works ok. The rework accumulated writes in memory // first, but the journal wiped the entire state object on create-revert. func TestDeleteCreateRevert(t *testing.T) { // Create an initial state with a single contract state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) addr := toAddr([]byte("so")) state.SetBalance(addr, big.NewInt(1)) root, _ := state.Commit(false) state.Reset(root) // Simulate self-destructing in one transaction, then create-reverting in another state.Suicide(addr) state.Finalise(true) id := state.Snapshot() state.SetBalance(addr, big.NewInt(2)) state.RevertToSnapshot(id) // Commit the entire state and make sure we don't crash and have the correct state root, _ = state.Commit(true) state.Reset(root) if state.getStateObject(addr) != nil { t.Fatalf("self-destructed contract came alive") } }