From 674595d6c9b0774d6e27daa233d329b6b2a2a68e Mon Sep 17 00:00:00 2001 From: Alexey Akhunov Date: Thu, 9 Aug 2018 20:49:40 +0100 Subject: [PATCH 1/3] #461 Add Caching to Store Database --- state/database.go | 15 ++++++++++----- state/trie.go | 15 +++++++++++++-- test/run.go | 3 ++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/state/database.go b/state/database.go index 5e13ccc1..c41c4ae7 100644 --- a/state/database.go +++ b/state/database.go @@ -59,6 +59,8 @@ type Database struct { // EXTCODESIZE calls. codeSizeCache *lru.Cache + dataCache *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, dataCacheSize int) (*Database, error) { // Initialize an implementation of Ethereum state.Database and create a // Cosmos SDK multi-store. db := &Database{ @@ -94,6 +96,7 @@ func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) { db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB}) db.codeSizeCache, _ = lru.New(codeSizeCacheSize) + db.dataCache, _ = lru.New(dataCacheSize) return db, nil } @@ -133,6 +136,7 @@ func (db *Database) OpenTrie(root ethcmn.Hash) (ethstate.Trie, error) { store: db.accountsCache, accountsCache: db.accountsCache, storageCache: db.storageCache, + dataCache: db.dataCache, ethTrieDB: db.ethTrieDB, empty: isRootEmpty(root), root: rootHashFromVersion(db.stateStore.LastCommitID().Version), @@ -154,10 +158,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, + dataCache: db.dataCache, + prefix: addrHash.Bytes(), + empty: isRootEmpty(root), + root: rootHashFromVersion(db.stateStore.LastCommitID().Version), }, nil } diff --git a/state/trie.go b/state/trie.go index a29892ba..46017c89 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 + dataCache *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.dataCache.Get(keyStr); ok { + return cached.([]byte), nil + } + value := t.store.Get(key) + t.dataCache.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.dataCache.Add(string(key), value) return nil } @@ -98,6 +108,7 @@ func (t *Trie) TryDelete(key []byte) error { } t.store.Delete(key) + t.dataCache.Remove(string(key)) return nil } 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)) } From 80cfa77826e17cb933d805d0740bffbd3358b966 Mon Sep 17 00:00:00 2001 From: Alexey Akhunov Date: Fri, 10 Aug 2018 14:22:59 +0100 Subject: [PATCH 2/3] Addressed comments --- state/database.go | 25 +++++++++++++++---------- state/trie.go | 10 +++++----- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/state/database.go b/state/database.go index c41c4ae7..92e2c1df 100644 --- a/state/database.go +++ b/state/database.go @@ -59,7 +59,7 @@ type Database struct { // EXTCODESIZE calls. codeSizeCache *lru.Cache - dataCache *lru.Cache + storeCache *lru.Cache Tracing bool } @@ -68,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, dataCacheSize int) (*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{ @@ -95,8 +95,13 @@ func NewDatabase(stateDB, codeDB dbm.DB, dataCacheSize int) (*Database, error) { db.codeDB = codeDB db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB}) - db.codeSizeCache, _ = lru.New(codeSizeCacheSize) - db.dataCache, _ = lru.New(dataCacheSize) + 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 } @@ -136,7 +141,7 @@ func (db *Database) OpenTrie(root ethcmn.Hash) (ethstate.Trie, error) { store: db.accountsCache, accountsCache: db.accountsCache, storageCache: db.storageCache, - dataCache: db.dataCache, + storeCache: db.storeCache, ethTrieDB: db.ethTrieDB, empty: isRootEmpty(root), root: rootHashFromVersion(db.stateStore.LastCommitID().Version), @@ -158,11 +163,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, - dataCache: db.dataCache, - 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/trie.go b/state/trie.go index 46017c89..7d78042f 100644 --- a/state/trie.go +++ b/state/trie.go @@ -27,7 +27,7 @@ type Trie struct { // ordering. storageCache store.CacheKVStore - dataCache *lru.Cache + 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 @@ -69,11 +69,11 @@ func (t *Trie) TryGet(key []byte) ([]byte, error) { key = t.prefixKey(key) } keyStr := string(key) - if cached, ok := t.dataCache.Get(keyStr); ok { + if cached, ok := t.storeCache.Get(keyStr); ok { return cached.([]byte), nil } value := t.store.Get(key) - t.dataCache.Add(keyStr, value) + t.storeCache.Add(keyStr, value) return value, nil } @@ -92,7 +92,7 @@ func (t *Trie) TryUpdate(key, value []byte) error { } t.store.Set(key, value) - t.dataCache.Add(string(key), value) + t.storeCache.Add(string(key), value) return nil } @@ -108,7 +108,7 @@ func (t *Trie) TryDelete(key []byte) error { } t.store.Delete(key) - t.dataCache.Remove(string(key)) + t.storeCache.Remove(string(key)) return nil } From cb8d84783e68f3b297a6d7ad7945df395ace1b4f Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 15 Aug 2018 18:27:30 -0400 Subject: [PATCH 3/3] Fix broken state unit tests --- state/database.go | 4 +++- state/database_test.go | 32 +++++--------------------------- state/test_utils.go | 14 ++++++++++++++ state/trie_test.go | 7 ++----- 4 files changed, 24 insertions(+), 33 deletions(-) diff --git a/state/database.go b/state/database.go index 92e2c1df..bd350ad9 100644 --- a/state/database.go +++ b/state/database.go @@ -59,7 +59,7 @@ type Database struct { // EXTCODESIZE calls. codeSizeCache *lru.Cache - storeCache *lru.Cache + storeCache *lru.Cache Tracing bool } @@ -96,9 +96,11 @@ func NewDatabase(stateDB, codeDB dbm.DB, storeCacheSize int) (*Database, error) db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB}) 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 } 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_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)