Skip to content

Commit d03d340

Browse files
rjl493456442shekhirin
authored andcommitted
core/state: maintain destruction flag by default (ethereum#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.
1 parent d57a9e0 commit d03d340

File tree

3 files changed

+90
-67
lines changed

3 files changed

+90
-67
lines changed

core/state/journal.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ func (ch createObjectChange) dirtied() *common.Address {
156156

157157
func (ch resetObjectChange) revert(s *StateDB) {
158158
s.setStateObject(ch.prev)
159-
if !ch.prevdestruct && s.snap != nil {
160-
delete(s.snapDestructs, ch.prev.addrHash)
159+
if !ch.prevdestruct {
160+
delete(s.stateObjectsDestruct, ch.prev.address)
161161
}
162162
}
163163

core/state/state_object.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,21 +199,21 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
199199
if value, cached := s.originStorage[key]; cached {
200200
return value
201201
}
202+
// If the object was destructed in *this* block (and potentially resurrected),
203+
// the storage has been cleared out, and we should *not* consult the previous
204+
// database about any storage values. The only possible alternatives are:
205+
// 1) resurrect happened, and new slot values were set -- those should
206+
// have been handles via pendingStorage above.
207+
// 2) we don't have new values, and can deliver empty response back
208+
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
209+
return common.Hash{}
210+
}
202211
// If no live objects are available, attempt to use snapshots
203212
var (
204213
enc []byte
205214
err error
206215
)
207216
if s.db.snap != nil {
208-
// If the object was destructed in *this* block (and potentially resurrected),
209-
// the storage has been cleared out, and we should *not* consult the previous
210-
// snapshot about any storage values. The only possible alternatives are:
211-
// 1) resurrect happened, and new slot values were set -- those should
212-
// have been handles via pendingStorage above.
213-
// 2) we don't have new values, and can deliver empty response back
214-
if _, destructed := s.db.snapDestructs[s.addrHash]; destructed {
215-
return common.Hash{}
216-
}
217217
start := time.Now()
218218
enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
219219
if metrics.EnabledExpensive {

core/state/statedb.go

Lines changed: 79 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,16 @@ type StateDB struct {
7272
// It will be updated when the Commit is called.
7373
originalRoot common.Hash
7474

75-
snaps *snapshot.Tree
76-
snap snapshot.Snapshot
77-
snapDestructs map[common.Hash]struct{}
78-
snapAccounts map[common.Hash][]byte
79-
snapStorage map[common.Hash]map[common.Hash][]byte
75+
snaps *snapshot.Tree
76+
snap snapshot.Snapshot
77+
snapAccounts map[common.Hash][]byte
78+
snapStorage map[common.Hash]map[common.Hash][]byte
8079

8180
// This map holds 'live' objects, which will get modified while processing a state transition.
82-
stateObjects map[common.Address]*stateObject
83-
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
84-
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
81+
stateObjects map[common.Address]*stateObject
82+
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
83+
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
84+
stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block
8585

8686
// DB error.
8787
// 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)
139139
return nil, err
140140
}
141141
sdb := &StateDB{
142-
db: db,
143-
trie: tr,
144-
originalRoot: root,
145-
snaps: snaps,
146-
stateObjects: make(map[common.Address]*stateObject),
147-
stateObjectsPending: make(map[common.Address]struct{}),
148-
stateObjectsDirty: make(map[common.Address]struct{}),
149-
logs: make(map[common.Hash][]*types.Log),
150-
preimages: make(map[common.Hash][]byte),
151-
journal: newJournal(),
152-
accessList: newAccessList(),
153-
transientStorage: newTransientStorage(),
154-
hasher: crypto.NewKeccakState(),
142+
db: db,
143+
trie: tr,
144+
originalRoot: root,
145+
snaps: snaps,
146+
stateObjects: make(map[common.Address]*stateObject),
147+
stateObjectsPending: make(map[common.Address]struct{}),
148+
stateObjectsDirty: make(map[common.Address]struct{}),
149+
stateObjectsDestruct: make(map[common.Address]struct{}),
150+
logs: make(map[common.Hash][]*types.Log),
151+
preimages: make(map[common.Hash][]byte),
152+
journal: newJournal(),
153+
accessList: newAccessList(),
154+
transientStorage: newTransientStorage(),
155+
hasher: crypto.NewKeccakState(),
155156
}
156157
if sdb.snaps != nil {
157158
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
158-
sdb.snapDestructs = make(map[common.Hash]struct{})
159159
sdb.snapAccounts = make(map[common.Hash][]byte)
160160
sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
161161
}
@@ -622,10 +622,10 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
622622
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
623623

624624
var prevdestruct bool
625-
if s.snap != nil && prev != nil {
626-
_, prevdestruct = s.snapDestructs[prev.addrHash]
625+
if prev != nil {
626+
_, prevdestruct = s.stateObjectsDestruct[prev.address]
627627
if !prevdestruct {
628-
s.snapDestructs[prev.addrHash] = struct{}{}
628+
s.stateObjectsDestruct[prev.address] = struct{}{}
629629
}
630630
}
631631
newobj = newObject(s, addr, types.StateAccount{})
@@ -696,18 +696,19 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
696696
func (s *StateDB) Copy() *StateDB {
697697
// Copy all the basic fields, initialize the memory ones
698698
state := &StateDB{
699-
db: s.db,
700-
trie: s.db.CopyTrie(s.trie),
701-
originalRoot: s.originalRoot,
702-
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
703-
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
704-
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
705-
refund: s.refund,
706-
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
707-
logSize: s.logSize,
708-
preimages: make(map[common.Hash][]byte, len(s.preimages)),
709-
journal: newJournal(),
710-
hasher: crypto.NewKeccakState(),
699+
db: s.db,
700+
trie: s.db.CopyTrie(s.trie),
701+
originalRoot: s.originalRoot,
702+
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
703+
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
704+
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
705+
stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)),
706+
refund: s.refund,
707+
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
708+
logSize: s.logSize,
709+
preimages: make(map[common.Hash][]byte, len(s.preimages)),
710+
journal: newJournal(),
711+
hasher: crypto.NewKeccakState(),
711712
}
712713
// Copy the dirty states, logs, and preimages
713714
for addr := range s.journal.dirties {
@@ -725,9 +726,10 @@ func (s *StateDB) Copy() *StateDB {
725726
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
726727
}
727728
}
728-
// Above, we don't copy the actual journal. This means that if the copy is copied, the
729-
// loop above will be a no-op, since the copy's journal is empty.
730-
// Thus, here we iterate over stateObjects, to enable copies of copies
729+
// Above, we don't copy the actual journal. This means that if the copy
730+
// is copied, the loop above will be a no-op, since the copy's journal
731+
// is empty. Thus, here we iterate over stateObjects, to enable copies
732+
// of copies.
731733
for addr := range s.stateObjectsPending {
732734
if _, exist := state.stateObjects[addr]; !exist {
733735
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
@@ -740,6 +742,10 @@ func (s *StateDB) Copy() *StateDB {
740742
}
741743
state.stateObjectsDirty[addr] = struct{}{}
742744
}
745+
// Deep copy the destruction flag.
746+
for addr := range s.stateObjectsDestruct {
747+
state.stateObjectsDestruct[addr] = struct{}{}
748+
}
743749
for hash, logs := range s.logs {
744750
cpy := make([]*types.Log, len(logs))
745751
for i, l := range logs {
@@ -751,13 +757,13 @@ func (s *StateDB) Copy() *StateDB {
751757
for hash, preimage := range s.preimages {
752758
state.preimages[hash] = preimage
753759
}
754-
// Do we need to copy the access list? In practice: No. At the start of a
755-
// transaction, the access list is empty. In practice, we only ever copy state
756-
// _between_ transactions/blocks, never in the middle of a transaction.
757-
// However, it doesn't cost us much to copy an empty list, so we do it anyway
758-
// to not blow up if we ever decide copy it in the middle of a transaction
760+
// Do we need to copy the access list and transient storage?
761+
// In practice: No. At the start of a transaction, these two lists are empty.
762+
// In practice, we only ever copy state _between_ transactions/blocks, never
763+
// in the middle of a transaction. However, it doesn't cost us much to copy
764+
// empty lists, so we do it anyway to not blow up if we ever decide copy them
765+
// in the middle of a transaction.
759766
state.accessList = s.accessList.Copy()
760-
761767
state.transientStorage = s.transientStorage.Copy()
762768

763769
// If there's a prefetcher running, make an inactive copy of it that can
@@ -768,16 +774,13 @@ func (s *StateDB) Copy() *StateDB {
768774
}
769775
if s.snaps != nil {
770776
// In order for the miner to be able to use and make additions
771-
// to the snapshot tree, we need to copy that aswell.
777+
// to the snapshot tree, we need to copy that as well.
772778
// Otherwise, any block mined by ourselves will cause gaps in the tree,
773779
// and force the miner to operate trie-backed only
774780
state.snaps = s.snaps
775781
state.snap = s.snap
782+
776783
// deep copy needed
777-
state.snapDestructs = make(map[common.Hash]struct{})
778-
for k, v := range s.snapDestructs {
779-
state.snapDestructs[k] = v
780-
}
781784
state.snapAccounts = make(map[common.Hash][]byte)
782785
for k, v := range s.snapAccounts {
783786
state.snapAccounts[k] = v
@@ -842,14 +845,17 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
842845
if obj.suicided || (deleteEmptyObjects && obj.empty()) {
843846
obj.deleted = true
844847

848+
// We need to maintain account deletions explicitly (will remain
849+
// set indefinitely).
850+
s.stateObjectsDestruct[obj.address] = struct{}{}
851+
845852
// If state snapshotting is active, also mark the destruction there.
846853
// Note, we can't do this only at the end of a block because multiple
847854
// transactions within the same block might self destruct and then
848855
// resurrect an account; but the snapshotter needs both events.
849856
if s.snap != nil {
850-
s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely)
851-
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
852-
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
857+
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
858+
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
853859
}
854860
} else {
855861
obj.finalise(true) // Prefetch slots in the background
@@ -1037,7 +1043,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
10371043
start := time.Now()
10381044
// Only update if there's a state transition (skip empty Clique blocks)
10391045
if parent := s.snap.Root(); parent != root {
1040-
if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil {
1046+
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.snapAccounts, s.snapStorage); err != nil {
10411047
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
10421048
}
10431049
// Keep 128 diff layers in the memory, persistent layer is 129th.
@@ -1051,7 +1057,10 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
10511057
if metrics.EnabledExpensive {
10521058
s.SnapshotCommits += time.Since(start)
10531059
}
1054-
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
1060+
s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil
1061+
}
1062+
if len(s.stateObjectsDestruct) > 0 {
1063+
s.stateObjectsDestruct = make(map[common.Address]struct{})
10551064
}
10561065
if root == (common.Hash{}) {
10571066
root = emptyRoot
@@ -1148,3 +1157,17 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool {
11481157
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
11491158
return s.accessList.Contains(addr, slot)
11501159
}
1160+
1161+
// convertAccountSet converts a provided account set from address keyed to hash keyed.
1162+
func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} {
1163+
ret := make(map[common.Hash]struct{})
1164+
for addr := range set {
1165+
obj, exist := s.stateObjects[addr]
1166+
if !exist {
1167+
ret[crypto.Keccak256Hash(addr[:])] = struct{}{}
1168+
} else {
1169+
ret[obj.addrHash] = struct{}{}
1170+
}
1171+
}
1172+
return ret
1173+
}

0 commit comments

Comments
 (0)