diff --git a/state/database.go b/state/database.go index 5e13ccc1..bd350ad9 100644 --- a/state/database.go +++ b/state/database.go @@ -59,6 +59,8 @@ type Database struct { // EXTCODESIZE calls. codeSizeCache *lru.Cache + storeCache *lru.Cache + Tracing bool } @@ -66,7 +68,7 @@ type Database struct { // 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) { +func NewDatabase(stateDB, codeDB dbm.DB, storeCacheSize int) (*Database, error) { // Initialize an implementation of Ethereum state.Database and create a // Cosmos SDK multi-store. db := &Database{ @@ -93,7 +95,15 @@ func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) { db.codeDB = codeDB db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB}) - db.codeSizeCache, _ = lru.New(codeSizeCacheSize) + var err error + + if db.codeSizeCache, err = lru.New(codeSizeCacheSize); err != nil { + return nil, err + } + + if db.storeCache, err = lru.New(storeCacheSize); err != nil { + return nil, err + } return db, nil } @@ -133,6 +143,7 @@ func (db *Database) OpenTrie(root ethcmn.Hash) (ethstate.Trie, error) { store: db.accountsCache, accountsCache: db.accountsCache, storageCache: db.storageCache, + storeCache: db.storeCache, ethTrieDB: db.ethTrieDB, empty: isRootEmpty(root), root: rootHashFromVersion(db.stateStore.LastCommitID().Version), @@ -154,10 +165,11 @@ func (db *Database) OpenStorageTrie(addrHash, root ethcmn.Hash) (ethstate.Trie, // 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), + store: db.storageCache, + storeCache: db.storeCache, + prefix: addrHash.Bytes(), + empty: isRootEmpty(root), + root: rootHashFromVersion(db.stateStore.LastCommitID().Version), }, nil } diff --git a/state/database_test.go b/state/database_test.go index 906f457e..b300d33d 100644 --- a/state/database_test.go +++ b/state/database_test.go @@ -7,25 +7,17 @@ import ( ethcmn "github.com/ethereum/go-ethereum/common" ethstate "github.com/ethereum/go-ethereum/core/state" "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tendermint/libs/db" ) -func newDatabase() *Database { - memDB := dbm.NewMemDB() - db, _ := NewDatabase(memDB, memDB) - - return db -} - func TestDatabaseInterface(t *testing.T) { require.Implements(t, (*ethstate.Database)(nil), new(Database)) } func TestDatabaseLatestVersion(t *testing.T) { - testDB := newDatabase() - var version int64 + testDB := newTestDatabase() + version = testDB.LatestVersion() require.Equal(t, int64(0), version) @@ -34,27 +26,13 @@ func TestDatabaseLatestVersion(t *testing.T) { require.Equal(t, int64(1), version) } -// func TestDatabaseOpenTrie(t *testing.T) { -// testDB := newDatabase() - -// testTrie, err := testDB.OpenTrie(rootHashFromVersion(0)) -// require.Nil(t, err) -// require.IsType(t, &Trie{}, testTrie) -// require.NotNil(t, testTrie.(*Trie).store) -// require.NotNil(t, testTrie.(*Trie).accountsCache) -// require.NotNil(t, testTrie.(*Trie).storageCache) -// require.NotNil(t, testTrie.(*Trie).ethTrieDB) -// require.False(t, testTrie.(*Trie).empty) - -// } - func TestDatabaseCopyTrie(t *testing.T) { // TODO: Implement once CopyTrie is implemented t.SkipNow() } func TestDatabaseContractCode(t *testing.T) { - testDB := newDatabase() + testDB := newTestDatabase() testCases := []struct { db *Database @@ -87,7 +65,7 @@ func TestDatabaseContractCode(t *testing.T) { } func TestDatabaseContractCodeSize(t *testing.T) { - testDB := newDatabase() + testDB := newTestDatabase() testCases := []struct { db *Database @@ -125,7 +103,7 @@ func TestDatabaseContractCodeSize(t *testing.T) { } func TestDatabaseTrieDB(t *testing.T) { - testDB := newDatabase() + testDB := newTestDatabase() db := testDB.TrieDB() require.Equal(t, testDB.ethTrieDB, db) diff --git a/state/test_utils.go b/state/test_utils.go index 937d7caa..66b52fdc 100644 --- a/state/test_utils.go +++ b/state/test_utils.go @@ -1,10 +1,13 @@ package state import ( + "fmt" "math/rand" "time" ethcmn "github.com/ethereum/go-ethereum/common" + + dbm "github.com/tendermint/tendermint/libs/db" ) type ( @@ -21,3 +24,14 @@ type ( func init() { rand.Seed(time.Now().UnixNano()) } + +func newTestDatabase() *Database { + memDB := dbm.NewMemDB() + + testDB, err := NewDatabase(memDB, memDB, 100) + if err != nil { + panic(fmt.Sprintf("failed to create database: %v", err)) + } + + return testDB +} diff --git a/state/trie.go b/state/trie.go index a29892ba..7d78042f 100644 --- a/state/trie.go +++ b/state/trie.go @@ -8,6 +8,8 @@ import ( ethcmn "github.com/ethereum/go-ethereum/common" ethdb "github.com/ethereum/go-ethereum/ethdb" ethtrie "github.com/ethereum/go-ethereum/trie" + + lru "github.com/hashicorp/golang-lru" ) const ( @@ -25,6 +27,8 @@ type Trie struct { // ordering. storageCache store.CacheKVStore + storeCache *lru.Cache + // Store is an IAVL KV store that is part of a larger store except it used // for a specific prefix. It will either be an accountsCache or a // storageCache. @@ -64,8 +68,13 @@ func (t *Trie) TryGet(key []byte) ([]byte, error) { if t.IsStorageTrie() { key = t.prefixKey(key) } - - return t.store.Get(key), nil + keyStr := string(key) + if cached, ok := t.storeCache.Get(keyStr); ok { + return cached.([]byte), nil + } + value := t.store.Get(key) + t.storeCache.Add(keyStr, value) + return value, nil } // TryUpdate implements the Ethereum state.Trie interface. It associates a @@ -83,6 +92,7 @@ func (t *Trie) TryUpdate(key, value []byte) error { } t.store.Set(key, value) + t.storeCache.Add(string(key), value) return nil } @@ -98,6 +108,7 @@ func (t *Trie) TryDelete(key []byte) error { } t.store.Delete(key) + t.storeCache.Remove(string(key)) return nil } diff --git a/state/trie_test.go b/state/trie_test.go index 8de2628e..84a5c048 100644 --- a/state/trie_test.go +++ b/state/trie_test.go @@ -8,20 +8,17 @@ import ( ethcmn "github.com/ethereum/go-ethereum/common" ethstate "github.com/ethereum/go-ethereum/core/state" "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tendermint/libs/db" ) func newTestTrie() *Trie { - memDB := dbm.NewMemDB() - testDB, _ := NewDatabase(memDB, memDB) + testDB := newTestDatabase() testTrie, _ := testDB.OpenTrie(rootHashFromVersion(0)) return testTrie.(*Trie) } func newTestPrefixTrie() *Trie { - memDB := dbm.NewMemDB() - testDB, _ := NewDatabase(memDB, memDB) + testDB := newTestDatabase() prefix := make([]byte, ethcmn.HashLength) rand.Read(prefix) diff --git a/test/run.go b/test/run.go index 20a86fb3..e2b79a28 100644 --- a/test/run.go +++ b/test/run.go @@ -19,6 +19,7 @@ var ( cpuprofile = flag.String("cpu-profile", "", "write cpu profile `file`") blockchain = flag.String("blockchain", "data/blockchain", "file containing blocks to load") datadir = flag.String("datadir", path.Join(os.Getenv("HOME"), ".ethermint"), "directory for ethermint data") + cachesize = flag.Int("cachesize", 1024*1024, "number of key-value pairs for the state stored in memory") ) func main() { @@ -51,7 +52,7 @@ func main() { stateDB := dbm.NewDB("state", dbm.LevelDBBackend, *datadir) codeDB := dbm.NewDB("code", dbm.LevelDBBackend, *datadir) - ethermintDB, err := state.NewDatabase(stateDB, codeDB) + ethermintDB, err := state.NewDatabase(stateDB, codeDB, *cachesize) if err != nil { panic(fmt.Sprintf("failed to initialize geth Database: %v", err)) }