core/state: be smarter as to where we read state from

This commit is contained in:
Péter Szilágyi 2019-08-09 12:30:57 +03:00
parent a86220dfc3
commit aef093b73f
No known key found for this signature in database
GPG key ID: E9AE538CEDF8293D
4 changed files with 63 additions and 33 deletions

View file

@ -151,7 +151,7 @@ func (ch resetObjectChange) dirtied() *common.Address {
}
func (ch suicideChange) revert(s *StateDB) {
obj := s.getStateObject(*ch.account)
obj := s.getStateObject(*ch.account, true)
if obj != nil {
obj.suicided = ch.prev
obj.setBalance(ch.prevbalance)
@ -172,7 +172,7 @@ func (ch touchChange) dirtied() *common.Address {
}
func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev)
s.getStateObject(*ch.account, true).setBalance(ch.prev)
}
func (ch balanceChange) dirtied() *common.Address {
@ -180,7 +180,7 @@ func (ch balanceChange) dirtied() *common.Address {
}
func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev)
s.getStateObject(*ch.account, true).setNonce(ch.prev)
}
func (ch nonceChange) dirtied() *common.Address {
@ -188,7 +188,7 @@ func (ch nonceChange) dirtied() *common.Address {
}
func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
s.getStateObject(*ch.account, true).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
}
func (ch codeChange) dirtied() *common.Address {
@ -196,7 +196,7 @@ func (ch codeChange) dirtied() *common.Address {
}
func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
s.getStateObject(*ch.account, true).setState(ch.key, ch.prevalue)
}
func (ch storageChange) dirtied() *common.Address {

View file

@ -162,27 +162,56 @@ func (s *stateObject) getTrie(db Database) Trie {
return s.trie
}
// GetState retrieves a value from the account storage trie.
// GetState retrieves a value from the account storage. This method will use the
// state snapshot so retrieve the value (opposed to the trie directly) since the
// read doesn't need to pull in log(n) trie nodes in addition from disk.
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode)
// If the fake storage is set, only lookup the state here (debugging mode)
if s.fakeStorage != nil {
return s.fakeStorage[key]
}
// No fake storage, retrieve a real object from the storage trie. We're cheating
// a bit here since we know that this method is only using during simple reads.
// As such, we possibly will not write this key, so might as well avoid touching
// trie nodes and pull it directly from the state snapshot.
return s.getState(db, key, true)
}
// getState retrieves a value from the account's storage, but the caller gets to
// control whether to use the state snapshot or the state trie as the source. For
// simple reads, the snapshot should be used as it's faster. For writes however,
// using the trie will be a bit slower, but will pre-cache nodes needed during
// commit anyway.
func (s *stateObject) getState(db Database, key common.Hash, snapshot bool) common.Hash {
// If we have a dirty value for this state entry, return it
value, dirty := s.dirtyStorage[key]
if dirty {
return value
}
// Otherwise return the entry's original value
return s.GetCommittedState(db, key)
return s.getCommittedState(db, key, snapshot)
}
// GetCommittedState retrieves a value from the committed account storage trie.
// GetCommittedState retrieves a value from the committed account storage. This
// method will use the slow trie (opposed to state snapshots) since the committed
// value is only ever used to avoid writes, so we can pre-load trie nodes.
func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode)
// If the fake storage is set, only lookup the state here (debugging mode)
if s.fakeStorage != nil {
return s.fakeStorage[key]
}
// No fake storage, retrieve a real object from the storage trie. We're cheating
// a bit here since we know that this method is only using during net sstore gas
// metering. As such, we probably not only read, but also write this key in the
// same transaction, so might as well pre-cache trie nodes on the write path.
return s.getCommittedState(db, key, false)
}
// getCommittedState retrieves a value from the account's storage, but the caller
// gets to control whether to use the state snapshot or the state trie. For simple
// reads, the snapshot should be used as it's faster. For pre-writes however, using
// the trie will be a bit slower, but will pre-cache nodes needed during commit.
func (s *stateObject) getCommittedState(db Database, key common.Hash, snapshot bool) common.Hash {
// If we have the original value cached, return that
value, cached := s.originStorage[key]
if cached {
@ -193,7 +222,7 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
enc []byte
err error
)
if s.db.snap != nil {
if snapshot && s.db.snap != nil {
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now())
}
@ -227,8 +256,9 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) {
s.fakeStorage[key] = value
return
}
// If the new value is the same as old, don't set
prev := s.GetState(db, key)
// If the new value is the same as old, don't set (use the trie to cache any
// nodes if we decide to write)
prev := s.getState(db, key, false)
if prev == value {
return
}

View file

@ -234,19 +234,19 @@ func (self *StateDB) SubRefund(gas uint64) {
// Exist reports whether the given account address exists in the state.
// Notably this also returns true for suicided accounts.
func (self *StateDB) Exist(addr common.Address) bool {
return self.getStateObject(addr) != nil
return self.getStateObject(addr, true) != nil
}
// Empty returns whether the state object is either non-existent
// or empty according to the EIP161 specification (balance = nonce = code = 0)
func (self *StateDB) Empty(addr common.Address) bool {
so := self.getStateObject(addr)
so := self.getStateObject(addr, true)
return so == nil || so.empty()
}
// Retrieve the balance from the given address or 0 if object not found
func (self *StateDB) GetBalance(addr common.Address) *big.Int {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject != nil {
return stateObject.Balance()
}
@ -254,7 +254,7 @@ func (self *StateDB) GetBalance(addr common.Address) *big.Int {
}
func (self *StateDB) GetNonce(addr common.Address) uint64 {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject != nil {
return stateObject.Nonce()
}
@ -273,7 +273,7 @@ func (self *StateDB) BlockHash() common.Hash {
}
func (self *StateDB) GetCode(addr common.Address) []byte {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject != nil {
return stateObject.Code(self.db)
}
@ -281,7 +281,7 @@ func (self *StateDB) GetCode(addr common.Address) []byte {
}
func (self *StateDB) GetCodeSize(addr common.Address) int {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject == nil {
return 0
}
@ -296,7 +296,7 @@ func (self *StateDB) GetCodeSize(addr common.Address) int {
}
func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject == nil {
return common.Hash{}
}
@ -305,7 +305,7 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
// GetState retrieves a value from the given account's storage trie.
func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject != nil {
return stateObject.GetState(self.db, hash)
}
@ -330,9 +330,11 @@ func (self *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byt
return [][]byte(proof), err
}
// GetCommittedState retrieves a value from the given account's committed storage trie.
// GetCommittedState retrieves a value from the given account's committed storage.
// This method will use the slow trie (opposed to state snapshots) since the value
// committed is only ever used to avoid writes, so we can pre-load trie nodes.
func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, false)
if stateObject != nil {
return stateObject.GetCommittedState(self.db, hash)
}
@ -347,7 +349,7 @@ func (self *StateDB) Database() Database {
// StorageTrie returns the storage trie of an account.
// The return value is a copy and is nil for non-existent accounts.
func (self *StateDB) StorageTrie(addr common.Address) Trie {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject == nil {
return nil
}
@ -356,7 +358,7 @@ func (self *StateDB) StorageTrie(addr common.Address) Trie {
}
func (self *StateDB) HasSuicided(addr common.Address) bool {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject != nil {
return stateObject.suicided
}
@ -426,7 +428,7 @@ func (self *StateDB) SetStorage(addr common.Address, storage map[common.Hash]com
// The account's state object is still available until the state is committed,
// getStateObject will return a non-nil account after Suicide.
func (self *StateDB) Suicide(addr common.Address) bool {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, false)
if stateObject == nil {
return false
}
@ -485,7 +487,7 @@ func (s *StateDB) deleteStateObject(stateObject *stateObject) {
}
// Retrieve a state object given by the address. Returns nil if not found.
func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
func (s *StateDB) getStateObject(addr common.Address, snapshot bool) (stateObject *stateObject) {
// Prefer live objects
if obj := s.stateObjects[addr]; obj != nil {
if obj.deleted {
@ -495,7 +497,7 @@ func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject)
}
// If no live objects are available, attempt to use snapshots
var data Account
if s.snap != nil {
if snapshot && s.snap != nil {
if metrics.EnabledExpensive {
defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now())
}
@ -538,7 +540,7 @@ func (self *StateDB) setStateObject(object *stateObject) {
// Retrieve a state object or create a new state object if nil.
func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
stateObject := self.getStateObject(addr)
stateObject := self.getStateObject(addr, true)
if stateObject == nil || stateObject.deleted {
stateObject, _ = self.createObject(addr)
}
@ -548,7 +550,7 @@ func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
prev = self.getStateObject(addr)
prev = self.getStateObject(addr, false)
newobj = newObject(self, addr, Account{})
newobj.setNonce(0) // sets the object to dirty
if prev == nil {
@ -578,7 +580,7 @@ func (self *StateDB) CreateAccount(addr common.Address) {
}
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
so := db.getStateObject(addr)
so := db.getStateObject(addr, true)
if so == nil {
return nil
}

View file

@ -65,8 +65,6 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
return // Ugh, something went horribly wrong, bail out
}
}
// All transactions processed, finalize the block to force loading written-only trie paths
statedb.Finalise(true) // TODO(karalabe): should we run this on interrupt too?
}
// precacheTransaction attempts to apply a transaction to the given state database