Partial stateDB implementation

This commit is contained in:
Aleksandr Bezobchuk 2018-10-03 21:32:13 -04:00
parent 7f4dfa5d59
commit 817f9605aa

View File

@ -3,6 +3,8 @@ package state
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"sort"
"sync"
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"
@ -10,10 +12,18 @@ import (
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 _ ethstate.StateDB = (*CommitStateDB)(nil) var (
_ ethstate.StateDB = (*CommitStateDB)(nil)
zeroBalance = sdk.ZeroInt().BigInt()
)
// CommitStateDB implements the Geth state.StateDB interface. Instead of using
// a trie and database for querying and persistence, KVStores and an account
// mapper is used to facilitate state transitions.
type CommitStateDB struct { type CommitStateDB struct {
// TODO: Figure out a way to not need to store a context as part of the // TODO: Figure out a way to not need to store a context as part of the
// structure // structure
@ -21,17 +31,25 @@ type CommitStateDB struct {
am auth.AccountMapper am auth.AccountMapper
storageKey sdk.StoreKey storageKey sdk.StoreKey
codeKey sdk.StoreKey
// maps that hold 'live' objects, which will get modified while processing a // maps that hold 'live' objects, which will get modified while processing a
// state transition // state transition
//
// TODO: Determine if we need this cache as the KVStore is cache-wrapped
stateObjects map[ethcmn.Address]*stateObject stateObjects map[ethcmn.Address]*stateObject
stateObjectsDirty map[ethcmn.Address]struct{} stateObjectsDirty map[ethcmn.Address]struct{}
// The refund counter, also used by state transitioning.
refund uint64
thash, bhash ethcmn.Hash thash, bhash ethcmn.Hash
txIndex int txIndex int
logs map[ethcmn.Hash][]*ethtypes.Log logs map[ethcmn.Hash][]*ethtypes.Log
logSize uint logSize uint
preimages map[ethcmn.Hash][]byte
// DB error. // DB error.
// State objects are used by the consensus core and VM which are // State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs // unable to deal with database-level errors. Any error that occurs
@ -44,23 +62,502 @@ type CommitStateDB struct {
journal *journal journal *journal
validRevisions []ethstate.Revision validRevisions []ethstate.Revision
nextRevisionID int nextRevisionID int
// mutex for state deep copying
lock sync.Mutex
} }
func NewCommitStateDB(ctx sdk.Context) (*CommitStateDB, error) { // TODO: Make sure storage is prefixed with address!!!
// tr, err := db.OpenTrie(root)
// if err != nil {
// return nil, err
// }
func NewCommitStateDB(ctx sdk.Context) (*CommitStateDB, error) {
return &CommitStateDB{ return &CommitStateDB{
// stateObjects: make(map[ethcmn.Address]*stateObject), stateObjects: make(map[ethcmn.Address]*stateObject),
// stateObjectsDirty: make(map[ethcmn.Address]struct{}), stateObjectsDirty: make(map[ethcmn.Address]struct{}),
// logs: make(map[ethcmn.Hash][]*types.Log), logs: make(map[ethcmn.Hash][]*ethtypes.Log),
// preimages: make(map[ethcmn.Hash][]byte),
journal: newJournal(), journal: newJournal(),
}, nil }, nil
} }
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
// SetBalance sets the balance of an account.
func (csdb *CommitStateDB) SetBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetBalance(amount)
}
}
// AddBalance adds amount to the account associated with addr.
func (csdb *CommitStateDB) AddBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.AddBalance(amount)
}
}
// SubBalance subtracts amount from the account associated with addr.
func (csdb *CommitStateDB) SubBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SubBalance(amount)
}
}
// SetNonce sets the nonce (sequence number) of an account.
func (csdb *CommitStateDB) SetNonce(addr ethcmn.Address, nonce uint64) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetNonce(nonce)
}
}
// SetState sets the storage state with a key, value pair for an account.
func (csdb *CommitStateDB) SetState(addr ethcmn.Address, key, value ethcmn.Hash) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetState(nil, key, value)
}
}
// SetCode sets the code for a given account.
func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetCode(crypto.Keccak256Hash(code), code)
}
}
// AddLog adds a new log to the state and sets the log metadata from the state.
func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) {
csdb.journal.append(addLogChange{txhash: csdb.thash})
log.TxHash = csdb.thash
log.BlockHash = csdb.bhash
log.TxIndex = uint(csdb.txIndex)
log.Index = csdb.logSize
csdb.logs[csdb.thash] = append(csdb.logs[csdb.thash], log)
csdb.logSize++
}
// AddPreimage records a SHA3 preimage seen by the VM.
func (csdb *CommitStateDB) AddPreimage(hash ethcmn.Hash, preimage []byte) {
if _, ok := csdb.preimages[hash]; !ok {
csdb.journal.append(addPreimageChange{hash: hash})
pi := make([]byte, len(preimage))
copy(pi, preimage)
csdb.preimages[hash] = pi
}
}
// AddRefund adds gas to the refund counter.
func (csdb *CommitStateDB) AddRefund(gas uint64) {
csdb.journal.append(refundChange{prev: csdb.refund})
csdb.refund += gas
}
// SubRefund removes gas from the refund counter. It will panic if the refund
// counter goes below zero.
func (csdb *CommitStateDB) SubRefund(gas uint64) {
csdb.journal.append(refundChange{prev: csdb.refund})
if gas > csdb.refund {
panic("refund counter below zero")
}
csdb.refund -= gas
}
// ----------------------------------------------------------------------------
// Getters
// ----------------------------------------------------------------------------
// GetBalance retrieves the balance from the given address or 0 if object not
// found.
func (csdb *CommitStateDB) GetBalance(addr ethcmn.Address) *big.Int {
so := csdb.getStateObject(addr)
if so != nil {
return so.Balance()
}
return zeroBalance
}
// GetNonce returns the nonce (sequence number) for a given account.
func (csdb *CommitStateDB) GetNonce(addr ethcmn.Address) uint64 {
so := csdb.getStateObject(addr)
if so != nil {
return so.Nonce()
}
return 0
}
// GetCode returns the code for a given account.
func (csdb *CommitStateDB) GetCode(addr ethcmn.Address) []byte {
so := csdb.getStateObject(addr)
if so != nil {
return so.Code(nil)
}
return nil
}
// GetCodeSize returns the code size for a given account.
func (csdb *CommitStateDB) GetCodeSize(addr ethcmn.Address) int {
so := csdb.getStateObject(addr)
if so == nil {
return 0
}
if so.code != nil {
return len(so.code)
}
// TODO: we may need to cache these lookups directly
return len(so.Code(nil))
}
// GetCodeHash returns the code hash for a given account.
func (csdb *CommitStateDB) GetCodeHash(addr ethcmn.Address) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so == nil {
return ethcmn.Hash{}
}
return ethcmn.BytesToHash(so.CodeHash())
}
// GetState retrieves a value from the given account's storage store.
func (csdb *CommitStateDB) GetState(addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so != nil {
return so.GetState(nil, hash)
}
return ethcmn.Hash{}
}
// GetCommittedState retrieves a value from the given account's committed
// storage.
func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so != nil {
return so.GetCommittedState(nil, hash)
}
return ethcmn.Hash{}
}
// GetLogs returns the current logs for a given hash in the state.
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) []*ethtypes.Log {
return csdb.logs[hash]
}
// Logs returns all the current logs in the state.
func (csdb *CommitStateDB) Logs() []*ethtypes.Log {
var logs []*ethtypes.Log
for _, lgs := range csdb.logs {
logs = append(logs, lgs...)
}
return logs
}
// GetRefund returns the current value of the refund counter.
func (csdb *CommitStateDB) GetRefund() uint64 {
return csdb.refund
}
// Preimages returns a list of SHA3 preimages that have been submitted.
func (csdb *CommitStateDB) Preimages() map[ethcmn.Hash][]byte {
return csdb.preimages
}
// HasSuicided returns if the given account for the specified address has been
// killed.
func (csdb *CommitStateDB) HasSuicided(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
if so != nil {
return so.suicided
}
return false
}
// StorageTrie returns nil as the state in Ethermint does not use a direct
// storage trie.
func (csdb *CommitStateDB) StorageTrie(addr ethcmn.Address) ethstate.Trie {
return nil
}
// ----------------------------------------------------------------------------
// Persistance
// ----------------------------------------------------------------------------
// TODO: Commit writes the state ...
func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (root ethcmn.Hash, err error) {
// TODO: ...
return
}
// TODO: ...
func (csdb *CommitStateDB) Finalize(deleteEmptyObjects bool) {
// TODO: ...
}
// 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.
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash {
// TODO: ...
// csdb.Finalize(deleteEmptyObjects)
// return csdb.trie.Hash()
}
// ----------------------------------------------------------------------------
// Snapshotting
// ----------------------------------------------------------------------------
// Snapshot returns an identifier for the current revision of the state.
func (csdb *CommitStateDB) Snapshot() int {
id := csdb.nextRevisionID
csdb.nextRevisionID++
csdb.validRevisions = append(csdb.validRevisions, ethstate.Revision{id, csdb.journal.length()})
return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
func (csdb *CommitStateDB) RevertToSnapshot(revID int) {
// find the snapshot in the stack of valid snapshots
idx := sort.Search(len(csdb.validRevisions), func(i int) bool {
return csdb.validRevisions[i].ID >= revID
})
if idx == len(csdb.validRevisions) || csdb.validRevisions[idx].ID != revID {
panic(fmt.Errorf("revision ID %v cannot be reverted", revID))
}
snapshot := csdb.validRevisions[idx].JournalIndex
// replay the journal to undo changes and remove invalidated snapshots
csdb.journal.revert(csdb, snapshot)
csdb.validRevisions = csdb.validRevisions[:idx]
}
// ----------------------------------------------------------------------------
// Auxiliary
// ----------------------------------------------------------------------------
// 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,
// getStateObject will return a non-nil account after Suicide.
func (csdb *CommitStateDB) Suicide(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
if so == nil {
return false
}
csdb.journal.append(suicideChange{
account: &addr,
prev: so.suicided,
prevBalance: sdk.NewIntFromBigInt(so.Balance()),
})
so.markSuicided()
so.SetBalance(new(big.Int))
return true
}
// Reset clears out all ephemeral state objects from the state db, but keeps
// the underlying account mapper and store keys to avoid reloading data for the
// next operations.
func (csdb *CommitStateDB) Reset(root ethcmn.Hash) error {
csdb.stateObjects = make(map[ethcmn.Address]*stateObject)
csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{})
csdb.thash = ethcmn.Hash{}
csdb.bhash = ethcmn.Hash{}
csdb.txIndex = 0
csdb.logs = make(map[ethcmn.Hash][]*ethtypes.Log)
csdb.logSize = 0
csdb.preimages = make(map[ethcmn.Hash][]byte)
csdb.clearJournalAndRefund()
return nil
}
func (csdb *CommitStateDB) clearJournalAndRefund() {
csdb.journal = newJournal()
csdb.validRevisions = csdb.validRevisions[:0]
csdb.refund = 0
}
// Prepare sets the current transaction hash and index and block hash which is
// used when the EVM emits new state logs.
func (csdb *CommitStateDB) Prepare(thash, bhash ethcmn.Hash, txi int) {
csdb.thash = thash
csdb.bhash = bhash
csdb.txIndex = txi
}
// CreateAccount explicitly creates a state object. If a state object with the
// address already exists the balance is carried over to the new account.
//
// CreateAccount is called during the EVM CREATE operation. The situation might
// arise that a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (csdb *CommitStateDB) CreateAccount(addr ethcmn.Address) {
newobj, prevobj := csdb.createObject(addr)
if prevobj != nil {
newobj.setBalance(sdk.NewIntFromBigInt(prevobj.Balance()))
}
}
// Copy creates a deep, independent copy of the state.
//
// NOTE: Snapshots of the copied state cannot be applied to the copy.
func (csdb *CommitStateDB) Copy() ethstate.StateDB {
csdb.lock.Lock()
defer csdb.lock.Unlock()
// copy all the basic fields, initialize the memory ones
state := &CommitStateDB{
ctx: csdb.ctx,
am: csdb.am,
storageKey: csdb.storageKey,
codeKey: csdb.codeKey,
stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)),
stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)),
refund: csdb.refund,
logs: make(map[ethcmn.Hash][]*ethtypes.Log, len(csdb.logs)),
logSize: csdb.logSize,
preimages: make(map[ethcmn.Hash][]byte),
journal: newJournal(),
}
// copy the dirty states, logs, and preimages
for addr := range csdb.journal.dirties {
// There is a case where an object is in the journal but not in the
// stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we
// need to check for nil.
//
// Ref: https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527
if object, exist := csdb.stateObjects[addr]; exist {
state.stateObjects[addr] = object.deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{}
}
}
// Above, we don't copy the actual journal. This means that if the copy is
// copied, the loop above will be a no-op, since the copy's journal is empty.
// Thus, here we iterate over stateObjects, to enable copies of copies.
for addr := range csdb.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = csdb.stateObjects[addr].deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{}
}
}
// copy logs
for hash, logs := range csdb.logs {
cpy := make([]*ethtypes.Log, len(logs))
for i, l := range logs {
cpy[i] = new(ethtypes.Log)
*cpy[i] = *l
}
state.logs[hash] = cpy
}
// copy pre-images
for hash, preimage := range csdb.preimages {
state.preimages[hash] = preimage
}
return state
}
// ForEachStorage iterates over each storage items, all invokes the provided
// callback on each key, value pair .
func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, value ethcmn.Hash) bool) {
so := csdb.getStateObject(addr)
if so == nil {
return
}
store := csdb.ctx.KVStore(csdb.storageKey)
iter := sdk.KVStorePrefixIterator(store, so.Address().Bytes())
for ; iter.Valid(); iter.Next() {
key := ethcmn.BytesToHash(iter.Key())
value := iter.Value()
if value, dirty := so.dirtyStorage[key]; dirty {
cb(key, value)
continue
}
cb(key, ethcmn.BytesToHash(value))
}
iter.Close()
}
// GetOrNewStateObject retrieves a state object or create a new state object if
// nil.
func (csdb *CommitStateDB) GetOrNewStateObject(addr ethcmn.Address) ethstate.StateObject {
so := csdb.getStateObject(addr)
if so == nil || so.deleted {
so, _ = csdb.createObject(addr)
}
return so
}
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *stateObject) {
prevObj = csdb.getStateObject(addr)
acc := csdb.am.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes()))
newObj = newObject(csdb, acc)
newObj.setNonce(0) // sets the object to dirty
if prevObj == nil {
csdb.journal.append(createObjectChange{account: &addr})
} else {
csdb.journal.append(resetObjectChange{prev: prevObj})
}
csdb.setStateObject(newObj)
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 {
@ -68,22 +565,8 @@ func (csdb *CommitStateDB) setError(err error) {
} }
} }
// Error returns the first non-nil error the StateDB encountered. // getStateObject attempts to retrieve a state object given by the address.
func (csdb *CommitStateDB) Error() error { // Returns nil and sets an error if not found.
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) { func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *stateObject) {
// prefer 'live' (cached) objects // prefer 'live' (cached) objects
if obj := csdb.stateObjects[addr]; obj != nil { if obj := csdb.stateObjects[addr]; obj != nil {
@ -94,9 +577,11 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta
return obj return obj
} }
// otherwise, attempt to fetch the account from the account mapper
acc := csdb.am.GetAccount(csdb.ctx, addr.Bytes()) acc := csdb.am.GetAccount(csdb.ctx, addr.Bytes())
if acc == nil { if acc == nil {
csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes())) csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes()))
return nil
} }
// insert the state object into the live set // insert the state object into the live set
@ -108,3 +593,9 @@ 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
}