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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

## Unreleased

### Bug Fixes

* (baseapp) [#25529](https://github.com/cosmos/cosmos-sdk/pull/25529) isolate CheckTx and simulation state from DeliverTx commits by loading the last committed snapshot and keeping the simulation context in sync.

## [v0.53.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-25

This patch update also includes minor dependency bumps.
Expand Down
38 changes: 36 additions & 2 deletions baseapp/state/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Manager struct {
// - checkState: Used for CheckTx, which is set based on the previous block's
// state. This state is never committed.
//
// - simulationState: Mirrors the last committed state for simulations. It shares
// the same root snapshot as CheckTx but is never written back.
//
// - prepareProposalState: Used for PrepareProposal, which is set based on the
// previous block's state. This state is never committed. In case of multiple
// consensus rounds, the state is always reset to the previous block's state.
Expand All @@ -35,6 +38,7 @@ type Manager struct {
// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
// previous block's state. This state is committed.
checkState *State
simulationState *State
prepareProposalState *State
processProposalState *State
finalizeBlockState *State
Expand Down Expand Up @@ -63,6 +67,15 @@ func (mgr *Manager) GetState(mode sdk.ExecMode) *State {
case sdk.ExecModeProcessProposal:
return mgr.processProposalState

case sdk.ExecModeSimulate:
// Keep the simulation context aligned with the CheckTx context while
// preserving its own store branch.
if mgr.checkState != nil && mgr.simulationState != nil {
mgr.simulationState.SetContext(mgr.checkState.Context().WithMultiStore(mgr.simulationState.MultiStore))
}

return mgr.simulationState

default:
return mgr.checkState
}
Expand All @@ -79,6 +92,20 @@ func (mgr *Manager) SetState(
streamingManager storetypes.StreamingManager,
) {
ms := unbranchedStore.CacheMultiStore()
if mode == sdk.ExecModeCheck {
// Load the last committed version so CheckTx (and by extension simulations)
// operate on the same state that DeliverTx committed in the previous block.
// Ref: https://github.com/cosmos/cosmos-sdk/issues/20685
//
// Using the versioned cache also unwraps any inter-block cache layers,
// preventing simulation runs from polluting the global inter-block cache
// with transient writes.
// Ref: https://github.com/cosmos/cosmos-sdk/issues/23891
if versionedCache, err := unbranchedStore.CacheMultiStoreWithVersion(h.Height); err == nil {
ms = versionedCache
}
}

headerInfo := header.Info{
Height: h.Height,
Time: h.Time,
Expand All @@ -97,8 +124,14 @@ func (mgr *Manager) SetState(

switch mode {
case sdk.ExecModeCheck:
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(mgr.gasConfig.MinGasPrices))
mgr.checkState = baseState
// Simulations never persist state, so they can reuse the base snapshot
// that was branched off the last committed height.
mgr.simulationState = baseState

// Branch again for CheckTx so AnteHandler writes do not leak back into
// the shared simulation snapshot.
checkMs := ms.CacheMultiStore()
mgr.checkState = NewState(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(mgr.gasConfig.MinGasPrices).WithMultiStore(checkMs), checkMs)

case sdk.ExecModePrepareProposal:
mgr.prepareProposalState = baseState
Expand All @@ -121,6 +154,7 @@ func (mgr *Manager) ClearState(mode sdk.ExecMode) {
switch mode {
case sdk.ExecModeCheck:
mgr.checkState = nil
mgr.simulationState = nil

case sdk.ExecModePrepareProposal:
mgr.prepareProposalState = nil
Expand Down
18 changes: 16 additions & 2 deletions store/cachemulti/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"maps"
"sync"

"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
Expand All @@ -21,7 +22,10 @@ const storeNameCtxKey = "store_name"
// NOTE: a Store (and MultiStores in general) should never expose the
// keys for the substores.
type Store struct {
stores map[types.StoreKey]types.CacheWrap
// storesMut ensures concurrent callers (e.g. ExportGenesisForModules goroutines)
// don't race when lazily creating substores.
storesMut *sync.RWMutex
stores map[types.StoreKey]types.CacheWrap

traceWriter io.Writer
traceContext types.TraceContext
Expand All @@ -38,6 +42,7 @@ func NewFromKVStore(
traceWriter io.Writer, traceContext types.TraceContext,
) Store {
cms := Store{
storesMut: &sync.RWMutex{},
stores: make(map[types.StoreKey]types.CacheWrap, len(stores)),
traceWriter: traceWriter,
traceContext: traceContext,
Expand Down Expand Up @@ -66,6 +71,7 @@ func NewFromParent(
traceWriter io.Writer, traceContext types.TraceContext,
) Store {
return Store{
storesMut: &sync.RWMutex{},
stores: make(map[types.StoreKey]types.CacheWrap),
traceWriter: traceWriter,
traceContext: traceContext,
Expand Down Expand Up @@ -126,7 +132,12 @@ func (cms Store) GetStoreType() types.StoreType {

// Write calls Write on each underlying store.
func (cms Store) Write() {
for _, store := range cms.stores {
cms.storesMut.RLock()
stores := make(map[types.StoreKey]types.CacheWrap, len(cms.stores))
maps.Copy(stores, cms.stores)
cms.storesMut.RUnlock()

for _, store := range stores {
store.Write()
}
}
Expand Down Expand Up @@ -157,6 +168,9 @@ func (cms Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, err
}

func (cms Store) getCacheWrapper(key types.StoreKey) types.CacheWrapper {
cms.storesMut.Lock()
defer cms.storesMut.Unlock()

store, ok := cms.stores[key]
if !ok && cms.parentStore != nil {
// load on demand
Expand Down
3 changes: 2 additions & 1 deletion store/cachemulti/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cachemulti

import (
"fmt"
"sync"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -12,7 +13,7 @@ import (
func TestStoreGetKVStore(t *testing.T) {
require := require.New(t)

s := Store{stores: map[types.StoreKey]types.CacheWrap{}}
s := Store{storesMut: &sync.RWMutex{}, stores: map[types.StoreKey]types.CacheWrap{}}
key := types.NewKVStoreKey("abc")
errMsg := fmt.Sprintf("kv store with key %v has not been registered in stores", key)

Expand Down
1 change: 1 addition & 0 deletions tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,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 => ../.
Expand Down
2 changes: 0 additions & 2 deletions tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
cosmossdk.io/store v1.3.0-beta.0 h1:jwJvAQkMsCY9xJHU/nz7yOo1WnNRvcI/9yLRSgZoFTk=
cosmossdk.io/store v1.3.0-beta.0/go.mod h1:CMz9JQGEA8eRcZv2pK07NgEbL4NEb9wVgzWK4tNQaPg=
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
Expand Down