diff --git a/giga/deps/store/fast_cachekv.go b/giga/deps/store/fast_cachekv.go new file mode 100644 index 0000000000..13e9eed8fe --- /dev/null +++ b/giga/deps/store/fast_cachekv.go @@ -0,0 +1,155 @@ +package store + +import ( + "bytes" + "io" + "sort" + + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" +) + +// FastStore is a single-goroutine giga cachekv store using plain maps instead of sync.Map. +// It MUST NOT be used from multiple goroutines concurrently. +// Use this for snapshot stores in the giga executor path where each store is +// owned by a single task goroutine. +type FastStore struct { + cache map[string]*types.CValue + deleted map[string]struct{} + parent types.KVStore + storeKey types.StoreKey +} + +var _ types.CacheKVStore = (*FastStore)(nil) + +// NewFastStore creates a new FastStore backed by plain maps. +func NewFastStore(parent types.KVStore, storeKey types.StoreKey) *FastStore { + return &FastStore{ + cache: make(map[string]*types.CValue), + deleted: make(map[string]struct{}), + parent: parent, + storeKey: storeKey, + } +} + +func (store *FastStore) GetWorkingHash() ([]byte, error) { + panic("should never attempt to get working hash from cache kv store") +} + +func (store *FastStore) GetStoreType() types.StoreType { + return store.parent.GetStoreType() +} + +func (store *FastStore) Get(key []byte) []byte { + types.AssertValidKey(key) + keyStr := UnsafeBytesToStr(key) + if cv, ok := store.cache[keyStr]; ok { + return cv.Value() + } + return store.parent.Get(key) +} + +func (store *FastStore) Set(key []byte, value []byte) { + types.AssertValidKey(key) + types.AssertValidValue(value) + store.setCacheValue(key, value, false, true) +} + +func (store *FastStore) Has(key []byte) bool { + return store.Get(key) != nil +} + +func (store *FastStore) Delete(key []byte) { + types.AssertValidKey(key) + store.setCacheValue(key, nil, true, true) +} + +func (store *FastStore) Write() { + keys := make([]string, 0, len(store.cache)) + for k, v := range store.cache { + if v.Dirty() { + keys = append(keys, k) + } + } + sort.Strings(keys) + + for _, key := range keys { + if _, del := store.deleted[key]; del { + store.parent.Delete([]byte(key)) + continue + } + if cv, ok := store.cache[key]; ok && cv.Value() != nil { + store.parent.Set([]byte(key), cv.Value()) + } + } + + // Clear both maps — in the FastStore snapshot chain, parents are either + // another FastStore or a VIS, both of which make writes immediately + // visible. Reads after Write() fall through to the parent. + clear(store.cache) + clear(store.deleted) +} + +func (store *FastStore) CacheWrap(storeKey types.StoreKey) types.CacheWrap { + return NewFastStore(store, storeKey) +} + +func (store *FastStore) CacheWrapWithTrace(storeKey types.StoreKey, w io.Writer, tc types.TraceContext) types.CacheWrap { + return NewFastStore(tracekv.NewStore(store, w, tc), storeKey) +} + +func (store *FastStore) VersionExists(version int64) bool { + return store.parent.VersionExists(version) +} + +func (store *FastStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { + types.AssertValidKey(key) + keyStr := UnsafeBytesToStr(key) + store.cache[keyStr] = types.NewCValue(value, dirty) + if deleted { + store.deleted[keyStr] = struct{}{} + } else { + delete(store.deleted, keyStr) + } +} + +func (store *FastStore) GetParent() types.KVStore { + return store.parent +} + +func (store *FastStore) DeleteAll(start, end []byte) error { + for _, k := range store.GetAllKeyStrsInRange(start, end) { + store.Delete([]byte(k)) + } + return nil +} + +func (store *FastStore) GetAllKeyStrsInRange(start, end []byte) (res []string) { + keyStrs := map[string]struct{}{} + for _, pk := range store.parent.GetAllKeyStrsInRange(start, end) { + keyStrs[pk] = struct{}{} + } + for k, v := range store.cache { + kbz := []byte(k) + if bytes.Compare(kbz, start) < 0 || bytes.Compare(kbz, end) >= 0 { + continue + } + if v.Value() == nil { + delete(keyStrs, k) + } else { + keyStrs[k] = struct{}{} + } + } + for k := range keyStrs { + res = append(res, k) + } + return res +} + +func (store *FastStore) Iterator(start, end []byte) types.Iterator { + panic("unexpected iterator call on fast giga cachekv store") +} + +func (store *FastStore) ReverseIterator(start, end []byte) types.Iterator { + panic("unexpected reverse iterator call on fast giga cachekv store") +} diff --git a/giga/deps/xevm/state/state.go b/giga/deps/xevm/state/state.go index e57e962221..70f048fb3e 100644 --- a/giga/deps/xevm/state/state.go +++ b/giga/deps/xevm/state/state.go @@ -101,8 +101,22 @@ func (s *DBImpl) HasSelfDestructed(acc common.Address) bool { return bytes.Equal(val, AccountDeleted) } +// gigaCacheMultiStorer is implemented by cachemulti.Store to provide a +// lightweight CMS that uses plain maps instead of sync.Map. This is safe +// for single-goroutine use in the giga executor snapshot path. +type gigaCacheMultiStorer interface { + CacheMultiStoreGiga() storetypes.CacheMultiStore +} + func (s *DBImpl) Snapshot() int { - newCtx := s.ctx.WithMultiStore(s.ctx.MultiStore().CacheMultiStore()).WithEventManager(sdk.NewEventManager()) + ms := s.ctx.MultiStore() + var cms storetypes.CacheMultiStore + if gms, ok := ms.(gigaCacheMultiStorer); ok { + cms = gms.CacheMultiStoreGiga() + } else { + cms = ms.CacheMultiStore() + } + newCtx := s.ctx.WithMultiStore(cms).WithEventManager(sdk.NewEventManager()) s.snapshottedCtxs = append(s.snapshottedCtxs, s.ctx) s.ctx = newCtx version := len(s.snapshottedCtxs) - 1 diff --git a/sei-cosmos/store/cachemulti/store.go b/sei-cosmos/store/cachemulti/store.go index eb4f8bce05..bfa03b3c28 100644 --- a/sei-cosmos/store/cachemulti/store.go +++ b/sei-cosmos/store/cachemulti/store.go @@ -37,6 +37,8 @@ type Store struct { materializeOnce *sync.Once closers []io.Closer + + fast bool // when true, lazy store creation uses FastStore (plain maps) } var _ types.CacheMultiStore = Store{} @@ -132,6 +134,75 @@ func newCacheMultiStoreFromCMS(cms Store) Store { return NewFromKVStore(cms.db, stores, gigaStores, cms.keys, cms.gigaKeys, cms.traceWriter, cms.traceContext) } +// newFastCacheMultiStoreFromCMS creates a lightweight child CMS using plain-map +// based stores instead of sync.Map-based stores. The child CMS is intended for +// single-goroutine use only (giga executor snapshot path). +// +// Stores are created lazily: only when actually accessed by the transaction. +// This avoids allocating ~50 cachekv/FastStore wrappers per Snapshot when +// only 3-4 stores are typically touched. +func newFastCacheMultiStoreFromCMS(cms Store) Store { + // Collect both materialized stores and unmaterialized parents as parents + // for the child CMS. The child's getOrCreateStore will create FastStores + // lazily on demand, avoiding the cost of eagerly creating wrappers for + // all ~50 store keys. + cms.mu.RLock() + parentCount := len(cms.stores) + len(cms.parents) + parents := make(map[types.StoreKey]types.CacheWrapper, parentCount) + for k, v := range cms.stores { + parents[k] = v + } + for k, v := range cms.parents { + parents[k] = v + } + cms.mu.RUnlock() + + gigaStores := make(map[types.StoreKey]types.KVStore, len(cms.gigaStores)) + for k, v := range cms.gigaStores { + gigaStores[k] = v + } + + return newFastFromKVStore(cms.db, parents, gigaStores, cms.keys, cms.gigaKeys, cms.traceWriter, cms.traceContext) +} + +// newFastFromKVStore is like NewFromKVStore but uses plain-map FastStore types +// instead of sync.Map-based stores. Single-goroutine use only. +func newFastFromKVStore( + store types.KVStore, stores map[types.StoreKey]types.CacheWrapper, + gigaStores map[types.StoreKey]types.KVStore, + keys map[string]types.StoreKey, gigaKeys []types.StoreKey, + traceWriter io.Writer, traceContext types.TraceContext, +) Store { + cms := Store{ + db: gigacachekv.NewFastStore(store, nil), + stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), + parents: make(map[types.StoreKey]types.CacheWrapper, len(stores)), + keys: keys, + gigaKeys: gigaKeys, + traceWriter: traceWriter, + traceContext: traceContext, + mu: &sync.RWMutex{}, + materializeOnce: &sync.Once{}, + fast: true, + } + + for key, s := range stores { + cms.parents[key] = s + } + + cms.gigaStores = make(map[types.StoreKey]types.KVStore, len(gigaKeys)) + for _, key := range gigaKeys { + if gigaStore, ok := gigaStores[key]; ok { + cms.gigaStores[key] = gigacachekv.NewFastStore(gigaStore, key) + } else { + parent := stores[key].(types.KVStore) + cms.gigaStores[key] = gigacachekv.NewFastStore(parent, key) + } + } + + return cms +} + // getOrCreateStore lazily creates a cachekv store from its parent on first access. // Thread-safe: concurrent callers (e.g. slashing BeginBlocker goroutines) may // call GetKVStore on the same CMS simultaneously. @@ -160,7 +231,12 @@ func (cms Store) getOrCreateStore(key types.StoreKey) types.CacheWrap { if cms.TracingEnabled() { cw = tracekv.NewStore(parent.(types.KVStore), cms.traceWriter, cms.traceContext) } - s := cachekv.NewStore(cw.(types.KVStore), key, types.DefaultCacheSizeLimit) + var s types.CacheWrap + if cms.fast { + s = gigacachekv.NewFastStore(cw.(types.KVStore), key) + } else { + s = cachekv.NewStore(cw.(types.KVStore), key, types.DefaultCacheSizeLimit) + } cms.stores[key] = s delete(cms.parents, key) return s @@ -228,6 +304,13 @@ func (cms Store) CacheMultiStore() types.CacheMultiStore { return newCacheMultiStoreFromCMS(cms) } +// CacheMultiStoreGiga creates a lightweight CMS using plain-map stores. +// Only safe when the returned CMS will be used by a single goroutine (e.g. +// giga executor snapshots within a single OCC task). +func (cms Store) CacheMultiStoreGiga() types.CacheMultiStore { + return newFastCacheMultiStoreFromCMS(cms) +} + // CacheMultiStoreWithVersion implements the MultiStore interface. It will panic // as an already cached multi-store cannot load previous versions. //