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:
parent
9921ca0f0a
commit
c87f321b8f
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user