207 lines
7.3 KiB
Go
207 lines
7.3 KiB
Go
package state
|
|
|
|
import (
|
|
"github.com/cosmos/cosmos-sdk/store"
|
|
"github.com/cosmos/cosmos-sdk/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/ethermint/core"
|
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
|
ethstate "github.com/ethereum/go-ethereum/core/state"
|
|
ethtrie "github.com/ethereum/go-ethereum/trie"
|
|
lru "github.com/hashicorp/golang-lru"
|
|
dbm "github.com/tendermint/tendermint/libs/db"
|
|
)
|
|
|
|
var (
|
|
// AccountsKey is the key used for storing Ethereum accounts in the Cosmos
|
|
// SDK multi-store.
|
|
AccountsKey = types.NewKVStoreKey("account")
|
|
|
|
// StorageKey is the key used for storing Ethereum contract storage in the
|
|
// Cosmos SDK multi-store.
|
|
StorageKey = types.NewKVStoreKey("storage")
|
|
|
|
// CodeKey is the key used for storing Ethereum contract code in the Cosmos
|
|
// SDK multi-store.
|
|
CodeKey = types.NewKVStoreKey("code")
|
|
)
|
|
|
|
const (
|
|
// codeSizeCacheSize is the number of codehash to size associations to
|
|
// keep in cached memory. This is to address any DoS attempts on
|
|
// EXTCODESIZE calls.
|
|
codeSizeCacheSize = 100000
|
|
)
|
|
|
|
// Database implements the Ethereum state.Database interface.
|
|
type Database struct {
|
|
// stateStore will be used for the history of accounts (balance, nonce,
|
|
// storage root hash, code hash) and for the history of contract data
|
|
// (effects of SSTORE instruction).
|
|
stateStore store.CommitMultiStore
|
|
accountsCache store.CacheKVStore
|
|
storageCache store.CacheKVStore
|
|
|
|
// codeDB contains mappings of codeHash => code
|
|
//
|
|
// NOTE: This database will store the information in memory until is it
|
|
// committed, using the function Commit. This function is called outside of
|
|
// the ApplyTransaction function, therefore in Ethermint we need to make
|
|
// sure this commit is invoked somewhere after each block or whatever the
|
|
// appropriate time for it.
|
|
codeDB dbm.DB
|
|
ethTrieDB *ethtrie.Database
|
|
|
|
// codeSizeCache contains an LRU cache of a specified capacity to cache
|
|
// EXTCODESIZE calls.
|
|
codeSizeCache *lru.Cache
|
|
|
|
Tracing bool
|
|
}
|
|
|
|
// NewDatabase returns a reference to an initialized Database type which
|
|
// implements Ethereum's state.Database interface. An error is returned if the
|
|
// latest state failed to load. The underlying storage structure is defined by
|
|
// the Cosmos SDK IAVL tree.
|
|
func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) {
|
|
// Initialize an implementation of Ethereum state.Database and create a
|
|
// Cosmos SDK multi-store.
|
|
db := &Database{
|
|
stateStore: store.NewCommitMultiStore(stateDB),
|
|
}
|
|
|
|
// currently do not prune any historical state
|
|
db.stateStore.SetPruning(sdk.PruneNothing)
|
|
|
|
// Create the underlying multi-store stores that will persist account and
|
|
// account storage data.
|
|
db.stateStore.MountStoreWithDB(AccountsKey, types.StoreTypeIAVL, nil)
|
|
db.stateStore.MountStoreWithDB(StorageKey, types.StoreTypeIAVL, nil)
|
|
|
|
// Load the latest account state from the Cosmos SDK multi-store.
|
|
if err := db.stateStore.LoadLatestVersion(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set the persistent Cosmos SDK Database and initialize an Ethereum
|
|
// trie.Database using an EthereumDB as the underlying implementation of
|
|
// the ethdb.Database interface. It will be used to facilitate persistence
|
|
// of contract byte code when committing state.
|
|
db.codeDB = codeDB
|
|
db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB})
|
|
|
|
db.codeSizeCache, _ = lru.New(codeSizeCacheSize)
|
|
|
|
return db, nil
|
|
}
|
|
|
|
// OpenTrie implements Ethereum's state.Database interface. It returns a Trie
|
|
// type which implements the Ethereum state.Trie interface. It us used for
|
|
// storage of accounts. An error is returned if state cannot load for a
|
|
// given version. The account cache is reset if the state is successfully
|
|
// loaded and the version is not the latest.
|
|
//
|
|
// CONTRACT: The root parameter is not interpreted as a state root hash, but as
|
|
// an encoding of an Cosmos SDK IAVL tree version.
|
|
func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) {
|
|
version := db.stateStore.LastCommitID().Version
|
|
|
|
if !isRootEmpty(root) {
|
|
version = versionFromRootHash(root)
|
|
|
|
if db.stateStore.LastCommitID().Version != version {
|
|
if err := db.stateStore.LoadVersion(version); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db.accountsCache = nil
|
|
}
|
|
}
|
|
|
|
if db.accountsCache == nil {
|
|
db.accountsCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(AccountsKey))
|
|
db.storageCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(StorageKey))
|
|
}
|
|
|
|
return &Trie{
|
|
store: db.accountsCache,
|
|
accountsCache: db.accountsCache,
|
|
storageCache: db.storageCache,
|
|
ethTrieDB: db.ethTrieDB,
|
|
empty: isRootEmpty(root),
|
|
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
|
|
}, nil
|
|
}
|
|
|
|
// OpenStorageTrie implements Ethereum's state.Database interface. It returns
|
|
// a Trie type which implements the Ethereum state.Trie interface. It is used
|
|
// for storage of contract storage (state). Also, this trie is never committed
|
|
// separately as all the data is in a single multi-store and is committed when
|
|
// the account IAVL tree is committed.
|
|
//
|
|
// NOTE: It is assumed that the account state has already been loaded via
|
|
// OpenTrie.
|
|
//
|
|
// CONTRACT: The root parameter is not interpreted as a state root hash, but as
|
|
// an encoding of an IAVL tree version.
|
|
func (db *Database) OpenStorageTrie(addrHash, root ethcommon.Hash) (ethstate.Trie, error) {
|
|
// a contract storage trie does not need an accountCache, storageCache or
|
|
// an Ethereum trie because it will not be used upon commitment.
|
|
return &Trie{
|
|
store: db.storageCache,
|
|
prefix: addrHash.Bytes(),
|
|
empty: isRootEmpty(root),
|
|
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
|
|
}, nil
|
|
}
|
|
|
|
// CopyTrie implements Ethereum's state.Database interface. For now, it
|
|
// performs a no-op as the underlying Cosmos SDK IAVL tree does not support
|
|
// such an operation.
|
|
//
|
|
// TODO: Does the IAVL tree need to support this operation? If so, why and
|
|
// how?
|
|
func (db *Database) CopyTrie(ethstate.Trie) ethstate.Trie {
|
|
return nil
|
|
}
|
|
|
|
// ContractCode implements Ethereum's state.Database interface. It will return
|
|
// the contract byte code for a given code hash. It will not return an error.
|
|
func (db *Database) ContractCode(addrHash, codeHash ethcommon.Hash) ([]byte, error) {
|
|
code := db.codeDB.Get(codeHash[:])
|
|
|
|
db.codeSizeCache.Add(codeHash, len(code))
|
|
return code, nil
|
|
}
|
|
|
|
// ContractCodeSize implements Ethereum's state.Database interface. It will
|
|
// return the contract byte code size for a given code hash. It will not return
|
|
// an error.
|
|
func (db *Database) ContractCodeSize(addrHash, codeHash ethcommon.Hash) (int, error) {
|
|
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
|
|
return cached.(int), nil
|
|
}
|
|
|
|
code, err := db.ContractCode(addrHash, codeHash)
|
|
return len(code), err
|
|
}
|
|
|
|
// Commit commits the underlying Cosmos SDK multi-store returning the commit
|
|
// ID.
|
|
func (db *Database) Commit() types.CommitID {
|
|
return db.stateStore.Commit()
|
|
}
|
|
|
|
// TrieDB implements Ethereum's state.Database interface. It returns Ethereum's
|
|
// trie.Database low level trie database used for contract state storage. In
|
|
// the context of Ethermint, it'll be used to solely store mappings of
|
|
// codeHash => code.
|
|
func (db *Database) TrieDB() *ethtrie.Database {
|
|
return db.ethTrieDB
|
|
}
|
|
|
|
// isRootEmpty returns true if a given root hash is empty or false otherwise.
|
|
func isRootEmpty(root ethcommon.Hash) bool {
|
|
return root == ethcommon.Hash{}
|
|
}
|