diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24ca58bfb2a4..8236052e83cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -291,7 +291,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: core/go.sum @@ -322,7 +322,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: depinject/go.sum @@ -351,7 +351,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: errors/go.sum @@ -382,7 +382,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: math/go.sum @@ -480,7 +480,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: orm/go.sum @@ -604,7 +604,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: store/go.sum @@ -635,7 +635,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: log/go.sum diff --git a/CHANGELOG.md b/CHANGELOG.md index cb45d1180abe..330a5499f075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features * (baseapp) [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Add `TxExecutor` baseapp option, add `TxIndex`/`TxCount`/`MsgIndex`/`BlockGasUsed` fields to `Context, to support tx parallel execution. +* (baseapp) [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Support mount object store in baseapp, add `ObjectStore` api in context.. ## [Unreleased-Upstream] diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 998cd19fa25b..eeb8a414d643 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -344,6 +344,17 @@ func (app *BaseApp) MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey } } +// MountObjectStores mounts all transient object stores with the BaseApp's internal +// commit multi-store. +func (app *BaseApp) MountObjectStores(keys map[string]*storetypes.ObjectStoreKey) { + skeys := maps.Keys(keys) + sort.Strings(skeys) + for _, key := range skeys { + memKey := keys[key] + app.MountStore(memKey, storetypes.StoreTypeObject) + } +} + // MountStore mounts a store to the provided key in the BaseApp multistore, // using the default DB. func (app *BaseApp) MountStore(key storetypes.StoreKey, typ storetypes.StoreType) { diff --git a/runtime/store.go b/runtime/store.go index 230f53c38c5d..aee2487b5c9d 100644 --- a/runtime/store.go +++ b/runtime/store.go @@ -2,9 +2,6 @@ package runtime import ( "context" - "io" - - dbm "github.com/cosmos/cosmos-db" "cosmossdk.io/core/store" storetypes "cosmossdk.io/store/types" @@ -104,10 +101,6 @@ func (kvStoreAdapter) CacheWrap() storetypes.CacheWrap { panic("unimplemented") } -func (kvStoreAdapter) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - panic("unimplemented") -} - func (kvStoreAdapter) GetStoreType() storetypes.StoreType { panic("unimplemented") } @@ -142,7 +135,7 @@ func (s kvStoreAdapter) Set(key, value []byte) { } } -func (s kvStoreAdapter) Iterator(start, end []byte) dbm.Iterator { +func (s kvStoreAdapter) Iterator(start, end []byte) storetypes.Iterator { it, err := s.store.Iterator(start, end) if err != nil { panic(err) @@ -150,7 +143,7 @@ func (s kvStoreAdapter) Iterator(start, end []byte) dbm.Iterator { return it } -func (s kvStoreAdapter) ReverseIterator(start, end []byte) dbm.Iterator { +func (s kvStoreAdapter) ReverseIterator(start, end []byte) storetypes.Iterator { it, err := s.store.ReverseIterator(start, end) if err != nil { panic(err) diff --git a/server/mock/store.go b/server/mock/store.go index 6e8a30364614..c6829bb18b6b 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -114,6 +114,10 @@ func (ms multiStore) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { return ms.kv[key] } +func (ms multiStore) GetObjKVStore(storetypes.StoreKey) storetypes.ObjKVStore { + panic("not implemented") +} + func (ms multiStore) GetStore(key storetypes.StoreKey) storetypes.Store { panic("not implemented") } @@ -178,10 +182,6 @@ func (kv kvStore) CacheWrap() storetypes.CacheWrap { panic("not implemented") } -func (kv kvStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - panic("not implemented") -} - func (kv kvStore) CacheWrapWithListeners(_ storetypes.StoreKey, _ []storetypes.MemoryListener) storetypes.CacheWrap { panic("not implemented") } diff --git a/store/CHANGELOG.md b/store/CHANGELOG.md index b56a47dd9af1..4066039f3f7e 100644 --- a/store/CHANGELOG.md +++ b/store/CHANGELOG.md @@ -28,6 +28,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements * [#207](https://github.com/crypto-org-chain/cosmos-sdk/pull/207) Remove api CacheWrapWithTrace. +* [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Support object store. ## v1.1.0 (March 20, 2024) diff --git a/store/cachekv/internal/mergeiterator.go b/store/cachekv/internal/mergeiterator.go index 58e9497b3028..c9a1e7d3a677 100644 --- a/store/cachekv/internal/mergeiterator.go +++ b/store/cachekv/internal/mergeiterator.go @@ -14,21 +14,24 @@ import ( // cache shadows (overrides) the parent. // // TODO: Optimize by memoizing. -type cacheMergeIterator struct { - parent types.Iterator - cache types.Iterator +type cacheMergeIterator[V any] struct { + parent types.GIterator[V] + cache types.GIterator[V] ascending bool valid bool + + isZero func(V) bool } -var _ types.Iterator = (*cacheMergeIterator)(nil) +var _ types.Iterator = (*cacheMergeIterator[[]byte])(nil) -func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.Iterator { - iter := &cacheMergeIterator{ +func NewCacheMergeIterator[V any](parent, cache types.GIterator[V], ascending bool, isZero func(V) bool) types.GIterator[V] { + iter := &cacheMergeIterator[V]{ parent: parent, cache: cache, ascending: ascending, + isZero: isZero, } iter.valid = iter.skipUntilExistsOrInvalid() @@ -37,17 +40,17 @@ func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.I // Domain implements Iterator. // Returns parent domain because cache and parent domains are the same. -func (iter *cacheMergeIterator) Domain() (start, end []byte) { +func (iter *cacheMergeIterator[V]) Domain() (start, end []byte) { return iter.parent.Domain() } // Valid implements Iterator. -func (iter *cacheMergeIterator) Valid() bool { +func (iter *cacheMergeIterator[V]) Valid() bool { return iter.valid } // Next implements Iterator -func (iter *cacheMergeIterator) Next() { +func (iter *cacheMergeIterator[V]) Next() { iter.assertValid() switch { @@ -74,7 +77,7 @@ func (iter *cacheMergeIterator) Next() { } // Key implements Iterator -func (iter *cacheMergeIterator) Key() []byte { +func (iter *cacheMergeIterator[V]) Key() []byte { iter.assertValid() // If parent is invalid, get the cache key. @@ -104,7 +107,7 @@ func (iter *cacheMergeIterator) Key() []byte { } // Value implements Iterator -func (iter *cacheMergeIterator) Value() []byte { +func (iter *cacheMergeIterator[V]) Value() V { iter.assertValid() // If parent is invalid, get the cache value. @@ -134,7 +137,7 @@ func (iter *cacheMergeIterator) Value() []byte { } // Close implements Iterator -func (iter *cacheMergeIterator) Close() error { +func (iter *cacheMergeIterator[V]) Close() error { err1 := iter.cache.Close() if err := iter.parent.Close(); err != nil { return err @@ -145,7 +148,7 @@ func (iter *cacheMergeIterator) Close() error { // Error returns an error if the cacheMergeIterator is invalid defined by the // Valid method. -func (iter *cacheMergeIterator) Error() error { +func (iter *cacheMergeIterator[V]) Error() error { if !iter.Valid() { return errors.New("invalid cacheMergeIterator") } @@ -155,14 +158,14 @@ func (iter *cacheMergeIterator) Error() error { // If not valid, panics. // NOTE: May have side-effect of iterating over cache. -func (iter *cacheMergeIterator) assertValid() { +func (iter *cacheMergeIterator[V]) assertValid() { if err := iter.Error(); err != nil { panic(err) } } // Like bytes.Compare but opposite if not ascending. -func (iter *cacheMergeIterator) compare(a, b []byte) int { +func (iter *cacheMergeIterator[V]) compare(a, b []byte) int { if iter.ascending { return bytes.Compare(a, b) } @@ -175,9 +178,9 @@ func (iter *cacheMergeIterator) compare(a, b []byte) int { // If the current cache item is not a delete item, does nothing. // If `until` is nil, there is no limit, and cache may end up invalid. // CONTRACT: cache is valid. -func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { +func (iter *cacheMergeIterator[V]) skipCacheDeletes(until []byte) { for iter.cache.Valid() && - iter.cache.Value() == nil && + iter.isZero(iter.cache.Value()) && (until == nil || iter.compare(iter.cache.Key(), until) < 0) { iter.cache.Next() } @@ -186,7 +189,7 @@ func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { // Fast forwards cache (or parent+cache in case of deleted items) until current // item exists, or until iterator becomes invalid. // Returns whether the iterator is valid. -func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { +func (iter *cacheMergeIterator[V]) skipUntilExistsOrInvalid() bool { for { // If parent is invalid, fast-forward cache. if !iter.parent.Valid() { @@ -211,7 +214,7 @@ func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { case 0: // parent == cache. // Skip over if cache item is a delete. valueC := iter.cache.Value() - if valueC == nil { + if iter.isZero(valueC) { iter.parent.Next() iter.cache.Next() @@ -223,7 +226,7 @@ func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { case 1: // cache < parent // Skip over if cache item is a delete. valueC := iter.cache.Value() - if valueC == nil { + if iter.isZero(valueC) { iter.skipCacheDeletes(keyP) continue } diff --git a/store/cachekv/search_benchmark_test.go b/store/cachekv/search_benchmark_test.go index ecdc86a8e43a..f0e29bc4ec18 100644 --- a/store/cachekv/search_benchmark_test.go +++ b/store/cachekv/search_benchmark_test.go @@ -4,7 +4,7 @@ import ( "strconv" "testing" - "cosmossdk.io/store/cachekv/internal" + "cosmossdk.io/store/internal/btree" ) func BenchmarkLargeUnsortedMisses(b *testing.B) { @@ -22,23 +22,23 @@ func BenchmarkLargeUnsortedMisses(b *testing.B) { } func generateStore() *Store { - cache := map[string]*cValue{} + cache := map[string]*cValue[[]byte]{} unsorted := map[string]struct{}{} for i := 0; i < 5000; i++ { key := "A" + strconv.Itoa(i) unsorted[key] = struct{}{} - cache[key] = &cValue{} + cache[key] = &cValue[[]byte]{} } for i := 0; i < 5000; i++ { key := "Z" + strconv.Itoa(i) unsorted[key] = struct{}{} - cache[key] = &cValue{} + cache[key] = &cValue[[]byte]{} } - return &Store{ + return &GStore[[]byte]{ cache: cache, unsortedCache: unsorted, - sortedCache: internal.NewBTree(), + sortedCache: btree.NewBTree[[]byte](), } } diff --git a/store/cachekv/store.go b/store/cachekv/store.go index 9c062c8ce619..9f7ff680db60 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -9,46 +9,70 @@ import ( "cosmossdk.io/math" "cosmossdk.io/store/cachekv/internal" + "cosmossdk.io/store/internal/btree" "cosmossdk.io/store/internal/conv" - "cosmossdk.io/store/internal/kv" "cosmossdk.io/store/types" ) // cValue represents a cached value. // If dirty is true, it indicates the cached value is different from the underlying value. -type cValue struct { - value []byte +type cValue[V any] struct { + value V dirty bool } -// Store wraps an in-memory cache around an underlying types.KVStore. -type Store struct { - mtx sync.Mutex - cache map[string]*cValue - unsortedCache map[string]struct{} - sortedCache internal.BTree // always ascending sorted - parent types.KVStore +type kvPair[V any] struct { + Key []byte + Value V } +type Store = GStore[[]byte] + var _ types.CacheKVStore = (*Store)(nil) -// NewStore creates a new Store object func NewStore(parent types.KVStore) *Store { - return &Store{ - cache: make(map[string]*cValue), + return NewGStore( + parent, + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + ) +} + +// GStore wraps an in-memory cache around an underlying types.KVStore. +type GStore[V any] struct { + mtx sync.Mutex + cache map[string]*cValue[V] + unsortedCache map[string]struct{} + sortedCache btree.BTree[V] // always ascending sorted + parent types.GKVStore[V] + + // isZero is a function that returns true if the value is considered "zero", for []byte and pointers the zero value + // is `nil`, zero value is not allowed to set to a key, and it's returned if the key is not found. + isZero func(V) bool + zeroValue V + // valueLen validates the value before it's set + valueLen func(V) int +} + +// NewStore creates a new Store object +func NewGStore[V any](parent types.GKVStore[V], isZero func(V) bool, valueLen func(V) int) *GStore[V] { + return &GStore[V]{ + cache: make(map[string]*cValue[V]), unsortedCache: make(map[string]struct{}), - sortedCache: internal.NewBTree(), + sortedCache: btree.NewBTree[V](), parent: parent, + isZero: isZero, + valueLen: valueLen, } } // GetStoreType implements Store. -func (store *Store) GetStoreType() types.StoreType { +func (store *GStore[V]) GetStoreType() types.StoreType { return store.parent.GetStoreType() } // Get implements types.KVStore. -func (store *Store) Get(key []byte) (value []byte) { +func (store *GStore[V]) Get(key []byte) (value V) { store.mtx.Lock() defer store.mtx.Unlock() @@ -65,10 +89,17 @@ func (store *Store) Get(key []byte) (value []byte) { return value } +func (store *GStore[V]) assertValidValue(value V) { + if store.isZero(value) { + panic("value is nil") + } + types.AssertValidValueLength(store.valueLen(value)) +} + // Set implements types.KVStore. -func (store *Store) Set(key, value []byte) { +func (store *GStore[V]) Set(key []byte, value V) { types.AssertValidKey(key) - types.AssertValidValue(value) + store.assertValidValue(value) store.mtx.Lock() defer store.mtx.Unlock() @@ -76,28 +107,28 @@ func (store *Store) Set(key, value []byte) { } // Has implements types.KVStore. -func (store *Store) Has(key []byte) bool { +func (store *GStore[V]) Has(key []byte) bool { value := store.Get(key) - return value != nil + return !store.isZero(value) } // Delete implements types.KVStore. -func (store *Store) Delete(key []byte) { +func (store *GStore[V]) Delete(key []byte) { types.AssertValidKey(key) store.mtx.Lock() defer store.mtx.Unlock() - store.setCacheValue(key, nil, true) + store.setCacheValue(key, store.zeroValue, true) } -func (store *Store) resetCaches() { +func (store *GStore[V]) resetCaches() { if len(store.cache) > 100_000 { // Cache is too large. We likely did something linear time // (e.g. Epoch block, Genesis block, etc). Free the old caches from memory, and let them get re-allocated. // TODO: In a future CacheKV redesign, such linear workloads should get into a different cache instantiation. // 100_000 is arbitrarily chosen as it solved Osmosis' InitGenesis RAM problem. - store.cache = make(map[string]*cValue) + store.cache = make(map[string]*cValue[V]) store.unsortedCache = make(map[string]struct{}) } else { // Clear the cache using the map clearing idiom @@ -110,22 +141,22 @@ func (store *Store) resetCaches() { delete(store.unsortedCache, key) } } - store.sortedCache = internal.NewBTree() + store.sortedCache = btree.NewBTree[V]() } // Implements Cachetypes.KVStore. -func (store *Store) Write() { +func (store *GStore[V]) Write() { store.mtx.Lock() defer store.mtx.Unlock() if len(store.cache) == 0 && len(store.unsortedCache) == 0 { - store.sortedCache = internal.NewBTree() + store.sortedCache = btree.NewBTree[V]() return } type cEntry struct { key string - val *cValue + val *cValue[V] } // We need a copy of all of the keys. @@ -150,7 +181,7 @@ func (store *Store) Write() { // be sure if the underlying store might do a save with the byteslice or // not. Once we get confirmation that .Delete is guaranteed not to // save the byteslice, then we can assume only a read-only copy is sufficient. - if obj.val.value != nil { + if !store.isZero(obj.val.value) { // It already exists in the parent, hence update it. store.parent.Set([]byte(obj.key), obj.val.value) } else { @@ -160,24 +191,24 @@ func (store *Store) Write() { } // CacheWrap implements CacheWrapper. -func (store *Store) CacheWrap() types.CacheWrap { - return NewStore(store) +func (store *GStore[V]) CacheWrap() types.CacheWrap { + return NewGStore(store, store.isZero, store.valueLen) } //---------------------------------------- // Iteration // Iterator implements types.KVStore. -func (store *Store) Iterator(start, end []byte) types.Iterator { +func (store *GStore[V]) Iterator(start, end []byte) types.GIterator[V] { return store.iterator(start, end, true) } // ReverseIterator implements types.KVStore. -func (store *Store) ReverseIterator(start, end []byte) types.Iterator { +func (store *GStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] { return store.iterator(start, end, false) } -func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { +func (store *GStore[V]) iterator(start, end []byte, ascending bool) types.GIterator[V] { store.mtx.Lock() defer store.mtx.Unlock() @@ -186,7 +217,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { var ( err error - parent, cache types.Iterator + parent, cache types.GIterator[V] ) if ascending { @@ -200,7 +231,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { panic(err) } - return internal.NewCacheMergeIterator(parent, cache, ascending) + return internal.NewCacheMergeIterator(parent, cache, ascending, store.isZero) } func findStartIndex(strL []string, startQ string) int { @@ -286,7 +317,7 @@ const ( const minSortSize = 1024 // Constructs a slice of dirty items, to use w/ memIterator. -func (store *Store) dirtyItems(start, end []byte) { +func (store *GStore[V]) dirtyItems(start, end []byte) { startStr, endStr := conv.UnsafeBytesToStr(start), conv.UnsafeBytesToStr(end) if end != nil && startStr > endStr { // Nothing to do here. @@ -294,7 +325,7 @@ func (store *Store) dirtyItems(start, end []byte) { } n := len(store.unsortedCache) - unsorted := make([]*kv.Pair, 0) + unsorted := make([]*kvPair[V], 0) // If the unsortedCache is too big, its costs too much to determine // whats in the subset we are concerned about. // If you are interleaving iterator calls with writes, this can easily become an @@ -306,7 +337,7 @@ func (store *Store) dirtyItems(start, end []byte) { // dbm.IsKeyInDomain is nil safe and returns true iff key is greater than start if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) { cacheValue := store.cache[key] - unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) + unsorted = append(unsorted, &kvPair[V]{Key: []byte(key), Value: cacheValue.value}) } } store.clearUnsortedCacheSubset(unsorted, stateUnsorted) @@ -349,18 +380,18 @@ func (store *Store) dirtyItems(start, end []byte) { } } - kvL := make([]*kv.Pair, 0, 1+endIndex-startIndex) + kvL := make([]*kvPair[V], 0, 1+endIndex-startIndex) for i := startIndex; i <= endIndex; i++ { key := strL[i] cacheValue := store.cache[key] - kvL = append(kvL, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) + kvL = append(kvL, &kvPair[V]{Key: []byte(key), Value: cacheValue.value}) } // kvL was already sorted so pass it in as is. store.clearUnsortedCacheSubset(kvL, stateAlreadySorted) } -func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sortState) { +func (store *GStore[V]) clearUnsortedCacheSubset(unsorted []*kvPair[V], sortState sortState) { n := len(store.unsortedCache) if len(unsorted) == n { // This pattern allows the Go compiler to emit the map clearing idiom for the entire map. for key := range store.unsortedCache { @@ -389,9 +420,9 @@ func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sort // Only entrypoint to mutate store.cache. // A `nil` value means a deletion. -func (store *Store) setCacheValue(key, value []byte, dirty bool) { +func (store *GStore[V]) setCacheValue(key []byte, value V, dirty bool) { keyStr := conv.UnsafeBytesToStr(key) - store.cache[keyStr] = &cValue{ + store.cache[keyStr] = &cValue[V]{ value: value, dirty: dirty, } diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go index 37fadb7790f2..5f6b4856052b 100644 --- a/store/cachemulti/store.go +++ b/store/cachemulti/store.go @@ -6,7 +6,6 @@ import ( dbm "github.com/cosmos/cosmos-db" - "cosmossdk.io/store/cachekv" "cosmossdk.io/store/dbadapter" "cosmossdk.io/store/tracekv" "cosmossdk.io/store/types" @@ -24,7 +23,7 @@ const storeNameCtxKey = "store_name" // NOTE: a Store (and MultiStores in general) should never expose the // keys for the substores. type Store struct { - db types.CacheKVStore + db types.CacheWrap stores map[types.StoreKey]types.CacheWrap keys map[string]types.StoreKey @@ -38,11 +37,11 @@ var _ types.CacheMultiStore = Store{} // CacheWrapper objects and a KVStore as the database. Each CacheWrapper store // is a branched store. func NewFromKVStore( - store types.KVStore, stores map[types.StoreKey]types.CacheWrapper, + store types.CacheWrapper, stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey, traceWriter io.Writer, traceContext types.TraceContext, ) Store { cms := Store{ - db: cachekv.NewStore(store), + db: store.CacheWrap(), stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), keys: keys, traceWriter: traceWriter, @@ -51,13 +50,16 @@ func NewFromKVStore( for key, store := range stores { if cms.TracingEnabled() { - tctx := cms.traceContext.Clone().Merge(types.TraceContext{ - storeNameCtxKey: key.Name(), - }) - - store = tracekv.NewStore(store.(types.KVStore), cms.traceWriter, tctx) + // only support tracing on KVStore. + if kvstore, ok := store.(types.KVStore); ok { + tctx := cms.traceContext.Clone().Merge(types.TraceContext{ + storeNameCtxKey: key.Name(), + }) + + store = tracekv.NewStore(kvstore, cms.traceWriter, tctx) + } } - cms.stores[key] = cachekv.NewStore(store.(types.KVStore)) + cms.stores[key] = store.CacheWrap() } return cms @@ -155,11 +157,28 @@ func (cms Store) GetStore(key types.StoreKey) types.Store { return s.(types.Store) } -// GetKVStore returns an underlying KVStore by key. -func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { +func (cms Store) getCacheWrap(key types.StoreKey) types.CacheWrap { store := cms.stores[key] if key == nil || store == nil { panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) } - return store.(types.KVStore) + return store +} + +// GetKVStore returns an underlying KVStore by key. +func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { + store, ok := cms.getCacheWrap(key).(types.KVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not KVStore", key)) + } + return store +} + +// GetObjKVStore returns an underlying KVStore by key. +func (cms Store) GetObjKVStore(key types.StoreKey) types.ObjKVStore { + store, ok := cms.getCacheWrap(key).(types.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + return store } diff --git a/store/gaskv/store.go b/store/gaskv/store.go index 59247d80008a..7aab63b0863a 100644 --- a/store/gaskv/store.go +++ b/store/gaskv/store.go @@ -1,65 +1,102 @@ package gaskv -import ( - "cosmossdk.io/store/types" -) +import "cosmossdk.io/store/types" + +// ObjectValueLength is the emulated number of bytes for storing transient objects in gas accounting. +const ObjectValueLength = 16 var _ types.KVStore = &Store{} -// Store applies gas tracking to an underlying KVStore. It implements the +type Store = GStore[[]byte] + +func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store { + return NewGStore(parent, gasMeter, gasConfig, + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + ) +} + +type ObjStore = GStore[any] + +func NewObjStore(parent types.ObjKVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *ObjStore { + return NewGStore(parent, gasMeter, gasConfig, + func(v any) bool { return v == nil }, + func(v any) int { return ObjectValueLength }, + ) +} + +// GStore applies gas tracking to an underlying KVStore. It implements the // KVStore interface. -type Store struct { +type GStore[V any] struct { gasMeter types.GasMeter gasConfig types.GasConfig - parent types.KVStore + parent types.GKVStore[V] + + isZero func(V) bool + valueLen func(V) int } -// NewStore returns a reference to a new GasKVStore. -func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store { - kvs := &Store{ +// NewGStore returns a reference to a new GasKVStore. +func NewGStore[V any]( + parent types.GKVStore[V], + gasMeter types.GasMeter, + gasConfig types.GasConfig, + isZero func(V) bool, + valueLen func(V) int, +) *GStore[V] { + kvs := &GStore[V]{ gasMeter: gasMeter, gasConfig: gasConfig, parent: parent, + isZero: isZero, + valueLen: valueLen, } return kvs } // Implements Store. -func (gs *Store) GetStoreType() types.StoreType { +func (gs *GStore[V]) GetStoreType() types.StoreType { return gs.parent.GetStoreType() } // Implements KVStore. -func (gs *Store) Get(key []byte) (value []byte) { +func (gs *GStore[V]) Get(key []byte) (value V) { gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc) value = gs.parent.Get(key) // TODO overflow-safe math? gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc) - gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc) + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(gs.valueLen(value)), types.GasReadPerByteDesc) return value } +func (gs *GStore[V]) assertValidValue(value V) { + if gs.isZero(value) { + panic("value is nil") + } + types.AssertValidValueLength(gs.valueLen(value)) +} + // Implements KVStore. -func (gs *Store) Set(key, value []byte) { +func (gs *GStore[V]) Set(key []byte, value V) { types.AssertValidKey(key) - types.AssertValidValue(value) + gs.assertValidValue(value) gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc) // TODO overflow-safe math? gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(key)), types.GasWritePerByteDesc) - gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(value)), types.GasWritePerByteDesc) + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(gs.valueLen(value)), types.GasWritePerByteDesc) gs.parent.Set(key, value) } // Implements KVStore. -func (gs *Store) Has(key []byte) bool { +func (gs *GStore[V]) Has(key []byte) bool { gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc) return gs.parent.Has(key) } // Implements KVStore. -func (gs *Store) Delete(key []byte) { +func (gs *GStore[V]) Delete(key []byte) { // charge gas to prevent certain attack vectors even though space is being freed gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc) gs.parent.Delete(key) @@ -68,7 +105,7 @@ func (gs *Store) Delete(key []byte) { // Iterator implements the KVStore interface. It returns an iterator which // incurs a flat gas cost for seeking to the first key/value pair and a variable // gas cost based on the current value's length if the iterator is valid. -func (gs *Store) Iterator(start, end []byte) types.Iterator { +func (gs *GStore[V]) Iterator(start, end []byte) types.GIterator[V] { return gs.iterator(start, end, true) } @@ -76,94 +113,95 @@ func (gs *Store) Iterator(start, end []byte) types.Iterator { // iterator which incurs a flat gas cost for seeking to the first key/value pair // and a variable gas cost based on the current value's length if the iterator // is valid. -func (gs *Store) ReverseIterator(start, end []byte) types.Iterator { +func (gs *GStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] { return gs.iterator(start, end, false) } // Implements KVStore. -func (gs *Store) CacheWrap() types.CacheWrap { +func (gs *GStore[V]) CacheWrap() types.CacheWrap { panic("cannot CacheWrap a GasKVStore") } -func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator { - var parent types.Iterator +func (gs *GStore[V]) iterator(start, end []byte, ascending bool) types.GIterator[V] { + var parent types.GIterator[V] if ascending { parent = gs.parent.Iterator(start, end) } else { parent = gs.parent.ReverseIterator(start, end) } - gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent) - gi.(*gasIterator).consumeSeekGas() + gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent, gs.valueLen) + gi.consumeSeekGas() return gi } -type gasIterator struct { +type gasIterator[V any] struct { gasMeter types.GasMeter gasConfig types.GasConfig - parent types.Iterator + parent types.GIterator[V] + valueLen func(V) int } -func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator) types.Iterator { - return &gasIterator{ +func newGasIterator[V any](gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.GIterator[V], valueLen func(V) int) *gasIterator[V] { + return &gasIterator[V]{ gasMeter: gasMeter, gasConfig: gasConfig, parent: parent, + valueLen: valueLen, } } // Implements Iterator. -func (gi *gasIterator) Domain() (start, end []byte) { +func (gi *gasIterator[V]) Domain() (start, end []byte) { return gi.parent.Domain() } // Implements Iterator. -func (gi *gasIterator) Valid() bool { +func (gi *gasIterator[V]) Valid() bool { return gi.parent.Valid() } // Next implements the Iterator interface. It seeks to the next key/value pair // in the iterator. It incurs a flat gas cost for seeking and a variable gas // cost based on the current value's length if the iterator is valid. -func (gi *gasIterator) Next() { +func (gi *gasIterator[V]) Next() { gi.consumeSeekGas() gi.parent.Next() } // Key implements the Iterator interface. It returns the current key and it does // not incur any gas cost. -func (gi *gasIterator) Key() (key []byte) { +func (gi *gasIterator[V]) Key() (key []byte) { key = gi.parent.Key() return key } // Value implements the Iterator interface. It returns the current value and it // does not incur any gas cost. -func (gi *gasIterator) Value() (value []byte) { - value = gi.parent.Value() - return value +func (gi *gasIterator[V]) Value() (value V) { + return gi.parent.Value() } // Implements Iterator. -func (gi *gasIterator) Close() error { +func (gi *gasIterator[V]) Close() error { return gi.parent.Close() } // Error delegates the Error call to the parent iterator. -func (gi *gasIterator) Error() error { +func (gi *gasIterator[V]) Error() error { return gi.parent.Error() } // consumeSeekGas consumes on each iteration step a flat gas cost and a variable gas cost // based on the current value's length. -func (gi *gasIterator) consumeSeekGas() { +func (gi *gasIterator[V]) consumeSeekGas() { if gi.Valid() { key := gi.Key() value := gi.Value() gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasValuePerByteDesc) - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasValuePerByteDesc) + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(gi.valueLen(value)), types.GasValuePerByteDesc) } gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc) } diff --git a/store/iavl/tree.go b/store/iavl/tree.go index a44559784a86..6819029f2642 100644 --- a/store/iavl/tree.go +++ b/store/iavl/tree.go @@ -3,9 +3,8 @@ package iavl import ( "fmt" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/iavl" - - "cosmossdk.io/store/types" ) var ( @@ -32,7 +31,7 @@ type ( GetVersioned(key []byte, version int64) ([]byte, error) GetImmutable(version int64) (*iavl.ImmutableTree, error) SetInitialVersion(version uint64) - Iterator(start, end []byte, ascending bool) (types.Iterator, error) + Iterator(start, end []byte, ascending bool) (dbm.Iterator, error) AvailableVersions() []int LoadVersionForOverwriting(targetVersion int64) error TraverseStateChanges(startVersion, endVersion int64, fn func(version int64, changeSet *iavl.ChangeSet) error) error diff --git a/store/cachekv/internal/btree.go b/store/internal/btree/btree.go similarity index 63% rename from store/cachekv/internal/btree.go rename to store/internal/btree/btree.go index 209f7e58c4dd..0fe28def41ee 100644 --- a/store/cachekv/internal/btree.go +++ b/store/internal/btree/btree.go @@ -1,4 +1,4 @@ -package internal +package btree import ( "bytes" @@ -22,44 +22,46 @@ var errKeyEmpty = errors.New("key cannot be empty") // we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests. // // We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly. -type BTree struct { - tree *btree.BTreeG[item] +type BTree[V any] struct { + tree *btree.BTreeG[item[V]] } // NewBTree creates a wrapper around `btree.BTreeG`. -func NewBTree() BTree { - return BTree{ - tree: btree.NewBTreeGOptions(byKeys, btree.Options{ +func NewBTree[V any]() BTree[V] { + return BTree[V]{ + tree: btree.NewBTreeGOptions(byKeys[V], btree.Options{ Degree: bTreeDegree, NoLocks: false, }), } } -func (bt BTree) Set(key, value []byte) { +func (bt BTree[V]) Set(key []byte, value V) { bt.tree.Set(newItem(key, value)) } -func (bt BTree) Get(key []byte) []byte { - i, found := bt.tree.Get(newItem(key, nil)) +func (bt BTree[V]) Get(key []byte) V { + var empty V + i, found := bt.tree.Get(newItem(key, empty)) if !found { - return nil + return empty } return i.value } -func (bt BTree) Delete(key []byte) { - bt.tree.Delete(newItem(key, nil)) +func (bt BTree[V]) Delete(key []byte) { + var empty V + bt.tree.Delete(newItem(key, empty)) } -func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { +func (bt BTree[V]) Iterator(start, end []byte) (types.GIterator[V], error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, errKeyEmpty } return newMemIterator(start, end, bt, true), nil } -func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { +func (bt BTree[V]) ReverseIterator(start, end []byte) (types.GIterator[V], error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, errKeyEmpty } @@ -68,24 +70,28 @@ func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { // Copy the tree. This is a copy-on-write operation and is very fast because // it only performs a shadowed copy. -func (bt BTree) Copy() BTree { - return BTree{ +func (bt BTree[V]) Copy() BTree[V] { + return BTree[V]{ tree: bt.tree.Copy(), } } +func (bt BTree[V]) Clear() { + bt.tree.Clear() +} + // item is a btree item with byte slices as keys and values -type item struct { +type item[V any] struct { key []byte - value []byte + value V } // byKeys compares the items by key -func byKeys(a, b item) bool { +func byKeys[V any](a, b item[V]) bool { return bytes.Compare(a.key, b.key) == -1 } // newItem creates a new pair item. -func newItem(key, value []byte) item { - return item{key: key, value: value} +func newItem[V any](key []byte, value V) item[V] { + return item[V]{key: key, value: value} } diff --git a/store/cachekv/internal/btree_test.go b/store/internal/btree/btree_test.go similarity index 98% rename from store/cachekv/internal/btree_test.go rename to store/internal/btree/btree_test.go index 06437997f636..89b5827e6156 100644 --- a/store/cachekv/internal/btree_test.go +++ b/store/internal/btree/btree_test.go @@ -1,4 +1,4 @@ -package internal +package btree import ( "testing" @@ -9,7 +9,7 @@ import ( ) func TestGetSetDelete(t *testing.T) { - db := NewBTree() + db := NewBTree[[]byte]() // A nonexistent key should return nil. value := db.Get([]byte("a")) @@ -40,7 +40,7 @@ func TestGetSetDelete(t *testing.T) { } func TestDBIterator(t *testing.T) { - db := NewBTree() + db := NewBTree[[]byte]() for i := 0; i < 10; i++ { if i != 6 { // but skip 6. @@ -171,7 +171,7 @@ func TestDBIterator(t *testing.T) { []int64(nil), "reverse iterator from 2 (ex) to 4") // Ensure that the iterators don't panic with an empty database. - db2 := NewBTree() + db2 := NewBTree[[]byte]() itr, err = db2.Iterator(nil, nil) require.NoError(t, err) diff --git a/store/cachekv/internal/memiterator.go b/store/internal/btree/memiterator.go similarity index 64% rename from store/cachekv/internal/memiterator.go rename to store/internal/btree/memiterator.go index 9dbba7587071..e98ae9cd834e 100644 --- a/store/cachekv/internal/memiterator.go +++ b/store/internal/btree/memiterator.go @@ -1,4 +1,4 @@ -package internal +package btree import ( "bytes" @@ -9,13 +9,13 @@ import ( "cosmossdk.io/store/types" ) -var _ types.Iterator = (*memIterator)(nil) +var _ types.Iterator = (*memIterator[[]byte])(nil) // memIterator iterates over iterKVCache items. // if value is nil, means it was deleted. // Implements Iterator. -type memIterator struct { - iter btree.IterG[item] +type memIterator[V any] struct { + iter btree.IterG[item[V]] start []byte end []byte @@ -23,18 +23,21 @@ type memIterator struct { valid bool } -func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { +func newMemIterator[V any](start, end []byte, items BTree[V], ascending bool) *memIterator[V] { + var ( + valid bool + empty V + ) iter := items.tree.Iter() - var valid bool if ascending { if start != nil { - valid = iter.Seek(newItem(start, nil)) + valid = iter.Seek(newItem(start, empty)) } else { valid = iter.First() } } else { if end != nil { - valid = iter.Seek(newItem(end, nil)) + valid = iter.Seek(newItem(end, empty)) if !valid { valid = iter.Last() } else { @@ -46,7 +49,7 @@ func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator } } - mi := &memIterator{ + mi := &memIterator[V]{ iter: iter, start: start, end: end, @@ -61,27 +64,27 @@ func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator return mi } -func (mi *memIterator) Domain() (start, end []byte) { +func (mi *memIterator[V]) Domain() (start, end []byte) { return mi.start, mi.end } -func (mi *memIterator) Close() error { +func (mi *memIterator[V]) Close() error { mi.iter.Release() return nil } -func (mi *memIterator) Error() error { +func (mi *memIterator[V]) Error() error { if !mi.Valid() { return errors.New("invalid memIterator") } return nil } -func (mi *memIterator) Valid() bool { +func (mi *memIterator[V]) Valid() bool { return mi.valid } -func (mi *memIterator) Next() { +func (mi *memIterator[V]) Next() { mi.assertValid() if mi.ascending { @@ -95,7 +98,7 @@ func (mi *memIterator) Next() { } } -func (mi *memIterator) keyInRange(key []byte) bool { +func (mi *memIterator[V]) keyInRange(key []byte) bool { if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { return false } @@ -105,15 +108,15 @@ func (mi *memIterator) keyInRange(key []byte) bool { return true } -func (mi *memIterator) Key() []byte { +func (mi *memIterator[V]) Key() []byte { return mi.iter.Item().key } -func (mi *memIterator) Value() []byte { +func (mi *memIterator[V]) Value() V { return mi.iter.Item().value } -func (mi *memIterator) assertValid() { +func (mi *memIterator[V]) assertValid() { if err := mi.Error(); err != nil { panic(err) } diff --git a/store/internal/btreeadaptor.go b/store/internal/btreeadaptor.go new file mode 100644 index 000000000000..322e0f4152fe --- /dev/null +++ b/store/internal/btreeadaptor.go @@ -0,0 +1,52 @@ +package internal + +import ( + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/internal/btree" + "cosmossdk.io/store/types" +) + +var _ types.KVStore = (*BTreeStore[[]byte])(nil) + +// BTreeStore is a wrapper for a BTree with GKVStore[V] implementation +type BTreeStore[V any] struct { + btree.BTree[V] + isZero func(V) bool + valueLen func(V) int +} + +// NewBTreeStore constructs new BTree adapter +func NewBTreeStore[V any](btree btree.BTree[V], isZero func(V) bool, valueLen func(V) int) *BTreeStore[V] { + return &BTreeStore[V]{btree, isZero, valueLen} +} + +// Hash Implements GKVStore. +func (ts *BTreeStore[V]) Has(key []byte) bool { + return !ts.isZero(ts.Get(key)) +} + +func (ts *BTreeStore[V]) Iterator(start, end []byte) types.GIterator[V] { + it, err := ts.BTree.Iterator(start, end) + if err != nil { + panic(err) + } + return it +} + +func (ts *BTreeStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] { + it, err := ts.BTree.ReverseIterator(start, end) + if err != nil { + panic(err) + } + return it +} + +// GetStoreType returns the type of the store. +func (ts *BTreeStore[V]) GetStoreType() types.StoreType { + return types.StoreTypeDB +} + +// CacheWrap branches the underlying store. +func (ts *BTreeStore[V]) CacheWrap() types.CacheWrap { + return cachekv.NewGStore(ts, ts.isZero, ts.valueLen) +} diff --git a/store/listenkv/store.go b/store/listenkv/store.go index 343be611b9f0..43d69fa663fc 100644 --- a/store/listenkv/store.go +++ b/store/listenkv/store.go @@ -1,6 +1,7 @@ package listenkv import ( + "cosmossdk.io/store/cachekv" "cosmossdk.io/store/types" ) @@ -130,5 +131,5 @@ func (s *Store) GetStoreType() types.StoreType { // CacheWrap implements the KVStore interface. It panics as a Store // cannot be cache wrapped. func (s *Store) CacheWrap() types.CacheWrap { - panic("cannot CacheWrap a ListenKVStore") + return cachekv.NewStore(s) } diff --git a/store/listenkv/store_test.go b/store/listenkv/store_test.go index 1f9ca4b2942e..f688f5c4c0d9 100644 --- a/store/listenkv/store_test.go +++ b/store/listenkv/store_test.go @@ -272,5 +272,5 @@ func TestListenKVStoreGetStoreType(t *testing.T) { func TestListenKVStoreCacheWrap(t *testing.T) { store := newEmptyListenKVStore(nil) - require.Panics(t, func() { store.CacheWrap() }) + store.CacheWrap() } diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 39b6d35e5f43..7908ac118303 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -63,17 +63,18 @@ type Store struct { iavlCacheSize int iavlDisableFastNode bool storesParams map[types.StoreKey]storeParams - stores map[types.StoreKey]types.CommitKVStore - keysByName map[string]types.StoreKey - initialVersion int64 - removalMap map[types.StoreKey]bool - traceWriter io.Writer - traceContext types.TraceContext - traceContextMutex sync.Mutex - interBlockCache types.MultiStorePersistentCache - listeners map[types.StoreKey]*types.MemoryListener - metrics metrics.StoreMetrics - commitHeader cmtproto.Header + // CommitStore is a common interface to unify generic CommitKVStore of different value types + stores map[types.StoreKey]types.CommitStore + keysByName map[string]types.StoreKey + initialVersion int64 + removalMap map[types.StoreKey]bool + traceWriter io.Writer + traceContext types.TraceContext + traceContextMutex sync.Mutex + interBlockCache types.MultiStorePersistentCache + listeners map[types.StoreKey]*types.MemoryListener + metrics metrics.StoreMetrics + commitHeader cmtproto.Header } var ( @@ -92,7 +93,7 @@ func NewStore(db dbm.DB, logger log.Logger, metricGatherer metrics.StoreMetrics) iavlCacheSize: iavl.DefaultIAVLCacheSize, iavlDisableFastNode: iavlDisablefastNodeDefault, storesParams: make(map[types.StoreKey]storeParams), - stores: make(map[types.StoreKey]types.CommitKVStore), + stores: make(map[types.StoreKey]types.CommitStore), keysByName: make(map[string]types.StoreKey), listeners: make(map[types.StoreKey]*types.MemoryListener), removalMap: make(map[types.StoreKey]bool), @@ -155,12 +156,6 @@ func (rs *Store) MountStoreWithDB(key types.StoreKey, typ types.StoreType, db db // GetCommitStore returns a mounted CommitStore for a given StoreKey. If the // store is wrapped in an inter-block cache, it will be unwrapped before returning. func (rs *Store) GetCommitStore(key types.StoreKey) types.CommitStore { - return rs.GetCommitKVStore(key) -} - -// GetCommitKVStore returns a mounted CommitKVStore for a given StoreKey. If the -// store is wrapped in an inter-block cache, it will be unwrapped before returning. -func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore { // If the Store has an inter-block cache, first attempt to lookup and unwrap // the underlying CommitKVStore by StoreKey. If it does not exist, fallback to // the main mapping of CommitKVStores. @@ -173,6 +168,17 @@ func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore { return rs.stores[key] } +// GetCommitKVStore returns a mounted CommitKVStore for a given StoreKey. If the +// store is wrapped in an inter-block cache, it will be unwrapped before returning. +func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore { + store, ok := rs.GetCommitStore(key).(types.CommitKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not CommitKVStore", key)) + } + + return store +} + // StoreKeysByName returns mapping storeNames -> StoreKeys func (rs *Store) StoreKeysByName() map[string]types.StoreKey { return rs.keysByName @@ -221,7 +227,7 @@ func (rs *Store) loadVersion(ver int64, upgrades *types.StoreUpgrades) error { } // load each Store (note this doesn't panic on unmounted keys now) - newStores := make(map[types.StoreKey]types.CommitKVStore) + newStores := make(map[types.StoreKey]types.CommitStore) storesKeys := make([]types.StoreKey, 0, len(rs.storesParams)) @@ -549,11 +555,13 @@ func (rs *Store) CacheWrap() types.CacheWrap { func (rs *Store) CacheMultiStore() types.CacheMultiStore { stores := make(map[types.StoreKey]types.CacheWrapper) for k, v := range rs.stores { - store := types.KVStore(v) - // Wire the listenkv.Store to allow listeners to observe the writes from the cache store, - // set same listeners on cache store will observe duplicated writes. - if rs.ListeningEnabled(k) { - store = listenkv.NewStore(store, k, rs.listeners[k]) + store := types.CacheWrapper(v) + if kv, ok := store.(types.KVStore); ok { + // Wire the listenkv.Store to allow listeners to observe the writes from the cache store, + // set same listeners on cache store will observe duplicated writes. + if rs.ListeningEnabled(k) { + store = listenkv.NewStore(kv, k, rs.listeners[k]) + } } stores[k] = store } @@ -569,7 +577,7 @@ func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStor var commitInfo *types.CommitInfo storeInfos := map[string]bool{} for key, store := range rs.stores { - var cacheStore types.KVStore + var cacheStore types.CacheWrapper switch store.GetStoreType() { case types.StoreTypeIAVL: // If the store is wrapped with an inter-block cache, we must first unwrap @@ -608,10 +616,12 @@ func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStor cacheStore = store } - // Wire the listenkv.Store to allow listeners to observe the writes from the cache store, - // set same listeners on cache store will observe duplicated writes. - if rs.ListeningEnabled(key) { - cacheStore = listenkv.NewStore(cacheStore, key, rs.listeners[key]) + if kv, ok := cacheStore.(types.KVStore); ok { + // Wire the listenkv.Store to allow listeners to observe the writes from the cache store, + // set same listeners on cache store will observe duplicated writes. + if rs.ListeningEnabled(key) { + cacheStore = listenkv.NewStore(kv, key, rs.listeners[key]) + } } cachedStores[key] = cacheStore @@ -627,7 +637,7 @@ func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStor // TODO: This isn't used directly upstream. Consider returning the Store as-is // instead of unwrapping. func (rs *Store) GetStore(key types.StoreKey) types.Store { - store := rs.GetCommitKVStore(key) + store := rs.GetCommitStore(key) if store == nil { panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) } @@ -646,7 +656,10 @@ func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore { if s == nil { panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) } - store := types.KVStore(s) + store, ok := s.(types.KVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not KVStore", key)) + } if rs.TracingEnabled() { store = tracekv.NewStore(store, rs.traceWriter, rs.getTracingContext()) @@ -658,6 +671,20 @@ func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore { return store } +// GetObjKVStore returns a mounted ObjKVStore for a given StoreKey. +func (rs *Store) GetObjKVStore(key types.StoreKey) types.ObjKVStore { + s := rs.stores[key] + if s == nil { + panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) + } + store, ok := s.(types.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + + return store +} + func (rs *Store) handlePruning(version int64) error { pruneHeight := rs.pruningManager.GetPruningHeight(version) rs.logger.Debug("prune start", "height", version) @@ -709,7 +736,7 @@ func (rs *Store) GetStoreByName(name string) types.Store { return nil } - return rs.GetCommitKVStore(key) + return rs.GetCommitStore(key) } // Query calls substore.Query with the same `req` where `req.Path` is @@ -824,7 +851,7 @@ func (rs *Store) Snapshot(height uint64, protoWriter protoio.Writer) error { stores := []namedStore{} keys := keysFromStoreKeyMap(rs.stores) for _, key := range keys { - switch store := rs.GetCommitKVStore(key).(type) { + switch store := rs.GetCommitStore(key).(type) { case *iavl.Store: stores = append(stores, namedStore{name: key.Name(), Store: store}) case *transient.Store, *mem.Store: @@ -988,7 +1015,7 @@ loop: return snapshotItem, rs.LoadLatestVersion() } -func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitKVStore, error) { +func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitStore, error) { var db dbm.DB if params.db != nil { @@ -1037,12 +1064,20 @@ func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID return transient.NewStore(), nil case types.StoreTypeMemory: + _, ok := key.(*types.ObjectStoreKey) + if !ok { + return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) + } + if _, ok := key.(*types.MemoryStoreKey); !ok { return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String()) } return mem.NewStore(), nil + case types.StoreTypeObject: + return transient.NewObjStore(), nil + default: panic(fmt.Sprintf("unrecognized store type %v", params.typ)) } @@ -1054,7 +1089,7 @@ func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo { for _, key := range keys { store := rs.stores[key] storeType := store.GetStoreType() - if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory { + if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory || storeType == types.StoreTypeObject { continue } storeInfos = append(storeInfos, types.StoreInfo{ @@ -1172,7 +1207,7 @@ func GetLatestVersion(db dbm.DB) int64 { } // Commits each store and returns a new commitInfo. -func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore, removalMap map[types.StoreKey]bool) *types.CommitInfo { +func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore, removalMap map[types.StoreKey]bool) *types.CommitInfo { storeInfos := make([]types.StoreInfo, 0, len(storeMap)) storeKeys := keysFromStoreKeyMap(storeMap) @@ -1192,7 +1227,7 @@ func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore } storeType := store.GetStoreType() - if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory { + if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory || storeType == types.StoreTypeObject { continue } diff --git a/store/rootmulti/store_test.go b/store/rootmulti/store_test.go index 983fc411f3da..34a3d8b1f626 100644 --- a/store/rootmulti/store_test.go +++ b/store/rootmulti/store_test.go @@ -786,6 +786,7 @@ var ( testStoreKey1 = types.NewKVStoreKey("store1") testStoreKey2 = types.NewKVStoreKey("store2") testStoreKey3 = types.NewKVStoreKey("store3") + testStoreKey4 = types.NewKVStoreKey("store4") ) func newMultiStoreWithMounts(db dbm.DB, pruningOpts pruningtypes.PruningOptions) *Store { @@ -858,7 +859,7 @@ func getExpectedCommitID(store *Store, ver int64) types.CommitID { } } -func hashStores(stores map[types.StoreKey]types.CommitKVStore) []byte { +func hashStores(stores map[types.StoreKey]types.CommitStore) []byte { m := make(map[string][]byte, len(stores)) for key, store := range stores { name := key.Name() @@ -913,35 +914,39 @@ func TestStateListeners(t *testing.T) { require.Empty(t, ms.PopStateCache()) } -type commitKVStoreStub struct { - types.CommitKVStore +type commitStoreStub struct { + types.CommitStore Committed int } -func (stub *commitKVStoreStub) Commit() types.CommitID { - commitID := stub.CommitKVStore.Commit() +func (stub *commitStoreStub) Commit() types.CommitID { + commitID := stub.CommitStore.Commit() stub.Committed++ return commitID } -func prepareStoreMap() (map[types.StoreKey]types.CommitKVStore, error) { +func prepareStoreMap() (map[types.StoreKey]types.CommitStore, error) { var db dbm.DB = dbm.NewMemDB() store := NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) store.MountStoreWithDB(types.NewKVStoreKey("iavl1"), types.StoreTypeIAVL, nil) store.MountStoreWithDB(types.NewKVStoreKey("iavl2"), types.StoreTypeIAVL, nil) store.MountStoreWithDB(types.NewTransientStoreKey("trans1"), types.StoreTypeTransient, nil) + store.MountStoreWithDB(types.NewObjectStoreKey("obj1"), types.StoreTypeObject, nil) if err := store.LoadLatestVersion(); err != nil { return nil, err } - return map[types.StoreKey]types.CommitKVStore{ - testStoreKey1: &commitKVStoreStub{ - CommitKVStore: store.GetStoreByName("iavl1").(types.CommitKVStore), + return map[types.StoreKey]types.CommitStore{ + testStoreKey1: &commitStoreStub{ + CommitStore: store.GetStoreByName("iavl1").(types.CommitStore), }, - testStoreKey2: &commitKVStoreStub{ - CommitKVStore: store.GetStoreByName("iavl2").(types.CommitKVStore), + testStoreKey2: &commitStoreStub{ + CommitStore: store.GetStoreByName("iavl2").(types.CommitStore), }, - testStoreKey3: &commitKVStoreStub{ - CommitKVStore: store.GetStoreByName("trans1").(types.CommitKVStore), + testStoreKey3: &commitStoreStub{ + CommitStore: store.GetStoreByName("trans1").(types.CommitStore), + }, + testStoreKey4: &commitStoreStub{ + CommitStore: store.GetStoreByName("obj1").(types.CommitStore), }, }, nil } @@ -972,7 +977,7 @@ func TestCommitStores(t *testing.T) { t.Run(tc.name, func(t *testing.T) { storeMap, err := prepareStoreMap() require.NoError(t, err) - store := storeMap[testStoreKey1].(*commitKVStoreStub) + store := storeMap[testStoreKey1].(*commitStoreStub) for i := tc.committed; i > 0; i-- { store.Commit() } diff --git a/store/tracekv/store.go b/store/tracekv/store.go index 7df8b0f97ba7..9fbc1bb4a3ef 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -6,6 +6,7 @@ import ( "io" "cosmossdk.io/errors" + "cosmossdk.io/store/cachekv" "cosmossdk.io/store/types" ) @@ -164,7 +165,7 @@ func (tkv *Store) GetStoreType() types.StoreType { // CacheWrap implements the KVStore interface. It panics because a Store // cannot be branched. func (tkv *Store) CacheWrap() types.CacheWrap { - panic("cannot CacheWrap a TraceKVStore") + return cachekv.NewStore(tkv) } // writeOperation writes a KVStore operation to the underlying io.Writer as diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index d276accd530a..00e4406c7574 100644 --- a/store/tracekv/store_test.go +++ b/store/tracekv/store_test.go @@ -283,5 +283,5 @@ func TestTraceKVStoreGetStoreType(t *testing.T) { func TestTraceKVStoreCacheWrap(t *testing.T) { store := newEmptyTraceKVStore(nil) - require.Panics(t, func() { store.CacheWrap() }) + store.CacheWrap() } diff --git a/store/transient/store.go b/store/transient/store.go index 6f393279f571..53332e9f33c1 100644 --- a/store/transient/store.go +++ b/store/transient/store.go @@ -1,9 +1,8 @@ package transient import ( - dbm "github.com/cosmos/cosmos-db" - - "cosmossdk.io/store/dbadapter" + "cosmossdk.io/store/internal" + "cosmossdk.io/store/internal/btree" pruningtypes "cosmossdk.io/store/pruning/types" "cosmossdk.io/store/types" ) @@ -11,43 +10,73 @@ import ( var ( _ types.Committer = (*Store)(nil) _ types.KVStore = (*Store)(nil) + + _ types.Committer = (*ObjStore)(nil) + _ types.ObjKVStore = (*ObjStore)(nil) ) // Store is a wrapper for a MemDB with Commiter implementation +type GStore[V any] struct { + internal.BTreeStore[V] +} + +// NewGStore constructs new generic transient store +func NewGStore[V any](isZero func(V) bool, valueLen func(V) int) *GStore[V] { + return &GStore[V]{*internal.NewBTreeStore(btree.NewBTree[V](), isZero, valueLen)} +} + +// Store specializes GStore for []byte type Store struct { - dbadapter.Store + GStore[[]byte] } -// Constructs new MemDB adapter func NewStore() *Store { - return &Store{Store: dbadapter.Store{DB: dbm.NewMemDB()}} + return &Store{*NewGStore( + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + )} +} + +func (*Store) GetStoreType() types.StoreType { + return types.StoreTypeTransient +} + +// ObjStore specializes GStore for any +type ObjStore struct { + GStore[any] +} + +func NewObjStore() *ObjStore { + return &ObjStore{*NewGStore( + func(v any) bool { return v == nil }, + func(v any) int { return 1 }, // for value length validation + )} +} + +func (*ObjStore) GetStoreType() types.StoreType { + return types.StoreTypeObject } // Implements CommitStore // Commit cleans up Store. -func (ts *Store) Commit() (id types.CommitID) { - ts.Store = dbadapter.Store{DB: dbm.NewMemDB()} +func (ts *GStore[V]) Commit() (id types.CommitID) { + ts.Clear() return } -func (ts *Store) SetPruning(_ pruningtypes.PruningOptions) {} +func (ts *GStore[V]) SetPruning(_ pruningtypes.PruningOptions) {} // GetPruning is a no-op as pruning options cannot be directly set on this store. // They must be set on the root commit multi-store. -func (ts *Store) GetPruning() pruningtypes.PruningOptions { +func (ts *GStore[V]) GetPruning() pruningtypes.PruningOptions { return pruningtypes.NewPruningOptions(pruningtypes.PruningUndefined) } // Implements CommitStore -func (ts *Store) LastCommitID() types.CommitID { +func (ts *GStore[V]) LastCommitID() types.CommitID { return types.CommitID{} } -func (ts *Store) WorkingHash() []byte { +func (ts *GStore[V]) WorkingHash() []byte { return []byte{} } - -// Implements Store. -func (ts *Store) GetStoreType() types.StoreType { - return types.StoreTypeTransient -} diff --git a/store/types/store.go b/store/types/store.go index f37e9860baaa..2def13f3f2d3 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -136,6 +136,7 @@ type MultiStore interface { // If the store does not exist, panics. GetStore(StoreKey) Store GetKVStore(StoreKey) KVStore + GetObjKVStore(StoreKey) ObjKVStore // TracingEnabled returns if tracing is enabled for the MultiStore. TracingEnabled() bool @@ -229,25 +230,25 @@ type CommitMultiStore interface { //---------subsp------------------------------- // KVStore -// BasicKVStore is a simple interface to get/set data -type BasicKVStore interface { +// GBasicKVStore is a simple interface to get/set data +type GBasicKVStore[V any] interface { // Get returns nil if key doesn't exist. Panics on nil key. - Get(key []byte) []byte + Get(key []byte) V // Has checks if a key exists. Panics on nil key. Has(key []byte) bool // Set sets the key. Panics on nil key or value. - Set(key, value []byte) + Set(key []byte, value V) // Delete deletes the key. Panics on nil key. Delete(key []byte) } -// KVStore additionally provides iteration and deletion -type KVStore interface { +// GKVStore additionally provides iteration and deletion +type GKVStore[V any] interface { Store - BasicKVStore + GBasicKVStore[V] // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. @@ -255,18 +256,54 @@ type KVStore interface { // To iterate over entire domain, use store.Iterator(nil, nil) // CONTRACT: No writes may happen within a domain while an iterator exists over it. // Exceptionally allowed for cachekv.Store, safe to write in the modules. - Iterator(start, end []byte) Iterator + Iterator(start, end []byte) GIterator[V] // Iterator over a domain of keys in descending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. // CONTRACT: No writes may happen within a domain while an iterator exists over it. // Exceptionally allowed for cachekv.Store, safe to write in the modules. - ReverseIterator(start, end []byte) Iterator + ReverseIterator(start, end []byte) GIterator[V] } -// Iterator is an alias db's Iterator for convenience. -type Iterator = dbm.Iterator +// GIterator is the generic version of dbm's Iterator +type GIterator[V any] interface { + // Domain returns the start (inclusive) and end (exclusive) limits of the iterator. + // CONTRACT: start, end readonly []byte + Domain() (start, end []byte) + + // Valid returns whether the current iterator is valid. Once invalid, the Iterator remains + // invalid forever. + Valid() bool + + // Next moves the iterator to the next key in the database, as defined by order of iteration. + // If Valid returns false, this method will panic. + Next() + + // Key returns the key at the current position. Panics if the iterator is invalid. + // CONTRACT: key readonly []byte + Key() (key []byte) + + // Value returns the value at the current position. Panics if the iterator is invalid. + // CONTRACT: value readonly []byte + Value() (value V) + + // Error returns the last error encountered by the iterator, if any. + Error() error + + // Close closes the iterator, relasing any allocated resources. + Close() error +} + +type ( + Iterator = GIterator[[]byte] + BasicKVStore = GBasicKVStore[[]byte] + KVStore = GKVStore[[]byte] + + ObjIterator = GIterator[any] + ObjBasicKVStore = GBasicKVStore[any] + ObjKVStore = GKVStore[any] +) // CacheKVStore branches a KVStore and provides read cache functionality. // After calling .Write() on the CacheKVStore, all previously created @@ -292,11 +329,10 @@ type CommitKVStore interface { // a Committer, since Commit ephemeral store make no sense. It can return KVStore, // HeapStore, SpaceStore, etc. type CacheWrap interface { + CacheWrapper + // Write syncs with the underlying store. Write() - - // CacheWrap recursively wraps again. - CacheWrap() CacheWrap } type CacheWrapper interface { @@ -326,6 +362,7 @@ const ( StoreTypeMemory StoreTypeSMT StoreTypePersistent + StoreTypeObject ) func (st StoreType) String() string { @@ -350,6 +387,9 @@ func (st StoreType) String() string { case StoreTypePersistent: return "StoreTypePersistent" + + case StoreTypeObject: + return "StoreTypeObject" } return "unknown store type" @@ -429,6 +469,29 @@ func (key *TransientStoreKey) String() string { return fmt.Sprintf("TransientStoreKey{%p, %s}", key, key.name) } +// ObjectStoreKey is used for indexing transient stores in a MultiStore +type ObjectStoreKey struct { + name string +} + +// Constructs new ObjectStoreKey +// Must return a pointer according to the ocap principle +func NewObjectStoreKey(name string) *ObjectStoreKey { + return &ObjectStoreKey{ + name: name, + } +} + +// Implements StoreKey +func (key *ObjectStoreKey) Name() string { + return key.name +} + +// Implements StoreKey +func (key *ObjectStoreKey) String() string { + return fmt.Sprintf("ObjectStoreKey{%p, %s}", key, key.name) +} + // MemoryStoreKey defines a typed key to be used with an in-memory KVStore. type MemoryStoreKey struct { name string diff --git a/store/types/validity.go b/store/types/validity.go index a1fbaba999c7..a1363ff28b1d 100644 --- a/store/types/validity.go +++ b/store/types/validity.go @@ -1,5 +1,7 @@ package types +import "errors" + var ( // 128K - 1 MaxKeyLength = (1 << 17) - 1 @@ -22,7 +24,12 @@ func AssertValidValue(value []byte) { if value == nil { panic("value is nil") } - if len(value) > MaxValueLength { - panic("value is too large") + AssertValidValueLength(len(value)) +} + +// AssertValidValueLength checks if the value length is within length limit +func AssertValidValueLength(l int) { + if l > MaxValueLength { + panic(errors.New("value is too large")) } } diff --git a/tests/go.mod b/tests/go.mod index c5e52f2bf018..4dd82ea6c82e 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -207,6 +207,7 @@ require ( replace ( // We always want to test against the latest version of the simapp. cosmossdk.io/simapp => ../simapp + cosmossdk.io/store => ../store github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 // We always want to test against the latest version of the SDK. github.com/cosmos/cosmos-sdk => ../. diff --git a/tests/go.sum b/tests/go.sum index e97a332d6581..9853a5bdfd93 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -200,8 +200,6 @@ cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= -cosmossdk.io/store v1.0.2 h1:lSg5BTvJBHUDwswNNyeh4K/CbqiHER73VU4nDNb8uk0= -cosmossdk.io/store v1.0.2/go.mod h1:EFtENTqVTuWwitGW1VwaBct+yDagk7oG/axBMPH+FXs= cosmossdk.io/x/circuit v0.1.0 h1:IAej8aRYeuOMritczqTlljbUVHq1E85CpBqaCTwYgXs= cosmossdk.io/x/circuit v0.1.0/go.mod h1:YDzblVE8+E+urPYQq5kq5foRY/IzhXovSYXb4nwd39w= cosmossdk.io/x/evidence v0.1.0 h1:J6OEyDl1rbykksdGynzPKG5R/zm6TacwW2fbLTW4nCk= diff --git a/types/context.go b/types/context.go index 36c5ccdc8094..248b5af95793 100644 --- a/types/context.go +++ b/types/context.go @@ -377,6 +377,11 @@ func (c Context) TransientStore(key storetypes.StoreKey) storetypes.KVStore { return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.transientKVGasConfig) } +// ObjectStore fetches an object store from the MultiStore, +func (c Context) OjectStore(key storetypes.StoreKey) storetypes.ObjKVStore { + return gaskv.NewObjStore(c.ms.GetObjKVStore(key), c.gasMeter, c.transientKVGasConfig) +} + // CacheContext returns a new Context with the multi-store cached and a new // EventManager. The cached context is written to the context when writeCache // is called. Note, events are automatically emitted on the parent context's diff --git a/x/group/internal/orm/testsupport.go b/x/group/internal/orm/testsupport.go index b4fe3d0354ee..b03fcec42ba9 100644 --- a/x/group/internal/orm/testsupport.go +++ b/x/group/internal/orm/testsupport.go @@ -26,8 +26,8 @@ func NewMockContext() *MockContext { } func (m MockContext) KVStore(key storetypes.StoreKey) storetypes.KVStore { - if s := m.store.GetCommitKVStore(key); s != nil { - return s + if s := m.store.GetCommitStore(key); s != nil { + return s.(storetypes.KVStore) } m.store.MountStoreWithDB(key, storetypes.StoreTypeIAVL, m.db) if err := m.store.LoadLatestVersion(); err != nil {