core/state: track all accounts in canon state

This change introduces a global, per-state cache that keeps
account data in the canon state.
This commit is contained in:
Felix Lange 2016-09-23 02:34:42 +02:00 committed by Péter Szilágyi
parent aa2a4f8659
commit 8ecd29b4de
4 changed files with 90 additions and 61 deletions

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
) )
type DumpAccount struct { type DumpAccount struct {
@ -46,20 +47,22 @@ func (self *StateDB) RawDump() Dump {
it := self.trie.Iterator() it := self.trie.Iterator()
for it.Next() { for it.Next() {
addr := self.trie.GetKey(it.Key) addr := self.trie.GetKey(it.Key)
stateObject, err := DecodeObject(common.BytesToAddress(addr), self.db, it.Value) var data Account
if err != nil { if err := rlp.DecodeBytes(it.Value, &data); err != nil {
panic(err) panic(err)
} }
obj := NewObject(common.BytesToAddress(addr), data)
account := DumpAccount{ account := DumpAccount{
Balance: stateObject.Balance().String(), Balance: data.Balance.String(),
Nonce: stateObject.Nonce(), Nonce: data.Nonce,
Root: common.Bytes2Hex(stateObject.data.Root[:]), Root: common.Bytes2Hex(data.Root[:]),
CodeHash: common.Bytes2Hex(stateObject.CodeHash()), CodeHash: common.Bytes2Hex(data.CodeHash),
Code: common.Bytes2Hex(stateObject.Code(self.db)), Code: common.Bytes2Hex(obj.Code(self.db)),
Storage: make(map[string]string), Storage: make(map[string]string),
} }
stateObject.initTrie(self.db) obj.initTrie(self.db)
storageIt := stateObject.trie.Iterator() storageIt := obj.trie.Iterator()
for storageIt.Next() { for storageIt.Next() {
account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value)
} }

View file

@ -65,7 +65,7 @@ func (self Storage) Copy() Storage {
// Finally, call CommitTrie to write the modified storage trie into a database. // Finally, call CommitTrie to write the modified storage trie into a database.
type StateObject struct { type StateObject struct {
address common.Address // Ethereum address of this account address common.Address // Ethereum address of this account
data accountData data Account
// DB error. // DB error.
// State objects are used by the consensus core and VM which are // State objects are used by the consensus core and VM which are
@ -87,23 +87,29 @@ type StateObject struct {
deleted bool 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. // These objects are stored in the main account trie.
type accountData struct { type Account struct {
Nonce uint64 Nonce uint64
Balance *big.Int Balance *big.Int
Root common.Hash // merkle root of the storage trie Root common.Hash // merkle root of the storage trie
CodeHash []byte CodeHash []byte
} }
func NewStateObject(address common.Address, db trie.Database) *StateObject { // NewObject creates a state object.
object := &StateObject{ func NewObject(address common.Address, data Account) *StateObject {
address: address, if data.Balance == nil {
data: accountData{Balance: new(big.Int), CodeHash: emptyCodeHash}, data.Balance = new(big.Int)
storage: make(Storage),
dirty: true,
} }
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. // 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 (c *StateObject) ReturnGas(gas, price *big.Int) {}
func (self *StateObject) Copy(db trie.Database) *StateObject { func (self *StateObject) Copy(db trie.Database) *StateObject {
stateObject := NewStateObject(self.address, db) stateObject := NewObject(self.address, self.data)
stateObject.data = self.data
stateObject.data.Balance.Set(self.data.Balance) stateObject.data.Balance.Set(self.data.Balance)
stateObject.trie = self.trie stateObject.trie = self.trie
stateObject.code = self.code stateObject.code = self.code
@ -233,7 +238,6 @@ func (self *StateObject) Copy(db trie.Database) *StateObject {
stateObject.remove = self.remove stateObject.remove = self.remove
stateObject.dirty = self.dirty stateObject.dirty = self.dirty
stateObject.deleted = self.deleted stateObject.deleted = self.deleted
return stateObject 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
}

View file

@ -43,8 +43,14 @@ type StateDB struct {
db ethdb.Database db ethdb.Database
trie *trie.SecureTrie 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 stateObjects map[common.Address]*StateObject
// The refund counter, also used by state transitioning.
refund *big.Int refund *big.Int
thash, bhash common.Hash thash, bhash common.Hash
@ -62,6 +68,7 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
return &StateDB{ return &StateDB{
db: db, db: db,
trie: tr, trie: tr,
all: make(map[common.Address]Account),
stateObjects: make(map[common.Address]*StateObject), stateObjects: make(map[common.Address]*StateObject),
refund: new(big.Int), refund: new(big.Int),
logs: make(map[common.Hash]vm.Logs), logs: make(map[common.Hash]vm.Logs),
@ -74,15 +81,19 @@ func (self *StateDB) Reset(root common.Hash) error {
var ( var (
err error err error
tr = self.trie tr = self.trie
all = self.all
) )
if self.trie.Hash() != root { if self.trie.Hash() != root {
// The root has changed, invalidate canon state.
if tr, err = trie.NewSecure(root, self.db); err != nil { if tr, err = trie.NewSecure(root, self.db); err != nil {
return err return err
} }
all = make(map[common.Address]Account)
} }
*self = StateDB{ *self = StateDB{
db: self.db, db: self.db,
trie: tr, trie: tr,
all: all,
stateObjects: make(map[common.Address]*StateObject), stateObjects: make(map[common.Address]*StateObject),
refund: new(big.Int), refund: new(big.Int),
logs: make(map[common.Hash]vm.Logs), 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. // Retrieve a state object given my the address. Returns nil if not found.
func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObject) { func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObject) {
stateObject = self.stateObjects[addr] // Prefer 'live' objects.
if stateObject != nil { if obj := self.stateObjects[addr]; obj != nil {
if stateObject.deleted { if obj.deleted {
stateObject = nil
}
return stateObject
}
data := self.trie.Get(addr[:])
if len(data) == 0 {
return nil return nil
} }
stateObject, err := DecodeObject(addr, self.db, data) return obj
if err != nil { }
// 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
}
var data Account
if err := rlp.DecodeBytes(enc, &data); err != nil {
glog.Errorf("can't decode object at %x: %v", addr[:], err) glog.Errorf("can't decode object at %x: %v", addr[:], err)
return nil return nil
} }
self.SetStateObject(stateObject) // Update the all cache. Content in DB always corresponds
return stateObject // 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) { func (self *StateDB) SetStateObject(object *StateObject) {
@ -285,15 +307,14 @@ func (self *StateDB) newStateObject(addr common.Address) *StateObject {
if glog.V(logger.Core) { if glog.V(logger.Core) {
glog.Infof("(+) %x\n", addr) glog.Infof("(+) %x\n", addr)
} }
obj := NewObject(addr, Account{})
stateObject := NewStateObject(addr, self.db) obj.dirty = true
stateObject.SetNonce(StartingNonce) obj.SetNonce(StartingNonce)
self.stateObjects[addr] = stateObject self.stateObjects[addr] = obj
return obj
return stateObject
} }
// 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 { func (self *StateDB) CreateStateObject(addr common.Address) *StateObject {
// Get previous (if any) // Get previous (if any)
so := self.GetStateObject(addr) so := self.GetStateObject(addr)
@ -320,6 +341,7 @@ func (self *StateDB) Copy() *StateDB {
// ignore error - we assume state-to-be-copied always exists // ignore error - we assume state-to-be-copied always exists
state, _ := New(common.Hash{}, self.db) state, _ := New(common.Hash{}, self.db)
state.trie = self.trie state.trie = self.trie
state.all = self.all
for addr, stateObject := range self.stateObjects { for addr, stateObject := range self.stateObjects {
if stateObject.dirty { if stateObject.dirty {
state.stateObjects[addr] = stateObject.Copy(self.db) state.stateObjects[addr] = stateObject.Copy(self.db)
@ -340,6 +362,7 @@ func (self *StateDB) Copy() *StateDB {
func (self *StateDB) Set(state *StateDB) { func (self *StateDB) Set(state *StateDB) {
self.trie = state.trie self.trie = state.trie
self.stateObjects = state.stateObjects self.stateObjects = state.stateObjects
self.all = state.all
self.refund = state.refund self.refund = state.refund
self.logs = state.logs 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) { func (s *StateDB) commit(dbw trie.DatabaseWriter) (common.Hash, error) {
s.refund = new(big.Int) 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 stateObject.remove {
// If the object has been removed, don't bother syncing it // If the object has been removed, don't bother syncing it
// and just mark it for deletion in the trie. // and just mark it for deletion in the trie.
s.DeleteStateObject(stateObject) s.DeleteStateObject(stateObject)
delete(s.all, addr)
} else { } else {
// Write any contract code associated with the state object // Write any contract code associated with the state object
if stateObject.code != nil { 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. // Update the object in the main account trie.
s.UpdateStateObject(stateObject) s.UpdateStateObject(stateObject)
s.all[addr] = stateObject.data
} }
stateObject.dirty = false 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 { func (self *StateDB) Refunds() *big.Int {

View file

@ -104,15 +104,16 @@ func (self Log) Topics() [][]byte {
} }
func StateObjectFromAccount(db ethdb.Database, addr string, account Account) *state.StateObject { 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) { if common.IsHex(account.Code) {
account.Code = account.Code[2:] account.Code = account.Code[2:]
} }
obj.SetCode(common.Hex2Bytes(account.Code)) code := common.Hex2Bytes(account.Code)
obj.SetNonce(common.Big(account.Nonce).Uint64()) 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 return obj
} }