forked from cerc-io/laconicd-deprecated
Move old state code to x/evm/types
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
ethstate "github.com/ethereum/go-ethereum/core/state"
|
||||
)
|
||||
|
||||
// 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{}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var ripemd = ethcmn.HexToAddress("0000000000000000000000000000000000000003")
|
||||
|
||||
// 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() *ethcmn.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[ethcmn.Address]int // Dirty accounts and the number of changes
|
||||
}
|
||||
|
||||
// newJournal create a new initialized journal.
|
||||
func newJournal() *journal {
|
||||
return &journal{
|
||||
dirties: make(map[ethcmn.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 ethcmn.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 *ethcmn.Address
|
||||
}
|
||||
|
||||
resetObjectChange struct {
|
||||
prev *stateObject
|
||||
}
|
||||
|
||||
suicideChange struct {
|
||||
account *ethcmn.Address
|
||||
prev bool // whether account had already suicided
|
||||
prevBalance sdk.Int
|
||||
}
|
||||
|
||||
// Changes to individual accounts.
|
||||
balanceChange struct {
|
||||
account *ethcmn.Address
|
||||
prev sdk.Int
|
||||
}
|
||||
|
||||
nonceChange struct {
|
||||
account *ethcmn.Address
|
||||
prev int64
|
||||
}
|
||||
|
||||
storageChange struct {
|
||||
account *ethcmn.Address
|
||||
key, prevValue ethcmn.Hash
|
||||
}
|
||||
|
||||
codeChange struct {
|
||||
account *ethcmn.Address
|
||||
prevCode, prevHash []byte
|
||||
}
|
||||
|
||||
// Changes to other state values.
|
||||
refundChange struct {
|
||||
prev uint64
|
||||
}
|
||||
|
||||
addLogChange struct {
|
||||
txhash ethcmn.Hash
|
||||
}
|
||||
|
||||
addPreimageChange struct {
|
||||
hash ethcmn.Hash
|
||||
}
|
||||
|
||||
touchChange struct {
|
||||
account *ethcmn.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() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) revert(s *CommitStateDB) {
|
||||
s.setStateObject(ch.prev)
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) dirtied() *ethcmn.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch suicideChange) revert(s *CommitStateDB) {
|
||||
so := s.getStateObject(*ch.account)
|
||||
if so != nil {
|
||||
so.suicided = ch.prev
|
||||
so.setBalance(ch.prevBalance)
|
||||
}
|
||||
}
|
||||
|
||||
func (ch suicideChange) dirtied() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch touchChange) revert(s *CommitStateDB) {
|
||||
}
|
||||
|
||||
func (ch touchChange) dirtied() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch balanceChange) revert(s *CommitStateDB) {
|
||||
s.getStateObject(*ch.account).setBalance(ch.prev)
|
||||
}
|
||||
|
||||
func (ch balanceChange) dirtied() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch nonceChange) revert(s *CommitStateDB) {
|
||||
s.getStateObject(*ch.account).setNonce(ch.prev)
|
||||
}
|
||||
|
||||
func (ch nonceChange) dirtied() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch codeChange) revert(s *CommitStateDB) {
|
||||
s.getStateObject(*ch.account).setCode(ethcmn.BytesToHash(ch.prevHash), ch.prevCode)
|
||||
}
|
||||
|
||||
func (ch codeChange) dirtied() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch storageChange) revert(s *CommitStateDB) {
|
||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevValue)
|
||||
}
|
||||
|
||||
func (ch storageChange) dirtied() *ethcmn.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch refundChange) revert(s *CommitStateDB) {
|
||||
s.refund = ch.prev
|
||||
}
|
||||
|
||||
func (ch refundChange) dirtied() *ethcmn.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch addLogChange) revert(s *CommitStateDB) {
|
||||
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() *ethcmn.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) revert(s *CommitStateDB) {
|
||||
delete(s.preimages, ch.hash)
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) dirtied() *ethcmn.Address {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
package types
|
||||
|
||||
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"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ethstate.StateObject = (*stateObject)(nil)
|
||||
|
||||
emptyCodeHash = ethcrypto.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
|
||||
}
|
||||
)
|
||||
|
||||
func newObject(db *CommitStateDB, accProto auth.Account) *stateObject {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Setters
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// SetState updates a value in account storage. Note, the key will be prefixed
|
||||
// with the address of the state object.
|
||||
func (so *stateObject) SetState(db ethstate.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
|
||||
}
|
||||
|
||||
prefixKey := so.GetStorageByAddressKey(key.Bytes())
|
||||
|
||||
// since the new value is different, update and journal the change
|
||||
so.stateDB.journal.append(storageChange{
|
||||
account: &so.address,
|
||||
key: prefixKey,
|
||||
prevValue: prev,
|
||||
})
|
||||
|
||||
so.setState(prefixKey, value)
|
||||
}
|
||||
|
||||
func (so *stateObject) setState(key, value ethcmn.Hash) {
|
||||
so.dirtyStorage[key] = value
|
||||
}
|
||||
|
||||
// SetCode sets the state object's code.
|
||||
func (so *stateObject) SetCode(codeHash ethcmn.Hash, code []byte) {
|
||||
prevCode := so.Code(nil)
|
||||
|
||||
so.stateDB.journal.append(codeChange{
|
||||
account: &so.address,
|
||||
prevHash: so.CodeHash(),
|
||||
prevCode: prevCode,
|
||||
})
|
||||
|
||||
so.setCode(codeHash, code)
|
||||
}
|
||||
|
||||
func (so *stateObject) setCode(codeHash ethcmn.Hash, code []byte) {
|
||||
so.code = code
|
||||
so.account.CodeHash = codeHash.Bytes()
|
||||
so.dirtyCode = true
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// SubBalance removes an amount from the stateObject's balance. It is used to
|
||||
// remove funds from the origin account of a transfer.
|
||||
func (so *stateObject) SubBalance(amount *big.Int) {
|
||||
amt := sdk.NewIntFromBigInt(amount)
|
||||
|
||||
if amt.Sign() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newBalance := so.account.Balance().Sub(amt)
|
||||
so.SetBalance(newBalance.BigInt())
|
||||
}
|
||||
|
||||
// SetBalance sets the state object's balance.
|
||||
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)
|
||||
}
|
||||
|
||||
func (so *stateObject) setBalance(amount sdk.Int) {
|
||||
so.account.SetBalance(amount)
|
||||
}
|
||||
|
||||
// SetNonce sets the state object's nonce (sequence number).
|
||||
func (so *stateObject) SetNonce(nonce uint64) {
|
||||
so.stateDB.journal.append(nonceChange{
|
||||
account: &so.address,
|
||||
prev: so.account.Sequence,
|
||||
})
|
||||
|
||||
so.setNonce(int64(nonce))
|
||||
}
|
||||
|
||||
func (so *stateObject) setNonce(nonce int64) {
|
||||
so.account.Sequence = nonce
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (so *stateObject) setError(err error) {
|
||||
if so.dbErr == nil {
|
||||
so.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// commitCode persists the state object's code to the KVStore.
|
||||
func (so *stateObject) commitCode() {
|
||||
ctx := so.stateDB.ctx
|
||||
store := ctx.KVStore(so.stateDB.codeKey)
|
||||
store.Set(so.CodeHash(), so.code)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Getters
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Address returns the address of the state object.
|
||||
func (so stateObject) Address() ethcmn.Address {
|
||||
return so.address
|
||||
}
|
||||
|
||||
// Balance returns the state object's current balance.
|
||||
func (so *stateObject) Balance() *big.Int {
|
||||
return so.account.Balance().BigInt()
|
||||
}
|
||||
|
||||
// CodeHash returns the state object's code hash.
|
||||
func (so *stateObject) CodeHash() []byte {
|
||||
return so.account.CodeHash
|
||||
}
|
||||
|
||||
// Nonce returns the state object's current nonce (sequence number).
|
||||
func (so *stateObject) Nonce() uint64 {
|
||||
return uint64(so.account.Sequence)
|
||||
}
|
||||
|
||||
// Code returns the contract code associated with this object, if any.
|
||||
func (so *stateObject) Code(_ ethstate.Database) []byte {
|
||||
if so.code != nil {
|
||||
return so.code
|
||||
}
|
||||
|
||||
if bytes.Equal(so.CodeHash(), emptyCodeHash) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := so.stateDB.ctx
|
||||
store := ctx.KVStore(so.stateDB.codeKey)
|
||||
code := store.Get(so.CodeHash())
|
||||
|
||||
if len(code) == 0 {
|
||||
so.setError(fmt.Errorf("failed to get code hash %x for address: %x", so.CodeHash(), so.Address()))
|
||||
}
|
||||
|
||||
so.code = code
|
||||
return code
|
||||
}
|
||||
|
||||
// GetState retrieves a value from the account storage trie. Note, the key will
|
||||
// be prefixed with the address of the state object.
|
||||
func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
|
||||
prefixKey := so.GetStorageByAddressKey(key.Bytes())
|
||||
|
||||
// if we have a dirty value for this state entry, return it
|
||||
value, dirty := so.dirtyStorage[prefixKey]
|
||||
if dirty {
|
||||
return value
|
||||
}
|
||||
|
||||
// otherwise return the entry's original value
|
||||
return so.GetCommittedState(db, key)
|
||||
}
|
||||
|
||||
// GetCommittedState retrieves a value from the committed account storage trie.
|
||||
// Note, the key will be prefixed with the address of the state object.
|
||||
func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
|
||||
prefixKey := so.GetStorageByAddressKey(key.Bytes())
|
||||
|
||||
// if we have the original value cached, return that
|
||||
value, cached := so.originStorage[prefixKey]
|
||||
if cached {
|
||||
return value
|
||||
}
|
||||
|
||||
// otherwise load the value from the KVStore
|
||||
ctx := so.stateDB.ctx
|
||||
store := ctx.KVStore(so.stateDB.storageKey)
|
||||
rawValue := store.Get(prefixKey.Bytes())
|
||||
|
||||
if len(rawValue) > 0 {
|
||||
value.SetBytes(rawValue)
|
||||
}
|
||||
|
||||
so.originStorage[prefixKey] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Auxiliary
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ReturnGas returns the gas back to the origin. Used by the Virtual machine or
|
||||
// Closures. It performs a no-op.
|
||||
func (so *stateObject) ReturnGas(gas *big.Int) {}
|
||||
|
||||
func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject {
|
||||
newStateObj := newObject(db, so.account)
|
||||
|
||||
newStateObj.code = so.code
|
||||
newStateObj.dirtyStorage = so.dirtyStorage.Copy()
|
||||
newStateObj.originStorage = so.originStorage.Copy()
|
||||
newStateObj.suicided = so.suicided
|
||||
newStateObj.dirtyCode = so.dirtyCode
|
||||
newStateObj.deleted = so.deleted
|
||||
|
||||
return newStateObj
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStorageByAddressKey returns a hash of the composite key for a state
|
||||
// object's storage prefixed with it's address.
|
||||
func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash {
|
||||
prefix := so.Address().Bytes()
|
||||
compositeKey := make([]byte, len(prefix)+len(key))
|
||||
|
||||
copy(compositeKey, prefix)
|
||||
copy(compositeKey[len(prefix):], key)
|
||||
|
||||
return ethcrypto.Keccak256Hash(compositeKey)
|
||||
}
|
||||
@@ -0,0 +1,703 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
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"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
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, 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.
|
||||
type CommitStateDB struct {
|
||||
// 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.
|
||||
ctx sdk.Context
|
||||
|
||||
am auth.AccountMapper
|
||||
storageKey sdk.StoreKey
|
||||
codeKey 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{}
|
||||
|
||||
// The refund counter, also used by state transitioning.
|
||||
refund uint64
|
||||
|
||||
thash, bhash ethcmn.Hash
|
||||
txIndex int
|
||||
logs map[ethcmn.Hash][]*ethtypes.Log
|
||||
logSize uint
|
||||
|
||||
// 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.
|
||||
preimages map[ethcmn.Hash][]byte
|
||||
|
||||
// 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 memo-ized 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
|
||||
|
||||
// mutex for state deep copying
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// NewCommitStateDB returns a reference to a newly initialized CommitStateDB
|
||||
// which implements Geth's state.StateDB interface.
|
||||
//
|
||||
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
|
||||
// key/value space matters in determining the merkle root.
|
||||
func NewCommitStateDB(ctx sdk.Context, am auth.AccountMapper, storageKey, codeKey sdk.StoreKey) (*CommitStateDB, error) {
|
||||
return &CommitStateDB{
|
||||
ctx: ctx,
|
||||
am: am,
|
||||
storageKey: storageKey,
|
||||
codeKey: codeKey,
|
||||
stateObjects: make(map[ethcmn.Address]*stateObject),
|
||||
stateObjectsDirty: make(map[ethcmn.Address]struct{}),
|
||||
logs: make(map[ethcmn.Hash][]*ethtypes.Log),
|
||||
preimages: make(map[ethcmn.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
}, 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(ethcrypto.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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// 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.
|
||||
func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (root ethcmn.Hash, err error) {
|
||||
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 {
|
||||
so.commitCode()
|
||||
so.dirtyCode = false
|
||||
}
|
||||
|
||||
// update the object in the KVStore
|
||||
csdb.updateStateObject(so)
|
||||
}
|
||||
|
||||
delete(csdb.stateObjectsDirty, addr)
|
||||
}
|
||||
|
||||
// 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.
|
||||
return
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 returns the current root hash of the state. It is called in
|
||||
// between transactions to get the root hash that goes into transaction
|
||||
// receipts.
|
||||
//
|
||||
// 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.
|
||||
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash {
|
||||
csdb.Finalize(deleteEmptyObjects)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 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: id,
|
||||
JournalIndex: 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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// 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,
|
||||
// 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
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (csdb *CommitStateDB) setError(err error) {
|
||||
if csdb.dbErr == nil {
|
||||
csdb.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// getStateObject attempts to retrieve a state object given by the address.
|
||||
// Returns nil and sets an error if not found.
|
||||
func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *stateObject) {
|
||||
// prefer 'live' (cached) objects
|
||||
if so := csdb.stateObjects[addr]; so != nil {
|
||||
if so.deleted {
|
||||
return nil
|
||||
}
|
||||
|
||||
return so
|
||||
}
|
||||
|
||||
// otherwise, attempt to fetch the account from the account mapper
|
||||
acc := csdb.am.GetAccount(csdb.ctx, addr.Bytes())
|
||||
if acc == nil {
|
||||
csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// insert the state object into the live set
|
||||
so := newObject(csdb, acc)
|
||||
csdb.setStateObject(so)
|
||||
|
||||
return so
|
||||
}
|
||||
|
||||
func (csdb *CommitStateDB) setStateObject(so *stateObject) {
|
||||
csdb.stateObjects[so.Address()] = so
|
||||
}
|
||||
Reference in New Issue
Block a user