diff --git a/core/chain.go b/core/chain.go index 5c3c41b0..6a9aee66 100644 --- a/core/chain.go +++ b/core/chain.go @@ -84,7 +84,7 @@ func (cc *ChainContext) CalcDifficulty(_ ethcons.ChainReader, _ uint64, _ *ethty // // TODO: Figure out if this needs to be hooked up to any part of the ABCI? func (cc *ChainContext) Finalize( - _ ethcons.ChainReader, _ *ethtypes.Header, _ *ethstate.StateDB, + _ ethcons.ChainReader, _ *ethtypes.Header, _ ethstate.StateDB, _ []*ethtypes.Transaction, _ []*ethtypes.Header, _ []*ethtypes.Receipt, ) (*ethtypes.Block, error) { return nil, nil diff --git a/state/database.go b/state/database.go index 0be36a53..46e0a2eb 100644 --- a/state/database.go +++ b/state/database.go @@ -4,7 +4,6 @@ import ( "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ethermint/core" "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" @@ -73,7 +72,7 @@ func NewDatabase(stateStore store.CommitMultiStore, codeDB dbm.DB, storeCacheSiz // the ethdb.Database interface. It will be used to facilitate persistence // of contract byte code when committing state. db.codeDB = codeDB - db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB}) + db.ethTrieDB = ethtrie.NewDatabase(&EthereumDB{CodeDB: codeDB}) var err error diff --git a/core/ethdb.go b/state/ethdb.go similarity index 99% rename from core/ethdb.go rename to state/ethdb.go index 89211509..f05e3fa9 100644 --- a/core/ethdb.go +++ b/state/ethdb.go @@ -1,4 +1,4 @@ -package core +package state import ( ethdb "github.com/ethereum/go-ethereum/ethdb" diff --git a/core/ethdb_test.go b/state/ethdb_test.go similarity index 99% rename from core/ethdb_test.go rename to state/ethdb_test.go index 881169b2..626be049 100644 --- a/core/ethdb_test.go +++ b/state/ethdb_test.go @@ -1,4 +1,4 @@ -package core +package state // NOTE: A bulk of these unit tests will change and evolve as the context and // implementation of ChainConext evolves. diff --git a/state/journal.go b/state/journal.go new file mode 100644 index 00000000..77a09d2e --- /dev/null +++ b/state/journal.go @@ -0,0 +1,236 @@ +package state + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/common" +) + +// journalEntry is a modification entry in the state change journal that can be +// reverted on demand. +type journalEntry interface { + // revert undoes the changes introduced by this journal entry. + revert(*CommitStateDB) + + // dirtied returns the Ethereum address modified by this journal entry. + dirtied() *common.Address +} + +// journal contains the list of state modifications applied since the last state +// commit. These are tracked to be able to be reverted in case of an execution +// exception or revertal request. +type journal struct { + entries []journalEntry // Current changes tracked by the journal + dirties map[common.Address]int // Dirty accounts and the number of changes +} + +// newJournal create a new initialized journal. +func newJournal() *journal { + return &journal{ + dirties: make(map[common.Address]int), + } +} + +// append inserts a new modification entry to the end of the change journal. +func (j *journal) append(entry journalEntry) { + j.entries = append(j.entries, entry) + if addr := entry.dirtied(); addr != nil { + j.dirties[*addr]++ + } +} + +// revert undoes a batch of journalled modifications along with any reverted +// dirty handling too. +func (j *journal) revert(statedb *CommitStateDB, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].revert(statedb) + + // Drop any dirty tracking induced by the change + if addr := j.entries[i].dirtied(); addr != nil { + if j.dirties[*addr]--; j.dirties[*addr] == 0 { + delete(j.dirties, *addr) + } + } + } + j.entries = j.entries[:snapshot] +} + +// dirty explicitly sets an address to dirty, even if the change entries would +// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD +// precompile consensus exception. +func (j *journal) dirty(addr common.Address) { + j.dirties[addr]++ +} + +// length returns the current number of entries in the journal. +func (j *journal) length() int { + return len(j.entries) +} + +type ( + // Changes to the account trie. + createObjectChange struct { + account *common.Address + } + + resetObjectChange struct { + prev *stateObject + } + + suicideChange struct { + account *common.Address + prev bool // whether account had already suicided + prevbalance sdk.Int + } + + // Changes to individual accounts. + balanceChange struct { + account *common.Address + prev sdk.Int + } + + nonceChange struct { + account *common.Address + prev uint64 + } + + storageChange struct { + account *common.Address + key, prevalue common.Hash + } + + codeChange struct { + account *common.Address + prevcode, prevhash []byte + } + + // Changes to other state values. + refundChange struct { + prev uint64 + } + + addLogChange struct { + txhash common.Hash + } + + addPreimageChange struct { + hash common.Hash + } + + touchChange struct { + account *common.Address + prev bool + prevDirty bool + } +) + +func (ch createObjectChange) revert(s *CommitStateDB) { + delete(s.stateObjects, *ch.account) + delete(s.stateObjectsDirty, *ch.account) +} + +func (ch createObjectChange) dirtied() *common.Address { + return ch.account +} + +func (ch resetObjectChange) revert(s *CommitStateDB) { + // TODO: ... + // s.setStateObject(ch.prev) +} + +func (ch resetObjectChange) dirtied() *common.Address { + return nil +} + +func (ch suicideChange) revert(s *CommitStateDB) { + // TODO: ... + // obj := s.getStateObject(*ch.account) + // if obj != nil { + // obj.suicided = ch.prev + // obj.setBalance(ch.prevbalance) + // } +} + +func (ch suicideChange) dirtied() *common.Address { + return ch.account +} + +var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") + +func (ch touchChange) revert(s *CommitStateDB) { +} + +func (ch touchChange) dirtied() *common.Address { + return ch.account +} + +func (ch balanceChange) revert(s *CommitStateDB) { + // TODO: ... + // s.getStateObject(*ch.account).setBalance(ch.prev) +} + +func (ch balanceChange) dirtied() *common.Address { + return ch.account +} + +func (ch nonceChange) revert(s *CommitStateDB) { + // TODO: ... + // s.getStateObject(*ch.account).setNonce(ch.prev) +} + +func (ch nonceChange) dirtied() *common.Address { + return ch.account +} + +func (ch codeChange) revert(s *CommitStateDB) { + // TODO: ... + // s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) +} + +func (ch codeChange) dirtied() *common.Address { + return ch.account +} + +func (ch storageChange) revert(s *CommitStateDB) { + // TODO: ... + // s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) +} + +func (ch storageChange) dirtied() *common.Address { + return ch.account +} + +func (ch refundChange) revert(s *CommitStateDB) { + // TODO: ... + // s.refund = ch.prev +} + +func (ch refundChange) dirtied() *common.Address { + return nil +} + +func (ch addLogChange) revert(s *CommitStateDB) { + // TODO: ... + // logs := s.logs[ch.txhash] + // if len(logs) == 1 { + // delete(s.logs, ch.txhash) + // } else { + // s.logs[ch.txhash] = logs[:len(logs)-1] + // } + + // s.logSize-- +} + +func (ch addLogChange) dirtied() *common.Address { + return nil +} + +func (ch addPreimageChange) revert(s *CommitStateDB) { + // TODO: ... + // delete(s.preimages, ch.hash) +} + +func (ch addPreimageChange) dirtied() *common.Address { + return nil +} diff --git a/state/state_object.go b/state/state_object.go new file mode 100644 index 00000000..12444304 --- /dev/null +++ b/state/state_object.go @@ -0,0 +1,247 @@ +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) + } +} diff --git a/state/statedb.go b/state/statedb.go new file mode 100644 index 00000000..50b305af --- /dev/null +++ b/state/statedb.go @@ -0,0 +1,110 @@ +package state + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + + ethcmn "github.com/ethereum/go-ethereum/common" + ethstate "github.com/ethereum/go-ethereum/core/state" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +var _ ethstate.StateDB = (*CommitStateDB)(nil) + +type CommitStateDB struct { + // TODO: Figure out a way to not need to store a context as part of the + // structure + ctx sdk.Context + + am auth.AccountMapper + storageKey sdk.StoreKey + + // maps that hold 'live' objects, which will get modified while processing a + // state transition + stateObjects map[ethcmn.Address]*stateObject + stateObjectsDirty map[ethcmn.Address]struct{} + + thash, bhash ethcmn.Hash + txIndex int + logs map[ethcmn.Hash][]*ethtypes.Log + logSize uint + + // 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 + + // Journal of state modifications. This is the backbone of + // Snapshot and RevertToSnapshot. + journal *journal + validRevisions []ethstate.Revision + nextRevisionID int +} + +func NewCommitStateDB(ctx sdk.Context) (*CommitStateDB, error) { + // tr, err := db.OpenTrie(root) + // if err != nil { + // return nil, err + // } + + return &CommitStateDB{ + // stateObjects: make(map[ethcmn.Address]*stateObject), + // stateObjectsDirty: make(map[ethcmn.Address]struct{}), + // logs: make(map[ethcmn.Hash][]*types.Log), + // preimages: make(map[ethcmn.Hash][]byte), + journal: newJournal(), + }, nil +} + +// setError remembers the first non-nil error it is called with. +func (csdb *CommitStateDB) setError(err error) { + if csdb.dbErr == nil { + csdb.dbErr = err + } +} + +// Error returns the first non-nil error the StateDB encountered. +func (csdb *CommitStateDB) Error() error { + return csdb.dbErr +} + +// Retrieve the balance from the given address or 0 if object not found +func (csdb *CommitStateDB) GetBalance(addr ethcmn.Address) *big.Int { + stateObject := csdb.getStateObject(addr) + if stateObject != nil { + return stateObject.Balance() + } + + return common.Big0 +} + +// Retrieve a state object given by the address. Returns nil if not found. +func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *stateObject) { + // prefer 'live' (cached) objects + if obj := csdb.stateObjects[addr]; obj != nil { + if obj.deleted { + return nil + } + + return obj + } + + acc := csdb.am.GetAccount(csdb.ctx, addr.Bytes()) + if acc == nil { + csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes())) + } + + // insert the state object into the live set + obj := newObject(csdb, acc) + csdb.setStateObject(obj) + return obj +} + +func (csdb *CommitStateDB) setStateObject(object *stateObject) { + csdb.stateObjects[object.Address()] = object +} diff --git a/test/importer/utils.go b/test/importer/utils.go index 4e29b2a8..a70e783d 100644 --- a/test/importer/utils.go +++ b/test/importer/utils.go @@ -18,7 +18,7 @@ var ( // accumulateRewards credits the coinbase of the given block with the mining // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. -func accumulateRewards(config *ethparams.ChainConfig, state *ethstate.StateDB, header *ethtypes.Header, uncles []*ethtypes.Header) { +func accumulateRewards(config *ethparams.ChainConfig, state ethstate.StateDB, header *ethtypes.Header, uncles []*ethtypes.Header) { // select the correct block reward based on chain progression blockReward := ethash.FrontierBlockReward if config.IsByzantium(header.Number) { diff --git a/types/account.go b/types/account.go index b82808c4..6a2a8989 100644 --- a/types/account.go +++ b/types/account.go @@ -1,53 +1,94 @@ package types import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/ethermint/x/bank" ethcmn "github.com/ethereum/go-ethereum/common" ) var _ auth.Account = (*Account)(nil) -type ( - // Storage defines account storage - Storage map[ethcmn.Hash]ethcmn.Hash +// BaseAccount implements the auth.Account interface and embeds an +// auth.BaseAccount type. It is compatible with the auth.AccountMapper. +type Account struct { + *auth.BaseAccount - // Account defines an auth.BaseAccount extension for Ethermint. It is - // compatible with the auth.AccountMapper. - Account struct { - auth.BaseAccount - - Code []byte - Storage Storage - } -) - -// NewAccount returns a reference to a new initialized account. -func NewAccount(base auth.BaseAccount, code []byte, storage Storage) *Account { - return &Account{ - BaseAccount: base, - Code: code, - Storage: storage, - } + Root ethcmn.Hash // merkle root of the storage trie + CodeHash []byte } +// ProtoBaseAccount defines the prototype function for BaseAccount used for an +// account mapper. +func ProtoBaseAccount() auth.Account { + return &Account{BaseAccount: &auth.BaseAccount{}} +} + +// Balance returns the balance of an account. +func (acc Account) Balance() sdk.Int { + return acc.GetCoins().AmountOf(bank.DenomEthereum) +} + +// SetBalance sets an account's balance. +func (acc Account) SetBalance(amt sdk.Int) { + acc.SetCoins(sdk.Coins{sdk.NewCoin(bank.DenomEthereum, amt)}) +} + +// // NewAccount returns a reference to a new initialized account. +// func NewAccount(base auth.BaseAccount, code []byte) *Account { +// return &Account{ +// BaseAccount: base, +// Code: code, +// Storage: storage, +// } +// } + // GetAccountDecoder returns the auth.AccountDecoder function for the custom // Account type. -func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { - return func(accBytes []byte) (auth.Account, error) { - if len(accBytes) == 0 { - return nil, sdk.ErrTxDecode("account bytes are empty") - } +// func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { +// return func(accBytes []byte) (auth.Account, error) { +// if len(accBytes) == 0 { +// return nil, sdk.ErrTxDecode("account bytes are empty") +// } - acc := new(Account) +// acc := new(Account) - err := cdc.UnmarshalBinaryBare(accBytes, &acc) - if err != nil { - return nil, sdk.ErrTxDecode("failed to decode account bytes") - } +// err := cdc.UnmarshalBinaryBare(accBytes, &acc) +// if err != nil { +// return nil, sdk.ErrTxDecode("failed to decode account bytes") +// } - return acc, err - } +// return acc, err +// } +// } + +// Account code and storage type aliases. +type ( + Code []byte + Storage map[ethcmn.Hash]ethcmn.Hash +) + +func (c Code) String() string { + return string(c) +} + +func (c Storage) String() (str string) { + for key, value := range c { + str += fmt.Sprintf("%X : %X\n", key, value) + } + + return +} + +// Copy returns a copy of storage. +func (c Storage) Copy() Storage { + cpy := make(Storage) + for key, value := range c { + cpy[key] = value + } + + return cpy } diff --git a/x/bank/keeper.go b/x/bank/keeper.go new file mode 100644 index 00000000..d63b5346 --- /dev/null +++ b/x/bank/keeper.go @@ -0,0 +1,7 @@ +package bank + +const ( + // DenomEthereum defines the single coin type/denomination supported in + // Ethermint. + DenomEthereum = "ETH" +)