diff --git a/core/state/journal.go b/core/state/journal.go index ea2a6ae6e..cf8ef3e38 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -93,6 +93,8 @@ type ( account *common.Address prev *stateObject prevdestruct bool + prevAccount []byte + prevStorage map[common.Hash][]byte } suicideChange struct { account *common.Address @@ -160,6 +162,12 @@ func (ch resetObjectChange) revert(s *StateDB) { if !ch.prevdestruct { delete(s.stateObjectsDestruct, ch.prev.address) } + if ch.prevAccount != nil { + s.snapAccounts[ch.prev.addrHash] = ch.prevAccount + } + if ch.prevStorage != nil { + s.snapStorage[ch.prev.addrHash] = ch.prevStorage + } } func (ch resetObjectChange) dirtied() *common.Address { diff --git a/core/state/statedb.go b/core/state/statedb.go index 0f5b50d2f..9d50f4bec 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -627,11 +627,34 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { + // The original account should be marked as destructed and all cached + // account and storage data should be cleared as well. Note, it must + // be done here, otherwise the destruction event of original one will + // be lost. _, prevdestruct := s.stateObjectsDestruct[prev.address] if !prevdestruct { s.stateObjectsDestruct[prev.address] = struct{}{} } - s.journal.append(resetObjectChange{account: &addr, prev: prev, prevdestruct: prevdestruct}) + var ( + account []byte + storage map[common.Hash][]byte + ) + // There may be some cached account/storage data already since IntermediateRoot + // will be called for each transaction before byzantium fork which will always + // cache the latest account/storage data. + if s.snap != nil { + account = s.snapAccounts[prev.addrHash] + storage = s.snapStorage[prev.addrHash] + delete(s.snapAccounts, prev.addrHash) + delete(s.snapStorage, prev.addrHash) + } + s.journal.append(resetObjectChange{ + account: &addr, + prev: prev, + prevdestruct: prevdestruct, + prevAccount: account, + prevStorage: storage, + }) } s.setStateObject(newobj) if prev != nil && !prev.deleted { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index e73b1b8c6..82118fc5f 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -31,7 +31,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/trie" ) // Tests that updating a state trie does not leak any database writes prior to @@ -998,3 +1001,37 @@ func TestStateDBTransientStorage(t *testing.T) { t.Fatalf("transient storage mismatch: have %x, want %x", got, value) } } + +func TestResetObject(t *testing.T) { + var ( + disk = rawdb.NewMemoryDatabase() + tdb = trie.NewDatabase(disk) + db = NewDatabaseWithNodeDB(disk, tdb) + snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash) + state, _ = New(types.EmptyRootHash, db, snaps) + addr = common.HexToAddress("0x1") + slotA = common.HexToHash("0x1") + slotB = common.HexToHash("0x2") + ) + // Initialize account with balance and storage in first transaction. + state.SetBalance(addr, big.NewInt(1)) + state.SetState(addr, slotA, common.BytesToHash([]byte{0x1})) + state.IntermediateRoot(true) + + // Reset account and mutate balance and storages + state.CreateAccount(addr) + state.SetBalance(addr, big.NewInt(2)) + state.SetState(addr, slotB, common.BytesToHash([]byte{0x2})) + root, _ := state.Commit(true) + + // Ensure the original account is wiped properly + snap := snaps.Snapshot(root) + slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes())) + if len(slot) != 0 { + t.Fatalf("Unexpected storage slot") + } + slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes())) + if !bytes.Equal(slot, []byte{0x2}) { + t.Fatalf("Unexpected storage slot value %v", slot) + } +}