initial refactor of state and core modules

This commit is contained in:
Aleksandr Bezobchuk 2018-07-04 21:00:29 -04:00
parent fb9f6a7cdc
commit d01a73218a
3 changed files with 97 additions and 63 deletions

View File

@ -8,39 +8,39 @@ import (
// EthereumDB implements Ethereum's ethdb.Database and ethdb.Batch interfaces. // EthereumDB implements Ethereum's ethdb.Database and ethdb.Batch interfaces.
// It will be used to facilitate persistence of codeHash => code mappings. // It will be used to facilitate persistence of codeHash => code mappings.
type EthereumDB struct { type EthereumDB struct {
codeDB dbm.DB CodeDB dbm.DB
} }
// Put implements Ethereum's ethdb.Putter interface. It wraps the database // Put implements Ethereum's ethdb.Putter interface. It wraps the database
// write operation supported by both batches and regular databases. // write operation supported by both batches and regular databases.
func (edb *EthereumDB) Put(key []byte, value []byte) error { func (edb *EthereumDB) Put(key []byte, value []byte) error {
edb.codeDB.Set(key, value) edb.CodeDB.Set(key, value)
return nil return nil
} }
// Get implements Ethereum's ethdb.Database interface. It returns a value for a // Get implements Ethereum's ethdb.Database interface. It returns a value for a
// given key. // given key.
func (edb *EthereumDB) Get(key []byte) ([]byte, error) { func (edb *EthereumDB) Get(key []byte) ([]byte, error) {
return edb.codeDB.Get(key), nil return edb.CodeDB.Get(key), nil
} }
// Has implements Ethereum's ethdb.Database interface. It returns a boolean // Has implements Ethereum's ethdb.Database interface. It returns a boolean
// determining if the underlying database has the given key or not. // determining if the underlying database has the given key or not.
func (edb *EthereumDB) Has(key []byte) (bool, error) { func (edb *EthereumDB) Has(key []byte) (bool, error) {
return edb.codeDB.Has(key), nil return edb.CodeDB.Has(key), nil
} }
// Delete implements Ethereum's ethdb.Database interface. It removes a given // Delete implements Ethereum's ethdb.Database interface. It removes a given
// key from the underlying database. // key from the underlying database.
func (edb *EthereumDB) Delete(key []byte) error { func (edb *EthereumDB) Delete(key []byte) error {
edb.codeDB.Delete(key) edb.CodeDB.Delete(key)
return nil return nil
} }
// Close implements Ethereum's ethdb.Database interface. It closes the // Close implements Ethereum's ethdb.Database interface. It closes the
// underlying database. // underlying database.
func (edb *EthereumDB) Close() { func (edb *EthereumDB) Close() {
edb.codeDB.Close() edb.CodeDB.Close()
} }
// NewBatch implements Ethereum's ethdb.Database interface. It returns a new // NewBatch implements Ethereum's ethdb.Database interface. It returns a new

View File

@ -1,8 +1,6 @@
package state package state
import ( import (
"encoding/binary"
"github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
@ -35,7 +33,7 @@ type Database struct {
accountsCache store.CacheKVStore accountsCache store.CacheKVStore
storageCache store.CacheKVStore storageCache store.CacheKVStore
// codeDB is a mapping of codeHash => code // codeDB contains mappings of codeHash => code
// //
// NOTE: This database will store the information in memory until is it // NOTE: This database will store the information in memory until is it
// committed, using the function Commit. This function is called outside of // committed, using the function Commit. This function is called outside of
@ -75,7 +73,7 @@ func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) {
// the ethdb.Database interface. It will be used to facilitate persistence // the ethdb.Database interface. It will be used to facilitate persistence
// of contract byte code when committing state. // of contract byte code when committing state.
db.codeDB = codeDB db.codeDB = codeDB
db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{codeDB: codeDB}) db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB})
return db, nil return db, nil
} }
@ -89,7 +87,8 @@ func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) {
// CONTRACT: The root parameter is not interpreted as a state root hash, but as // CONTRACT: The root parameter is not interpreted as a state root hash, but as
// an encoding of an Cosmos SDK IAVL tree version. // an encoding of an Cosmos SDK IAVL tree version.
func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) { func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) {
version := d.stateStore.LastCommitID().Version var loadedState bool
version := db.stateStore.LastCommitID().Version
if !isRootEmpty(root) { if !isRootEmpty(root) {
version = versionFromRootHash(root) version = versionFromRootHash(root)
@ -99,27 +98,33 @@ func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) {
return nil, err return nil, err
} }
d.accountsCache = nil loadedState = true
} }
} }
// reset the cache if the state was loaded for an older version ID // reset the cache if the state was loaded for an older version
if db.accountsCache == nil { if loadedState {
d.accountsCache = store.NewCacheKVStore(d.stateStore.GetCommitKVStore(AccountsKey)) db.accountsCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(AccountsKey))
d.storageCache = store.NewCacheKVStore(d.stateStore.GetCommitKVStore(StorageKey)) db.storageCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(StorageKey))
} }
// binary.BigEndian.PutUint64(commitHash[:8], uint64(t.od.stateStore.LastCommitID().Version+1))
return &Trie{ return &Trie{
od: db, store: db.accountsCache,
st: od.accountsCache, accountsCache: db.accountsCache,
prefix: nil, storageCache: db.storageCache,
empty: isRootEmpty(root), ethTrieDB: db.ethTrieDB,
empty: isRootEmpty(root),
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
}, nil }, nil
} }
// OpenStorageTrie implements Ethereum's state.Database interface. It returns // OpenStorageTrie implements Ethereum's state.Database interface. It returns
// a Trie type which implements the Ethereum state.Trie interface. It is used // a Trie type which implements the Ethereum state.Trie interface. It is used
// for storage of accounts storage (state). // 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 // NOTE: It is assumed that the account state has already been loaded via
// OpenTrie. // OpenTrie.
@ -127,11 +132,13 @@ func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) {
// CONTRACT: The root parameter is not interpreted as a state root hash, but as // CONTRACT: The root parameter is not interpreted as a state root hash, but as
// an encoding of an IAVL tree version. // an encoding of an IAVL tree version.
func (db *Database) OpenStorageTrie(addrHash, root ethcommon.Hash) (ethstate.Trie, error) { 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{ return &Trie{
od: d, store: db.storageCache,
st: d.storageCache,
prefix: addrHash.Bytes(), prefix: addrHash.Bytes(),
empty: isRootEmpty(root), empty: isRootEmpty(root),
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
}, nil }, nil
} }
@ -148,32 +155,25 @@ func (db *Database) CopyTrie(ethstate.Trie) ethstate.Trie {
// ContractCode implements Ethereum's state.Database interface. It will return // 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. // 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) { func (db *Database) ContractCode(addrHash, codeHash ethcommon.Hash) ([]byte, error) {
return d.codeDB.Get(codeHash[:]), nil return db.codeDB.Get(codeHash[:]), nil
} }
// ContractCodeSize implements Ethereum's state.Database interface. It will // ContractCodeSize implements Ethereum's state.Database interface. It will
// return the contract byte code size for a given code hash. It will not return // return the contract byte code size for a given code hash. It will not return
// an error. // an error.
func (db *Database) ContractCodeSize(addrHash, codeHash ethcommon.Hash) (int, error) { func (db *Database) ContractCodeSize(addrHash, codeHash ethcommon.Hash) (int, error) {
return len(d.codeDB.Get(codeHash[:])), nil return len(db.codeDB.Get(codeHash[:])), nil
} }
// TrieDB implements Ethereum's state.Database interface. It returns Ethereum's // TrieDB implements Ethereum's state.Database interface. It returns Ethereum's
// trie.Database low level trie database used for data storage. In the context // trie.Database low level trie database used for contract state storage. In
// of Ethermint, it'll be used to solely store mappings of codeHash => code. // the context of Ethermint, it'll be used to solely store mappings of
// codeHash => code.
func (db *Database) TrieDB() *ethtrie.Database { func (db *Database) TrieDB() *ethtrie.Database {
return d.ethTrieDB return db.ethTrieDB
} }
// isRootEmpty returns true if a given root hash is empty or false otherwise. // isRootEmpty returns true if a given root hash is empty or false otherwise.
func isRootEmpty(root ethcommon.Hash) bool { func isRootEmpty(root ethcommon.Hash) bool {
return root == ethcommon.Hash{} return root == ethcommon.Hash{}
} }
// versionFromRootHash returns an Cosmos SDK IAVL version from an Ethereum
// state root hash.
//
// CONTRACT: The encoded version is the eight MSB bytes of the root hash.
func versionFromRootHash(root ethcommon.Hash) int64 {
return int64(binary.BigEndian.Uint64(root[:8]))
}

