diff --git a/CHANGELOG.md b/CHANGELOG.md index 77579ff6a7a2..07f983eff8b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (auth) [#13460](https://github.com/cosmos/cosmos-sdk/pull/13460) The `q auth address-by-id` CLI command has been renamed to `q auth address-by-acc-num` to be more explicit. However, the old `address-by-id` version is still kept as an alias, for backwards compatibility. +### Bug Fixes + +* (store) [#13459](https://github.com/cosmos/cosmos-sdk/pull/13459) Don't let state listener observe the uncommitted writes. + ## [v0.46.2](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.2) - 2022-10-03 ### API Breaking Changes diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go index 2e39554e17ce..deb1d46272dd 100644 --- a/store/cachemulti/store.go +++ b/store/cachemulti/store.go @@ -45,6 +45,9 @@ func NewFromKVStore( keys map[string]types.StoreKey, traceWriter io.Writer, traceContext types.TraceContext, listeners map[types.StoreKey][]types.WriteListener, ) Store { + if listeners == nil { + listeners = make(map[types.StoreKey][]types.WriteListener) + } cms := Store{ db: cachekv.NewStore(store), stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), @@ -86,7 +89,8 @@ func newCacheMultiStoreFromCMS(cms Store) Store { stores[k] = v } - return NewFromKVStore(cms.db, stores, nil, cms.traceWriter, cms.traceContext, cms.listeners) + // don't pass listeners to nested cache store. + return NewFromKVStore(cms.db, stores, nil, cms.traceWriter, cms.traceContext, nil) } // SetTracer sets the tracer for the MultiStore that the underlying diff --git a/store/rootmulti/store_test.go b/store/rootmulti/store_test.go index ccd58b153feb..064b724f78b8 100644 --- a/store/rootmulti/store_test.go +++ b/store/rootmulti/store_test.go @@ -912,3 +912,52 @@ func hashStores(stores map[types.StoreKey]types.CommitKVStore) []byte { } return sdkmaps.HashFromMap(m) } + +type MockListener struct { + stateCache []types.StoreKVPair +} + +func (tl *MockListener) OnWrite(storeKey types.StoreKey, key []byte, value []byte, delete bool) error { + tl.stateCache = append(tl.stateCache, types.StoreKVPair{ + StoreKey: storeKey.Name(), + Key: key, + Value: value, + Delete: delete, + }) + return nil +} + +func TestStateListeners(t *testing.T) { + var db dbm.DB = dbm.NewMemDB() + ms := newMultiStoreWithMounts(db, pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) + + listener := &MockListener{} + ms.AddListeners(testStoreKey1, []types.WriteListener{listener}) + + require.NoError(t, ms.LoadLatestVersion()) + cacheMulti := ms.CacheMultiStore() + + store1 := cacheMulti.GetKVStore(testStoreKey1) + store1.Set([]byte{1}, []byte{1}) + require.Empty(t, listener.stateCache) + + // writes are observed when cache store commit. + cacheMulti.Write() + require.Equal(t, 1, len(listener.stateCache)) + + // test nested cache store + listener.stateCache = []types.StoreKVPair{} + nested := cacheMulti.CacheMultiStore() + + store1 = nested.GetKVStore(testStoreKey1) + store1.Set([]byte{1}, []byte{1}) + require.Empty(t, listener.stateCache) + + // writes are not observed when nested cache store commit + nested.Write() + require.Empty(t, listener.stateCache) + + // writes are observed when inner cache store commit + cacheMulti.Write() + require.Equal(t, 1, len(listener.stateCache)) +}