core/state: accumulate writes and only update tries when must

parent 96fb8391
...@@ -79,9 +79,10 @@ type stateObject struct { ...@@ -79,9 +79,10 @@ type stateObject struct {
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded code Code // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
dirtyStorage Storage // Storage entries that need to be flushed to disk pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose. dirtyStorage Storage // Storage entries that have been modified in the current transaction execution
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
// Cache flags. // Cache flags.
// When an object is marked suicided it will be delete from the trie // When an object is marked suicided it will be delete from the trie
...@@ -113,13 +114,17 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject { ...@@ -113,13 +114,17 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject {
if data.CodeHash == nil { if data.CodeHash == nil {
data.CodeHash = emptyCodeHash data.CodeHash = emptyCodeHash
} }
if data.Root == (common.Hash{}) {
data.Root = emptyRoot
}
return &stateObject{ return &stateObject{
db: db, db: db,
address: address, address: address,
addrHash: crypto.Keccak256Hash(address[:]), addrHash: crypto.Keccak256Hash(address[:]),
data: data, data: data,
originStorage: make(Storage), originStorage: make(Storage),
dirtyStorage: make(Storage), pendingStorage: make(Storage),
dirtyStorage: make(Storage),
} }
} }
...@@ -183,9 +188,11 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has ...@@ -183,9 +188,11 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
if s.fakeStorage != nil { if s.fakeStorage != nil {
return s.fakeStorage[key] return s.fakeStorage[key]
} }
// If we have the original value cached, return that // If we have a pending write or clean cached, return that
value, cached := s.originStorage[key] if value, pending := s.pendingStorage[key]; pending {
if cached { return value
}
if value, cached := s.originStorage[key]; cached {
return value return value
} }
// Track the amount of time wasted on reading the storage trie // Track the amount of time wasted on reading the storage trie
...@@ -198,6 +205,7 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has ...@@ -198,6 +205,7 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
s.setError(err) s.setError(err)
return common.Hash{} return common.Hash{}
} }
var value common.Hash
if len(enc) > 0 { if len(enc) > 0 {
_, content, _, err := rlp.Split(enc) _, content, _, err := rlp.Split(enc)
if err != nil { if err != nil {
...@@ -252,17 +260,29 @@ func (s *stateObject) setState(key, value common.Hash) { ...@@ -252,17 +260,29 @@ func (s *stateObject) setState(key, value common.Hash) {
s.dirtyStorage[key] = value s.dirtyStorage[key] = value
} }
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise() {
for key, value := range s.dirtyStorage {
s.pendingStorage[key] = value
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
}
}
// updateTrie writes cached storage modifications into the object's storage trie. // updateTrie writes cached storage modifications into the object's storage trie.
func (s *stateObject) updateTrie(db Database) Trie { func (s *stateObject) updateTrie(db Database) Trie {
// Make sure all dirty slots are finalized into the pending storage area
s.finalise()
// Track the amount of time wasted on updating the storge trie // Track the amount of time wasted on updating the storge trie
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
} }
// Update all the dirty slots in the trie // Insert all the pending updates into the trie
tr := s.getTrie(db) tr := s.getTrie(db)
for key, value := range s.dirtyStorage { for key, value := range s.pendingStorage {
delete(s.dirtyStorage, key)
// Skip noop changes, persist actual changes // Skip noop changes, persist actual changes
if value == s.originStorage[key] { if value == s.originStorage[key] {
continue continue
...@@ -277,6 +297,9 @@ func (s *stateObject) updateTrie(db Database) Trie { ...@@ -277,6 +297,9 @@ func (s *stateObject) updateTrie(db Database) Trie {
v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
s.setError(tr.TryUpdate(key[:], v)) s.setError(tr.TryUpdate(key[:], v))
} }
if len(s.pendingStorage) > 0 {
s.pendingStorage = make(Storage)
}
return tr return tr
} }
......
This diff is collapsed.
...@@ -449,3 +449,38 @@ func TestCopyOfCopy(t *testing.T) { ...@@ -449,3 +449,38 @@ func TestCopyOfCopy(t *testing.T) {
t.Fatalf("2nd copy fail, expected 42, got %v", got) t.Fatalf("2nd copy fail, expected 42, got %v", got)
} }
} }
// TestDeleteCreateRevert tests a weird state transition corner case that we hit
// while changing the internals of statedb. The workflow is that a contract is
// self destructed, then in a followup transaction (but same block) it's created
// again and the transaction reverted.
//
// The original statedb implementation flushed dirty objects to the tries after
// each transaction, so this works ok. The rework accumulated writes in memory
// first, but the journal wiped the entire state object on create-revert.
func TestDeleteCreateRevert(t *testing.T) {
// Create an initial state with a single contract
state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
addr := toAddr([]byte("so"))
state.SetBalance(addr, big.NewInt(1))
root, _ := state.Commit(false)
state.Reset(root)
// Simulate self-destructing in one transaction, then create-reverting in another
state.Suicide(addr)
state.Finalise(true)
id := state.Snapshot()
state.SetBalance(addr, big.NewInt(2))
state.RevertToSnapshot(id)
// Commit the entire state and make sure we don't crash and have the correct state
root, _ = state.Commit(true)
state.Reset(root)
if state.getStateObject(addr) != nil {
t.Fatalf("self-destructed contract came alive")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment