diff --git a/core/state/journal.go b/core/state/journal.go index a03ca57dbc..85daa442b1 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -111,6 +111,11 @@ type ( account *common.Address key, prevalue common.Hash } + storageByteArrayChange struct { + account *common.Address + key common.Hash + prevalue []byte + } codeChange struct { account *common.Address prevcode, prevhash []byte @@ -179,6 +184,14 @@ func (ch balanceChange) dirtied() *common.Address { return ch.account } +func (ch storageByteArrayChange) revert(s *StateDB) { + s.getStateObject(*ch.account).setStateByteArray(ch.key, ch.prevalue) +} + +func (ch storageByteArrayChange) dirtied() *common.Address { + return ch.account +} + func (ch nonceChange) revert(s *StateDB) { s.getStateObject(*ch.account).setNonce(ch.prev) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 8680de021f..b5c7ba7594 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -39,6 +39,8 @@ func (c Code) String() string { type Storage map[common.Hash]common.Hash +type StorageByteArray map[common.Hash][]byte + func (s Storage) String() (str string) { for key, value := range s { str += fmt.Sprintf("%X : %X\n", key, value) @@ -56,6 +58,15 @@ func (s Storage) Copy() Storage { return cpy } +func (self StorageByteArray) Copy() StorageByteArray { + cpy := make(StorageByteArray) + for key, value := range self { + cpy[key] = value + } + + return cpy +} + // stateObject represents an Ethereum account which is being modified. // // The usage pattern is as follows: @@ -84,6 +95,9 @@ type stateObject struct { dirtyStorage Storage // Storage entries that have been modified in the current transaction execution fakeStorage Storage // Fake storage which constructed by caller for debugging purpose. + cachedStorageByteArray StorageByteArray + dirtyStorageByteArray StorageByteArray + // Cache flags. // When an object is marked suicided it will be delete from the trie // during the "update" phase of the state transition. @@ -94,7 +108,7 @@ type stateObject struct { // empty returns whether the account is considered empty. func (s *stateObject) empty() bool { - return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, emptyCodeHash) + return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, emptyCodeHash) && len(s.dirtyStorage) == 0 && len(s.dirtyStorageByteArray) == 0 } // Account is the Ethereum consensus representation of accounts. @@ -118,13 +132,15 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject { data.Root = emptyRoot } return &stateObject{ - db: db, - address: address, - addrHash: crypto.Keccak256Hash(address[:]), - data: data, - originStorage: make(Storage), - pendingStorage: make(Storage), - dirtyStorage: make(Storage), + db: db, + address: address, + addrHash: crypto.Keccak256Hash(address[:]), + data: data, + originStorage: make(Storage), + pendingStorage: make(Storage), + dirtyStorage: make(Storage), + cachedStorageByteArray: make(StorageByteArray), + dirtyStorageByteArray: make(StorageByteArray), } } @@ -260,6 +276,34 @@ func (s *stateObject) setState(key, value common.Hash) { s.dirtyStorage[key] = value } +func (s *stateObject) GetStateByteArray(db Database, key common.Hash) []byte { + value, exists := s.cachedStorageByteArray[key] + if exists { + return value + } + // Load from DB in case it is missing. + value, err := s.getTrie(db).TryGet(key[:]) + if err == nil && len(value) != 0 { + s.cachedStorageByteArray[key] = value + } + return value +} + +func (s *stateObject) SetStateByteArray(db Database, key common.Hash, value []byte) { + s.db.journal.append(storageByteArrayChange{ + account: &s.address, + key: key, + prevalue: s.GetStateByteArray(db, key), + }) + s.setStateByteArray(key, value) + +} + +func (self *stateObject) setStateByteArray(key common.Hash, value []byte) { + self.cachedStorageByteArray[key] = value + self.dirtyStorageByteArray[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() { @@ -300,6 +344,19 @@ func (s *stateObject) updateTrie(db Database) Trie { if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) } + + for key, value := range s.dirtyStorageByteArray { + delete(s.dirtyStorageByteArray, key) + if len(value) == 0 { + s.setError(tr.TryDelete(key[:])) + continue + } + s.setError(tr.TryUpdate(key[:], value)) + } + if len(s.dirtyStorageByteArray) > 0 { + s.dirtyStorageByteArray = make(StorageByteArray) + } + return tr } @@ -380,6 +437,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { stateObject.dirtyStorage = s.dirtyStorage.Copy() stateObject.originStorage = s.originStorage.Copy() stateObject.pendingStorage = s.pendingStorage.Copy() + stateObject.dirtyStorageByteArray = s.dirtyStorageByteArray.Copy() + stateObject.cachedStorageByteArray = s.cachedStorageByteArray.Copy() stateObject.suicided = s.suicided stateObject.dirtyCode = s.dirtyCode stateObject.deleted = s.deleted diff --git a/core/state/statedb.go b/core/state/statedb.go index 9034d55336..576e3f509c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -398,6 +398,51 @@ func (self *StateDB) SetStorage(addr common.Address, storage map[common.Hash]com } } +// cb is callback function. cb return true indicating like to continue, return false indicating stop +func (db *StateDB) ForEachStorageByteArray(addr common.Address, cb func(key common.Hash, value []byte) bool) { + so := db.getStateObject(addr) + if so == nil { + return + } + + // When iterating over the storage check the cache first + for h, value := range so.cachedStorageByteArray { + if !cb(h, value) { + return + } + } + + it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil)) + for it.Next() { + // ignore cached values + key := common.BytesToHash(db.trie.GetKey(it.Key)) + if _, ok := so.fakeStorage[key]; !ok { + if !cb(key, it.Value) { + return + } + } else if _, ok := so.dirtyStorage[key]; !ok { + if !cb(key, it.Value) { + return + } + } + } +} + +func (self *StateDB) GetStateByteArray(a common.Address, b common.Hash) []byte { + stateObject := self.getStateObject(a) + if stateObject != nil { + return stateObject.GetStateByteArray(self.db, b) + } + return nil +} + +func (self *StateDB) SetStateByteArray(addr common.Address, key common.Hash, value []byte) { + stateObject := self.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetStateByteArray(self.db, key, value) + } +} + // Suicide marks the given account as suicided. // This clears the account balance. // diff --git a/core/vm/interface.go b/core/vm/interface.go index dd401466ad..0b6df61d11 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -47,6 +47,9 @@ type StateDB interface { GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) + GetStateByteArray(common.Address, common.Hash) []byte + SetStateByteArray(common.Address, common.Hash, []byte) + Suicide(common.Address) bool HasSuicided(common.Address) bool @@ -64,6 +67,8 @@ type StateDB interface { AddPreimage(common.Hash, []byte) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error + + ForEachStorageByteArray(common.Address, func(common.Hash, []byte) bool) } // CallContext provides a basic interface for the EVM calling conventions. The EVM