aboutsummaryrefslogtreecommitdiff
path: root/core/state/state_object.go
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2020-06-28 14:47:41 -0400
committerDeterminant <[email protected]>2020-06-28 14:47:41 -0400
commitd235e2c6a5788ec4a6cff15a16f56b38a3876a0d (patch)
tree5f2727f7a50ee5840f889c82776d3a30a88dd59b /core/state/state_object.go
parent13ebd8bd9468e9d769d598b0ca2afb72ba78cb97 (diff)
...
Diffstat (limited to 'core/state/state_object.go')
-rw-r--r--core/state/state_object.go499
1 files changed, 499 insertions, 0 deletions
diff --git a/core/state/state_object.go b/core/state/state_object.go
new file mode 100644
index 0000000..9c47dc4
--- /dev/null
+++ b/core/state/state_object.go
@@ -0,0 +1,499 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package state
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "math/big"
+ "time"
+
+ "github.com/ava-labs/go-ethereum/common"
+ "github.com/ava-labs/go-ethereum/crypto"
+ "github.com/ava-labs/go-ethereum/metrics"
+ "github.com/ava-labs/go-ethereum/rlp"
+)
+
+var emptyCodeHash = crypto.Keccak256(nil)
+
+type Code []byte
+
+func (c Code) String() string {
+ return string(c) //strings.Join(Disassemble(c), " ")
+}
+
+type Storage map[common.Hash]common.Hash
+
+func (s Storage) String() (str string) {
+ for key, value := range s {
+ str += fmt.Sprintf("%X : %X\n", key, value)
+ }
+
+ return
+}
+
+func (s Storage) Copy() Storage {
+ cpy := make(Storage)
+ for key, value := range s {
+ cpy[key] = value
+ }
+
+ 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 {
+ address common.Address
+ addrHash common.Hash // hash of ethereum address of the account
+ data Account
+ db *StateDB
+
+ // 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 // storage trie, which becomes non-nil on first access
+ code Code // contract bytecode, which gets set when code is loaded
+
+ originStorage Storage // Storage cache of original entries to dedup rewrites
+ dirtyStorage Storage // Storage entries that need to be flushed to disk
+ fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
+
+ // Cache flags.
+ // When an object is marked suicided it will be delete from the trie
+ // during the "update" phase of the state transition.
+ dirtyCode bool // true if the code was updated
+ suicided bool
+ deleted bool
+}
+
+// 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)
+}
+
+// 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
+ IsMultiCoin bool
+}
+
+// newObject creates a state object.
+func newObject(db *StateDB, address common.Address, data Account) *stateObject {
+ if data.Balance == nil {
+ data.Balance = new(big.Int)
+ }
+ if data.CodeHash == nil {
+ data.CodeHash = emptyCodeHash
+ }
+ return &stateObject{
+ db: db,
+ address: address,
+ addrHash: crypto.Keccak256Hash(address[:]),
+ data: data,
+ originStorage: make(Storage),
+ dirtyStorage: make(Storage),
+ }
+}
+
+// EncodeRLP implements rlp.Encoder.
+func (s *stateObject) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, s.data)
+}
+
+// setError remembers the first non-nil error it is called with.
+func (s *stateObject) setError(err error) {
+ if s.dbErr == nil {
+ s.dbErr = err
+ }
+}
+
+func (s *stateObject) markSuicided() {
+ s.suicided = true
+}
+
+func (s *stateObject) touch() {
+ s.db.journal.append(touchChange{
+ account: &s.address,
+ })
+ if s.address == ripemd {
+ // Explicitly put it in the dirty-cache, which is otherwise generated from
+ // flattened journals.
+ s.db.journal.dirty(s.address)
+ }
+}
+
+func (s *stateObject) getTrie(db Database) Trie {
+ if s.trie == nil {
+ var err error
+ s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
+ if err != nil {
+ s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{})
+ s.setError(fmt.Errorf("can't create storage trie: %v", err))
+ }
+ }
+ return s.trie
+}
+
+// GetState retrieves a value from the account storage trie.
+func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
+ // If the fake storage is set, only lookup the state here(in the debugging mode)
+ if s.fakeStorage != nil {
+ return s.fakeStorage[key]
+ }
+ // If we have a dirty value for this state entry, return it
+ value, dirty := s.dirtyStorage[key]
+ if dirty {
+ return value
+ }
+ // Otherwise return the entry's original value
+ return s.GetCommittedState(db, key)
+}
+
+// GetCommittedState retrieves a value from the committed account storage trie.
+func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
+ // If the fake storage is set, only lookup the state here(in the debugging mode)
+ if s.fakeStorage != nil {
+ return s.fakeStorage[key]
+ }
+ // If we have the original value cached, return that
+ value, cached := s.originStorage[key]
+ if cached {
+ return value
+ }
+ // Track the amount of time wasted on reading the storage trie
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now())
+ }
+ // Otherwise load the value from the database
+ enc, err := s.getTrie(db).TryGet(key[:])
+ if err != nil {
+ s.setError(err)
+ return common.Hash{}
+ }
+ if len(enc) > 0 {
+ _, content, _, err := rlp.Split(enc)
+ if err != nil {
+ s.setError(err)
+ }
+ value.SetBytes(content)
+ }
+ s.originStorage[key] = value
+ return value
+}
+
+// SetState updates a value in account storage.
+func (s *stateObject) SetState(db Database, key, value common.Hash) {
+ // If the fake storage is set, put the temporary state update here.
+ if s.fakeStorage != nil {
+ s.fakeStorage[key] = value
+ return
+ }
+ // If the new value is the same as old, don't set
+ prev := s.GetState(db, key)
+ if prev == value {
+ return
+ }
+ // New value is different, update and journal the change
+ s.db.journal.append(storageChange{
+ account: &s.address,
+ key: key,
+ prevalue: prev,
+ })
+ s.setState(key, value)
+}
+
+// SetStorage replaces the entire state storage with the given one.
+//
+// After this function is called, all original state will be ignored and state
+// lookup only happens in the fake state storage.
+//
+// Note this function should only be used for debugging purpose.
+func (s *stateObject) SetStorage(storage map[common.Hash]common.Hash) {
+ // Allocate fake storage if it's nil.
+ if s.fakeStorage == nil {
+ s.fakeStorage = make(Storage)
+ }
+ for key, value := range storage {
+ s.fakeStorage[key] = value
+ }
+ // Don't bother journal since this function should only be used for
+ // debugging and the `fake` storage won't be committed to database.
+}
+
+func (s *stateObject) setState(key, value common.Hash) {
+ s.dirtyStorage[key] = value
+}
+
+// updateTrie writes cached storage modifications into the object's storage trie.
+func (s *stateObject) updateTrie(db Database) Trie {
+ // Track the amount of time wasted on updating the storge trie
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
+ }
+ // Update all the dirty slots in the trie
+ tr := s.getTrie(db)
+ for key, value := range s.dirtyStorage {
+ delete(s.dirtyStorage, key)
+
+ // Skip noop changes, persist actual changes
+ if value == s.originStorage[key] {
+ continue
+ }
+ s.originStorage[key] = value
+
+ if (value == common.Hash{}) {
+ s.setError(tr.TryDelete(key[:]))
+ continue
+ }
+ // Encoding []byte cannot fail, ok to ignore the error.
+ v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
+ s.setError(tr.TryUpdate(key[:], v))
+ }
+ return tr
+}
+
+// UpdateRoot sets the trie root to the current root hash of
+func (s *stateObject) updateRoot(db Database) {
+ s.updateTrie(db)
+
+ // Track the amount of time wasted on hashing the storge trie
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
+ }
+ s.data.Root = s.trie.Hash()
+}
+
+// CommitTrie the storage trie of the object to db.
+// This updates the trie root.
+func (s *stateObject) CommitTrie(db Database) error {
+ s.updateTrie(db)
+ if s.dbErr != nil {
+ return s.dbErr
+ }
+ // Track the amount of time wasted on committing the storge trie
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
+ }
+ root, err := s.trie.Commit(nil)
+ if err == nil {
+ s.data.Root = root
+ }
+ return err
+}
+
+// AddBalance removes amount from c's balance.
+// It is used to add funds to the destination account of a transfer.
+func (s *stateObject) AddBalance(amount *big.Int) {
+ // EIP158: We must check emptiness for the objects such that the account
+ // clearing (0,0,0 objects) can take effect.
+ if amount.Sign() == 0 {
+ if s.empty() {
+ s.touch()
+ }
+
+ return
+ }
+ s.SetBalance(new(big.Int).Add(s.Balance(), amount))
+}
+
+// SubBalance removes amount from c's balance.
+// It is used to remove funds from the origin account of a transfer.
+func (s *stateObject) SubBalance(amount *big.Int) {
+ if amount.Sign() == 0 {
+ return
+ }
+ s.SetBalance(new(big.Int).Sub(s.Balance(), amount))
+}
+
+func (s *stateObject) SetBalance(amount *big.Int) {
+ s.db.journal.append(balanceChange{
+ account: &s.address,
+ prev: new(big.Int).Set(s.data.Balance),
+ })
+ s.setBalance(amount)
+}
+
+// AddBalance removes amount from c's balance.
+// It is used to add funds to the destination account of a transfer.
+func (s *stateObject) AddBalanceMultiCoin(coinID common.Hash, amount *big.Int, db Database) {
+ if amount.Sign() == 0 {
+ if s.empty() {
+ s.touch()
+ }
+
+ return
+ }
+ s.SetBalanceMultiCoin(coinID, new(big.Int).Add(s.BalanceMultiCoin(coinID, db), amount), db)
+}
+
+// SubBalance removes amount from c's balance.
+// It is used to remove funds from the origin account of a transfer.
+func (s *stateObject) SubBalanceMultiCoin(coinID common.Hash, amount *big.Int, db Database) {
+ if amount.Sign() == 0 {
+ return
+ }
+ s.SetBalanceMultiCoin(coinID, new(big.Int).Sub(s.BalanceMultiCoin(coinID, db), amount), db)
+}
+
+func (s *stateObject) SetBalanceMultiCoin(coinID common.Hash, amount *big.Int, db Database) {
+ NormalizeCoinID(&coinID)
+ s.SetState(db, coinID, common.BigToHash(amount))
+}
+
+func (s *stateObject) setBalance(amount *big.Int) {
+ s.data.Balance = amount
+}
+
+func (s *stateObject) enableMultiCoin() {
+ s.data.IsMultiCoin = true
+}
+
+// Return the gas back to the origin. Used by the Virtual machine or Closures
+func (s *stateObject) ReturnGas(gas *big.Int) {}
+
+func (s *stateObject) deepCopy(db *StateDB) *stateObject {
+ stateObject := newObject(db, s.address, s.data)
+ if s.trie != nil {
+ stateObject.trie = db.db.CopyTrie(s.trie)
+ }
+ stateObject.code = s.code
+ stateObject.dirtyStorage = s.dirtyStorage.Copy()
+ stateObject.originStorage = s.originStorage.Copy()
+ stateObject.suicided = s.suicided
+ stateObject.dirtyCode = s.dirtyCode
+ stateObject.deleted = s.deleted
+ return stateObject
+}
+
+//
+// Attribute accessors
+//
+
+// Returns the address of the contract/account
+func (s *stateObject) Address() common.Address {
+ return s.address
+}
+
+// Code returns the contract code associated with this object, if any.
+func (s *stateObject) Code(db Database) []byte {
+ if s.code != nil {
+ return s.code
+ }
+ if bytes.Equal(s.CodeHash(), emptyCodeHash) {
+ return nil
+ }
+ code, err := db.ContractCode(s.addrHash, common.BytesToHash(s.CodeHash()))
+ if err != nil {
+ s.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
+ }
+ s.code = code
+ return code
+}
+
+func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
+ prevcode := s.Code(s.db.db)
+ s.db.journal.append(codeChange{
+ account: &s.address,
+ prevhash: s.CodeHash(),
+ prevcode: prevcode,
+ })
+ s.setCode(codeHash, code)
+}
+
+func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
+ s.code = code
+ s.data.CodeHash = codeHash[:]
+ s.dirtyCode = true
+}
+
+func (s *stateObject) SetNonce(nonce uint64) {
+ s.db.journal.append(nonceChange{
+ account: &s.address,
+ prev: s.data.Nonce,
+ })
+ s.setNonce(nonce)
+}
+
+func (s *stateObject) setNonce(nonce uint64) {
+ s.data.Nonce = nonce
+}
+
+func (s *stateObject) CodeHash() []byte {
+ return s.data.CodeHash
+}
+
+func (s *stateObject) Balance() *big.Int {
+ return s.data.Balance
+}
+
+func IsMultiCoinKey(key common.Hash) bool {
+ return key[0]&0x01 == 0x01
+}
+
+func NormalizeCoinID(coinID *common.Hash) {
+ coinID[0] |= 0x01
+}
+
+func NormalizeStateKey(key *common.Hash) {
+ key[0] &= 0xfe
+}
+
+func (s *stateObject) BalanceMultiCoin(coinID common.Hash, db Database) *big.Int {
+ NormalizeCoinID(&coinID)
+ return s.GetState(db, coinID).Big()
+}
+
+func (s *stateObject) EnableMultiCoin() bool {
+ if s.data.IsMultiCoin {
+ return false
+ }
+ s.db.journal.append(multiCoinEnable{
+ account: &s.address,
+ })
+ s.enableMultiCoin()
+ return true
+}
+
+func (s *stateObject) IsMultiCoin() bool {
+ return s.data.IsMultiCoin
+}
+
+func (s *stateObject) Nonce() uint64 {
+ return s.data.Nonce
+}
+
+// Never called, but must be present to allow stateObject to be used
+// as a vm.Account interface that also satisfies the vm.ContractRef
+// interface. Interfaces are awesome.
+func (s *stateObject) Value() *big.Int {
+ panic("Value on stateObject should never be called")
+}