diff --git a/core/state/dump.go b/core/state/dump.go index c92111a804..849f3548f0 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" ) type DumpAccount struct { @@ -46,20 +47,22 @@ func (self *StateDB) RawDump() Dump { it := self.trie.Iterator() for it.Next() { addr := self.trie.GetKey(it.Key) - stateObject, err := DecodeObject(common.BytesToAddress(addr), self.db, it.Value) - if err != nil { + var data Account + if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) } + + obj := NewObject(common.BytesToAddress(addr), data) account := DumpAccount{ - Balance: stateObject.Balance().String(), - Nonce: stateObject.Nonce(), - Root: common.Bytes2Hex(stateObject.data.Root[:]), - CodeHash: common.Bytes2Hex(stateObject.CodeHash()), - Code: common.Bytes2Hex(stateObject.Code(self.db)), + Balance: data.Balance.String(), + Nonce: data.Nonce, + Root: common.Bytes2Hex(data.Root[:]), + CodeHash: common.Bytes2Hex(data.CodeHash), + Code: common.Bytes2Hex(obj.Code(self.db)), Storage: make(map[string]string), } - stateObject.initTrie(self.db) - storageIt := stateObject.trie.Iterator() + obj.initTrie(self.db) + storageIt := obj.trie.Iterator() for storageIt.Next() { account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 777e48ab7f..aea0d798a2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -65,7 +65,7 @@ func (self Storage) Copy() Storage { // Finally, call CommitTrie to write the modified storage trie into a database. type StateObject struct { address common.Address // Ethereum address of this account - data accountData + data Account // DB error. // State objects are used by the consensus core and VM which are @@ -87,23 +87,29 @@ type StateObject struct { deleted bool } -// accountData is the Ethereum consensus representation of accounts. +// Account is the Ethereum consensus representation of accounts. // These objects are stored in the main account trie. -type accountData struct { +type Account struct { Nonce uint64 Balance *big.Int Root common.Hash // merkle root of the storage trie CodeHash []byte } -func NewStateObject(address common.Address, db trie.Database) *StateObject { - object := &StateObject{ - address: address, - data: accountData{Balance: new(big.Int), CodeHash: emptyCodeHash}, - storage: make(Storage), - dirty: true, +// NewObject creates a state object. +func NewObject(address common.Address, data Account) *StateObject { + if data.Balance == nil { + data.Balance = new(big.Int) } - return object + if data.CodeHash == nil { + data.CodeHash = emptyCodeHash + } + return &StateObject{address: address, data: data, storage: make(Storage)} +} + +// EncodeRLP implements rlp.Encoder. +func (c *StateObject) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, c.data) } // setError remembers the first non-nil error it is called with. @@ -224,8 +230,7 @@ func (c *StateObject) SetBalance(amount *big.Int) { func (c *StateObject) ReturnGas(gas, price *big.Int) {} func (self *StateObject) Copy(db trie.Database) *StateObject { - stateObject := NewStateObject(self.address, db) - stateObject.data = self.data + stateObject := NewObject(self.address, self.data) stateObject.data.Balance.Set(self.data.Balance) stateObject.trie = self.trie stateObject.code = self.code @@ -233,7 +238,6 @@ func (self *StateObject) Copy(db trie.Database) *StateObject { stateObject.remove = self.remove stateObject.dirty = self.dirty stateObject.deleted = self.deleted - return stateObject } @@ -307,15 +311,3 @@ func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { } } } - -// EncodeRLP implements rlp.Encoder. -func (c *StateObject) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, c.data) -} - -// DecodeObject decodes an RLP-encoded state object. -func DecodeObject(address common.Address, db trie.Database, data []byte) (*StateObject, error) { - obj := StateObject{address: address, storage: make(Storage)} - err := rlp.DecodeBytes(data, &obj.data) - return &obj, err -} diff --git a/core/state/statedb.go b/core/state/statedb.go index 8d261212fa..f21c6b0efd 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -43,8 +43,14 @@ type StateDB struct { db ethdb.Database trie *trie.SecureTrie + // This map caches canon state accounts. + all map[common.Address]Account + + // This map holds 'live' objects, which will get modified + // while processing a state transition. stateObjects map[common.Address]*StateObject + // The refund counter, also used by state transitioning. refund *big.Int thash, bhash common.Hash @@ -62,6 +68,7 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) { return &StateDB{ db: db, trie: tr, + all: make(map[common.Address]Account), stateObjects: make(map[common.Address]*StateObject), refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), @@ -74,15 +81,19 @@ func (self *StateDB) Reset(root common.Hash) error { var ( err error tr = self.trie + all = self.all ) if self.trie.Hash() != root { + // The root has changed, invalidate canon state. if tr, err = trie.NewSecure(root, self.db); err != nil { return err } + all = make(map[common.Address]Account) } *self = StateDB{ db: self.db, trie: tr, + all: all, stateObjects: make(map[common.Address]*StateObject), refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), @@ -244,26 +255,37 @@ func (self *StateDB) DeleteStateObject(stateObject *StateObject) { // Retrieve a state object given my the address. Returns nil if not found. func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObject) { - stateObject = self.stateObjects[addr] - if stateObject != nil { - if stateObject.deleted { - stateObject = nil + // Prefer 'live' objects. + if obj := self.stateObjects[addr]; obj != nil { + if obj.deleted { + return nil } - - return stateObject + return obj } - data := self.trie.Get(addr[:]) - if len(data) == 0 { + // Use cached account data from the canon state if possible. + if data, ok := self.all[addr]; ok { + return NewObject(addr, data) + } + + // Load the object from the database. + enc := self.trie.Get(addr[:]) + if len(enc) == 0 { return nil } - stateObject, err := DecodeObject(addr, self.db, data) - if err != nil { + var data Account + if err := rlp.DecodeBytes(enc, &data); err != nil { glog.Errorf("can't decode object at %x: %v", addr[:], err) return nil } - self.SetStateObject(stateObject) - return stateObject + // Update the all cache. Content in DB always corresponds + // to the current head state so this is ok to do here. + // The object we just loaded has no storage trie and code yet. + self.all[addr] = data + // Insert into the live set. + obj := NewObject(addr, data) + self.SetStateObject(obj) + return obj } func (self *StateDB) SetStateObject(object *StateObject) { @@ -285,15 +307,14 @@ func (self *StateDB) newStateObject(addr common.Address) *StateObject { if glog.V(logger.Core) { glog.Infof("(+) %x\n", addr) } - - stateObject := NewStateObject(addr, self.db) - stateObject.SetNonce(StartingNonce) - self.stateObjects[addr] = stateObject - - return stateObject + obj := NewObject(addr, Account{}) + obj.dirty = true + obj.SetNonce(StartingNonce) + self.stateObjects[addr] = obj + return obj } -// Creates creates a new state object and takes ownership. This is different from "NewStateObject" +// Creates creates a new state object and takes ownership. func (self *StateDB) CreateStateObject(addr common.Address) *StateObject { // Get previous (if any) so := self.GetStateObject(addr) @@ -320,6 +341,7 @@ func (self *StateDB) Copy() *StateDB { // ignore error - we assume state-to-be-copied always exists state, _ := New(common.Hash{}, self.db) state.trie = self.trie + state.all = self.all for addr, stateObject := range self.stateObjects { if stateObject.dirty { state.stateObjects[addr] = stateObject.Copy(self.db) @@ -340,6 +362,7 @@ func (self *StateDB) Copy() *StateDB { func (self *StateDB) Set(state *StateDB) { self.trie = state.trie self.stateObjects = state.stateObjects + self.all = state.all self.refund = state.refund self.logs = state.logs @@ -407,11 +430,13 @@ func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) { func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) { s.refund = new(big.Int) - for _, stateObject := range s.stateObjects { + // Commit objects to the trie. + for addr, stateObject := range s.stateObjects { if stateObject.remove { // If the object has been removed, don't bother syncing it // and just mark it for deletion in the trie. s.DeleteStateObject(stateObject) + delete(s.all, addr) } else { // Write any contract code associated with the state object if stateObject.code != nil { @@ -426,10 +451,18 @@ func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) { } // Update the object in the main account trie. s.UpdateStateObject(stateObject) + s.all[addr] = stateObject.data } stateObject.dirty = false } - return s.trie.CommitTo(dbw) + + // Write trie changes. + root, err := s.trie.CommitTo(dbw) + if err != nil { + // Committing failed, any updates to the canon state are invalid. + s.all = make(map[common.Address]Account) + } + return root, err } func (self *StateDB) Refunds() *big.Int { diff --git a/tests/util.go b/tests/util.go index 79c3bfad12..8cd07726c9 100644 --- a/tests/util.go +++ b/tests/util.go @@ -104,15 +104,16 @@ func (self Log) Topics() [][]byte { } func StateObjectFromAccount(db ethdb.Database, addr string, account Account) *state.StateObject { - obj := state.NewStateObject(common.HexToAddress(addr), db) - obj.SetBalance(common.Big(account.Balance)) - if common.IsHex(account.Code) { account.Code = account.Code[2:] } - obj.SetCode(common.Hex2Bytes(account.Code)) - obj.SetNonce(common.Big(account.Nonce).Uint64()) - + code := common.Hex2Bytes(account.Code) + obj := state.NewObject(common.HexToAddress(addr), state.Account{ + Balance: common.Big(account.Balance), + CodeHash: crypto.Keccak256(code), + Nonce: common.Big(account.Nonce).Uint64(), + }) + obj.SetCode(code) return obj }