Skip to content

Commit 7d01116

Browse files
author
yihuang
authored
Problem: no API to use the new CoW branch store (#243)
* Support RunAtomic API * add unit test
1 parent 56f3e15 commit 7d01116

File tree

9 files changed

+123
-11
lines changed

9 files changed

+123
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4444
* (baseapp) [#206](https://github.com/crypto-org-chain/cosmos-sdk/pull/206) Support mount object store in baseapp, add `ObjectStore` api in context.
4545
* (bank) [#237](https://github.com/crypto-org-chain/cosmos-sdk/pull/237) Support virtual accounts in sending coins.
4646
* (x/bank) [#239](https://github.com/crypto-org-chain/cosmos-sdk/pull/239) Add low level `AddBalance`,`SubBalance` APIs to bank keeper.
47+
* [#243](https://github.com/crypto-org-chain/cosmos-sdk/pull/243) Support `RunAtomic` API in `Context` to use new CoW branched cache store.
4748

4849
## [Unreleased-Upstream]
4950

store/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
3232
* [#240](https://github.com/crypto-org-chain/cosmos-sdk/pull/240) Split methods from `MultiStore` into specialized `RootMultiStore`, keep `MultiStore` generic.
3333
* [#241](https://github.com/crypto-org-chain/cosmos-sdk/pull/241) Refactor the cache store to be btree backed, prepare to support copy-on-write atomic branching.
3434
* [#242](https://github.com/crypto-org-chain/cosmos-sdk/pull/242) Init cache on cache lazily, save memory allocations.
35+
* [#243](https://github.com/crypto-org-chain/cosmos-sdk/pull/243) Support `RunAtomic` API to use new CoW cache store.
3536

3637
## v1.1.0 (March 20, 2024)
3738

store/cachekv/store.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ func (store *GStore[V]) GetStoreType() types.StoreType {
5353
// Clone creates a copy-on-write snapshot of the cache store,
5454
// it only performs a shallow copy so is very fast.
5555
func (store *GStore[V]) Clone() types.BranchStore {
56-
return &GStore[V]{
57-
writeSet: store.writeSet.Copy(),
58-
parent: store.parent,
59-
}
56+
v := *store
57+
v.writeSet = store.writeSet.Copy()
58+
return &v
6059
}
6160

6261
// swapCache swap out the internal cache store and leave the current store unusable.

store/cachekv/store_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,20 @@ func TestIteratorDeadlock(t *testing.T) {
447447
defer it2.Close()
448448
}
449449

450+
func TestBranchStore(t *testing.T) {
451+
mem := dbadapter.Store{DB: dbm.NewMemDB()}
452+
store := cachekv.NewStore(mem)
453+
454+
store.Set([]byte("key1"), []byte("value1"))
455+
456+
branch := store.Clone().(types.CacheKVStore)
457+
branch.Set([]byte("key1"), []byte("value2"))
458+
459+
require.Equal(t, []byte("value1"), store.Get([]byte("key1")))
460+
store.Restore(branch.(types.BranchStore))
461+
require.Equal(t, []byte("value2"), store.Get([]byte("key1")))
462+
}
463+
450464
//-------------------------------------------------------------------------------------------
451465
// do some random ops
452466

store/cachemulti/store.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type Store struct {
2525
traceWriter io.Writer
2626
traceContext types.TraceContext
2727
parentStore func(types.StoreKey) types.CacheWrap
28+
29+
branched bool
2830
}
2931

3032
var _ types.CacheMultiStore = Store{}
@@ -43,7 +45,7 @@ func NewFromKVStore(
4345
}
4446

4547
for key, store := range stores {
46-
cms.initStore(key, store)
48+
cms.stores[key] = cms.initStore(key, store)
4749
}
4850

4951
return cms
@@ -78,9 +80,7 @@ func (cms Store) initStore(key types.StoreKey, store types.CacheWrapper) types.C
7880
store = tracekv.NewStore(kvstore, cms.traceWriter, tctx)
7981
}
8082
}
81-
cache := store.CacheWrap()
82-
cms.stores[key] = cache
83-
return cache
83+
return store.CacheWrap()
8484
}
8585

8686
// SetTracer sets the tracer for the MultiStore that the underlying
@@ -118,6 +118,9 @@ func (cms Store) GetStoreType() types.StoreType {
118118

119119
// Write calls Write on each underlying store.
120120
func (cms Store) Write() {
121+
if cms.branched {
122+
panic("cannot Write on branched store")
123+
}
121124
for _, store := range cms.stores {
122125
store.Write()
123126
}
@@ -135,9 +138,14 @@ func (cms Store) CacheMultiStore() types.CacheMultiStore {
135138

136139
func (cms Store) getCacheWrap(key types.StoreKey) types.CacheWrap {
137140
store, ok := cms.stores[key]
138-
if !ok && cms.parentStore != nil {
141+
if !ok {
139142
// load on demand
140-
store = cms.initStore(key, cms.parentStore(key))
143+
if cms.branched {
144+
store = cms.parentStore(key).(types.BranchStore).Clone().(types.CacheWrap)
145+
} else if cms.parentStore != nil {
146+
store = cms.initStore(key, cms.parentStore(key))
147+
}
148+
cms.stores[key] = store
141149
}
142150
if key == nil || store == nil {
143151
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
@@ -171,3 +179,36 @@ func (cms Store) GetObjKVStore(key types.StoreKey) types.ObjKVStore {
171179
}
172180
return store
173181
}
182+
183+
func (cms Store) Clone() Store {
184+
return Store{
185+
stores: make(map[types.StoreKey]types.CacheWrap),
186+
187+
traceWriter: cms.traceWriter,
188+
traceContext: cms.traceContext,
189+
parentStore: cms.getCacheWrap,
190+
191+
branched: true,
192+
}
193+
}
194+
195+
func (cms Store) Restore(other Store) {
196+
if !other.branched {
197+
panic("cannot restore from non-branched store")
198+
}
199+
200+
// restore the stores
201+
for k, v := range other.stores {
202+
cms.stores[k].(types.BranchStore).Restore(v.(types.BranchStore))
203+
}
204+
}
205+
206+
func (cms Store) RunAtomic(cb func(types.CacheMultiStore) error) error {
207+
branch := cms.Clone()
208+
if err := cb(branch); err != nil {
209+
return err
210+
}
211+
212+
cms.Restore(branch)
213+
return nil
214+
}

store/cachemulti/store_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import (
44
"fmt"
55
"testing"
66

7+
dbm "github.com/cosmos/cosmos-db"
78
"github.com/stretchr/testify/require"
89

10+
"cosmossdk.io/store/dbadapter"
11+
"cosmossdk.io/store/internal"
12+
"cosmossdk.io/store/internal/btree"
913
"cosmossdk.io/store/types"
1014
)
1115

@@ -22,3 +26,29 @@ func TestStoreGetKVStore(t *testing.T) {
2226
require.PanicsWithValue(errMsg,
2327
func() { s.GetKVStore(key) })
2428
}
29+
30+
func TestRunAtomic(t *testing.T) {
31+
store := dbadapter.Store{DB: dbm.NewMemDB()}
32+
objStore := internal.NewBTreeStore(btree.NewBTree[any](),
33+
func(v any) bool { return v == nil },
34+
func(v any) int { return 1 },
35+
)
36+
keys := map[string]types.StoreKey{
37+
"abc": types.NewKVStoreKey("abc"),
38+
"obj": types.NewObjectStoreKey("obj"),
39+
"lazy": types.NewKVStoreKey("lazy"),
40+
}
41+
s := Store{stores: map[types.StoreKey]types.CacheWrap{
42+
keys["abc"]: store.CacheWrap(),
43+
keys["obj"]: objStore.CacheWrap(),
44+
keys["lazy"]: nil,
45+
}}
46+
47+
s.RunAtomic(func(ms types.CacheMultiStore) error {
48+
ms.GetKVStore(keys["abc"]).Set([]byte("key"), []byte("value"))
49+
ms.GetObjKVStore(keys["obj"]).Set([]byte("key"), "value")
50+
return nil
51+
})
52+
require.Equal(t, []byte("value"), s.GetKVStore(keys["abc"]).Get([]byte("key")))
53+
require.Equal(t, "value", s.GetObjKVStore(keys["obj"]).Get([]byte("key")).(string))
54+
}

store/internal/btreeadaptor.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import (
66
"cosmossdk.io/store/types"
77
)
88

9-
var _ types.KVStore = (*BTreeStore[[]byte])(nil)
9+
var (
10+
_ types.KVStore = (*BTreeStore[[]byte])(nil)
11+
_ types.ObjKVStore = (*BTreeStore[any])(nil)
12+
)
1013

1114
// BTreeStore is a wrapper for a BTree with GKVStore[V] implementation
1215
type BTreeStore[V any] struct {

store/types/store.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ type RootMultiStore interface {
163163
type CacheMultiStore interface {
164164
MultiStore
165165
Write() // Writes operations to underlying KVStore
166+
167+
RunAtomic(func(CacheMultiStore) error) error
166168
}
167169

168170
// CommitMultiStore is an interface for a MultiStore without cache capabilities.

types/context.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package types
22

33
import (
44
"context"
5+
"errors"
56
"time"
67

78
abci "github.com/cometbft/cometbft/abci/types"
@@ -398,6 +399,26 @@ func (c Context) CacheContext() (cc Context, writeCache func()) {
398399
return cc, writeCache
399400
}
400401

402+
// RunAtomic execute the callback function atomically, i.e. the state and event changes are
403+
// only persisted if the callback returns no error, or discarded as a whole.
404+
// It uses an efficient approach than CacheContext, without wrapping stores.
405+
func (c Context) RunAtomic(cb func(Context) error) error {
406+
evtManager := NewEventManager()
407+
cacheMS, ok := c.ms.(storetypes.CacheMultiStore)
408+
if !ok {
409+
return errors.New("multistore is not a CacheMultiStore")
410+
}
411+
if err := cacheMS.RunAtomic(func(ms storetypes.CacheMultiStore) error {
412+
ctx := c.WithMultiStore(ms).WithEventManager(evtManager)
413+
return cb(ctx)
414+
}); err != nil {
415+
return err
416+
}
417+
418+
c.EventManager().EmitEvents(evtManager.Events())
419+
return nil
420+
}
421+
401422
var (
402423
_ context.Context = Context{}
403424
_ storetypes.Context = Context{}

0 commit comments

Comments
 (0)