aboutsummaryrefslogtreecommitdiffstats
path: root/core/state/state_object.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/state/state_object.go')
-rw-r--r--core/state/state_object.go302
1 files changed, 171 insertions, 131 deletions
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 9f0ce5b4b..3496008a6 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -57,108 +57,163 @@ 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 {
- db trie.Database // State database for storing state changes
- trie *trie.SecureTrie
-
- // 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
-
- // Mark for deletion
+ address common.Address // Ethereum address of this account
+ data Account
+
+ // 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
+
+ // 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
- remove bool
- deleted bool
- dirty bool
+ dirtyCode bool // true if the code was updated
+ remove bool
+ deleted bool
+ onDirty func(addr common.Address) // Callback method to mark a state object newly dirty
}
-func NewStateObject(address common.Address, db trie.Database) *StateObject {
- object := &StateObject{
- db: db,
- address: address,
- balance: new(big.Int),
- dirty: true,
- codeHash: emptyCodeHash,
- storage: make(Storage),
- }
- object.trie, _ = trie.NewSecure(common.Hash{}, db)
- return object
-}
+// Account is the Ethereum consensus representation of accounts.
+// These objects are stored in the main account trie.
+type Account struct {
+ Nonce uint64
+ Balance *big.Int
+ Root common.Hash // merkle root of the storage trie
+ CodeHash []byte
-func (self *StateObject) MarkForDeletion() {
- self.remove = true
- self.dirty = true
+ codeSize *int
+}
- if glog.V(logger.Core) {
- glog.Infof("%x: #%d %v X\n", self.Address(), self.nonce, self.balance)
+// NewObject creates a state object.
+func NewObject(address common.Address, data Account, onDirty func(addr common.Address)) *StateObject {
+ if data.Balance == nil {
+ data.Balance = new(big.Int)
+ }
+ if data.CodeHash == nil {
+ data.CodeHash = emptyCodeHash
}
+ return &StateObject{address: address, data: data, storage: make(Storage), onDirty: onDirty}
}
-func (c *StateObject) getAddr(addr common.Hash) common.Hash {
- var ret []byte
- rlp.DecodeBytes(c.trie.Get(addr[:]), &ret)
- return common.BytesToHash(ret)
+// EncodeRLP implements rlp.Encoder.
+func (c *StateObject) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, c.data)
}
-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)
+// setError remembers the first non-nil error it is called with.
+func (self *StateObject) setError(err error) {
+ if self.dbErr == nil {
+ self.dbErr = err
}
- c.trie.Update(addr[:], v)
}
-func (self *StateObject) Storage() Storage {
- return self.storage
+func (self *StateObject) MarkForDeletion() {
+ self.remove = true
+ if self.onDirty != nil {
+ self.onDirty(self.Address())
+ self.onDirty = nil
+ }
+ if glog.V(logger.Core) {
+ glog.Infof("%x: #%d %v X\n", self.Address(), self.Nonce(), self.Balance())
+ }
}
-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) getTrie(db trie.Database) *trie.SecureTrie {
+ 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))
}
}
+ return c.trie
+}
+// 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.
+ tr := self.getTrie(db)
+ var ret []byte
+ rlp.DecodeBytes(tr.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
+ if self.onDirty != nil {
+ self.onDirty(self.Address())
+ self.onDirty = nil
+ }
}
-// 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) {
+ tr := self.getTrie(db)
for key, value := range self.storage {
if (value == common.Hash{}) {
- self.trie.Delete(key[:])
+ tr.Delete(key[:])
continue
}
- self.setAddr(key, value)
+ // Encoding []byte cannot fail, ok to ignore the error.
+ v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
+ tr.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
+ }
+ root, err := self.trie.CommitTo(dbw)
+ if err == nil {
+ self.data.Root = root
+ }
+ return err
+}
+
func (c *StateObject) AddBalance(amount *big.Int) {
if amount.Cmp(common.Big0) == 0 {
return
}
- 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)
}
}
@@ -166,37 +221,32 @@ func (c *StateObject) SubBalance(amount *big.Int) {
if amount.Cmp(common.Big0) == 0 {
return
}
- 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.dirty = true
-}
-
-func (c *StateObject) St() Storage {
- return c.storage
+func (self *StateObject) SetBalance(amount *big.Int) {
+ self.data.Balance = amount
+ if self.onDirty != nil {
+ self.onDirty(self.Address())
+ self.onDirty = nil
+ }
}
// 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)
- stateObject.balance.Set(self.balance)
- stateObject.codeHash = common.CopyBytes(self.codeHash)
- stateObject.nonce = self.nonce
+func (self *StateObject) Copy(db trie.Database, onDirty func(addr common.Address)) *StateObject {
+ stateObject := NewObject(self.address, self.data, onDirty)
stateObject.trie = self.trie
stateObject.code = self.code
stateObject.storage = self.storage.Copy()
stateObject.remove = self.remove
- stateObject.dirty = self.dirty
+ stateObject.dirtyCode = self.dirtyCode
stateObject.deleted = self.deleted
-
return stateObject
}
@@ -204,40 +254,66 @@ func (self *StateObject) Copy() *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()
+// Code 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) Code() []byte {
- return self.code
+// CodeSize returns the size of the contract code associated with this object.
+func (self *StateObject) CodeSize(db trie.Database) int {
+ if self.data.codeSize == nil {
+ self.data.codeSize = new(int)
+ *self.data.codeSize = len(self.Code(db))
+ }
+ return *self.data.codeSize
}
func (self *StateObject) SetCode(code []byte) {
self.code = code
- self.codeHash = crypto.Keccak256(code)
- self.dirty = true
+ self.data.CodeHash = crypto.Keccak256(code)
+ self.data.codeSize = new(int)
+ *self.data.codeSize = len(code)
+ self.dirtyCode = true
+ if self.onDirty != nil {
+ self.onDirty(self.Address())
+ self.onDirty = nil
+ }
}
func (self *StateObject) SetNonce(nonce uint64) {
- self.nonce = nonce
- self.dirty = true
+ self.data.Nonce = nonce
+ if self.onDirty != nil {
+ self.onDirty(self.Address())
+ self.onDirty = nil
+ }
+}
+
+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
@@ -262,39 +338,3 @@ 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})
-}
-
-// 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)}
- 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
-}