View File

@ -9,15 +9,24 @@ import (
ethtrie "github.com/ethereum/go-ethereum/trie" ethtrie "github.com/ethereum/go-ethereum/trie"
) )
const (
versionLen = 8
)
// Trie implements the Ethereum state.Trie interface. // Trie implements the Ethereum state.Trie interface.
type Trie struct { type Trie struct {
// // db is an implementation of Ethereum's state.Database. It will provide a // accountsCache contains all the accounts in memory to persit when
// // means to persist accounts and contract storage to a persistent // committing the trie. A CacheKVStore is used to provide deterministic
// // multi-store. // ordering.
// db *Database accountsCache store.CacheKVStore
// storageCache contains all the contract storage in memory to persit when
// committing the trie. A CacheKVStore is used to provide deterministic
// ordering.
storageCache store.CacheKVStore
// Store is an IAVL KV store that is part of a larger store except it used // Store is an IAVL KV store that is part of a larger store except it used
// for a specific prefix. // for a specific prefix. It will either be an accountsCache or a
// storageCache.
store store.KVStore store store.KVStore
// prefix is a static prefix used for persistence operations where the // prefix is a static prefix used for persistence operations where the
@ -30,6 +39,8 @@ type Trie struct {
// root is the encoding of an IAVL tree root (version) // root is the encoding of an IAVL tree root (version)
root ethcommon.Hash root ethcommon.Hash
ethTrieDB *ethtrie.Database
} }
// prefixKey returns a composite key composed of a static prefix and a given // prefixKey returns a composite key composed of a static prefix and a given
@ -82,45 +93,52 @@ func (t *Trie) TryUpdate(key, value []byte) error {
// will be sorted giving us a deterministic ordering. // will be sorted giving us a deterministic ordering.
func (t *Trie) TryDelete(key []byte) error { func (t *Trie) TryDelete(key []byte) error {
if t.prefix != nil { if t.prefix != nil {
key = t.makePrefix(key) key = t.prefixKey(key)
} }
t.store.Delete(key) t.store.Delete(key)
return nil return nil
} }
// Commit implements the Ethereum state.Trie interface. TODO: ... // Commit implements the Ethereum state.Trie interface. It persists transient
// state. State is held by a merkelized multi-store IAVL tree. Commitment will
// only occur through an account trie, in other words, when the prefix of the
// trie is nil. In such a case, if either the accountCache or the storageCache
// are not nil, they are persisted. In addition, all the mappings of
// codeHash => code are also persisted. All these operations are performed in a
// deterministic order. Transient state is built up in a CacheKVStore. Finally,
// a root hash is returned or an error if any operation fails.
// //
// CONTRACT: The root is an encoded IAVL tree version. // CONTRACT: The root is an encoded IAVL tree version and each new commitment
// increments the version by one.
func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcommon.Hash, error) { func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcommon.Hash, error) {
if t.empty { if t.empty {
return ethcommon.Hash{}, nil return ethcommon.Hash{}, nil
} }
var root ethcommon.Hash newRoot := rootHashFromVersion(versionFromRootHash(t.root) + 1)
// We assume that the next committed version will be the od.stateStore.LastCommitID().Version+1
binary.BigEndian.PutUint64(commitHash[:8], uint64(t.od.stateStore.LastCommitID().Version+1))
if t.prefix == nil { if t.prefix == nil {
if t.od.accountsCache != nil { if t.accountsCache != nil {
t.od.accountsCache.Write() t.accountsCache.Write()
t.od.accountsCache = nil t.accountsCache = nil
} }
if t.od.storageCache != nil {
t.od.storageCache.Write() if t.storageCache != nil {
t.od.storageCache = nil t.storageCache.Write()
t.storageCache = nil
} }
// Enumerate cached nodes from trie.Database
for _, n := range t.od.trieDbDummy.Nodes() { // persist the mappings of codeHash => code
if err := t.od.trieDbDummy.Commit(n, false); err != nil { for _, n := range t.ethTrieDB.Nodes() {
return eth_common.Hash{}, err if err := t.ethTrieDB.Commit(n, false); err != nil {
return ethcommon.Hash{}, err
} }
} }
} }
t.root = root t.root = newRoot
return root, nil return newRoot, nil
} }
// Hash implements the Ethereum state.Trie interface. It returns the state root // Hash implements the Ethereum state.Trie interface. It returns the state root
@ -138,11 +156,11 @@ func (t *Trie) Hash() ethcommon.Hash {
// TODO: Determine if we need to implement such functionality for an IAVL tree. // TODO: Determine if we need to implement such functionality for an IAVL tree.
// This will ultimately be related to if we want to support web3. // This will ultimately be related to if we want to support web3.
func (t *Trie) NodeIterator(startKey []byte) ethtrie.NodeIterator { func (t *Trie) NodeIterator(startKey []byte) ethtrie.NodeIterator {
return nil, fffsadf return nil
} }
// GetKey implements the Ethereum state.Trie interface. Since the IAVL does not // GetKey implements the Ethereum state.Trie interface. Since the IAVL does not
// need to store preimages of keys, a simply identity can be returned. // need to store preimages of keys, a simple identity can be returned.
func (t *Trie) GetKey(key []byte) []byte { func (t *Trie) GetKey(key []byte) []byte {
return key return key
} }
@ -150,7 +168,23 @@ func (t *Trie) GetKey(key []byte) []byte {
// Prove implements the Ethereum state.Trie interface. It writes a Merkle proof // Prove implements the Ethereum state.Trie interface. It writes a Merkle proof
// to a ethdb.Putter, proofDB, for a given key starting at fromLevel. // to a ethdb.Putter, proofDB, for a given key starting at fromLevel.
// //
// TODO: Determine how to use the Cosmos SDK to provide such proof. // TODO: Determine how to integrate this with Cosmos SDK to provide such
// proofs.
func (t *Trie) Prove(key []byte, fromLevel uint, proofDB ethdb.Putter) error { func (t *Trie) Prove(key []byte, fromLevel uint, proofDB ethdb.Putter) error {
return nil return nil
} }
// versionFromRootHash returns a Cosmos SDK IAVL version from an Ethereum state
// root hash.
//
// CONTRACT: The encoded version is the eight MSB bytes of the root hash.
func versionFromRootHash(root ethcommon.Hash) int64 {
return int64(binary.BigEndian.Uint64(root[:versionLen]))
}
// rootHashFromVersion returns a state root hash from a Cosmos SDK IAVL
// version.
func rootHashFromVersion(version int64) (root ethcommon.Hash) {
binary.BigEndian.PutUint64(root[:versionLen], uint64(version))
return
}