From 39509c76edbabc3498b50de44bd26f3e5d7c0431 Mon Sep 17 00:00:00 2001 From: mossid Date: Fri, 29 Jun 2018 16:32:25 -0700 Subject: [PATCH] Merge pull request #1481: Transient Stores move gasconfig to types, make GetKVStoreWithGas take GasConfig fix lint modify transientstore in progress add test for transientstore fix errors fix test fix errors and lint last fix in progress move transient to KVStore fix syntax errors finalize rebase remove NewMemDBStoreAdapter for lint --- server/mock/store.go | 16 +++++-- store/cachekvstore.go | 90 +++++++++++++++++++++++++----------- store/cachemultistore.go | 9 +++- store/dbstoreadapter.go | 14 +++++- store/gaskvstore.go | 73 ++++++++++++++++------------- store/gaskvstore_test.go | 14 +++--- store/iavlstore.go | 23 +++++++++ store/iavlstore_test.go | 1 + store/prefixstore.go | 32 ++++++++++--- store/prefixstore_test.go | 2 +- store/rootmultistore.go | 5 +- store/rootmultistore_test.go | 62 ++++++++++++------------- store/transientstore.go | 17 +++++++ store/transientstore_test.go | 55 ++++++++++++++++++++++ store/types.go | 3 ++ types/context.go | 7 ++- types/gas.go | 47 +++++++++++++++++++ types/getter.go | 41 ++++++++++++++++ types/store.go | 34 ++++++-------- 19 files changed, 410 insertions(+), 135 deletions(-) create mode 100644 store/transientstore.go create mode 100644 store/transientstore_test.go create mode 100644 types/getter.go diff --git a/server/mock/store.go b/server/mock/store.go index d62b9ad23edb..7d58c36e0b7e 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -6,6 +6,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var _ sdk.MultiStore = multiStore{} + type multiStore struct { kv map[sdk.StoreKey]kvStore } @@ -50,10 +52,6 @@ func (ms multiStore) GetKVStore(key sdk.StoreKey) sdk.KVStore { return ms.kv[key] } -func (ms multiStore) GetKVStoreWithGas(meter sdk.GasMeter, key sdk.StoreKey) sdk.KVStore { - panic("not implemented") -} - func (ms multiStore) GetStore(key sdk.StoreKey) sdk.Store { panic("not implemented") } @@ -62,6 +60,8 @@ func (ms multiStore) GetStoreType() sdk.StoreType { panic("not implemented") } +var _ sdk.KVStore = kvStore{} + type kvStore struct { store map[string][]byte } @@ -99,6 +99,14 @@ func (kv kvStore) Prefix(prefix []byte) sdk.KVStore { panic("not implemented") } +func (kv kvStore) Gas(meter sdk.GasMeter, config sdk.GasConfig) sdk.KVStore { + panic("not implmeneted") +} + +func (kv kvStore) Transient() sdk.KVStore { + panic("not implemented") +} + func (kv kvStore) Iterator(start, end []byte) sdk.Iterator { panic("not implemented") } diff --git a/store/cachekvstore.go b/store/cachekvstore.go index 1263cef5f366..47a9643f6e29 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -8,6 +8,41 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" ) +var _ CacheKVStore = (*cacheKVStore)(nil) + +type cacheKVStore struct { + *cache + transient *cache +} + +// nolint +func NewCacheKVStore(parent KVStore) CacheKVStore { + ci := &cache{ + cache: make(map[string]cValue), + parent: parent, + } + return &cacheKVStore{ci, nil} +} + +func (ci *cacheKVStore) Transient() KVStore { + if ci.transient == nil { + ci.transient = &cache{ + cache: make(map[string]cValue), + parent: ci.parent.Transient(), + } + } + return ci.transient +} + +func (ci *cacheKVStore) Write() { + ci.cache.Write() + if ci.transient != nil { + ci.transient.Write() + } +} + +var _ CacheKVStore = (*cache)(nil) + // If value is nil but deleted is false, it means the parent doesn't have the // key. (No need to delete upon Write()) type cValue struct { @@ -16,31 +51,20 @@ type cValue struct { dirty bool } -// cacheKVStore wraps an in-memory cache around an underlying KVStore. -type cacheKVStore struct { +// cache wraps an in-memory cache around an underlying KVStore. +type cache struct { mtx sync.Mutex cache map[string]cValue parent KVStore } -var _ CacheKVStore = (*cacheKVStore)(nil) - -// nolint -func NewCacheKVStore(parent KVStore) *cacheKVStore { - ci := &cacheKVStore{ - cache: make(map[string]cValue), - parent: parent, - } - return ci -} - // Implements Store. -func (ci *cacheKVStore) GetStoreType() StoreType { +func (ci *cache) GetStoreType() StoreType { return ci.parent.GetStoreType() } // Implements KVStore. -func (ci *cacheKVStore) Get(key []byte) (value []byte) { +func (ci *cache) Get(key []byte) (value []byte) { ci.mtx.Lock() defer ci.mtx.Unlock() ci.assertValidKey(key) @@ -57,7 +81,7 @@ func (ci *cacheKVStore) Get(key []byte) (value []byte) { } // Implements KVStore. -func (ci *cacheKVStore) Set(key []byte, value []byte) { +func (ci *cache) Set(key []byte, value []byte) { ci.mtx.Lock() defer ci.mtx.Unlock() ci.assertValidKey(key) @@ -66,13 +90,13 @@ func (ci *cacheKVStore) Set(key []byte, value []byte) { } // Implements KVStore. -func (ci *cacheKVStore) Has(key []byte) bool { +func (ci *cache) Has(key []byte) bool { value := ci.Get(key) return value != nil } // Implements KVStore. -func (ci *cacheKVStore) Delete(key []byte) { +func (ci *cache) Delete(key []byte) { ci.mtx.Lock() defer ci.mtx.Unlock() ci.assertValidKey(key) @@ -81,12 +105,22 @@ func (ci *cacheKVStore) Delete(key []byte) { } // Implements KVStore -func (ci *cacheKVStore) Prefix(prefix []byte) KVStore { +func (ci *cache) Prefix(prefix []byte) KVStore { return prefixStore{ci, prefix} } +// Implements KVStore +func (ci *cache) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, ci) +} + +// Implements KVStore +func (ci *cache) Transient() KVStore { + panic("Transient() called on cache") +} + // Implements CacheKVStore. -func (ci *cacheKVStore) Write() { +func (ci *cache) Write() { ci.mtx.Lock() defer ci.mtx.Unlock() @@ -118,10 +152,10 @@ func (ci *cacheKVStore) Write() { } //---------------------------------------- -// To cache-wrap this cacheKVStore further. +// To cache-wrap this cache further. // Implements CacheWrapper. -func (ci *cacheKVStore) CacheWrap() CacheWrap { +func (ci *cache) CacheWrap() CacheWrap { return NewCacheKVStore(ci) } @@ -129,16 +163,16 @@ func (ci *cacheKVStore) CacheWrap() CacheWrap { // Iteration // Implements KVStore. -func (ci *cacheKVStore) Iterator(start, end []byte) Iterator { +func (ci *cache) Iterator(start, end []byte) Iterator { return ci.iterator(start, end, true) } // Implements KVStore. -func (ci *cacheKVStore) ReverseIterator(start, end []byte) Iterator { +func (ci *cache) ReverseIterator(start, end []byte) Iterator { return ci.iterator(start, end, false) } -func (ci *cacheKVStore) iterator(start, end []byte, ascending bool) Iterator { +func (ci *cache) iterator(start, end []byte, ascending bool) Iterator { var parent, cache Iterator if ascending { parent = ci.parent.Iterator(start, end) @@ -151,7 +185,7 @@ func (ci *cacheKVStore) iterator(start, end []byte, ascending bool) Iterator { } // Constructs a slice of dirty items, to use w/ memIterator. -func (ci *cacheKVStore) dirtyItems(ascending bool) []cmn.KVPair { +func (ci *cache) dirtyItems(ascending bool) []cmn.KVPair { items := make([]cmn.KVPair, 0, len(ci.cache)) for key, cacheValue := range ci.cache { if !cacheValue.dirty { @@ -172,14 +206,14 @@ func (ci *cacheKVStore) dirtyItems(ascending bool) []cmn.KVPair { //---------------------------------------- // etc -func (ci *cacheKVStore) assertValidKey(key []byte) { +func (ci *cache) assertValidKey(key []byte) { if key == nil { panic("key is nil") } } // Only entrypoint to mutate ci.cache. -func (ci *cacheKVStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { +func (ci *cache) setCacheValue(key, value []byte, deleted bool, dirty bool) { cacheValue := cValue{ value: value, deleted: deleted, diff --git a/store/cachemultistore.go b/store/cachemultistore.go index 47878fb15511..bae89243abe3 100644 --- a/store/cachemultistore.go +++ b/store/cachemultistore.go @@ -73,7 +73,12 @@ func (cms cacheMultiStore) GetKVStore(key StoreKey) KVStore { return cms.stores[key].(KVStore) } +// Implements Multistore. +func (cms cacheMultiStore) GetTransientStore(key StoreKey) KVStore { + return cms.stores[key].(KVStore) +} + // Implements MultiStore. -func (cms cacheMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, key StoreKey) KVStore { - return NewGasKVStore(meter, cms.GetKVStore(key)) +func (cms cacheMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, config sdk.GasConfig, key StoreKey) KVStore { + return NewGasKVStore(meter, config, cms.GetKVStore(key)) } diff --git a/store/dbstoreadapter.go b/store/dbstoreadapter.go index 09d48cf9d91b..98bac0c69111 100644 --- a/store/dbstoreadapter.go +++ b/store/dbstoreadapter.go @@ -5,6 +5,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" ) +// Wrapper type for dbm.Db with implementation of KVStore type dbStoreAdapter struct { dbm.DB } @@ -24,5 +25,16 @@ func (dsa dbStoreAdapter) Prefix(prefix []byte) KVStore { return prefixStore{dsa, prefix} } +// Implements KVStore +func (dsa dbStoreAdapter) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, dsa) +} + +// Implements KVStore +// TODO: Transient() must be defined for CommitKVStores +func (dsa dbStoreAdapter) Transient() KVStore { + panic("Transient() on dbStoreAdapter") +} + // dbm.DB implements KVStore so we can CacheKVStore it. -var _ KVStore = dbStoreAdapter{dbm.DB(nil)} +var _ KVStore = dbStoreAdapter{} diff --git a/store/gaskvstore.go b/store/gaskvstore.go index 6dc699dfb37d..5d42ea0cd97e 100644 --- a/store/gaskvstore.go +++ b/store/gaskvstore.go @@ -4,29 +4,21 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// nolint -const ( - HasCost = 10 - ReadCostFlat = 10 - ReadCostPerByte = 1 - WriteCostFlat = 10 - WriteCostPerByte = 10 - KeyCostFlat = 5 - ValueCostFlat = 10 - ValueCostPerByte = 1 -) +var _ KVStore = &gasKVStore{} // gasKVStore applies gas tracking to an underlying kvstore type gasKVStore struct { - gasMeter sdk.GasMeter - parent sdk.KVStore + gasMeter sdk.GasMeter + gasConfig sdk.GasConfig + parent sdk.KVStore } // nolint -func NewGasKVStore(gasMeter sdk.GasMeter, parent sdk.KVStore) *gasKVStore { +func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KVStore) *gasKVStore { kvs := &gasKVStore{ - gasMeter: gasMeter, - parent: parent, + gasMeter: gasMeter, + gasConfig: gasConfig, + parent: parent, } return kvs } @@ -38,24 +30,25 @@ func (gi *gasKVStore) GetStoreType() sdk.StoreType { // Implements KVStore. func (gi *gasKVStore) Get(key []byte) (value []byte) { - gi.gasMeter.ConsumeGas(ReadCostFlat, "GetFlat") + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostFlat, "ReadFlat") value = gi.parent.Get(key) // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte") + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte") + return value } // Implements KVStore. func (gi *gasKVStore) Set(key []byte, value []byte) { - gi.gasMeter.ConsumeGas(WriteCostFlat, "SetFlat") + gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostFlat, "WriteFlat") // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(WriteCostPerByte*sdk.Gas(len(value)), "SetPerByte") + gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), "WritePerByte") gi.parent.Set(key, value) } // Implements KVStore. func (gi *gasKVStore) Has(key []byte) bool { - gi.gasMeter.ConsumeGas(HasCost, "Has") + gi.gasMeter.ConsumeGas(gi.gasConfig.HasCost, "Has") return gi.parent.Has(key) } @@ -67,7 +60,23 @@ func (gi *gasKVStore) Delete(key []byte) { // Implements KVStore func (gi *gasKVStore) Prefix(prefix []byte) KVStore { - return prefixStore{gi, prefix} + // Keep gasstore layer at the top + return &gasKVStore{ + gasMeter: gi.gasMeter, + gasConfig: gi.gasConfig, + parent: prefixStore{gi.parent, prefix}, + } +} + +// Implements KVStore +func (gi *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { + // Override existing meter/config + return NewGasKVStore(meter, config, gi.parent) +} + +// Implements KVStore +func (gi *gasKVStore) Transient() KVStore { + return NewGasKVStore(gi.gasMeter, gi.gasConfig, gi.parent.Transient()) } // Implements KVStore. @@ -92,18 +101,20 @@ func (gi *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { } else { parent = gi.parent.ReverseIterator(start, end) } - return newGasIterator(gi.gasMeter, parent) + return newGasIterator(gi.gasMeter, gi.gasConfig, parent) } type gasIterator struct { - gasMeter sdk.GasMeter - parent sdk.Iterator + gasMeter sdk.GasMeter + gasConfig sdk.GasConfig + parent sdk.Iterator } -func newGasIterator(gasMeter sdk.GasMeter, parent sdk.Iterator) sdk.Iterator { +func newGasIterator(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.Iterator) sdk.Iterator { return &gasIterator{ - gasMeter: gasMeter, - parent: parent, + gasMeter: gasMeter, + gasConfig: gasConfig, + parent: parent, } } @@ -124,7 +135,7 @@ func (g *gasIterator) Next() { // Implements Iterator. func (g *gasIterator) Key() (key []byte) { - g.gasMeter.ConsumeGas(KeyCostFlat, "KeyFlat") + g.gasMeter.ConsumeGas(g.gasConfig.KeyCostFlat, "KeyFlat") key = g.parent.Key() return key } @@ -132,8 +143,8 @@ func (g *gasIterator) Key() (key []byte) { // Implements Iterator. func (g *gasIterator) Value() (value []byte) { value = g.parent.Value() - g.gasMeter.ConsumeGas(ValueCostFlat, "ValueFlat") - g.gasMeter.ConsumeGas(ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte") + g.gasMeter.ConsumeGas(g.gasConfig.ValueCostFlat, "ValueFlat") + g.gasMeter.ConsumeGas(g.gasConfig.ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte") return value } diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go index fe84affa2889..4583176b497b 100644 --- a/store/gaskvstore_test.go +++ b/store/gaskvstore_test.go @@ -3,21 +3,23 @@ package store import ( "testing" + dbm "github.com/tendermint/tendermint/libs/db" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tendermint/libs/db" ) func newGasKVStore() KVStore { meter := sdk.NewGasMeter(1000) mem := dbStoreAdapter{dbm.NewMemDB()} - return NewGasKVStore(meter, mem) + return NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) } func TestGasKVStoreBasic(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(1000) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") st.Set(keyFmt(1), valFmt(1)) require.Equal(t, valFmt(1), st.Get(keyFmt(1))) @@ -29,7 +31,7 @@ func TestGasKVStoreBasic(t *testing.T) { func TestGasKVStoreIterator(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(1000) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty") st.Set(keyFmt(1), valFmt(1)) @@ -53,14 +55,14 @@ func TestGasKVStoreIterator(t *testing.T) { func TestGasKVStoreOutOfGasSet(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(0) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas") } func TestGasKVStoreOutOfGasIterator(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(200) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) st.Set(keyFmt(1), valFmt(1)) iterator := st.Iterator(nil, nil) iterator.Next() diff --git a/store/iavlstore.go b/store/iavlstore.go index 26a0c9ea17b8..edc0a7f0fdf6 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -54,6 +54,11 @@ type iavlStore struct { // so that nodes can know the waypoints their peers store // TODO if set to non-default, signal to peers that the node is not suitable as a state sync source storeEvery int64 + // A value of 0 means keep all history. + numHistory int64 + + // transient store is wiped out at Commti + transient *transientStore } // CONTRACT: tree should be fully loaded. @@ -62,6 +67,7 @@ func newIAVLStore(tree *iavl.VersionedTree, numRecent int64, storeEvery int64) * tree: tree, numRecent: numRecent, storeEvery: storeEvery, + transient: nil, } return st } @@ -69,6 +75,10 @@ func newIAVLStore(tree *iavl.VersionedTree, numRecent int64, storeEvery int64) * // Implements Committer. func (st *iavlStore) Commit() CommitID { + // Clear transient store + // transient store uses MemDB internally, GC will handle it + st.transient = nil + // Save a new version. hash, version, err := st.tree.SaveVersion() if err != nil { @@ -143,6 +153,19 @@ func (st *iavlStore) Prefix(prefix []byte) KVStore { return prefixStore{st, prefix} } +// Implements KVStore +func (st *iavlStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, st) +} + +// Implements KVStore +func (st *iavlStore) Transient() KVStore { + if st.transient == nil { + st.transient = newTransientStore() + } + return st.transient +} + // Implements KVStore. func (st *iavlStore) Iterator(start, end []byte) Iterator { return newIAVLIterator(st.tree.Tree(), start, end, true) diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index f6f236dd8e28..e8478537db2a 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -413,6 +413,7 @@ func TestIAVLStoreQuery(t *testing.T) { require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v3, qres.Value) query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version} + qres = iavlStore.Query(query2) require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v2, qres.Value) diff --git a/store/prefixstore.go b/store/prefixstore.go index c9f124a8895f..899a1d38db18 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -4,8 +4,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var _ KVStore = prefixStore{} + type prefixStore struct { - store KVStore + parent KVStore prefix []byte } @@ -21,22 +23,22 @@ func (s prefixStore) CacheWrap() CacheWrap { // Implements KVStore func (s prefixStore) Get(key []byte) []byte { - return s.store.Get(append(s.prefix, key...)) + return s.parent.Get(append(s.prefix, key...)) } // Implements KVStore func (s prefixStore) Has(key []byte) bool { - return s.store.Has(append(s.prefix, key...)) + return s.parent.Has(append(s.prefix, key...)) } // Implements KVStore func (s prefixStore) Set(key, value []byte) { - s.store.Set(append(s.prefix, key...), value) + s.parent.Set(append(s.prefix, key...), value) } // Implements KVStore func (s prefixStore) Delete(key []byte) { - s.store.Delete(append(s.prefix, key...)) + s.parent.Delete(append(s.prefix, key...)) } // Implements KVStore @@ -44,6 +46,22 @@ func (s prefixStore) Prefix(prefix []byte) KVStore { return prefixStore{s, prefix} } +// Implements KVStore +func (s prefixStore) Gas(meter GasMeter, config GasConfig) KVStore { + return prefixStore{ + parent: s.parent.Gas(meter, config), + prefix: s.prefix, + } +} + +// Implements KVStore +func (s prefixStore) Transient() KVStore { + return prefixStore{ + parent: s.parent.Transient(), + prefix: s.prefix, + } +} + // Implements KVStore func (s prefixStore) Iterator(start, end []byte) Iterator { if end == nil { @@ -53,7 +71,7 @@ func (s prefixStore) Iterator(start, end []byte) Iterator { } return prefixIterator{ prefix: s.prefix, - iter: s.store.Iterator(append(s.prefix, start...), end), + iter: s.parent.Iterator(append(s.prefix, start...), end), } } @@ -66,7 +84,7 @@ func (s prefixStore) ReverseIterator(start, end []byte) Iterator { } return prefixIterator{ prefix: s.prefix, - iter: s.store.ReverseIterator(start, end), + iter: s.parent.ReverseIterator(start, end), } } diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go index 1961bb4bb07b..10b7b40b72f0 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -80,7 +80,7 @@ func TestCacheKVStorePrefix(t *testing.T) { func TestGasKVStorePrefix(t *testing.T) { meter := sdk.NewGasMeter(100000000) mem := dbStoreAdapter{dbm.NewMemDB()} - gasStore := NewGasKVStore(meter, mem) + gasStore := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) testPrefixStore(t, gasStore, []byte("test")) } diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 10926e4bcb81..09b521f7fff8 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -183,10 +183,7 @@ func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore { return rs.stores[key].(KVStore) } -// Implements MultiStore. -func (rs *rootMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, key StoreKey) KVStore { - return NewGasKVStore(meter, rs.GetKVStore(key)) -} +// Implements MultiStore // getStoreByName will first convert the original name to // a special key, before looking up the CommitStore. diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go index f564114626aa..3963d8aac684 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmultistore_test.go @@ -3,7 +3,7 @@ package store import ( "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" dbm "github.com/tendermint/tendermint/libs/db" @@ -20,7 +20,7 @@ func TestMultistoreCommitLoad(t *testing.T) { } store := newMultiStoreWithMounts(db) err := store.LoadLatestVersion() - require.Nil(t, err) + assert.Nil(t, err) // New store has empty last commit. commitID := CommitID{} @@ -28,11 +28,11 @@ func TestMultistoreCommitLoad(t *testing.T) { // Make sure we can get stores by name. s1 := store.getStoreByName("store1") - require.NotNil(t, s1) + assert.NotNil(t, s1) s3 := store.getStoreByName("store3") - require.NotNil(t, s3) + assert.NotNil(t, s3) s77 := store.getStoreByName("store77") - require.Nil(t, s77) + assert.Nil(t, s77) // Make a few commits and check them. nCommits := int64(3) @@ -45,7 +45,7 @@ func TestMultistoreCommitLoad(t *testing.T) { // Load the latest multistore again and check version. store = newMultiStoreWithMounts(db) err = store.LoadLatestVersion() - require.Nil(t, err) + assert.Nil(t, err) commitID = getExpectedCommitID(store, nCommits) checkStore(t, store, commitID, commitID) @@ -58,7 +58,7 @@ func TestMultistoreCommitLoad(t *testing.T) { ver := nCommits - 1 store = newMultiStoreWithMounts(db) err = store.LoadVersion(ver) - require.Nil(t, err) + assert.Nil(t, err) commitID = getExpectedCommitID(store, ver) checkStore(t, store, commitID, commitID) @@ -71,29 +71,29 @@ func TestMultistoreCommitLoad(t *testing.T) { // LatestVersion store = newMultiStoreWithMounts(db) err = store.LoadLatestVersion() - require.Nil(t, err) + assert.Nil(t, err) commitID = getExpectedCommitID(store, ver+1) checkStore(t, store, commitID, commitID) } func TestParsePath(t *testing.T) { _, _, err := parsePath("foo") - require.Error(t, err) + assert.Error(t, err) store, subpath, err := parsePath("/foo") - require.NoError(t, err) - require.Equal(t, store, "foo") - require.Equal(t, subpath, "") + assert.NoError(t, err) + assert.Equal(t, store, "foo") + assert.Equal(t, subpath, "") store, subpath, err = parsePath("/fizz/bang/baz") - require.NoError(t, err) - require.Equal(t, store, "fizz") - require.Equal(t, subpath, "/bang/baz") + assert.NoError(t, err) + assert.Equal(t, store, "fizz") + assert.Equal(t, subpath, "/bang/baz") substore, subsubpath, err := parsePath(subpath) - require.NoError(t, err) - require.Equal(t, substore, "bang") - require.Equal(t, subsubpath, "/baz") + assert.NoError(t, err) + assert.Equal(t, substore, "bang") + assert.Equal(t, subsubpath, "/baz") } @@ -101,7 +101,7 @@ func TestMultiStoreQuery(t *testing.T) { db := dbm.NewMemDB() multi := newMultiStoreWithMounts(db) err := multi.LoadLatestVersion() - require.Nil(t, err) + assert.Nil(t, err) k, v := []byte("wind"), []byte("blows") k2, v2 := []byte("water"), []byte("flows") @@ -111,7 +111,7 @@ func TestMultiStoreQuery(t *testing.T) { // Make sure we can get by name. garbage := multi.getStoreByName("bad-name") - require.Nil(t, garbage) + assert.Nil(t, garbage) // Set and commit data in one store. store1 := multi.getStoreByName("store1").(KVStore) @@ -128,35 +128,35 @@ func TestMultiStoreQuery(t *testing.T) { // Test bad path. query := abci.RequestQuery{Path: "/key", Data: k, Height: ver} qres := multi.Query(query) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) query.Path = "h897fy32890rf63296r92" qres = multi.Query(query) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) // Test invalid store name. query.Path = "/garbage/key" qres = multi.Query(query) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) // Test valid query with data. query.Path = "/store1/key" qres = multi.Query(query) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) - require.Equal(t, v, qres.Value) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + assert.Equal(t, v, qres.Value) // Test valid but empty query. query.Path = "/store2/key" query.Prove = true qres = multi.Query(query) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) - require.Nil(t, qres.Value) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + assert.Nil(t, qres.Value) // Test store2 data. query.Data = k2 qres = multi.Query(query) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) - require.Equal(t, v2, qres.Value) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + assert.Equal(t, v2, qres.Value) } //----------------------------------------------------------------------- @@ -174,8 +174,8 @@ func newMultiStoreWithMounts(db dbm.DB) *rootMultiStore { } func checkStore(t *testing.T, store *rootMultiStore, expect, got CommitID) { - require.Equal(t, expect, got) - require.Equal(t, expect, store.LastCommitID()) + assert.Equal(t, expect, got) + assert.Equal(t, expect, store.LastCommitID()) } diff --git a/store/transientstore.go b/store/transientstore.go new file mode 100644 index 000000000000..d7a557eb3850 --- /dev/null +++ b/store/transientstore.go @@ -0,0 +1,17 @@ +package store + +import ( + dbm "github.com/tendermint/tendermint/libs/db" +) + +var _ KVStore = (*transientStore)(nil) + +// transientStore is a wrapper for a MemDB with Commiter implementation +type transientStore struct { + dbStoreAdapter +} + +// Constructs new MemDB adapter +func newTransientStore() *transientStore { + return &transientStore{dbStoreAdapter{dbm.NewMemDB()}} +} diff --git a/store/transientstore_test.go b/store/transientstore_test.go new file mode 100644 index 000000000000..486c0815c60e --- /dev/null +++ b/store/transientstore_test.go @@ -0,0 +1,55 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/tendermint/iavl" + dbm "github.com/tendermint/tendermint/libs/db" +) + +var k, v = []byte("hello"), []byte("world") + +func setKV(t *testing.T, store KVStore) { + tstore := store.Transient() + + assert.Nil(t, store.Get(k)) + assert.Nil(t, tstore.Get(k)) + + store.Set(k, v) + tstore.Set(k, v) + + assert.Equal(t, v, store.Get(k)) + assert.Equal(t, v, tstore.Get(k)) + +} + +func commitKV(t *testing.T, store CommitKVStore) { + store.Commit() + + tstore := store.Transient() + assert.Equal(t, v, store.Get(k)) + assert.Nil(t, tstore.Get(k)) +} + +func TestTransientIAVLStore(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + setKV(t, iavlStore) + commitKV(t, iavlStore) +} + +func TestTransientCacheKVStore(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + cacheStore := NewCacheKVStore(iavlStore) + + setKV(t, cacheStore) + cacheStore.Write() + assert.Equal(t, v, iavlStore.Get(k)) + assert.Equal(t, v, iavlStore.Transient().Get(k)) + commitKV(t, iavlStore) +} diff --git a/store/types.go b/store/types.go index e232e6ec7198..689b8ecfc2b4 100644 --- a/store/types.go +++ b/store/types.go @@ -23,3 +23,6 @@ type CommitID = types.CommitID type StoreKey = types.StoreKey type StoreType = types.StoreType type Queryable = types.Queryable +type Gas = types.Gas +type GasMeter = types.GasMeter +type GasConfig = types.GasConfig diff --git a/types/context.go b/types/context.go index e55eff1ab014..92d76fb5bd84 100644 --- a/types/context.go +++ b/types/context.go @@ -71,7 +71,12 @@ func (c Context) Value(key interface{}) interface{} { // KVStore fetches a KVStore from the MultiStore. func (c Context) KVStore(key StoreKey) KVStore { - return c.multiStore().GetKVStoreWithGas(c.GasMeter(), key) + return c.multiStore().GetKVStore(key).Gas(c.GasMeter(), cachedDefaultGasConfig) +} + +// TransientStore fetches a TransientStore from the MultiStore. +func (c Context) TransientStore(key StoreKey) KVStore { + return c.multiStore().GetKVStore(key).Transient().Gas(c.GasMeter(), cachedTransientGasConfig) } //---------------------------------------- diff --git a/types/gas.go b/types/gas.go index 18d3aa570205..66da509a5670 100644 --- a/types/gas.go +++ b/types/gas.go @@ -54,3 +54,50 @@ func (g *infiniteGasMeter) GasConsumed() Gas { func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { g.consumed += amount } + +// GasConfig defines gas cost for each operation on KVStores +type GasConfig struct { + HasCost Gas + ReadCostFlat Gas + ReadCostPerByte Gas + WriteCostFlat Gas + WriteCostPerByte Gas + KeyCostFlat Gas + ValueCostFlat Gas + ValueCostPerByte Gas +} + +var ( + cachedDefaultGasConfig = DefaultGasConfig() + cachedTransientGasConfig = TransientGasConfig() +) + +// Default gas config for KVStores +func DefaultGasConfig() GasConfig { + return GasConfig{ + HasCost: 10, + ReadCostFlat: 10, + ReadCostPerByte: 1, + WriteCostFlat: 10, + WriteCostPerByte: 10, + KeyCostFlat: 5, + ValueCostFlat: 10, + ValueCostPerByte: 1, + } +} + +// Default gas config for TransientStores +func TransientGasConfig() GasConfig { + // TODO: reduce the gas cost in transient store + return GasConfig{ + HasCost: 10, + ReadCostFlat: 10, + ReadCostPerByte: 1, + WriteCostFlat: 10, + WriteCostPerByte: 10, + KeyCostFlat: 5, + ValueCostFlat: 10, + ValueCostPerByte: 1, + } + +} diff --git a/types/getter.go b/types/getter.go new file mode 100644 index 000000000000..a4d31fbc4d06 --- /dev/null +++ b/types/getter.go @@ -0,0 +1,41 @@ +package types + +import ( + "fmt" +) + +// Getter struct for prefixed stores +type PrefixStoreGetter struct { + key StoreKey + prefix []byte +} + +func NewPrefixStoreGetter(key StoreKey, prefix []byte) PrefixStoreGetter { + return PrefixStoreGetter{key, prefix} +} + +// Implements sdk.KVStoreGetter +func (getter PrefixStoreGetter) KVStore(ctx Context) KVStore { + return ctx.KVStore(getter.key).Prefix(getter.prefix) +} + +// TransientStoreKey is used for accessing transient substores. +// Each TransientStoreKeys are related with the underlying StoreKey +// Which is registered as a substore to the multistore +type TransientStoreKey struct { + key StoreKey +} + +func NewTransientStoreKey(key StoreKey) TransientStoreKey { + return TransientStoreKey{ + key: key, + } +} + +func (key TransientStoreKey) Name() string { + return "!" + key.key.Name() +} + +func (key TransientStoreKey) String() string { + return fmt.Sprintf("TransientStoreKey{%p, %s}", key.key, key.key.Name()) +} diff --git a/types/store.go b/types/store.go index 04de2c7c1a85..a7344aba242b 100644 --- a/types/store.go +++ b/types/store.go @@ -49,7 +49,6 @@ type MultiStore interface { //nolint // Convenience for fetching substores. GetStore(StoreKey) Store GetKVStore(StoreKey) KVStore - GetKVStoreWithGas(GasMeter, StoreKey) KVStore } // From MultiStore.CacheMultiStore().... @@ -103,9 +102,6 @@ type KVStore interface { // Delete deletes the key. Panics on nil key. Delete(key []byte) - // Prefix applied keys with the argument - Prefix(prefix []byte) KVStore - // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. @@ -124,6 +120,20 @@ type KVStore interface { // TODO Not yet implemented. // GetSubKVStore(key *storeKey) KVStore + + // Prefix applied keys with the argument + // CONTRACT: when Prefix is called on a KVStore more than once, + // the concatanation of the prefixes is applied + Prefix(prefix []byte) KVStore + + // Gas consuming store + // CONTRACT: when Gas is called on a KVStore more than once, + // the most latest overwrites the meter and the config. + Gas(GasMeter, GasConfig) KVStore + + // Transient store associated with the KVStore + // TODO: it should be in CommitKVStore + Transient() KVStore } // Alias iterator to db's Iterator for convenience. @@ -213,6 +223,7 @@ const ( StoreTypeDB StoreTypeIAVL StoreTypePrefix + StoreTypeEmpty ) //---------------------------------------- @@ -277,21 +288,6 @@ func PrefixEndBytes(prefix []byte) []byte { return end } -// Getter struct for prefixed stores -type PrefixStoreGetter struct { - key StoreKey - prefix []byte -} - -func NewPrefixStoreGetter(key StoreKey, prefix []byte) PrefixStoreGetter { - return PrefixStoreGetter{key, prefix} -} - -// Implements sdk.KVStoreGetter -func (getter PrefixStoreGetter) KVStore(ctx Context) KVStore { - return ctx.KVStore(getter.key).Prefix(getter.prefix) -} - //---------------------------------------- // key-value result for iterator queries