2018-10-24 13:46:14 +00:00
|
|
|
package types
|
2018-10-03 00:22:15 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
2018-10-04 01:32:13 +00:00
|
|
|
"sort"
|
|
|
|
"sync"
|
2018-10-03 00:22:15 +00:00
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
2018-10-03 00:22:15 +00:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2020-04-02 00:43:59 +00:00
|
|
|
|
2019-11-15 17:02:13 +00:00
|
|
|
emint "github.com/cosmos/ethermint/types"
|
|
|
|
|
2018-10-03 00:22:15 +00:00
|
|
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
|
|
ethstate "github.com/ethereum/go-ethereum/core/state"
|
|
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
2019-07-11 18:05:34 +00:00
|
|
|
ethvm "github.com/ethereum/go-ethereum/core/vm"
|
2018-10-24 12:21:14 +00:00
|
|
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
2018-10-03 00:22:15 +00:00
|
|
|
)
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
var (
|
2019-07-11 18:05:34 +00:00
|
|
|
_ ethvm.StateDB = (*CommitStateDB)(nil)
|
2018-10-03 00:22:15 +00:00
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
zeroBalance = sdk.ZeroInt().BigInt()
|
|
|
|
)
|
|
|
|
|
2019-07-11 18:05:34 +00:00
|
|
|
type revision struct {
|
|
|
|
id int
|
|
|
|
journalIndex int
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// CommitStateDB implements the Geth state.StateDB interface. Instead of using
|
2018-10-24 13:46:14 +00:00
|
|
|
// a trie and database for querying and persistence, the Keeper uses KVStores
|
|
|
|
// and an account mapper is used to facilitate state transitions.
|
|
|
|
//
|
|
|
|
// TODO: This implementation is subject to change in regards to its statefull
|
|
|
|
// manner. In otherwords, how this relates to the keeper in this module.
|
2018-10-03 00:22:15 +00:00
|
|
|
type CommitStateDB struct {
|
2018-10-24 13:46:14 +00:00
|
|
|
// TODO: We need to store the context as part of the structure itself opposed
|
|
|
|
// to being passed as a parameter (as it should be) in order to implement the
|
|
|
|
// StateDB interface. Perhaps there is a better way.
|
2018-10-03 00:22:15 +00:00
|
|
|
ctx sdk.Context
|
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
storeKey sdk.StoreKey
|
2020-04-01 18:49:21 +00:00
|
|
|
accountKeeper AccountKeeper
|
2020-04-22 19:26:01 +00:00
|
|
|
bankKeeper BankKeeper
|
2018-10-03 00:22:15 +00:00
|
|
|
|
|
|
|
// maps that hold 'live' objects, which will get modified while processing a
|
|
|
|
// state transition
|
|
|
|
stateObjects map[ethcmn.Address]*stateObject
|
|
|
|
stateObjectsDirty map[ethcmn.Address]struct{}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// The refund counter, also used by state transitioning.
|
|
|
|
refund uint64
|
|
|
|
|
2018-10-03 00:22:15 +00:00
|
|
|
thash, bhash ethcmn.Hash
|
|
|
|
txIndex int
|
|
|
|
logSize uint
|
|
|
|
|
2018-10-04 16:09:59 +00:00
|
|
|
// TODO: Determine if we actually need this as we do not need preimages in
|
|
|
|
// the SDK, but it seems to be used elsewhere in Geth.
|
2018-10-04 01:32:13 +00:00
|
|
|
preimages map[ethcmn.Hash][]byte
|
|
|
|
|
2018-10-03 00:22:15 +00:00
|
|
|
// 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
|
2018-10-24 13:46:14 +00:00
|
|
|
// during a database read is memo-ized here and will eventually be returned
|
2018-10-03 00:22:15 +00:00
|
|
|
// by StateDB.Commit.
|
|
|
|
dbErr error
|
|
|
|
|
|
|
|
// Journal of state modifications. This is the backbone of
|
|
|
|
// Snapshot and RevertToSnapshot.
|
|
|
|
journal *journal
|
2019-07-11 18:05:34 +00:00
|
|
|
validRevisions []revision
|
2018-10-03 00:22:15 +00:00
|
|
|
nextRevisionID int
|
2018-10-04 01:32:13 +00:00
|
|
|
|
|
|
|
// mutex for state deep copying
|
|
|
|
lock sync.Mutex
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 16:09:59 +00:00
|
|
|
// NewCommitStateDB returns a reference to a newly initialized CommitStateDB
|
|
|
|
// which implements Geth's state.StateDB interface.
|
2018-10-04 20:11:50 +00:00
|
|
|
//
|
|
|
|
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
|
|
|
|
// key/value space matters in determining the merkle root.
|
2020-04-01 18:49:21 +00:00
|
|
|
func NewCommitStateDB(
|
2020-06-04 10:40:21 +00:00
|
|
|
ctx sdk.Context, storeKey sdk.StoreKey, ak AccountKeeper, bk BankKeeper,
|
2020-04-01 18:49:21 +00:00
|
|
|
) *CommitStateDB {
|
2018-10-03 00:22:15 +00:00
|
|
|
return &CommitStateDB{
|
2018-10-04 16:09:59 +00:00
|
|
|
ctx: ctx,
|
2020-04-01 18:49:21 +00:00
|
|
|
storeKey: storeKey,
|
|
|
|
accountKeeper: ak,
|
2020-04-22 19:26:01 +00:00
|
|
|
bankKeeper: bk,
|
2018-10-04 01:32:13 +00:00
|
|
|
stateObjects: make(map[ethcmn.Address]*stateObject),
|
|
|
|
stateObjectsDirty: make(map[ethcmn.Address]struct{}),
|
2018-10-04 16:09:59 +00:00
|
|
|
preimages: make(map[ethcmn.Hash][]byte),
|
2018-10-04 01:32:13 +00:00
|
|
|
journal: newJournal(),
|
2019-07-24 22:14:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 16:46:02 +00:00
|
|
|
// WithContext returns a Database with an updated sdk context
|
2019-07-24 22:14:12 +00:00
|
|
|
func (csdb *CommitStateDB) WithContext(ctx sdk.Context) *CommitStateDB {
|
|
|
|
csdb.ctx = ctx
|
|
|
|
return csdb
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// 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)
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// 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 {
|
2018-10-24 12:21:14 +00:00
|
|
|
so.SetCode(ethcrypto.Keccak256Hash(code), code)
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Transaction logs
|
|
|
|
// Required for upgrade logic or ease of querying.
|
|
|
|
// NOTE: we use BinaryLengthPrefixed since the tx logs are also included on Result data,
|
|
|
|
// which can't use BinaryBare.
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-04-13 19:18:50 +00:00
|
|
|
// SetLogs sets the logs for a transaction in the KVStore.
|
|
|
|
func (csdb *CommitStateDB) SetLogs(hash ethcmn.Hash, logs []*ethtypes.Log) error {
|
2020-06-04 10:40:21 +00:00
|
|
|
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
|
|
|
|
bz, err := MarshalLogs(logs)
|
2020-04-13 19:18:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
store.Set(hash.Bytes(), bz)
|
|
|
|
csdb.logSize = uint(len(logs))
|
2020-04-13 19:18:50 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
// DeleteLogs removes the logs from the KVStore. It is used during journal.Revert.
|
|
|
|
func (csdb *CommitStateDB) DeleteLogs(hash ethcmn.Hash) {
|
|
|
|
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
|
|
|
|
store.Delete(hash.Bytes())
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// 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
|
2020-06-04 10:40:21 +00:00
|
|
|
|
|
|
|
logs, err := csdb.GetLogs(csdb.thash)
|
|
|
|
if err != nil {
|
|
|
|
// panic on unmarshal error
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = csdb.SetLogs(csdb.thash, append(logs, log)); err != nil {
|
|
|
|
// panic on marshal error
|
|
|
|
panic(err)
|
|
|
|
}
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-07-11 18:05:34 +00:00
|
|
|
// TxIndex returns the current transaction index set by Prepare.
|
|
|
|
func (csdb *CommitStateDB) TxIndex() int {
|
|
|
|
return csdb.txIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
// BlockHash returns the current block hash set by Prepare.
|
|
|
|
func (csdb *CommitStateDB) BlockHash() ethcmn.Hash {
|
|
|
|
return csdb.bhash
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// 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{}
|
|
|
|
}
|
|
|
|
|
2020-04-13 19:18:50 +00:00
|
|
|
// GetLogs returns the current logs for a given transaction hash from the KVStore.
|
2020-04-01 18:49:21 +00:00
|
|
|
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) {
|
2020-06-04 10:40:21 +00:00
|
|
|
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
|
|
|
|
bz := store.Get(hash.Bytes())
|
|
|
|
if len(bz) == 0 {
|
|
|
|
// return nil error if logs are not found
|
2020-04-01 18:49:21 +00:00
|
|
|
return []*ethtypes.Log{}, nil
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
return UnmarshalLogs(bz)
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 15:47:39 +00:00
|
|
|
// AllLogs returns all the current logs in the state.
|
2020-04-01 18:49:21 +00:00
|
|
|
func (csdb *CommitStateDB) AllLogs() []*ethtypes.Log {
|
2020-06-04 10:40:21 +00:00
|
|
|
store := csdb.ctx.KVStore(csdb.storeKey)
|
|
|
|
iterator := sdk.KVStorePrefixIterator(store, KeyPrefixLogs)
|
|
|
|
defer iterator.Close()
|
|
|
|
|
|
|
|
allLogs := []*ethtypes.Log{}
|
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
|
|
var logs []*ethtypes.Log
|
|
|
|
ModuleCdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &logs)
|
|
|
|
allLogs = append(allLogs, logs...)
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
return allLogs
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2018-10-24 14:53:23 +00:00
|
|
|
// Persistence
|
2018-10-04 01:32:13 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2018-10-04 16:15:16 +00:00
|
|
|
// Commit writes the state to the appropriate KVStores. For each state object
|
|
|
|
// in the cache, it will either be removed, or have it's code set and/or it's
|
|
|
|
// state (storage) updated. In addition, the state object (account) itself will
|
|
|
|
// be written. Finally, the root hash (version) will be returned.
|
2020-03-16 22:53:24 +00:00
|
|
|
func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (ethcmn.Hash, error) {
|
2018-10-04 16:06:19 +00:00
|
|
|
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]
|
2018-10-23 02:30:13 +00:00
|
|
|
|
2018-10-04 16:06:19 +00:00
|
|
|
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 {
|
2018-10-23 02:30:13 +00:00
|
|
|
so.commitCode()
|
2018-10-04 16:06:19 +00:00
|
|
|
so.dirtyCode = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the object in the KVStore
|
2020-04-01 18:49:21 +00:00
|
|
|
if err := csdb.updateStateObject(so); err != nil {
|
|
|
|
return ethcmn.Hash{}, err
|
|
|
|
}
|
2018-10-04 16:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
delete(csdb.stateObjectsDirty, addr)
|
|
|
|
}
|
|
|
|
|
2018-10-24 13:46:14 +00:00
|
|
|
// NOTE: Ethereum returns the trie merkle root here, but as commitment
|
|
|
|
// actually happens in the BaseApp at EndBlocker, we do not know the root at
|
|
|
|
// this time.
|
2020-03-16 22:53:24 +00:00
|
|
|
return ethcmn.Hash{}, nil
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
2019-10-03 16:46:02 +00:00
|
|
|
// Finalise finalizes the state objects (accounts) state by setting their state,
|
2018-10-04 16:06:19 +00:00
|
|
|
// removing the csdb destructed objects and clearing the journal as well as the
|
|
|
|
// refunds.
|
2020-04-01 18:49:21 +00:00
|
|
|
func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) error {
|
2018-10-04 16:06:19 +00:00
|
|
|
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()
|
2020-04-01 18:49:21 +00:00
|
|
|
if err := csdb.updateStateObject(so); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-04 16:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
csdb.stateObjectsDirty[addr] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// invalidate journal because reverting across transactions is not allowed
|
|
|
|
csdb.clearJournalAndRefund()
|
2020-04-01 18:49:21 +00:00
|
|
|
return nil
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 16:06:19 +00:00
|
|
|
// 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.
|
2018-10-24 13:46:14 +00:00
|
|
|
//
|
|
|
|
// NOTE: The SDK has not concept or method of getting any intermediate merkle
|
|
|
|
// root as commitment of the merkle-ized tree doesn't happen until the
|
|
|
|
// BaseApps' EndBlocker.
|
2020-04-01 18:49:21 +00:00
|
|
|
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) (ethcmn.Hash, error) {
|
|
|
|
if err := csdb.Finalise(deleteEmptyObjects); err != nil {
|
|
|
|
return ethcmn.Hash{}, err
|
|
|
|
}
|
2018-10-04 16:06:19 +00:00
|
|
|
|
2020-04-01 18:49:21 +00:00
|
|
|
return ethcmn.Hash{}, nil
|
2018-10-04 16:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// updateStateObject writes the given state object to the store.
|
2020-04-01 18:49:21 +00:00
|
|
|
func (csdb *CommitStateDB) updateStateObject(so *stateObject) error {
|
|
|
|
csdb.accountKeeper.SetAccount(csdb.ctx, so.account)
|
2020-04-22 19:26:01 +00:00
|
|
|
// NOTE: we don't use sdk.NewCoin here to avoid panic on test importer's genesis
|
|
|
|
newBalance := sdk.Coin{Denom: emint.DenomDefault, Amount: sdk.NewIntFromBigInt(so.Balance())}
|
|
|
|
if !newBalance.IsValid() {
|
|
|
|
return fmt.Errorf("invalid balance %s", newBalance)
|
|
|
|
}
|
|
|
|
return csdb.bankKeeper.SetBalance(csdb.ctx, so.account.Address, newBalance)
|
2018-10-04 16:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// deleteStateObject removes the given state object from the state store.
|
|
|
|
func (csdb *CommitStateDB) deleteStateObject(so *stateObject) {
|
|
|
|
so.deleted = true
|
2020-04-01 18:49:21 +00:00
|
|
|
csdb.accountKeeper.RemoveAccount(csdb.ctx, so.account)
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Snapshotting
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// Snapshot returns an identifier for the current revision of the state.
|
|
|
|
func (csdb *CommitStateDB) Snapshot() int {
|
|
|
|
id := csdb.nextRevisionID
|
|
|
|
csdb.nextRevisionID++
|
2018-10-23 02:30:13 +00:00
|
|
|
|
|
|
|
csdb.validRevisions = append(
|
|
|
|
csdb.validRevisions,
|
2019-07-11 18:05:34 +00:00
|
|
|
revision{
|
|
|
|
id: id,
|
|
|
|
journalIndex: csdb.journal.length(),
|
2018-10-23 02:30:13 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
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 {
|
2019-07-11 18:05:34 +00:00
|
|
|
return csdb.validRevisions[i].id >= revID
|
2018-10-04 01:32:13 +00:00
|
|
|
})
|
|
|
|
|
2019-07-11 18:05:34 +00:00
|
|
|
if idx == len(csdb.validRevisions) || csdb.validRevisions[idx].id != revID {
|
2018-10-04 01:32:13 +00:00
|
|
|
panic(fmt.Errorf("revision ID %v cannot be reverted", revID))
|
|
|
|
}
|
|
|
|
|
2019-07-11 18:05:34 +00:00
|
|
|
snapshot := csdb.validRevisions[idx].journalIndex
|
2018-10-04 01:32:13 +00:00
|
|
|
|
|
|
|
// replay the journal to undo changes and remove invalidated snapshots
|
|
|
|
csdb.journal.revert(csdb, snapshot)
|
|
|
|
csdb.validRevisions = csdb.validRevisions[:idx]
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Auxiliary
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2018-10-04 16:06:19 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// 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.
|
2019-11-15 17:02:13 +00:00
|
|
|
func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
|
2018-10-04 01:32:13 +00:00
|
|
|
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.logSize = 0
|
|
|
|
csdb.preimages = make(map[ethcmn.Hash][]byte)
|
|
|
|
|
|
|
|
csdb.clearJournalAndRefund()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-15 17:02:13 +00:00
|
|
|
// UpdateAccounts updates the nonce and coin balances of accounts
|
|
|
|
func (csdb *CommitStateDB) UpdateAccounts() {
|
|
|
|
for addr, so := range csdb.stateObjects {
|
2020-04-01 18:49:21 +00:00
|
|
|
currAcc := csdb.accountKeeper.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes()))
|
2020-04-22 19:26:01 +00:00
|
|
|
emintAcc, ok := currAcc.(*emint.EthAccount)
|
|
|
|
if !ok {
|
2020-05-29 15:50:22 +00:00
|
|
|
continue
|
2019-11-15 17:02:13 +00:00
|
|
|
}
|
|
|
|
|
2020-04-22 19:26:01 +00:00
|
|
|
balance := csdb.bankKeeper.GetBalance(csdb.ctx, emintAcc.GetAddress(), emint.DenomDefault)
|
|
|
|
if so.Balance() != balance.Amount.BigInt() && balance.IsValid() {
|
|
|
|
so.balance = balance.Amount
|
|
|
|
}
|
|
|
|
|
|
|
|
if so.Nonce() != emintAcc.GetSequence() {
|
|
|
|
so.account = emintAcc
|
|
|
|
}
|
2019-11-15 17:02:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClearStateObjects clears cache of state objects to handle account changes outside of the EVM
|
|
|
|
func (csdb *CommitStateDB) ClearStateObjects() {
|
|
|
|
csdb.stateObjects = make(map[ethcmn.Address]*stateObject)
|
|
|
|
csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{})
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
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.
|
2019-11-04 16:59:16 +00:00
|
|
|
func (csdb *CommitStateDB) Copy() *CommitStateDB {
|
2018-10-04 01:32:13 +00:00
|
|
|
csdb.lock.Lock()
|
|
|
|
defer csdb.lock.Unlock()
|
|
|
|
|
|
|
|
// copy all the basic fields, initialize the memory ones
|
|
|
|
state := &CommitStateDB{
|
|
|
|
ctx: csdb.ctx,
|
2020-04-01 18:49:21 +00:00
|
|
|
storeKey: csdb.storeKey,
|
|
|
|
accountKeeper: csdb.accountKeeper,
|
2020-04-22 19:26:01 +00:00
|
|
|
bankKeeper: csdb.bankKeeper,
|
2018-10-04 01:32:13 +00:00
|
|
|
stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)),
|
|
|
|
stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)),
|
|
|
|
refund: csdb.refund,
|
|
|
|
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 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 .
|
2019-07-11 18:05:34 +00:00
|
|
|
func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, value ethcmn.Hash) bool) error {
|
2018-10-04 01:32:13 +00:00
|
|
|
so := csdb.getStateObject(addr)
|
|
|
|
if so == nil {
|
2019-07-11 18:05:34 +00:00
|
|
|
return nil
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
2020-04-01 18:49:21 +00:00
|
|
|
store := csdb.ctx.KVStore(csdb.storeKey)
|
2020-06-04 10:40:21 +00:00
|
|
|
iterator := sdk.KVStorePrefixIterator(store, AddressStoragePrefix(so.Address()))
|
|
|
|
defer iterator.Close()
|
2018-10-04 01:32:13 +00:00
|
|
|
|
2020-06-04 10:40:21 +00:00
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
|
|
key := ethcmn.BytesToHash(iterator.Key())
|
|
|
|
value := ethcmn.BytesToHash(iterator.Value())
|
2018-10-04 01:32:13 +00:00
|
|
|
|
|
|
|
if value, dirty := so.dirtyStorage[key]; dirty {
|
2020-05-18 19:21:12 +00:00
|
|
|
// check if iteration stops
|
|
|
|
if cb(key, value) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-05-18 19:21:12 +00:00
|
|
|
// check if iteration stops
|
2020-06-04 10:40:21 +00:00
|
|
|
if cb(key, value) {
|
2020-05-18 19:21:12 +00:00
|
|
|
break
|
|
|
|
}
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:05:34 +00:00
|
|
|
return nil
|
2018-10-04 01:32:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetOrNewStateObject retrieves a state object or create a new state object if
|
|
|
|
// nil.
|
2019-07-11 18:05:34 +00:00
|
|
|
func (csdb *CommitStateDB) GetOrNewStateObject(addr ethcmn.Address) StateObject {
|
2018-10-04 01:32:13 +00:00
|
|
|
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)
|
|
|
|
|
2020-04-01 18:49:21 +00:00
|
|
|
acc := csdb.accountKeeper.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes()))
|
|
|
|
|
2020-04-22 19:26:01 +00:00
|
|
|
newObj = newStateObject(csdb, acc, sdk.ZeroInt())
|
2018-10-04 01:32:13 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// setError remembers the first non-nil error it is called with.
|
|
|
|
func (csdb *CommitStateDB) setError(err error) {
|
|
|
|
if csdb.dbErr == nil {
|
|
|
|
csdb.dbErr = err
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// getStateObject attempts to retrieve a state object given by the address.
|
|
|
|
// Returns nil and sets an error if not found.
|
2018-10-03 00:22:15 +00:00
|
|
|
func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *stateObject) {
|
|
|
|
// prefer 'live' (cached) objects
|
2018-10-23 02:30:13 +00:00
|
|
|
if so := csdb.stateObjects[addr]; so != nil {
|
|
|
|
if so.deleted {
|
2018-10-03 00:22:15 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-23 02:30:13 +00:00
|
|
|
return so
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 01:32:13 +00:00
|
|
|
// otherwise, attempt to fetch the account from the account mapper
|
2020-04-01 18:49:21 +00:00
|
|
|
acc := csdb.accountKeeper.GetAccount(csdb.ctx, addr.Bytes())
|
2018-10-03 00:22:15 +00:00
|
|
|
if acc == nil {
|
|
|
|
csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes()))
|
2018-10-04 01:32:13 +00:00
|
|
|
return nil
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
|
2020-04-22 19:26:01 +00:00
|
|
|
balance := csdb.bankKeeper.GetBalance(csdb.ctx, acc.GetAddress(), emint.DenomDefault)
|
|
|
|
|
2018-10-03 00:22:15 +00:00
|
|
|
// insert the state object into the live set
|
2020-04-22 19:26:01 +00:00
|
|
|
so := newStateObject(csdb, acc, balance.Amount)
|
2018-10-23 02:30:13 +00:00
|
|
|
csdb.setStateObject(so)
|
|
|
|
|
|
|
|
return so
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
|
|
|
|
2018-10-23 02:30:13 +00:00
|
|
|
func (csdb *CommitStateDB) setStateObject(so *stateObject) {
|
|
|
|
csdb.stateObjects[so.Address()] = so
|
2018-10-03 00:22:15 +00:00
|
|
|
}
|
2020-04-01 18:49:21 +00:00
|
|
|
|
|
|
|
// RawDump returns a raw state dump.
|
|
|
|
//
|
|
|
|
// TODO: Implement if we need it, especially for the RPC API.
|
|
|
|
func (csdb *CommitStateDB) RawDump() ethstate.Dump {
|
|
|
|
return ethstate.Dump{}
|
|
|
|
}
|