laconicd/x/evm/types/state_object.go
Federico Kunze 4d609b2a22
bump Cosmos SDK version to v0.38.2 (#183)
* evm: move Keeper and Querier to /keeper package

* keeper: update keeper_test.go

* fix format

* evm: use aliased types

* bump SDK version to v0.38.1

* app: updates from new version

* errors: switch sdk.Error -> error

* errors: switch sdk.Error -> error. Continuation

* more fixes

* update app/

* update keys and client pkgs

* build

* fix tests

* lint

* minor changes

* changelog

* address @austinbell comments

* Fix keyring usage in rpc API and CLI

* fix keyring

* break line

* Misc cleanup (#188)

* evm: move Begin and EndBlock to abci.go

* evm: use expected keeper interfaces

* app: use EthermintApp for integration and unit test setup

* evm: remove count type; update codec

* go mod verify

* evm: rename msgs for consistency

* evm: events

* minor cleanup

* lint

* ante: update tests

* changelog

* nolint

* evm: update statedb to create ethermint Account instead of BaseAccount

* fix importer test

* address @austinabell comments

* update README

* changelog

* evm: update codec

* fix event sender

* store logs in keeper after transition (#210)

* add some comments

* begin log handler test

* update TransitionCSDB to return ReturnData

* use rlp for result data encode/decode

* update tests

* implement SetBlockLogs

* implement GetBlockLogs

* test log set/get

* update keeper get/set logs to use hash as key

* fix test

* move logsKey to csdb

* attempt to fix test

* attempt to fix test

* attempt to fix test

* lint

* lint

* lint

* save logs after handling msg

* update k.Logs

* cleanup

* remove unused

* fix issues

* comment out handler test

* address comments

* lint

* fix handler test

* address comments

* use amino

* lint

* address comments

* merge

* fix encoding bug

* minor fix

* rpc: error handling

* rpc: simulate only returns gasConsumed

* rpc: error ineffassign

* go: bump version to 1.14 and SDK version to latest master

* rpc: fix simulation return value

* breaking changes from SDK

* sdk: breaking changes; build

* tests: fixes

* minor fix

* proto: ethermint types attempt

* proto: define EthAccount proto type and extend sdk std.Codec

* evm: fix panic on handler test

* evm: minor state object changes

* cleanup

* tests: update test-importer

* fix pubkey registration

* lint

* cleanup

* more test checks for importer

* minor change

* codec fixes

* rm init func

* fix importer test build

* fix marshaling for TxDecoder

* use amino codec for evm

* fix marshaling for SimulationResponse

* use jsonpb for unmarshaling

* fix method handler crashed

* return err on VerifySig

* switch stateObject balance to sdk.Int

* fixes to codec and encoding

* cleanup

* set tmhash -> ethhash in state transition

* add tmhash->ethereumhash to csdb.GetLogs

* attempt to  fix tests

* update GetLogs to switch with Has

* ante panic

* diff changes

* update SetLogs

* evm/cli: use ethermint codec

* use LengthPrefixed for encoding

* add check for nil *big.Int

* add balance to UpdateAccounts

* fix previous balance

* fix balance bug

* prevent panic on make test-import

Co-authored-by: austinabell <austinabell8@gmail.com>
Co-authored-by: noot <36753753+noot@users.noreply.github.com>
Co-authored-by: noot <elizabethjbinks@gmail.com>
2020-04-22 15:26:01 -04:00

406 lines
11 KiB
Go

package types
import (
"bytes"
"fmt"
"io"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
var (
_ StateObject = (*stateObject)(nil)
emptyCodeHash = ethcrypto.Keccak256(nil)
)
type (
// StateObject interface for interacting with state object
StateObject interface {
GetCommittedState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash
GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash
SetState(db ethstate.Database, key, value ethcmn.Hash)
Code(db ethstate.Database) []byte
SetCode(codeHash ethcmn.Hash, code []byte)
CodeHash() []byte
AddBalance(amount *big.Int)
SubBalance(amount *big.Int)
SetBalance(amount *big.Int)
Balance() *big.Int
ReturnGas(gas *big.Int)
Address() ethcmn.Address
SetNonce(nonce uint64)
Nonce() uint64
}
// 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 {
code types.Code // contract bytecode, which gets set when code is loaded
// 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
stateDB *CommitStateDB
account *types.EthAccount
balance sdk.Int
originStorage types.Storage // Storage cache of original entries to dedup rewrites
dirtyStorage types.Storage // Storage entries that need to be flushed to disk
address ethcmn.Address
// 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
}
)
func newStateObject(db *CommitStateDB, accProto authexported.Account, balance sdk.Int) *stateObject {
ethermintAccount, ok := accProto.(*types.EthAccount)
if !ok {
panic(fmt.Sprintf("invalid account type for state object: %T", accProto))
}
// set empty code hash
if ethermintAccount.CodeHash == nil {
ethermintAccount.CodeHash = emptyCodeHash
}
return &stateObject{
stateDB: db,
account: ethermintAccount,
balance: balance,
address: ethcmn.BytesToAddress(ethermintAccount.GetAddress().Bytes()),
originStorage: make(types.Storage),
dirtyStorage: make(types.Storage),
}
}
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
// SetState updates a value in account storage. Note, the key will be prefixed
// with the address of the state object.
func (so *stateObject) SetState(db ethstate.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
}
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// since the new value is different, update and journal the change
so.stateDB.journal.append(storageChange{
account: &so.address,
key: prefixKey,
prevValue: prev,
})
so.setState(prefixKey, value)
}
func (so *stateObject) setState(key, value ethcmn.Hash) {
so.dirtyStorage[key] = value
}
// SetCode sets the state object's code.
func (so *stateObject) SetCode(codeHash ethcmn.Hash, code []byte) {
prevCode := so.Code(nil)
so.stateDB.journal.append(codeChange{
account: &so.address,
prevHash: so.CodeHash(),
prevCode: prevCode,
})
so.setCode(codeHash, code)
}
func (so *stateObject) setCode(codeHash ethcmn.Hash, code []byte) {
so.code = code
so.account.CodeHash = codeHash.Bytes()
so.dirtyCode = true
}
// 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.
// NOTE: this will panic if amount is nil
if amt.IsZero() {
if so.empty() {
so.touch()
}
return
}
newBalance := so.balance.Add(amt)
so.SetBalance(newBalance.BigInt())
}
// SubBalance removes an amount from the stateObject's balance. It is used to
// remove funds from the origin account of a transfer.
func (so *stateObject) SubBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
if amt.IsZero() {
return
}
newBalance := so.balance.Sub(amt)
so.SetBalance(newBalance.BigInt())
}
// SetBalance sets the state object's balance.
func (so *stateObject) SetBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
so.stateDB.journal.append(balanceChange{
account: &so.address,
prev: so.balance,
})
so.setBalance(amt)
}
func (so *stateObject) setBalance(amount sdk.Int) {
so.balance = amount
}
// SetNonce sets the state object's nonce (i.e sequence number of the account).
func (so *stateObject) SetNonce(nonce uint64) {
so.stateDB.journal.append(nonceChange{
account: &so.address,
prev: so.account.Sequence,
})
so.setNonce(nonce)
}
func (so *stateObject) setNonce(nonce uint64) {
if so.account == nil {
panic("state object account is empty")
}
so.account.Sequence = nonce
}
// setError remembers the first non-nil error it is called with.
func (so *stateObject) setError(err error) {
if so.dbErr == nil {
so.dbErr = err
}
}
func (so *stateObject) markSuicided() {
so.suicided = true
}
// commitState commits all dirty storage to a KVStore.
func (so *stateObject) commitState() {
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storeKey)
for key, value := range so.dirtyStorage {
delete(so.dirtyStorage, key)
// skip no-op changes, persist actual changes
if value == so.originStorage[key] {
continue
}
so.originStorage[key] = value
// delete empty values
if (value == ethcmn.Hash{}) {
store.Delete(key.Bytes())
continue
}
store.Set(key.Bytes(), value.Bytes())
}
// TODO: Set the account (storage) root (but we probably don't need this)
}
// commitCode persists the state object's code to the KVStore.
func (so *stateObject) commitCode() {
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.codeKey)
store.Set(so.CodeHash(), so.code)
}
// ----------------------------------------------------------------------------
// Getters
// ----------------------------------------------------------------------------
// Address returns the address of the state object.
func (so stateObject) Address() ethcmn.Address {
return so.address
}
// Balance returns the state object's current balance.
func (so *stateObject) Balance() *big.Int {
balance := so.balance.BigInt()
if balance == nil {
return zeroBalance
}
return balance
}
// CodeHash returns the state object's code hash.
func (so *stateObject) CodeHash() []byte {
if so.account == nil || len(so.account.CodeHash) == 0 {
return emptyCodeHash
}
return so.account.CodeHash
}
// Nonce returns the state object's current nonce (sequence number).
func (so *stateObject) Nonce() uint64 {
if so.account == nil {
return 0
}
return so.account.Sequence
}
// Code returns the contract code associated with this object, if any.
func (so *stateObject) Code(_ ethstate.Database) []byte {
if len(so.code) > 0 {
return so.code
}
if bytes.Equal(so.CodeHash(), emptyCodeHash) {
return nil
}
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.codeKey)
code := store.Get(so.CodeHash())
if len(code) == 0 {
so.setError(fmt.Errorf("failed to get code hash %x for address %s", so.CodeHash(), so.Address().String()))
}
return code
}
// GetState retrieves a value from the account storage trie. Note, the key will
// be prefixed with the address of the state object.
func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// if we have a dirty value for this state entry, return it
value, dirty := so.dirtyStorage[prefixKey]
if dirty {
return value
}
// otherwise return the entry's original value
return so.GetCommittedState(db, key)
}
// GetCommittedState retrieves a value from the committed account storage trie.
// Note, the key will be prefixed with the address of the state object.
func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// if we have the original value cached, return that
value, cached := so.originStorage[prefixKey]
if cached {
return value
}
// otherwise load the value from the KVStore
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storeKey)
rawValue := store.Get(prefixKey.Bytes())
if len(rawValue) > 0 {
value.SetBytes(rawValue)
}
so.originStorage[prefixKey] = value
return value
}
// ----------------------------------------------------------------------------
// Auxiliary
// ----------------------------------------------------------------------------
// ReturnGas returns the gas back to the origin. Used by the Virtual machine or
// Closures. It performs a no-op.
func (so *stateObject) ReturnGas(gas *big.Int) {}
func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject {
newStateObj := newStateObject(db, so.account, so.balance)
newStateObj.code = so.code
newStateObj.dirtyStorage = so.dirtyStorage.Copy()
newStateObj.originStorage = so.originStorage.Copy()
newStateObj.suicided = so.suicided
newStateObj.dirtyCode = so.dirtyCode
newStateObj.deleted = so.deleted
return newStateObj
}
// empty returns whether the account is considered empty.
func (so *stateObject) empty() bool {
return so.account == nil ||
(so.account != nil &&
so.account.Sequence == 0 &&
(so.balance.BigInt() == nil || so.balance.IsZero()) &&
bytes.Equal(so.account.CodeHash, emptyCodeHash))
}
// EncodeRLP implements rlp.Encoder.
func (so *stateObject) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, so.account)
}
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)
}
}
// GetStorageByAddressKey returns a hash of the composite key for a state
// object's storage prefixed with it's address.
func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash {
prefix := so.Address().Bytes()
compositeKey := make([]byte, len(prefix)+len(key))
copy(compositeKey, prefix)
copy(compositeKey[len(prefix):], key)
return ethcrypto.Keccak256Hash(compositeKey)
}