Skip to content

Commit c49b905

Browse files
committed
fix: isolate CheckTx and simulation state from DeliverTx commits by loading the last committed snapshot and keeping the simulation context in sync
1 parent 768cb21 commit c49b905

File tree

2 files changed

+33
-2
lines changed

2 files changed

+33
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4545
### Bug Fixes
4646

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

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

baseapp/baseapp.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ type BaseApp struct {
109109
// - checkState: Used for CheckTx, which is set based on the previous block's
110110
// state. This state is never committed.
111111
//
112+
// - simulationState: Mirrors the last committed state for simulations. It shares
113+
// the same root snapshot as CheckTx but is never written back.
114+
//
112115
// - prepareProposalState: Used for PrepareProposal, which is set based on the
113116
// previous block's state. This state is never committed. In case of multiple
114117
// consensus rounds, the state is always reset to the previous block's state.
@@ -120,6 +123,7 @@ type BaseApp struct {
120123
// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
121124
// previous block's state. This state is committed.
122125
checkState *state
126+
simulationState *state
123127
prepareProposalState *state
124128
processProposalState *state
125129
finalizeBlockState *state
@@ -493,6 +497,19 @@ func (app *BaseApp) IsSealed() bool { return app.sealed }
493497
// multi-store branch, and provided header.
494498
func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {
495499
ms := app.cms.CacheMultiStore()
500+
if mode == execModeCheck {
501+
// Load the last committed version so CheckTx (and by extension simulations)
502+
// operate on the same state that DeliverTx committed in the previous block.
503+
// Ref: https://github.com/cosmos/cosmos-sdk/issues/20685
504+
//
505+
// Using the versioned cache also unwraps any inter-block cache layers,
506+
// preventing simulation runs from polluting the global inter-block cache
507+
// with transient writes.
508+
// Ref: https://github.com/cosmos/cosmos-sdk/issues/23891
509+
if versionedCache, err := app.cms.CacheMultiStoreWithVersion(h.Height); err == nil {
510+
ms = versionedCache
511+
}
512+
}
496513
headerInfo := header.Info{
497514
Height: h.Height,
498515
Time: h.Time,
@@ -508,8 +525,14 @@ func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {
508525

509526
switch mode {
510527
case execModeCheck:
511-
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
512-
app.checkState = baseState
528+
// Simulations never persist state, so they can reuse the base snapshot
529+
// that was branched off the last committed height.
530+
app.simulationState = baseState
531+
532+
// Branch again for CheckTx so AnteHandler writes do not leak back into
533+
// the shared simulation snapshot.
534+
checkMs := ms.CacheMultiStore()
535+
app.checkState = &state{ctx: baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices).WithMultiStore(checkMs), ms: checkMs}
513536

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

656679
case execModeProcessProposal:
657680
return app.processProposalState
681+
case execModeSimulate:
682+
// Keep the simulation context aligned with the CheckTx context while
683+
// preserving its own store branch.
684+
if app.checkState != nil && app.simulationState != nil {
685+
app.simulationState.SetContext(app.checkState.Context().WithMultiStore(app.simulationState.ms))
686+
}
658687

688+
return app.simulationState
659689
default:
660690
return app.checkState
661691
}

0 commit comments

Comments
 (0)