diff --git a/core/ethdb.go b/core/ethdb.go index 80a17982..b2f0a07a 100644 --- a/core/ethdb.go +++ b/core/ethdb.go @@ -8,39 +8,39 @@ import ( // EthereumDB implements Ethereum's ethdb.Database and ethdb.Batch interfaces. // It will be used to facilitate persistence of codeHash => code mappings. type EthereumDB struct { - codeDB dbm.DB + CodeDB dbm.DB } // Put implements Ethereum's ethdb.Putter interface. It wraps the database // write operation supported by both batches and regular databases. func (edb *EthereumDB) Put(key []byte, value []byte) error { - edb.codeDB.Set(key, value) + edb.CodeDB.Set(key, value) return nil } // Get implements Ethereum's ethdb.Database interface. It returns a value for a // given key. 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 // determining if the underlying database has the given key or not. 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 // key from the underlying database. func (edb *EthereumDB) Delete(key []byte) error { - edb.codeDB.Delete(key) + edb.CodeDB.Delete(key) return nil } // Close implements Ethereum's ethdb.Database interface. It closes the // underlying database. func (edb *EthereumDB) Close() { - edb.codeDB.Close() + edb.CodeDB.Close() } // NewBatch implements Ethereum's ethdb.Database interface. It returns a new diff --git a/state/database.go b/state/database.go index 57ab780f..e60c3127 100644 --- a/state/database.go +++ b/state/database.go @@ -1,8 +1,6 @@ package state import ( - "encoding/binary" - "github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" @@ -35,7 +33,7 @@ type Database struct { accountsCache 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 // 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 // of contract byte code when committing state. db.codeDB = codeDB - db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{codeDB: codeDB}) + db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB}) 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 // an encoding of an Cosmos SDK IAVL tree version. 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) { version = versionFromRootHash(root) @@ -99,27 +98,33 @@ func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) { return nil, err } - d.accountsCache = nil + loadedState = true } } - // reset the cache if the state was loaded for an older version ID - if db.accountsCache == nil { - d.accountsCache = store.NewCacheKVStore(d.stateStore.GetCommitKVStore(AccountsKey)) - d.storageCache = store.NewCacheKVStore(d.stateStore.GetCommitKVStore(StorageKey)) + // reset the cache if the state was loaded for an older version + if loadedState { + db.accountsCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(AccountsKey)) + db.storageCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(StorageKey)) } + // binary.BigEndian.PutUint64(commitHash[:8], uint64(t.od.stateStore.LastCommitID().Version+1)) + return &Trie{ - od: db, - st: od.accountsCache, - prefix: nil, - empty: isRootEmpty(root), + 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 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 // 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 // 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{ - od: d, - st: d.storageCache, + store: db.storageCache, prefix: addrHash.Bytes(), empty: isRootEmpty(root), + root: rootHashFromVersion(db.stateStore.LastCommitID().Version), }, nil } @@ -148,32 +155,25 @@ func (db *Database) CopyTrie(ethstate.Trie) ethstate.Trie { // 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) { - return d.codeDB.Get(codeHash[:]), nil + return db.codeDB.Get(codeHash[:]), 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) { - 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 -// trie.Database low level trie database used for data storage. In the context -// of Ethermint, it'll be used to solely store mappings of codeHash => code. +// 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 d.ethTrieDB + 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{} } - -// 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])) -} diff --git a/state/trie.go b/state/trie.go index 306523cd..86f10c1a 100644 --- a/state/trie.go +++ b/state/trie.go @@ -9,15 +9,24 @@ import ( ethtrie "github.com/ethereum/go-ethereum/trie" ) +const ( + versionLen = 8 +) + // Trie implements the Ethereum state.Trie interface. type Trie struct { - // // db is an implementation of Ethereum's state.Database. It will provide a - // // means to persist accounts and contract storage to a persistent - // // multi-store. - // db *Database + // accountsCache contains all the accounts in memory to persit when + // committing the trie. A CacheKVStore is used to provide deterministic + // ordering. + 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 - // for a specific prefix. + // for a specific prefix. It will either be an accountsCache or a + // storageCache. store store.KVStore // 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 ethcommon.Hash + + ethTrieDB *ethtrie.Database } // 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. func (t *Trie) TryDelete(key []byte) error { if t.prefix != nil { - key = t.makePrefix(key) + key = t.prefixKey(key) } t.store.Delete(key) 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) { if t.empty { return ethcommon.Hash{}, nil } - var root ethcommon.Hash - - // 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)) + newRoot := rootHashFromVersion(versionFromRootHash(t.root) + 1) if t.prefix == nil { - if t.od.accountsCache != nil { - t.od.accountsCache.Write() - t.od.accountsCache = nil + if t.accountsCache != nil { + t.accountsCache.Write() + t.accountsCache = nil } - if t.od.storageCache != nil { - t.od.storageCache.Write() - t.od.storageCache = nil + + if t.storageCache != nil { + t.storageCache.Write() + t.storageCache = nil } - // Enumerate cached nodes from trie.Database - for _, n := range t.od.trieDbDummy.Nodes() { - if err := t.od.trieDbDummy.Commit(n, false); err != nil { - return eth_common.Hash{}, err + + // persist the mappings of codeHash => code + for _, n := range t.ethTrieDB.Nodes() { + if err := t.ethTrieDB.Commit(n, false); err != nil { + return ethcommon.Hash{}, err } } } - t.root = root - return root, nil + t.root = newRoot + return newRoot, nil } // 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. // This will ultimately be related to if we want to support web3. 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 -// 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 { 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 // 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 { 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 +}