Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1513,7 +1513,8 @@ func (app *App) CacheContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore)
func (app *App) ExecuteTxsConcurrently(ctx sdk.Context, txs [][]byte, typedTxs []sdk.Tx) ([]*abci.ExecTxResult, sdk.Context) {
// Giga only supports synchronous execution for now
if app.GigaExecutorEnabled && app.GigaOCCEnabled {
return app.ProcessTXsWithOCCGiga(ctx, txs, typedTxs)
results, _ := app.ProcessTXsWithOCCGiga(ctx, txs, typedTxs)
return results, ctx
} else if app.GigaExecutorEnabled {
return app.ProcessTxsSynchronousGiga(ctx, txs, typedTxs), ctx
} else if !ctx.IsOCCEnabled() {
Expand Down Expand Up @@ -1593,6 +1594,9 @@ func (app *App) ProcessTXsWithOCCV2(ctx sdk.Context, txs [][]byte, typedTxs []sd

// ProcessTXsWithOCCGiga runs the transactions concurrently via OCC, using the Giga executor
func (app *App) ProcessTXsWithOCCGiga(ctx sdk.Context, txs [][]byte, typedTxs []sdk.Tx) ([]*abci.ExecTxResult, sdk.Context) {
ms := ctx.MultiStore().CacheMultiStore()
defer ms.Write()
ctx = ctx.WithMultiStore(ms)
evmEntries := make([]*sdk.DeliverTxEntry, 0, len(txs))
v2Entries := make([]*sdk.DeliverTxEntry, 0, len(txs))
firstCosmosSeen := false
Expand Down Expand Up @@ -1679,6 +1683,13 @@ func (app *App) ProcessTXsWithOCCGiga(ctx sdk.Context, txs [][]byte, typedTxs []
return nil, ctx
}
}
// Flush block-level giga Layer A (gigacachekv wrapping interblock.Cache) to
// the write-through interblock.Cache and on to CommitKVStore. This is
// required because the v2 OCC scheduler writes giga-key winning values into
// Layer A via MVS WriteLatestToStore(), but only WriteGiga() propagates
// those dirty entries to the parent. WriteState() only flushes the regular
// (non-giga) cachekv layer.
ctx.GigaMultiStore().WriteGiga()

execResults := make([]*abci.ExecTxResult, 0, len(evmBatchResult)+len(v2BatchResult))
for _, r := range evmBatchResult {
Expand Down
27 changes: 24 additions & 3 deletions sei-cosmos/baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,21 @@ func (app *BaseApp) Seal() { app.sealed = true }
// IsSealed returns true if the BaseApp is sealed and false otherwise.
func (app *BaseApp) IsSealed() bool { return app.sealed }

// perStateCacheMultiStore is implemented by store backends that maintain
// isolated inter-block caches for deliverState and processProposalState. When
// app.cms satisfies this interface the two setter helpers use the dedicated
// per-state methods so that in-memory cache state does not leak across states.
// checkState intentionally does not use an inter-block cache.
type perStateCacheMultiStore interface {
CacheMultiStoreForDeliver() sdk.CacheMultiStore
CacheMultiStoreForProcessProposal() sdk.CacheMultiStore
}

// setCheckState sets the BaseApp's checkState with a branched multi-store
// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch,
// provided header, and minimum gas prices set. It is set on InitChain and reset
// on Commit.
// on Commit. checkState does not use an inter-block cache so that it always
// reads from the last committed state without any cross-state cache pollution.
func (app *BaseApp) setCheckState(header tmproto.Header) {
ms := app.cms.CacheMultiStore()
ctx := sdk.NewContext(ms, header, true).WithMinGasPrices(app.minGasPrices)
Expand All @@ -554,7 +565,12 @@ func (app *BaseApp) setCheckState(header tmproto.Header) {
// and provided header. It is set on InitChain and BeginBlock and set to nil on
// Commit.
func (app *BaseApp) setDeliverState(header tmproto.Header) {
ms := app.cms.CacheMultiStore()
var ms sdk.CacheMultiStore
if psms, ok := app.cms.(perStateCacheMultiStore); ok {
ms = psms.CacheMultiStoreForDeliver()
} else {
ms = app.cms.CacheMultiStore()
}
ctx := sdk.NewContext(ms, header, false)
if app.deliverState == nil {
app.deliverState = &state{
Expand All @@ -569,7 +585,12 @@ func (app *BaseApp) setDeliverState(header tmproto.Header) {
}

func (app *BaseApp) setProcessProposalState(header tmproto.Header) {
ms := app.cms.CacheMultiStore()
var ms sdk.CacheMultiStore
if psms, ok := app.cms.(perStateCacheMultiStore); ok {
ms = psms.CacheMultiStoreForProcessProposal()
} else {
ms = app.cms.CacheMultiStore()
}
ctx := sdk.NewContext(ms, header, false)
if app.processProposalState == nil {
app.processProposalState = &state{
Expand Down
12 changes: 12 additions & 0 deletions sei-cosmos/store/cachemulti/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ type Store struct {

gigaStores map[types.StoreKey]types.KVStore
gigaKeys []types.StoreKey
// interBlockCaches holds the raw inter-block cache references (one per
// giga-registered store key) propagated from the OCC rootmulti. When a
// child cachemulti is created via CacheMultiStore(), it uses these caches
// directly as the giga parent so that the layer chain is always
// gigacachekv → interblock.Cache → CommitKVStore
// rather than adding an extra gigacachekv level of indirection.
interBlockCaches map[types.StoreKey]types.KVStore

traceWriter io.Writer
traceContext types.TraceContext
Expand All @@ -51,6 +58,11 @@ func NewFromKVStore(
) Store {
cms := newStoreWithoutGiga(store, stores, keys, gigaKeys, traceWriter, traceContext)

// Preserve the raw inter-block cache references so child cachemultis
// created via CacheMultiStore() can use them directly as giga parents,
// avoiding an extra gigacachekv indirection level.
cms.interBlockCaches = gigaStores

cms.gigaStores = make(map[types.StoreKey]types.KVStore, len(gigaKeys))
for _, key := range gigaKeys {
if gigaStore, ok := gigaStores[key]; ok {
Expand Down
175 changes: 175 additions & 0 deletions sei-cosmos/store/interblock/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package interblock

import (
"bytes"
"io"
"sync"

"github.com/sei-protocol/sei-chain/sei-cosmos/store/types"
)

// Cache is a KVStore that persists across block boundaries. It is a
// write-through read cache: every Set/Delete propagates to the parent
// CommitKVStore immediately, so the parent is always authoritative and visible
// to all readers (including the v2/legacy execution phase that follows the
// giga execution phase within the same block). The in-memory map accelerates
// reads by serving recently-written and recently-read keys from memory rather
// than hitting the on-disk CommitKVStore, and this warmth survives across
// block boundaries.
//
// FlushDirty is a no-op because the parent is always current; it exists only
// to satisfy the InterBlockCache interface. UpdateParent must be called after
// each Commit because the underlying CommitKVStore may be reloaded.
type Cache struct {
mtx sync.RWMutex
cache *sync.Map // string key -> *types.CValue (always clean; parent is authoritative)
parent types.KVStore
storeKey types.StoreKey
}

var _ types.InterBlockCache = (*Cache)(nil)

func NewCache(parent types.KVStore, storeKey types.StoreKey) *Cache {
return &Cache{
cache: &sync.Map{},
parent: parent,
storeKey: storeKey,
}
}

// UpdateParent refreshes the parent store reference. Must be called at block
// start because the underlying CommitKVStore may be reloaded after Commit.
func (c *Cache) UpdateParent(parent types.KVStore) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.parent = parent
}

// FlushDirty is a no-op for a write-through cache: every Set/Delete already
// propagated to the parent store immediately, so there is nothing to flush.
// Kept to satisfy the InterBlockCache interface.
func (c *Cache) FlushDirty() {}

// Get implements types.KVStore.
func (c *Cache) Get(key []byte) []byte {
types.AssertValidKey(key)
if cv, ok := c.cache.Load(string(key)); ok {
return cv.(*types.CValue).Value()
}
c.mtx.RLock()
val := c.parent.Get(key)
c.mtx.RUnlock()
// Populate the cache on miss so subsequent reads of the same key stay
// in memory across transactions and blocks (analogous to sei-v3's
// CacheKVStore populate-on-miss). Only non-nil values are cached; nil
// means the key is absent in the parent and we don't want to mask a
// future write from another path.
if val != nil {
c.cache.Store(string(key), types.NewCValue(val, false))
}
return val
}

// Has implements types.KVStore.
func (c *Cache) Has(key []byte) bool {
return c.Get(key) != nil
}

// Set implements types.KVStore. Write-through: updates both the in-memory
// cache and the parent CommitKVStore so the write is immediately visible to
// all readers, including the v2/legacy execution phase that runs after the
// giga phase within the same block.
func (c *Cache) Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
c.cache.Store(string(key), types.NewCValue(value, false))
c.mtx.RLock()
c.parent.Set(key, value)
c.mtx.RUnlock()
}

// Delete implements types.KVStore. Write-through: removes from both the
// in-memory cache and the parent CommitKVStore immediately.
func (c *Cache) Delete(key []byte) {
types.AssertValidKey(key)
c.cache.Store(string(key), types.NewCValue(nil, false))
c.mtx.RLock()
c.parent.Delete(key)
c.mtx.RUnlock()
}

// DeleteAll implements types.KVStore.
func (c *Cache) DeleteAll(start, end []byte) error {
for _, k := range c.GetAllKeyStrsInRange(start, end) {
c.Delete([]byte(k))
}
return nil
}

// GetAllKeyStrsInRange implements types.KVStore.
func (c *Cache) GetAllKeyStrsInRange(start, end []byte) []string {
c.mtx.RLock()
parentKeys := c.parent.GetAllKeyStrsInRange(start, end)
c.mtx.RUnlock()

keySet := make(map[string]struct{}, len(parentKeys))
for _, k := range parentKeys {
keySet[k] = struct{}{}
}
c.cache.Range(func(key, value any) bool {
kb := []byte(key.(string))
if bytes.Compare(kb, start) < 0 || bytes.Compare(kb, end) >= 0 {
return true
}
if value.(*types.CValue).Value() == nil {
delete(keySet, key.(string))
} else {
keySet[key.(string)] = struct{}{}
}
return true
})
result := make([]string, 0, len(keySet))
for k := range keySet {
result = append(result, k)
}
return result
}

// GetStoreType implements types.Store.
func (c *Cache) GetStoreType() types.StoreType {
c.mtx.RLock()
defer c.mtx.RUnlock()
return c.parent.GetStoreType()
}

// CacheWrap implements types.Store.
func (c *Cache) CacheWrap(storeKey types.StoreKey) types.CacheWrap {
panic("CacheWrap not supported on InterBlockCache")
}

// CacheWrapWithTrace implements types.Store.
func (c *Cache) CacheWrapWithTrace(storeKey types.StoreKey, w io.Writer, tc types.TraceContext) types.CacheWrap {
panic("CacheWrapWithTrace not supported on InterBlockCache")
}

// GetWorkingHash implements types.KVStore.
func (c *Cache) GetWorkingHash() ([]byte, error) {
panic("GetWorkingHash not supported on InterBlockCache")
}

// VersionExists implements types.KVStore.
func (c *Cache) VersionExists(version int64) bool {
c.mtx.RLock()
defer c.mtx.RUnlock()
return c.parent.VersionExists(version)
}

// Iterator implements types.KVStore.
func (c *Cache) Iterator(start, end []byte) types.Iterator {
panic("Iterator not supported on InterBlockCache")
}

// ReverseIterator implements types.KVStore.
func (c *Cache) ReverseIterator(start, end []byte) types.Iterator {
panic("ReverseIterator not supported on InterBlockCache")
}
13 changes: 13 additions & 0 deletions sei-cosmos/store/types/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,16 @@ type GigaMultiStore interface {
IsStoreGiga(key StoreKey) bool
SetGigaKVStores(handler func(sk StoreKey, s KVStore) KVStore) MultiStore
}

// InterBlockCache is a KVStore that persists across block boundaries. It wraps
// an underlying parent CommitKVStore with an in-memory write-through cache and
// supports O(dirty) selective flushing rather than full eviction each block.
type InterBlockCache interface {
KVStore
// UpdateParent refreshes the parent store reference. Must be called at
// block start when the underlying CommitKVStore is reloaded after Commit.
UpdateParent(parent KVStore)
// FlushDirty writes only entries modified since the last flush to the
// parent store, then marks them clean without evicting cached entries.
FlushDirty()
}
Loading
Loading