core/state: clear out cached state data when reset occurs (#27376)

* core/state: remove cached snap data if reset occurs

* core/state: address comment from peter

* core/state: skip revert in case data is nil
This commit is contained in:
rjl493456442 2023-06-05 21:25:57 +08:00 committed by GitHub
parent 78f7a6b7f2
commit 380fb4e249
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 1 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}
}