Finish initial StateDB implementation

This commit is contained in:
Aleksandr Bezobchuk 2018-10-04 12:06:19 -04:00
parent 817f9605aa
commit 2feb5bbb5b
3 changed files with 147 additions and 39 deletions

View File

@ -8,16 +8,17 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth" auth "github.com/cosmos/cosmos-sdk/x/auth"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state" ethstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
) )
var ( var (
_ ethstate.StateObject = (*stateObject)(nil) _ ethstate.StateObject = (*stateObject)(nil)
emptyCodeHash = crypto.Keccak256(nil) emptyCodeHash = tmcrypto.Sha256(nil)
) )
type ( type (
@ -194,6 +195,33 @@ func (so *stateObject) markSuicided() {
so.suicided = true 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 // Getters
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -271,6 +299,8 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e
store := ctx.KVStore(so.stateDB.storageKey) store := ctx.KVStore(so.stateDB.storageKey)
rawValue := store.Get(prefixKey.Bytes()) rawValue := store.Get(prefixKey.Bytes())
// TODO: Do we need to RLP split/decode?
if len(rawValue) > 0 { if len(rawValue) > 0 {
value.SetBytes(rawValue) value.SetBytes(rawValue)
} }
@ -328,5 +358,5 @@ func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash {
copy(compositeKey, prefix) copy(compositeKey, prefix)
copy(compositeKey[len(prefix):], key) copy(compositeKey[len(prefix):], key)
return crypto.Keccak256Hash(compositeKey) return ethcmn.BytesToHash(tmcrypto.Sha256(compositeKey))
} }

View File

@ -9,10 +9,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
tmcrypto "github.com/tendermint/tendermint/crypto"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state" ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
) )
var ( 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) { func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
so := csdb.GetOrNewStateObject(addr) so := csdb.GetOrNewStateObject(addr)
if so != nil { 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 ... // TODO: Commit writes the state ...
func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (root ethcmn.Hash, err error) { 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 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) { 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. // IntermediateRoot returns the current root hash of the state. It is called in
// It is called in between transactions to get the root hash that // between transactions to get the root hash that goes into transaction
// goes into transaction receipts. // receipts.
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash { func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash {
// TODO: ... csdb.Finalize(deleteEmptyObjects)
// csdb.Finalize(deleteEmptyObjects)
// return csdb.trie.Hash() // 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 // 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. // 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, // 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 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. // setError remembers the first non-nil error it is called with.
func (csdb *CommitStateDB) setError(err error) { func (csdb *CommitStateDB) setError(err error) {
if csdb.dbErr == nil { if csdb.dbErr == nil {
@ -593,9 +673,3 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta
func (csdb *CommitStateDB) setStateObject(object *stateObject) { func (csdb *CommitStateDB) setStateObject(object *stateObject) {
csdb.stateObjects[object.Address()] = object 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
}

View File

@ -21,7 +21,11 @@ var _ auth.Account = (*Account)(nil)
type Account struct { type Account struct {
*auth.BaseAccount *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 CodeHash []byte
} }