core/state: maintain destruction flag by default (#26371)

This changes moves the tracking of "deleted in this block" out from snap-only domain, so that it happens regardless of whether the execution is snapshot-backed or trie-backed.
This commit is contained in:
rjl493456442 2022-12-28 21:53:43 +08:00 committed by GitHub
parent 9921ca0f0a
commit c87f321b8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 67 deletions

View File

@ -156,8 +156,8 @@ func (ch createObjectChange) dirtied() *common.Address {
func (ch resetObjectChange) revert(s *StateDB) { func (ch resetObjectChange) revert(s *StateDB) {
s.setStateObject(ch.prev) s.setStateObject(ch.prev)
if !ch.prevdestruct && s.snap != nil { if !ch.prevdestruct {
delete(s.snapDestructs, ch.prev.addrHash) delete(s.stateObjectsDestruct, ch.prev.address)
} }
} }

View File

@ -199,21 +199,21 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
if value, cached := s.originStorage[key]; cached { if value, cached := s.originStorage[key]; cached {
return value return value
} }
// If the object was destructed in *this* block (and potentially resurrected),
// the storage has been cleared out, and we should *not* consult the previous
// database about any storage values. The only possible alternatives are:
// 1) resurrect happened, and new slot values were set -- those should
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
return common.Hash{}
}
// If no live objects are available, attempt to use snapshots // If no live objects are available, attempt to use snapshots
var ( var (
enc []byte enc []byte
err error err error
) )
if s.db.snap != nil { if s.db.snap != nil {
// If the object was destructed in *this* block (and potentially resurrected),
// the storage has been cleared out, and we should *not* consult the previous
// snapshot about any storage values. The only possible alternatives are:
// 1) resurrect happened, and new slot values were set -- those should
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.snapDestructs[s.addrHash]; destructed {
return common.Hash{}
}
start := time.Now() start := time.Now()
enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
if metrics.EnabledExpensive { if metrics.EnabledExpensive {

View File

@ -72,16 +72,16 @@ type StateDB struct {
// It will be updated when the Commit is called. // It will be updated when the Commit is called.
originalRoot common.Hash originalRoot common.Hash
snaps *snapshot.Tree snaps *snapshot.Tree
snap snapshot.Snapshot snap snapshot.Snapshot
snapDestructs map[common.Hash]struct{} snapAccounts map[common.Hash][]byte
snapAccounts map[common.Hash][]byte snapStorage map[common.Hash]map[common.Hash][]byte
snapStorage map[common.Hash]map[common.Hash][]byte
// This map holds 'live' objects, which will get modified while processing a state transition. // This map holds 'live' objects, which will get modified while processing a state transition.
stateObjects map[common.Address]*stateObject stateObjects map[common.Address]*stateObject
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block
// DB error. // DB error.
// State objects are used by the consensus core and VM which are // State objects are used by the consensus core and VM which are
@ -139,23 +139,23 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
return nil, err return nil, err
} }
sdb := &StateDB{ sdb := &StateDB{
db: db, db: db,
trie: tr, trie: tr,
originalRoot: root, originalRoot: root,
snaps: snaps, snaps: snaps,
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}), stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}),
logs: make(map[common.Hash][]*types.Log), stateObjectsDestruct: make(map[common.Address]struct{}),
preimages: make(map[common.Hash][]byte), logs: make(map[common.Hash][]*types.Log),
journal: newJournal(), preimages: make(map[common.Hash][]byte),
accessList: newAccessList(), journal: newJournal(),
transientStorage: newTransientStorage(), accessList: newAccessList(),
hasher: crypto.NewKeccakState(), transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
} }
if sdb.snaps != nil { if sdb.snaps != nil {
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
sdb.snapDestructs = make(map[common.Hash]struct{})
sdb.snapAccounts = make(map[common.Hash][]byte) sdb.snapAccounts = make(map[common.Hash][]byte)
sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
} }
@ -622,10 +622,10 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
var prevdestruct bool var prevdestruct bool
if s.snap != nil && prev != nil { if prev != nil {
_, prevdestruct = s.snapDestructs[prev.addrHash] _, prevdestruct = s.stateObjectsDestruct[prev.address]
if !prevdestruct { if !prevdestruct {
s.snapDestructs[prev.addrHash] = struct{}{} s.stateObjectsDestruct[prev.address] = struct{}{}
} }
} }
newobj = newObject(s, addr, types.StateAccount{}) newobj = newObject(s, addr, types.StateAccount{})
@ -696,18 +696,19 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
func (s *StateDB) Copy() *StateDB { func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones // Copy all the basic fields, initialize the memory ones
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
trie: s.db.CopyTrie(s.trie), trie: s.db.CopyTrie(s.trie),
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
refund: s.refund, stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)),
logs: make(map[common.Hash][]*types.Log, len(s.logs)), refund: s.refund,
logSize: s.logSize, logs: make(map[common.Hash][]*types.Log, len(s.logs)),
preimages: make(map[common.Hash][]byte, len(s.preimages)), logSize: s.logSize,
journal: newJournal(), preimages: make(map[common.Hash][]byte, len(s.preimages)),
hasher: crypto.NewKeccakState(), journal: newJournal(),
hasher: crypto.NewKeccakState(),
} }
// Copy the dirty states, logs, and preimages // Copy the dirty states, logs, and preimages
for addr := range s.journal.dirties { for addr := range s.journal.dirties {
@ -725,9 +726,10 @@ func (s *StateDB) Copy() *StateDB {
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
} }
} }
// Above, we don't copy the actual journal. This means that if the copy is copied, the // Above, we don't copy the actual journal. This means that if the copy
// loop above will be a no-op, since the copy's journal is empty. // is copied, the loop above will be a no-op, since the copy's journal
// Thus, here we iterate over stateObjects, to enable copies of copies // is empty. Thus, here we iterate over stateObjects, to enable copies
// of copies.
for addr := range s.stateObjectsPending { for addr := range s.stateObjectsPending {
if _, exist := state.stateObjects[addr]; !exist { if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
@ -740,6 +742,10 @@ func (s *StateDB) Copy() *StateDB {
} }
state.stateObjectsDirty[addr] = struct{}{} state.stateObjectsDirty[addr] = struct{}{}
} }
// Deep copy the destruction flag.
for addr := range s.stateObjectsDestruct {
state.stateObjectsDestruct[addr] = struct{}{}
}
for hash, logs := range s.logs { for hash, logs := range s.logs {
cpy := make([]*types.Log, len(logs)) cpy := make([]*types.Log, len(logs))
for i, l := range logs { for i, l := range logs {
@ -751,13 +757,13 @@ func (s *StateDB) Copy() *StateDB {
for hash, preimage := range s.preimages { for hash, preimage := range s.preimages {
state.preimages[hash] = preimage state.preimages[hash] = preimage
} }
// Do we need to copy the access list? In practice: No. At the start of a // Do we need to copy the access list and transient storage?
// transaction, the access list is empty. In practice, we only ever copy state // In practice: No. At the start of a transaction, these two lists are empty.
// _between_ transactions/blocks, never in the middle of a transaction. // In practice, we only ever copy state _between_ transactions/blocks, never
// However, it doesn't cost us much to copy an empty list, so we do it anyway // in the middle of a transaction. However, it doesn't cost us much to copy
// to not blow up if we ever decide copy it in the middle of a transaction // empty lists, so we do it anyway to not blow up if we ever decide copy them
// in the middle of a transaction.
state.accessList = s.accessList.Copy() state.accessList = s.accessList.Copy()
state.transientStorage = s.transientStorage.Copy() state.transientStorage = s.transientStorage.Copy()
// If there's a prefetcher running, make an inactive copy of it that can // If there's a prefetcher running, make an inactive copy of it that can
@ -768,16 +774,13 @@ func (s *StateDB) Copy() *StateDB {
} }
if s.snaps != nil { if s.snaps != nil {
// In order for the miner to be able to use and make additions // In order for the miner to be able to use and make additions
// to the snapshot tree, we need to copy that aswell. // to the snapshot tree, we need to copy that as well.
// Otherwise, any block mined by ourselves will cause gaps in the tree, // Otherwise, any block mined by ourselves will cause gaps in the tree,
// and force the miner to operate trie-backed only // and force the miner to operate trie-backed only
state.snaps = s.snaps state.snaps = s.snaps
state.snap = s.snap state.snap = s.snap
// deep copy needed // deep copy needed
state.snapDestructs = make(map[common.Hash]struct{})
for k, v := range s.snapDestructs {
state.snapDestructs[k] = v
}
state.snapAccounts = make(map[common.Hash][]byte) state.snapAccounts = make(map[common.Hash][]byte)
for k, v := range s.snapAccounts { for k, v := range s.snapAccounts {
state.snapAccounts[k] = v state.snapAccounts[k] = v
@ -842,14 +845,17 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
if obj.suicided || (deleteEmptyObjects && obj.empty()) { if obj.suicided || (deleteEmptyObjects && obj.empty()) {
obj.deleted = true obj.deleted = true
// We need to maintain account deletions explicitly (will remain
// set indefinitely).
s.stateObjectsDestruct[obj.address] = struct{}{}
// If state snapshotting is active, also mark the destruction there. // If state snapshotting is active, also mark the destruction there.
// Note, we can't do this only at the end of a block because multiple // Note, we can't do this only at the end of a block because multiple
// transactions within the same block might self destruct and then // transactions within the same block might self destruct and then
// resurrect an account; but the snapshotter needs both events. // resurrect an account; but the snapshotter needs both events.
if s.snap != nil { if s.snap != nil {
s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely) delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
} }
} else { } else {
obj.finalise(true) // Prefetch slots in the background obj.finalise(true) // Prefetch slots in the background
@ -1037,7 +1043,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
start := time.Now() start := time.Now()
// Only update if there's a state transition (skip empty Clique blocks) // Only update if there's a state transition (skip empty Clique blocks)
if parent := s.snap.Root(); parent != root { if parent := s.snap.Root(); parent != root {
if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil { if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.snapAccounts, s.snapStorage); err != nil {
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
} }
// Keep 128 diff layers in the memory, persistent layer is 129th. // Keep 128 diff layers in the memory, persistent layer is 129th.
@ -1051,7 +1057,10 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
s.SnapshotCommits += time.Since(start) s.SnapshotCommits += time.Since(start)
} }
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil
}
if len(s.stateObjectsDestruct) > 0 {
s.stateObjectsDestruct = make(map[common.Address]struct{})
} }
if root == (common.Hash{}) { if root == (common.Hash{}) {
root = emptyRoot root = emptyRoot
@ -1148,3 +1157,17 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool {
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot) return s.accessList.Contains(addr, slot)
} }
// convertAccountSet converts a provided account set from address keyed to hash keyed.
func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} {
ret := make(map[common.Hash]struct{})
for addr := range set {
obj, exist := s.stateObjects[addr]
if !exist {
ret[crypto.Keccak256Hash(addr[:])] = struct{}{}
} else {
ret[obj.addrHash] = struct{}{}
}
}
return ret
}