laconicd/state/state_object.go
2018-10-02 20:22:15 -04:00

248 lines
6.0 KiB
Go

package state
import (
"bytes"
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
)
var (
_ ethstate.StateObject = (*stateObject)(nil)
emptyCodeHash = crypto.Keccak256(nil)
)
type (
// 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.
stateObject struct {
address ethcmn.Address
stateDB *CommitStateDB
account *types.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
code types.Code // contract bytecode, which gets set when code is loaded
originStorage types.Storage // Storage cache of original entries to dedup rewrites
dirtyStorage types.Storage // Storage entries that need to be flushed to disk
// 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
}
// // Account is the Ethereum consensus representation of accounts.
// // These objects are stored in the main account trie.
// Account struct {
// Nonce uint64
// Balance *big.Int
// Root ethcmn.Hash // merkle root of the storage trie
// CodeHash []byte
// }
)
func newObject(db *CommitStateDB, accProto auth.Account) *stateObject {
// if acc.Balance == nil {
// data.Balance = new(big.Int)
// }
acc, ok := accProto.(*types.Account)
if !ok {
panic(fmt.Sprintf("invalid account type for state object: %T", acc))
}
if acc.CodeHash == nil {
acc.CodeHash = emptyCodeHash
}
return &stateObject{
stateDB: db,
account: acc,
address: ethcmn.BytesToAddress(acc.Address.Bytes()),
originStorage: make(types.Storage),
dirtyStorage: make(types.Storage),
}
}
// Address returns the address of the state object.
func (so stateObject) Address() ethcmn.Address {
return so.address
}
// GetState retrieves a value from the account storage trie.
func (so *stateObject) GetState(_ Database, key ethcmn.Hash) ethcmn.Hash {
// if we have a dirty value for this state entry, return it
value, dirty := so.dirtyStorage[key]
if dirty {
return value
}
// otherwise return the entry's original value
return so.getCommittedState(key)
}
// SetState updates a value in account storage.
func (so *stateObject) SetState(db Database, key, value ethcmn.Hash) {
// if the new value is the same as old, don't set
prev := so.GetState(db, key)
if prev == value {
return
}
// since the new value is different, update and journal the change
so.stateDB.journal.append(storageChange{
account: &so.address,
key: key,
prevalue: prev,
})
so.setState(key, value)
}
// AddBalance adds an amount to a state object's balance. It is used to add
// funds to the destination account of a transfer.
func (so *stateObject) AddBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
// EIP158: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect.
if amt.Sign() == 0 {
if so.empty() {
so.touch()
}
return
}
newBalance := so.account.Balance().Add(amt)
so.SetBalance(newBalance.BigInt())
}
func (so *stateObject) SetBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
so.stateDB.journal.append(balanceChange{
account: &so.address,
prev: so.account.Balance(),
})
so.setBalance(amt)
}
// SubBalance removes amount from c's balance.
// It is used to remove funds from the origin account of a transfer.
func (so *stateObject) SubBalance(amount *big.Int) {
if amount.Sign() == 0 {
return
}
c.SetBalance(new(big.Int).Sub(c.Balance(), amount))
}
// func (so *stateObject) Balance() *big.Int {
// }
// func (so *stateObject) ReturnGas(gas *big.Int) {
// }
// func (so *stateObject) Address() ethcmn.Address {
// }
// func (so *stateObject) SetCode(codeHash ethcmn.Hash, code []byte) {
// }
// func (so *stateObject) SetNonce(nonce uint64) {
// }
// func (so *stateObject) Nonce() uint64 {
// }
// func (so *stateObject) Code(db Database) []byte {
// }
// func (so *stateObject) CodeHash() []byte {
// }
func (so *stateObject) setBalance(amount sdk.Int) {
so.account.SetBalance(amount)
}
// GetCommittedState retrieves a value from the committed account storage trie.
func (so *stateObject) getCommittedState(key ethcmn.Hash) ethcmn.Hash {
// if we have the original value cached, return that
value, cached := so.originStorage[key]
if cached {
return value
}
// otherwise load the value from the KVStore
store := so.stateDB.ctx.KVStore(so.stateDB.storageKey)
rawValue := store.Get(key.Bytes())
if len(rawValue) > 0 {
value.SetBytes(rawValue)
}
so.originStorage[key] = value
return value
}
func (so *stateObject) setState(key, value ethcmn.Hash) {
so.dirtyStorage[key] = value
}
// setError remembers the first non-nil error it is called with.
func (so *stateObject) setError(err error) {
if so.dbErr == nil {
so.dbErr = err
}
}
// empty returns whether the account is considered empty.
func (so *stateObject) empty() bool {
return so.account.Sequence == 0 &&
so.account.Balance().Sign() == 0 &&
bytes.Equal(so.account.CodeHash, emptyCodeHash)
}
func (so *stateObject) touch() {
so.stateDB.journal.append(touchChange{
account: &so.address,
})
if so.address == ripemd {
// Explicitly put it in the dirty-cache, which is otherwise generated from
// flattened journals.
so.stateDB.journal.dirty(so.address)
}
}