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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes

* (cli) [#25485](https://github.com/cosmos/cosmos-sdk/pull/25485) Avoid failed to convert address field in `withdraw-validator-commission` cmd.
* (baseapp) [#25531](https://github.com/cosmos/cosmos-sdk/pull/25531) 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

Expand Down
34 changes: 32 additions & 2 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ type BaseApp 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 @@ -120,6 +123,7 @@ type BaseApp 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 @@ -493,6 +497,19 @@ func (app *BaseApp) IsSealed() bool { return app.sealed }
// multi-store branch, and provided header.
func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {
ms := app.cms.CacheMultiStore()
if mode == 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 := app.cms.CacheMultiStoreWithVersion(h.Height); err == nil {
ms = versionedCache
}
}
headerInfo := header.Info{
Height: h.Height,
Time: h.Time,
Expand All @@ -508,8 +525,14 @@ func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {

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

// Branch again for CheckTx so AnteHandler writes do not leak back into
// the shared simulation snapshot.
checkMs := ms.CacheMultiStore()
app.checkState = &state{ctx: baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices).WithMultiStore(checkMs), ms: checkMs}

case execModePrepareProposal:
app.prepareProposalState = baseState
Expand Down Expand Up @@ -655,7 +678,14 @@ func (app *BaseApp) getState(mode execMode) *state {

case execModeProcessProposal:
return app.processProposalState
case execModeSimulate:
// Keep the simulation context aligned with the CheckTx context while
// preserving its own store branch.
if app.checkState != nil && app.simulationState != nil {
app.simulationState.SetContext(app.checkState.Context().WithMultiStore(app.simulationState.ms))
}

return app.simulationState
default:
return app.checkState
}
Expand Down