From 9df74cb584cba6ca5286ce72df798f735f0780bc Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 22 Sep 2016 21:04:58 +0200 Subject: [PATCH 1/6] core/state: remove StateObject.db, prepare for removal of address db is essentially unused and removing it shrinks StateObject by 16 bytes. I'd love to remove address as well but that's a very invasive change. --- core/state/managed_state.go | 17 ++++++++--------- core/state/managed_state_test.go | 10 +++++----- core/state/state_object.go | 16 +++------------- core/state/statedb.go | 18 +++++++++--------- 4 files changed, 25 insertions(+), 36 deletions(-) diff --git a/core/state/managed_state.go b/core/state/managed_state.go index f8e2f2b876..73d3a3fe3d 100644 --- a/core/state/managed_state.go +++ b/core/state/managed_state.go @@ -33,14 +33,14 @@ type ManagedState struct { mu sync.RWMutex - accounts map[string]*account + accounts map[common.Address]*account } // ManagedState returns a new managed state with the statedb as it's backing layer func ManageState(statedb *StateDB) *ManagedState { return &ManagedState{ StateDB: statedb.Copy(), - accounts: make(map[string]*account), + accounts: make(map[common.Address]*account), } } @@ -103,7 +103,7 @@ func (ms *ManagedState) SetNonce(addr common.Address, nonce uint64) { so := ms.GetOrNewStateObject(addr) so.SetNonce(nonce) - ms.accounts[addr.Str()] = newAccount(so) + ms.accounts[addr] = newAccount(so) } // HasAccount returns whether the given address is managed or not @@ -114,27 +114,26 @@ func (ms *ManagedState) HasAccount(addr common.Address) bool { } func (ms *ManagedState) hasAccount(addr common.Address) bool { - _, ok := ms.accounts[addr.Str()] + _, ok := ms.accounts[addr] return ok } // populate the managed state func (ms *ManagedState) getAccount(addr common.Address) *account { - straddr := addr.Str() - if account, ok := ms.accounts[straddr]; !ok { + if account, ok := ms.accounts[addr]; !ok { so := ms.GetOrNewStateObject(addr) - ms.accounts[straddr] = newAccount(so) + ms.accounts[addr] = newAccount(so) } else { // Always make sure the state account nonce isn't actually higher // than the tracked one. so := ms.StateDB.GetStateObject(addr) if so != nil && uint64(len(account.nonces))+account.nstart < so.nonce { - ms.accounts[straddr] = newAccount(so) + ms.accounts[addr] = newAccount(so) } } - return ms.accounts[straddr] + return ms.accounts[addr] } func newAccount(so *StateObject) *account { diff --git a/core/state/managed_state_test.go b/core/state/managed_state_test.go index 0b53a42c54..58e9369cf7 100644 --- a/core/state/managed_state_test.go +++ b/core/state/managed_state_test.go @@ -30,10 +30,10 @@ func create() (*ManagedState, *account) { statedb, _ := New(common.Hash{}, db) ms := ManageState(statedb) so := &StateObject{address: addr, nonce: 100} - ms.StateDB.stateObjects[addr.Str()] = so - ms.accounts[addr.Str()] = newAccount(so) + ms.StateDB.stateObjects[addr] = so + ms.accounts[addr] = newAccount(so) - return ms, ms.accounts[addr.Str()] + return ms, ms.accounts[addr] } func TestNewNonce(t *testing.T) { @@ -92,7 +92,7 @@ func TestRemoteNonceChange(t *testing.T) { account.nonces = append(account.nonces, nn...) nonce := ms.NewNonce(addr) - ms.StateDB.stateObjects[addr.Str()].nonce = 200 + ms.StateDB.stateObjects[addr].nonce = 200 nonce = ms.NewNonce(addr) if nonce != 200 { t.Error("expected nonce after remote update to be", 201, "got", nonce) @@ -100,7 +100,7 @@ func TestRemoteNonceChange(t *testing.T) { ms.NewNonce(addr) ms.NewNonce(addr) ms.NewNonce(addr) - ms.StateDB.stateObjects[addr.Str()].nonce = 200 + ms.StateDB.stateObjects[addr].nonce = 200 nonce = ms.NewNonce(addr) if nonce != 204 { t.Error("expected nonce after remote update to be", 201, "got", nonce) diff --git a/core/state/state_object.go b/core/state/state_object.go index c5462316dd..c40e4b47a6 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -58,7 +58,6 @@ func (self Storage) Copy() Storage { } type StateObject struct { - db trie.Database // State database for storing state changes trie *trie.SecureTrie // Address belonging to this account @@ -84,7 +83,6 @@ type StateObject struct { func NewStateObject(address common.Address, db trie.Database) *StateObject { object := &StateObject{ - db: db, address: address, balance: new(big.Int), dirty: true, @@ -119,10 +117,6 @@ func (c *StateObject) setAddr(addr, value common.Hash) { c.trie.Update(addr[:], v) } -func (self *StateObject) Storage() Storage { - return self.storage -} - func (self *StateObject) GetState(key common.Hash) common.Hash { value, exists := self.storage[key] if !exists { @@ -172,15 +166,11 @@ func (c *StateObject) SetBalance(amount *big.Int) { c.dirty = true } -func (c *StateObject) St() Storage { - return c.storage -} - // Return the gas back to the origin. Used by the Virtual machine or Closures func (c *StateObject) ReturnGas(gas, price *big.Int) {} -func (self *StateObject) Copy() *StateObject { - stateObject := NewStateObject(self.Address(), self.db) +func (self *StateObject) Copy(db trie.Database) *StateObject { + stateObject := NewStateObject(self.address, db) stateObject.balance.Set(self.balance) stateObject.codeHash = common.CopyBytes(self.codeHash) stateObject.nonce = self.nonce @@ -272,7 +262,7 @@ func (c *StateObject) EncodeRLP(w io.Writer) error { // DecodeObject decodes an RLP-encoded state object. func DecodeObject(address common.Address, db trie.Database, data []byte) (*StateObject, error) { var ( - obj = &StateObject{address: address, db: db, storage: make(Storage)} + obj = &StateObject{address: address, storage: make(Storage)} ext extStateObject err error ) diff --git a/core/state/statedb.go b/core/state/statedb.go index 8ba81613d6..082dab7356 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -43,7 +43,7 @@ type StateDB struct { db ethdb.Database trie *trie.SecureTrie - stateObjects map[string]*StateObject + stateObjects map[common.Address]*StateObject refund *big.Int @@ -62,7 +62,7 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) { return &StateDB{ db: db, trie: tr, - stateObjects: make(map[string]*StateObject), + stateObjects: make(map[common.Address]*StateObject), refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), }, nil @@ -83,7 +83,7 @@ func (self *StateDB) Reset(root common.Hash) error { *self = StateDB{ db: self.db, trie: tr, - stateObjects: make(map[string]*StateObject), + stateObjects: make(map[common.Address]*StateObject), refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), } @@ -242,12 +242,12 @@ func (self *StateDB) DeleteStateObject(stateObject *StateObject) { addr := stateObject.Address() self.trie.Delete(addr[:]) - //delete(self.stateObjects, addr.Str()) + //delete(self.stateObjects, addr) } // Retrieve a state object given my the address. Nil if not found func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObject) { - stateObject = self.stateObjects[addr.Str()] + stateObject = self.stateObjects[addr] if stateObject != nil { if stateObject.deleted { stateObject = nil @@ -270,7 +270,7 @@ func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObje } func (self *StateDB) SetStateObject(object *StateObject) { - self.stateObjects[object.Address().Str()] = object + self.stateObjects[object.Address()] = object } // Retrieve a state object or create a new state object if nil @@ -291,7 +291,7 @@ func (self *StateDB) newStateObject(addr common.Address) *StateObject { stateObject := NewStateObject(addr, self.db) stateObject.SetNonce(StartingNonce) - self.stateObjects[addr.Str()] = stateObject + self.stateObjects[addr] = stateObject return stateObject } @@ -323,9 +323,9 @@ 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 - for k, stateObject := range self.stateObjects { + for addr, stateObject := range self.stateObjects { if stateObject.dirty { - state.stateObjects[k] = stateObject.Copy() + state.stateObjects[addr] = stateObject.Copy(self.db) } } From e6ed80e8991703ae4e72a3c26361c1280b1b76a3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 23 Sep 2016 00:10:00 +0200 Subject: [PATCH 2/6] core/state: lazy-load account storage tries and code With this change, StateObject can work without loading the storage trie and contract code. This is required because we're about to cache state objects and want to reduce the amount of memory referenced by idle objects. --- accounts/abi/bind/backends/simulated.go | 7 +- core/state/dump.go | 21 +-- core/state/managed_state.go | 4 +- core/state/managed_state_test.go | 7 +- core/state/state_object.go | 229 ++++++++++++++---------- core/state/state_test.go | 39 ++-- core/state/statedb.go | 45 ++--- light/state_test.go | 2 +- tests/state_test_util.go | 2 +- tests/vm_test_util.go | 4 +- 10 files changed, 186 insertions(+), 174 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 29b4e8ea33..7e09abb119 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -135,11 +135,8 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres return nil, errBlockNumberUnsupported } statedb, _ := b.blockchain.State() - if obj := statedb.GetStateObject(contract); obj != nil { - val := obj.GetState(key) - return val[:], nil - } - return nil, nil + val := statedb.GetState(contract, key) + return val[:], nil } // TransactionReceipt returns the receipt of a transaction. diff --git a/core/state/dump.go b/core/state/dump.go index a328b05374..ee04dd3fcb 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -50,15 +50,15 @@ func (self *StateDB) RawDump() World { if err != nil { panic(err) } - account := Account{ - Balance: stateObject.balance.String(), - Nonce: stateObject.nonce, - Root: common.Bytes2Hex(stateObject.Root()), - CodeHash: common.Bytes2Hex(stateObject.codeHash), - Code: common.Bytes2Hex(stateObject.Code()), + 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)), Storage: make(map[string]string), } + stateObject.initTrie(self.db) storageIt := stateObject.trie.Iterator() for storageIt.Next() { account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) @@ -76,12 +76,3 @@ func (self *StateDB) Dump() []byte { return json } - -// Debug stuff -func (self *StateObject) CreateOutputForDiff() { - fmt.Printf("%x %x %x %x\n", self.Address(), self.Root(), self.balance.Bytes(), self.nonce) - it := self.trie.Iterator() - for it.Next() { - fmt.Printf("%x %x\n", it.Key, it.Value) - } -} diff --git a/core/state/managed_state.go b/core/state/managed_state.go index 73d3a3fe3d..ad73dc0dc6 100644 --- a/core/state/managed_state.go +++ b/core/state/managed_state.go @@ -127,7 +127,7 @@ func (ms *ManagedState) getAccount(addr common.Address) *account { // Always make sure the state account nonce isn't actually higher // than the tracked one. so := ms.StateDB.GetStateObject(addr) - if so != nil && uint64(len(account.nonces))+account.nstart < so.nonce { + if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() { ms.accounts[addr] = newAccount(so) } @@ -137,5 +137,5 @@ func (ms *ManagedState) getAccount(addr common.Address) *account { } func newAccount(so *StateObject) *account { - return &account{so, so.nonce, nil} + return &account{so, so.Nonce(), nil} } diff --git a/core/state/managed_state_test.go b/core/state/managed_state_test.go index 58e9369cf7..baa53428f9 100644 --- a/core/state/managed_state_test.go +++ b/core/state/managed_state_test.go @@ -29,7 +29,8 @@ func create() (*ManagedState, *account) { db, _ := ethdb.NewMemDatabase() statedb, _ := New(common.Hash{}, db) ms := ManageState(statedb) - so := &StateObject{address: addr, nonce: 100} + so := &StateObject{address: addr} + so.SetNonce(100) ms.StateDB.stateObjects[addr] = so ms.accounts[addr] = newAccount(so) @@ -92,7 +93,7 @@ func TestRemoteNonceChange(t *testing.T) { account.nonces = append(account.nonces, nn...) nonce := ms.NewNonce(addr) - ms.StateDB.stateObjects[addr].nonce = 200 + ms.StateDB.stateObjects[addr].data.Nonce = 200 nonce = ms.NewNonce(addr) if nonce != 200 { t.Error("expected nonce after remote update to be", 201, "got", nonce) @@ -100,7 +101,7 @@ func TestRemoteNonceChange(t *testing.T) { ms.NewNonce(addr) ms.NewNonce(addr) ms.NewNonce(addr) - ms.StateDB.stateObjects[addr].nonce = 200 + ms.StateDB.stateObjects[addr].data.Nonce = 200 nonce = ms.NewNonce(addr) if nonce != 204 { t.Error("expected nonce after remote update to be", 201, "got", nonce) diff --git a/core/state/state_object.go b/core/state/state_object.go index c40e4b47a6..95682654ff 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -57,112 +57,160 @@ func (self Storage) Copy() Storage { return cpy } +// StateObject represents an Ethereum account which is being modified. +// +// The usage pattern is as follows: +// First you need to obtain a state object. +// Account values can be accessed and modified through the object. +// Finally, call CommitTrie to write the modified storage trie into a database. type StateObject struct { - trie *trie.SecureTrie + address common.Address // Ethereum address of this account + data accountData - // Address belonging to this account - address common.Address - // The balance of the account - balance *big.Int - // The nonce of the account - nonce uint64 - // The code hash if code is present (i.e. a contract) - codeHash []byte - // The code for this account - code Code - // Cached storage (flushed when updated) - storage Storage + // DB error. + // State objects are used by the consensus core and VM which are + // unable to deal with database-level errors. Any error that occurs + // during a database read is memoized here and will eventually be returned + // by StateDB.Commit. + dbErr error - // Mark for deletion + // Write caches. + trie *trie.SecureTrie // storage trie, which becomes non-nil on first access + code Code // contract bytecode, which gets set when code is loaded + storage Storage // Cached storage (flushed when updated) + + // Cache flags. // When an object is marked for deletion it will be delete from the trie // during the "update" phase of the state transition + dirty bool // true if anything has changed remove bool deleted bool - dirty bool +} + +// accountData is the Ethereum consensus representation of accounts. +// These objects are stored in the main account trie. +type accountData 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, - balance: new(big.Int), - dirty: true, - codeHash: emptyCodeHash, - storage: make(Storage), + address: address, + data: accountData{Balance: new(big.Int), CodeHash: emptyCodeHash}, + storage: make(Storage), + dirty: true, } - object.trie, _ = trie.NewSecure(common.Hash{}, db) return object } +// setError remembers the first non-nil error it is called with. +func (self *StateObject) setError(err error) { + if self.dbErr == nil { + self.dbErr = err + } +} + func (self *StateObject) MarkForDeletion() { self.remove = true self.dirty = true if glog.V(logger.Core) { - glog.Infof("%x: #%d %v X\n", self.Address(), self.nonce, self.balance) + glog.Infof("%x: #%d %v X\n", self.Address(), self.Nonce(), self.Balance()) } } -func (c *StateObject) getAddr(addr common.Hash) common.Hash { - var ret []byte - rlp.DecodeBytes(c.trie.Get(addr[:]), &ret) - return common.BytesToHash(ret) -} - -func (c *StateObject) setAddr(addr, value common.Hash) { - v, err := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00")) - if err != nil { - // if RLPing failed we better panic and not fail silently. This would be considered a consensus issue - panic(err) - } - c.trie.Update(addr[:], v) -} - -func (self *StateObject) GetState(key common.Hash) common.Hash { - value, exists := self.storage[key] - if !exists { - value = self.getAddr(key) - if (value != common.Hash{}) { - self.storage[key] = value +func (c *StateObject) initTrie(db trie.Database) { + if c.trie == nil { + var err error + c.trie, err = trie.NewSecure(c.data.Root, db) + if err != nil { + c.trie, _ = trie.NewSecure(common.Hash{}, db) + c.setError(fmt.Errorf("can't create storage trie: %v", err)) } } +} +// GetState returns a value in account storage. +func (self *StateObject) GetState(db trie.Database, key common.Hash) common.Hash { + value, exists := self.storage[key] + if exists { + return value + } + // Load from DB in case it is missing. + self.initTrie(db) + var ret []byte + rlp.DecodeBytes(self.trie.Get(key[:]), &ret) + value = common.BytesToHash(ret) + if (value != common.Hash{}) { + self.storage[key] = value + } return value } +// SetState updates a value in account storage. func (self *StateObject) SetState(key, value common.Hash) { self.storage[key] = value self.dirty = true } -// Update updates the current cached storage to the trie -func (self *StateObject) Update() { +// updateTrie writes cached storage modifications into the object's storage trie. +func (self *StateObject) updateTrie(db trie.Database) { + self.initTrie(db) for key, value := range self.storage { if (value == common.Hash{}) { self.trie.Delete(key[:]) continue } - self.setAddr(key, value) + // Encoding []byte cannot fail, ok to ignore the error. + v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00")) + self.trie.Update(key[:], v) } } +// UpdateRoot sets the trie root to the current root hash of +func (self *StateObject) UpdateRoot(db trie.Database) { + self.updateTrie(db) + self.data.Root = self.trie.Hash() +} + +// CommitTrie the storage trie of the object to dwb. +// This updates the trie root. +func (self *StateObject) CommitTrie(db trie.Database, dbw trie.DatabaseWriter) error { + self.updateTrie(db) + if self.dbErr != nil { + fmt.Println("dbErr:", self.dbErr) + return self.dbErr + } + self.dbErr = nil + + root, err := self.trie.CommitTo(dbw) + if err == nil { + self.data.Root = root + } + return err +} + func (c *StateObject) AddBalance(amount *big.Int) { - c.SetBalance(new(big.Int).Add(c.balance, amount)) + c.SetBalance(new(big.Int).Add(c.Balance(), amount)) if glog.V(logger.Core) { - glog.Infof("%x: #%d %v (+ %v)\n", c.Address(), c.nonce, c.balance, amount) + glog.Infof("%x: #%d %v (+ %v)\n", c.Address(), c.Nonce(), c.Balance(), amount) } } func (c *StateObject) SubBalance(amount *big.Int) { - c.SetBalance(new(big.Int).Sub(c.balance, amount)) + c.SetBalance(new(big.Int).Sub(c.Balance(), amount)) if glog.V(logger.Core) { - glog.Infof("%x: #%d %v (- %v)\n", c.Address(), c.nonce, c.balance, amount) + glog.Infof("%x: #%d %v (- %v)\n", c.Address(), c.Nonce(), c.Balance(), amount) } } func (c *StateObject) SetBalance(amount *big.Int) { - c.balance = amount + c.data.Balance = amount c.dirty = true } @@ -171,9 +219,8 @@ func (c *StateObject) ReturnGas(gas, price *big.Int) {} func (self *StateObject) Copy(db trie.Database) *StateObject { stateObject := NewStateObject(self.address, db) - stateObject.balance.Set(self.balance) - stateObject.codeHash = common.CopyBytes(self.codeHash) - stateObject.nonce = self.nonce + stateObject.data = self.data + stateObject.data.Balance.Set(self.data.Balance) stateObject.trie = self.trie stateObject.code = self.code stateObject.storage = self.storage.Copy() @@ -188,40 +235,48 @@ func (self *StateObject) Copy(db trie.Database) *StateObject { // Attribute accessors // -func (self *StateObject) Balance() *big.Int { - return self.balance -} - // Returns the address of the contract/account func (c *StateObject) Address() common.Address { return c.address } -func (self *StateObject) Trie() *trie.SecureTrie { - return self.trie -} - -func (self *StateObject) Root() []byte { - return self.trie.Root() -} - -func (self *StateObject) Code() []byte { - return self.code +// LoadCode returns the contract code associated with this object, if any. +func (self *StateObject) Code(db trie.Database) []byte { + if self.code != nil { + return self.code + } + if bytes.Equal(self.CodeHash(), emptyCodeHash) { + return nil + } + code, err := db.Get(self.CodeHash()) + if err != nil { + self.setError(fmt.Errorf("can't load code hash %x: %v", self.CodeHash(), err)) + } + self.code = code + return code } func (self *StateObject) SetCode(code []byte) { self.code = code - self.codeHash = crypto.Keccak256(code) + self.data.CodeHash = crypto.Keccak256(code) self.dirty = true } func (self *StateObject) SetNonce(nonce uint64) { - self.nonce = nonce + self.data.Nonce = nonce self.dirty = true } +func (self *StateObject) CodeHash() []byte { + return self.data.CodeHash +} + +func (self *StateObject) Balance() *big.Int { + return self.data.Balance +} + func (self *StateObject) Nonce() uint64 { - return self.nonce + return self.data.Nonce } // Never called, but must be present to allow StateObject to be used @@ -247,38 +302,14 @@ func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { } } -type extStateObject struct { - Nonce uint64 - Balance *big.Int - Root common.Hash - CodeHash []byte -} - // EncodeRLP implements rlp.Encoder. func (c *StateObject) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{c.nonce, c.balance, c.Root(), c.codeHash}) + 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) { - var ( - obj = &StateObject{address: address, storage: make(Storage)} - ext extStateObject - err error - ) - if err = rlp.DecodeBytes(data, &ext); err != nil { - return nil, err - } - if obj.trie, err = trie.NewSecure(ext.Root, db); err != nil { - return nil, err - } - if !bytes.Equal(ext.CodeHash, emptyCodeHash) { - if obj.code, err = db.Get(ext.CodeHash); err != nil { - return nil, fmt.Errorf("can't get code for hash %x: %v", ext.CodeHash, err) - } - } - obj.nonce = ext.Nonce - obj.balance = ext.Balance - obj.codeHash = ext.CodeHash - return obj, nil + obj := StateObject{address: address, storage: make(Storage)} + err := rlp.DecodeBytes(data, &obj.data) + return &obj, err } diff --git a/core/state/state_test.go b/core/state/state_test.go index 69cf083cfa..c88274f3b2 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -146,8 +146,8 @@ func TestSnapshot2(t *testing.T) { // db, trie are already non-empty values so0 := state.GetStateObject(stateobjaddr0) - so0.balance = big.NewInt(42) - so0.nonce = 43 + so0.SetBalance(big.NewInt(42)) + so0.SetNonce(43) so0.SetCode([]byte{'c', 'a', 'f', 'e'}) so0.remove = false so0.deleted = false @@ -157,8 +157,8 @@ func TestSnapshot2(t *testing.T) { // and one with deleted == true so1 := state.GetStateObject(stateobjaddr1) - so1.balance = big.NewInt(52) - so1.nonce = 53 + so1.SetBalance(big.NewInt(52)) + so1.SetNonce(53) so1.SetCode([]byte{'c', 'a', 'f', 'e', '2'}) so1.remove = true so1.deleted = true @@ -174,41 +174,50 @@ func TestSnapshot2(t *testing.T) { state.Set(snapshot) so0Restored := state.GetStateObject(stateobjaddr0) - so0Restored.GetState(storageaddr) - so1Restored := state.GetStateObject(stateobjaddr1) + // Update lazily-loaded values before comparing. + so0Restored.GetState(db, storageaddr) + so0Restored.Code(db) // non-deleted is equal (restored) compareStateObjects(so0Restored, so0, t) + // deleted should be nil, both before and after restore of state copy + so1Restored := state.GetStateObject(stateobjaddr1) if so1Restored != nil { t.Fatalf("deleted object not nil after restoring snapshot") } } func compareStateObjects(so0, so1 *StateObject, t *testing.T) { - if so0.address != so1.address { + if so0.Address() != so1.Address() { t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address) } - if so0.balance.Cmp(so1.balance) != 0 { - t.Fatalf("Balance mismatch: have %v, want %v", so0.balance, so1.balance) + if so0.Balance().Cmp(so1.Balance()) != 0 { + t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance()) } - if so0.nonce != so1.nonce { - t.Fatalf("Nonce mismatch: have %v, want %v", so0.nonce, so1.nonce) + if so0.Nonce() != so1.Nonce() { + t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce()) } - if !bytes.Equal(so0.codeHash, so1.codeHash) { - t.Fatalf("CodeHash mismatch: have %v, want %v", so0.codeHash, so1.codeHash) + if so0.data.Root != so1.data.Root { + t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:]) + } + if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) { + t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash()) } if !bytes.Equal(so0.code, so1.code) { t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code) } + if len(so1.storage) != len(so0.storage) { + t.Errorf("Storage size mismatch: have %d, want %d", len(so1.storage), len(so0.storage)) + } for k, v := range so1.storage { if so0.storage[k] != v { - t.Fatalf("Storage key %s mismatch: have %v, want %v", k, so0.storage[k], v) + t.Errorf("Storage key %x mismatch: have %v, want %v", k, so0.storage[k], v) } } for k, v := range so0.storage { if so1.storage[k] != v { - t.Fatalf("Storage key %s mismatch: have %v, want none.", k, v) + t.Errorf("Storage key %x mismatch: have %v, want none.", k, v) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 082dab7356..8d261212fa 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -137,7 +137,7 @@ func (self *StateDB) GetAccount(addr common.Address) vm.Account { func (self *StateDB) GetBalance(addr common.Address) *big.Int { stateObject := self.GetStateObject(addr) if stateObject != nil { - return stateObject.balance + return stateObject.Balance() } return common.Big0 @@ -146,7 +146,7 @@ func (self *StateDB) GetBalance(addr common.Address) *big.Int { func (self *StateDB) GetNonce(addr common.Address) uint64 { stateObject := self.GetStateObject(addr) if stateObject != nil { - return stateObject.nonce + return stateObject.Nonce() } return StartingNonce @@ -155,18 +155,16 @@ func (self *StateDB) GetNonce(addr common.Address) uint64 { func (self *StateDB) GetCode(addr common.Address) []byte { stateObject := self.GetStateObject(addr) if stateObject != nil { - return stateObject.code + return stateObject.Code(self.db) } - return nil } func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash { stateObject := self.GetStateObject(a) if stateObject != nil { - return stateObject.GetState(b) + return stateObject.GetState(self.db, b) } - return common.Hash{} } @@ -214,8 +212,7 @@ func (self *StateDB) Delete(addr common.Address) bool { stateObject := self.GetStateObject(addr) if stateObject != nil { stateObject.MarkForDeletion() - stateObject.balance = new(big.Int) - + stateObject.data.Balance = new(big.Int) return true } @@ -245,7 +242,7 @@ func (self *StateDB) DeleteStateObject(stateObject *StateObject) { //delete(self.stateObjects, addr) } -// Retrieve a state object given my the address. Nil if not found +// 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 { @@ -305,7 +302,7 @@ func (self *StateDB) CreateStateObject(addr common.Address) *StateObject { // If it existed set the balance to the new account if so != nil { - newSo.balance = so.balance + newSo.data.Balance = so.data.Balance } return newSo @@ -363,7 +360,7 @@ func (s *StateDB) IntermediateRoot() common.Hash { if stateObject.remove { s.DeleteStateObject(stateObject) } else { - stateObject.Update() + stateObject.UpdateRoot(s.db) s.UpdateStateObject(stateObject) } } @@ -407,7 +404,7 @@ func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) { return root, batch } -func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) { +func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) { s.refund = new(big.Int) for _, stateObject := range s.stateObjects { @@ -417,36 +414,24 @@ func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) { s.DeleteStateObject(stateObject) } else { // Write any contract code associated with the state object - if len(stateObject.code) > 0 { - if err := db.Put(stateObject.codeHash, stateObject.code); err != nil { + if stateObject.code != nil { + if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil { return common.Hash{}, err } } - // Write any storage changes in the state object to its trie. - stateObject.Update() - // Commit the trie of the object to the batch. - // This updates the trie root internally, so - // getting the root hash of the storage trie - // through UpdateStateObject is fast. - if _, err := stateObject.trie.CommitTo(db); err != nil { + // Write any storage changes in the state object to its storage trie. + if err := stateObject.CommitTrie(s.db, dbw); err != nil { return common.Hash{}, err } - // Update the object in the account trie. + // Update the object in the main account trie. s.UpdateStateObject(stateObject) } stateObject.dirty = false } - return s.trie.CommitTo(db) + return s.trie.CommitTo(dbw) } func (self *StateDB) Refunds() *big.Int { return self.refund } - -// Debug stuff -func (self *StateDB) CreateOutputForDiff() { - for _, stateObject := range self.stateObjects { - stateObject.CreateOutputForDiff() - } -} diff --git a/light/state_test.go b/light/state_test.go index 2c2e6daea1..90c38604ac 100644 --- a/light/state_test.go +++ b/light/state_test.go @@ -62,7 +62,7 @@ func makeTestState() (common.Hash, ethdb.Database) { } so.AddBalance(big.NewInt(int64(i))) so.SetCode([]byte{i, i, i}) - so.Update() + so.UpdateRoot(sdb) st.UpdateStateObject(so) } root, _ := st.Commit() diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 36fa30881d..725d1acea8 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -187,7 +187,7 @@ func runStateTest(ruleSet RuleSet, test VmTest) error { } for addr, value := range account.Storage { - v := obj.GetState(common.HexToHash(addr)) + v := statedb.GetState(obj.Address(), common.HexToHash(addr)) vexp := common.HexToHash(value) if v != vexp { diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index 37f0af33c5..431fef5c41 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -205,11 +205,9 @@ func runVmTest(test VmTest) error { if obj == nil { continue } - for addr, value := range account.Storage { - v := obj.GetState(common.HexToHash(addr)) + v := statedb.GetState(obj.Address(), common.HexToHash(addr)) vexp := common.HexToHash(value) - if v != vexp { return fmt.Errorf("(%x: %s) storage failed. Expected %x, got %x (%v %v)\n", obj.Address().Bytes()[0:4], addr, vexp, v, vexp.Big(), v.Big()) } From 2b584d1dbe8150d70ea7b5fe0e111e13ace98cf6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 23 Sep 2016 02:33:09 +0200 Subject: [PATCH 3/6] core/state: rename dump types to Dump* --- core/state/dump.go | 20 ++++++++++---------- eth/api.go | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/state/dump.go b/core/state/dump.go index ee04dd3fcb..c92111a804 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -type Account struct { +type DumpAccount struct { Balance string `json:"balance"` Nonce uint64 `json:"nonce"` Root string `json:"root"` @@ -32,15 +32,15 @@ type Account struct { Storage map[string]string `json:"storage"` } -type World struct { - Root string `json:"root"` - Accounts map[string]Account `json:"accounts"` +type Dump struct { + Root string `json:"root"` + Accounts map[string]DumpAccount `json:"accounts"` } -func (self *StateDB) RawDump() World { - world := World{ +func (self *StateDB) RawDump() Dump { + dump := Dump{ Root: common.Bytes2Hex(self.trie.Root()), - Accounts: make(map[string]Account), + Accounts: make(map[string]DumpAccount), } it := self.trie.Iterator() @@ -50,7 +50,7 @@ func (self *StateDB) RawDump() World { if err != nil { panic(err) } - account := Account{ + account := DumpAccount{ Balance: stateObject.Balance().String(), Nonce: stateObject.Nonce(), Root: common.Bytes2Hex(stateObject.data.Root[:]), @@ -63,9 +63,9 @@ func (self *StateDB) RawDump() World { for storageIt.Next() { account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) } - world.Accounts[common.Bytes2Hex(addr)] = account + dump.Accounts[common.Bytes2Hex(addr)] = account } - return world + return dump } func (self *StateDB) Dump() []byte { diff --git a/eth/api.go b/eth/api.go index f4bce47b84..d6c0826eda 100644 --- a/eth/api.go +++ b/eth/api.go @@ -288,14 +288,14 @@ func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI { } // DumpBlock retrieves the entire state of the database at a given block. -func (api *PublicDebugAPI) DumpBlock(number uint64) (state.World, error) { +func (api *PublicDebugAPI) DumpBlock(number uint64) (state.Dump, error) { block := api.eth.BlockChain().GetBlockByNumber(number) if block == nil { - return state.World{}, fmt.Errorf("block #%d not found", number) + return state.Dump{}, fmt.Errorf("block #%d not found", number) } stateDb, err := state.New(block.Root(), api.eth.ChainDb()) if err != nil { - return state.World{}, err + return state.Dump{}, err } return stateDb.RawDump(), nil } From 1c920578fbc91645563e30df249ea9197d0dcabc Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 23 Sep 2016 02:34:42 +0200 Subject: [PATCH 4/6] core/state: track all accounts in canon state This change introduces a global, per-state cache that keeps account data in the canon state. --- core/state/dump.go | 21 ++++++----- core/state/state_object.go | 42 +++++++++------------ core/state/statedb.go | 75 +++++++++++++++++++++++++++----------- tests/util.go | 13 ++++--- 4 files changed, 90 insertions(+), 61 deletions(-) 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 95682654ff..fb3438cbf1 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. @@ -218,8 +224,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 @@ -227,7 +232,6 @@ func (self *StateObject) Copy(db trie.Database) *StateObject { stateObject.remove = self.remove stateObject.dirty = self.dirty stateObject.deleted = self.deleted - return stateObject } @@ -301,15 +305,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 } From 4fbcd9bff23a9f53fcd47f1b4f1a6d9176bc24a5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 23 Sep 2016 03:46:03 +0200 Subject: [PATCH 5/6] core/state: add note about code size caching --- core/state/state_object.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/state/state_object.go b/core/state/state_object.go index fb3438cbf1..8a4bc7cd5f 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -94,6 +94,9 @@ type Account struct { Balance *big.Int Root common.Hash // merkle root of the storage trie CodeHash []byte + + // TODO(fjl): track code size here to get it into the cache + // codeSize int } // NewObject creates a state object. From 835e40439b38dec119dd60bbec56f6ba15c2397c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 23 Sep 2016 04:16:42 +0200 Subject: [PATCH 6/6] core/state: robustify commit error handling --- core/state/statedb.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index f21c6b0efd..9b3cf0eb00 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -427,8 +427,14 @@ func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) { return root, batch } -func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) { +func (s *StateDB) commit(dbw trie.DatabaseWriter) (root common.Hash, err error) { s.refund = new(big.Int) + defer func() { + if err != nil { + // Committing failed, any updates to the canon state are invalid. + s.all = make(map[common.Address]Account) + } + }() // Commit objects to the trie. for addr, stateObject := range s.stateObjects { @@ -444,7 +450,6 @@ func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) { return common.Hash{}, err } } - // Write any storage changes in the state object to its storage trie. if err := stateObject.CommitTrie(s.db, dbw); err != nil { return common.Hash{}, err @@ -455,14 +460,8 @@ func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) { } stateObject.dirty = false } - // 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 + return s.trie.CommitTo(dbw) } func (self *StateDB) Refunds() *big.Int {