diff --git a/state/state_object.go b/state/state_object.go index 95304de5..b4e1a95c 100644 --- a/state/state_object.go +++ b/state/state_object.go @@ -8,16 +8,17 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth" + tmcrypto "github.com/tendermint/tendermint/crypto" + "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) + emptyCodeHash = tmcrypto.Sha256(nil) ) type ( @@ -194,6 +195,33 @@ 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.storageKey) + + 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) +} + // ---------------------------------------------------------------------------- // Getters // ---------------------------------------------------------------------------- @@ -271,6 +299,8 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e store := ctx.KVStore(so.stateDB.storageKey) rawValue := store.Get(prefixKey.Bytes()) + // TODO: Do we need to RLP split/decode? + if len(rawValue) > 0 { value.SetBytes(rawValue) } @@ -328,5 +358,5 @@ func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash { copy(compositeKey, prefix) copy(compositeKey[len(prefix):], key) - return crypto.Keccak256Hash(compositeKey) + return ethcmn.BytesToHash(tmcrypto.Sha256(compositeKey)) } diff --git a/state/statedb.go b/state/statedb.go index 7f145b9c..7cbdc641 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -9,10 +9,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + tmcrypto "github.com/tendermint/tendermint/crypto" + ethcmn "github.com/ethereum/go-ethereum/common" ethstate "github.com/ethereum/go-ethereum/core/state" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" ) var ( @@ -126,7 +127,8 @@ func (csdb *CommitStateDB) SetState(addr ethcmn.Address, key, value ethcmn.Hash) func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) { so := csdb.GetOrNewStateObject(addr) if so != nil { - so.SetCode(crypto.Keccak256Hash(code), code) + key := ethcmn.BytesToHash(tmcrypto.Sha256(code)) + so.SetCode(key, code) } } @@ -299,22 +301,94 @@ func (csdb *CommitStateDB) StorageTrie(addr ethcmn.Address) ethstate.Trie { // TODO: Commit writes the state ... func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (root ethcmn.Hash, err error) { - // TODO: ... + defer csdb.clearJournalAndRefund() + + // remove dirty state object entries based on the journal + for addr := range csdb.journal.dirties { + csdb.stateObjectsDirty[addr] = struct{}{} + } + + // set the state objects + for addr, so := range csdb.stateObjects { + _, isDirty := csdb.stateObjectsDirty[addr] + switch { + case so.suicided || (isDirty && deleteEmptyObjects && so.empty()): + // If the state object has been removed, don't bother syncing it and just + // remove it from the store. + csdb.deleteStateObject(so) + + case isDirty: + // write any contract code associated with the state object + if so.code != nil && so.dirtyCode { + csdb.SetCode(so.Address(), so.code) + so.dirtyCode = false + } + + // update the object in the KVStore + csdb.updateStateObject(so) + } + + delete(csdb.stateObjectsDirty, addr) + } + + // TODO: Get and return the commit/root from the context return } -// TODO: ... +// Finalize finalizes the state objects (accounts) state by setting their state, +// removing the csdb destructed objects and clearing the journal as well as the +// refunds. func (csdb *CommitStateDB) Finalize(deleteEmptyObjects bool) { - // TODO: ... + for addr := range csdb.journal.dirties { + so, exist := csdb.stateObjects[addr] + if !exist { + // ripeMD is 'touched' at block 1714175, in tx: + // 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 + // + // That tx goes out of gas, and although the notion of 'touched' does not + // exist there, the touch-event will still be recorded in the journal. + // Since ripeMD is a special snowflake, it will persist in the journal even + // though the journal is reverted. In this special circumstance, it may + // exist in journal.dirties but not in stateObjects. Thus, we can safely + // ignore it here. + continue + } + + if so.suicided || (deleteEmptyObjects && so.empty()) { + csdb.deleteStateObject(so) + } else { + // Set all the dirty state storage items for the state object in the + // KVStore and finally set the account in the account mapper. + so.commitState() + csdb.updateStateObject(so) + } + + csdb.stateObjectsDirty[addr] = struct{}{} + } + + // invalidate journal because reverting across transactions is not allowed + csdb.clearJournalAndRefund() } -// IntermediateRoot computes the current root hash of the state trie. -// It is called in between transactions to get the root hash that -// goes into transaction receipts. +// IntermediateRoot returns the current root hash of the state. It is called in +// between transactions to get the root hash that goes into transaction +// receipts. func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash { - // TODO: ... - // csdb.Finalize(deleteEmptyObjects) - // return csdb.trie.Hash() + csdb.Finalize(deleteEmptyObjects) + + // TODO: Get and return the commit/root from the context + return ethcmn.Hash{} +} + +// updateStateObject writes the given state object to the store. +func (csdb *CommitStateDB) updateStateObject(so *stateObject) { + csdb.am.SetAccount(csdb.ctx, so.account) +} + +// deleteStateObject removes the given state object from the state store. +func (csdb *CommitStateDB) deleteStateObject(so *stateObject) { + so.deleted = true + csdb.am.RemoveAccount(csdb.ctx, so.account) } // ---------------------------------------------------------------------------- @@ -351,6 +425,30 @@ func (csdb *CommitStateDB) RevertToSnapshot(revID int) { // Auxiliary // ---------------------------------------------------------------------------- +// Database retrieves the low level database supporting the lower level trie +// ops. It is not used in Ethermint, so it returns nil. +func (csdb *CommitStateDB) Database() ethstate.Database { + return nil +} + +// Empty returns whether the state object is either non-existent or empty +// according to the EIP161 specification (balance = nonce = code = 0). +func (csdb *CommitStateDB) Empty(addr ethcmn.Address) bool { + so := csdb.getStateObject(addr) + return so == nil || so.empty() +} + +// Exist reports whether the given account address exists in the state. Notably, +// this also returns true for suicided accounts. +func (csdb *CommitStateDB) Exist(addr ethcmn.Address) bool { + return csdb.getStateObject(addr) != nil +} + +// Error returns the first non-nil error the StateDB encountered. +func (csdb *CommitStateDB) Error() error { + return csdb.dbErr +} + // Suicide marks the given account as suicided and clears the account balance. // // The account's state object is still available until the state is committed, @@ -540,24 +638,6 @@ func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *s return newObj, prevObj } -// Empty returns whether the state object is either non-existent or empty -// according to the EIP161 specification (balance = nonce = code = 0). -func (csdb *CommitStateDB) Empty(addr ethcmn.Address) bool { - so := csdb.getStateObject(addr) - return so == nil || so.empty() -} - -// Exist reports whether the given account address exists in the state. Notably, -// this also returns true for suicided accounts. -func (csdb *CommitStateDB) Exist(addr ethcmn.Address) bool { - return csdb.getStateObject(addr) != nil -} - -// Error returns the first non-nil error the StateDB encountered. -func (csdb *CommitStateDB) Error() error { - return csdb.dbErr -} - // setError remembers the first non-nil error it is called with. func (csdb *CommitStateDB) setError(err error) { if csdb.dbErr == nil { @@ -593,9 +673,3 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta func (csdb *CommitStateDB) setStateObject(object *stateObject) { csdb.stateObjects[object.Address()] = object } - -// Database retrieves the low level database supporting the lower level trie -// ops. It is not used in Ethermint, so it returns nil. -func (csdb *CommitStateDB) Database() ethstate.Database { - return nil -} diff --git a/types/account.go b/types/account.go index 67fae188..4d58e431 100644 --- a/types/account.go +++ b/types/account.go @@ -21,7 +21,11 @@ var _ auth.Account = (*Account)(nil) type Account struct { *auth.BaseAccount - Root ethcmn.Hash // merkle root of the storage trie + // merkle root of the storage trie + // + // TODO: good chance we may not need this + Root ethcmn.Hash + CodeHash []byte }