Skip to content

Commit 14be262

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 c625f47 commit 14be262

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
@@ -46,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4646

4747
* (baseapp) [#23879](https://github.com/cosmos/cosmos-sdk/pull/23879) Ensure finalize block response is not empty in the defer check of FinalizeBlock to avoid panic by nil pointer.
4848
* (query) [#23884](https://github.com/cosmos/cosmos-sdk/pull/23884) Fix NPE in query pagination.
49+
* (baseapp) [#25530](https://github.com/cosmos/cosmos-sdk/pull/25530) isolate CheckTx and simulation state from DeliverTx commits by loading the last committed snapshot and keeping the simulation context in sync.
4950

5051
## [v0.50.14](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.50.14) - 2025-07-08
5152

baseapp/baseapp.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ type BaseApp struct {
105105
// - checkState: Used for CheckTx, which is set based on the previous block's
106106
// state. This state is never committed.
107107
//
108+
// - simulationState: Mirrors the last committed state for simulations. It shares
109+
// the same root snapshot as CheckTx but is never written back.
110+
//
108111
// - prepareProposalState: Used for PrepareProposal, which is set based on the
109112
// previous block's state. This state is never committed. In case of multiple
110113
// consensus rounds, the state is always reset to the previous block's state.
@@ -116,6 +119,7 @@ type BaseApp struct {
116119
// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
117120
// previous block's state. This state is committed.
118121
checkState *state
122+
simulationState *state
119123
prepareProposalState *state
120124
processProposalState *state
121125
finalizeBlockState *state
@@ -478,6 +482,19 @@ func (app *BaseApp) IsSealed() bool { return app.sealed }
478482
// multi-store branch, and provided header.
479483
func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {
480484
ms := app.cms.CacheMultiStore()
485+
if mode == execModeCheck {
486+
// Load the last committed version so CheckTx (and by extension simulations)
487+
// operate on the same state that DeliverTx committed in the previous block.
488+
// Ref: https://github.com/cosmos/cosmos-sdk/issues/20685
489+
//
490+
// Using the versioned cache also unwraps any inter-block cache layers,
491+
// preventing simulation runs from polluting the global inter-block cache
492+
// with transient writes.
493+
// Ref: https://github.com/cosmos/cosmos-sdk/issues/23891
494+
if versionedCache, err := app.cms.CacheMultiStoreWithVersion(h.Height); err == nil {
495+
ms = versionedCache
496+
}
497+
}
481498
headerInfo := header.Info{
482499
Height: h.Height,
483500
Time: h.Time,
@@ -493,8 +510,14 @@ func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {
493510

494511
switch mode {
495512
case execModeCheck:
496-
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
497-
app.checkState = baseState
513+
// Simulations never persist state, so they can reuse the base snapshot
514+
// that was branched off the last committed height.
515+
app.simulationState = baseState
516+
517+
// Branch again for CheckTx so AnteHandler writes do not leak back into
518+
// the shared simulation snapshot.
519+
checkMs := ms.CacheMultiStore()
520+
app.checkState = &state{ctx: baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices).WithMultiStore(checkMs), ms: checkMs}
498521

499522
case execModePrepareProposal:
500523
app.prepareProposalState = baseState
@@ -640,7 +663,14 @@ func (app *BaseApp) getState(mode execMode) *state {
640663

641664
case execModeProcessProposal:
642665
return app.processProposalState
666+
case execModeSimulate:
667+
// Keep the simulation context aligned with the CheckTx context while
668+
// preserving its own store branch.
669+
if app.checkState != nil && app.simulationState != nil {
670+
app.simulationState.SetContext(app.checkState.Context().WithMultiStore(app.simulationState.ms))
671+
}
643672

673+
return app.simulationState
644674
default:
645675
return app.checkState
646676
}

0 commit comments

Comments
 (0)