Repository: cosmos/cosmos-sdk
Pull request #24655: fix: race condition in baseapp states
Author: login: aljo242
Labels: name: C:Store
Created: 2025-05-02T14:35:15Z | Merged: 2025-05-13T00:18:21Z

# Description

Closes: SDK-208

<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review. -->

---

## Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.  Your PR will not be merged unless you satisfy
all of these items.*

I have...

* [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title, you can find examples of the prefixes below:
    <!-- * `feat`: A new feature
    * `fix`: A bug fix
    * `docs`: Documentation only changes
    * `style`: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
    * `refactor`: A code change that neither fixes a bug nor adds a feature
    * `perf`: A code change that improves performance
    * `test`: Adding missing tests or correcting existing tests
    * `build`: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
    * `ci`: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
    * `chore`: Other changes that don't modify src or test files
    * `revert`: Reverts a previous commit -->
* [ ] confirmed `!` in the type prefix if API or client breaking change
* [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
* [ ] provided a link to the relevant issue or specification
* [ ] reviewed "Files changed" and left comments if necessary
* [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
* [ ] added a changelog entry to `CHANGELOG.md`
* [ ] updated the relevant documentation or specification, including comments for [documenting Go code](https://blog.golang.org/godoc)
* [ ] confirmed all CI checks have passed



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced centralized state management for improved consistency and modularity.
  - Added a new configuration structure for gas-related settings.
- **Improvements**
  - Enhanced concurrency safety by using mutex locks and atomic operations to prevent race conditions during block commit and query operations.
- **Bug Fixes**
  - Improved thread-safety in state and commit information access.
- **Tests**
  - Added a concurrency stress test to detect race conditions between commit and query operations.
  - Updated test utilities and output capture for more robust testing.
- **Refactor**
  - Unified state access patterns and streamlined internal configuration management.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b7b27dbedac1..0e4dc2b189cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,8 +38,13 @@ Ref: https://keepachangelog.com/en/1.0.0/
 
 ## [Unreleased]
 
+### Improvements
+
+* (baseapp) [#24655](https://github.com/cosmos/cosmos-sdk/pull/24655) Add mutex locks for `state` and make `lastCommitInfo` atomic to prevent race conditions between `Commit` and `CreateQueryContext`.
+
 ### Bug Fixes
 
+* (client, client/rpc, x/auth/tx) [#24551](https://github.com/cosmos/cosmos-sdk/pull/24551) Handle cancellation properly when supplying context to client methods.
 * (x/authz) [#24638](https://github.com/cosmos/cosmos-sdk/pull/24638) Fixed a minor bug where the grant key was cast as a string and dumped directly into the error message leading to an error string possibly containing invalid UTF-8.
 
 ### Deprecated
@@ -48,10 +53,6 @@ Ref: https://keepachangelog.com/en/1.0.0/
 * (x/group) [#24571](https://github.com/cosmos/cosmos-sdk/pull/24571) Deprecate the `x/group` module in the Cosmos SDK repository.  This module will not be maintained to the extent that our core modules will and will be kept in a [legacy repo](https://github.com/cosmos/cosmos-legacy).
 * (types) [#24664](https://github.com/cosmos/cosmos-sdk/pull/24664) Deprecate the `Invariant` type in the Cosmos SDK.
 
-### Bug Fixes
-
-* (client, client/rpc, x/auth/tx) [#24551](https://github.com/cosmos/cosmos-sdk/pull/24551) Handle cancellation properly when supplying context to client methods.
-
 ## [v0.53.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.0) - 2025-04-29
 
 ### Features
diff --git a/baseapp/abci.go b/baseapp/abci.go
index edfc43da08f3..58e41b6aff25 100644
--- a/baseapp/abci.go
+++ b/baseapp/abci.go
@@ -20,6 +20,7 @@ import (
 	snapshottypes "cosmossdk.io/store/snapshots/types"
 	storetypes "cosmossdk.io/store/types"
 
+	"github.com/cosmos/cosmos-sdk/baseapp/state"
 	"github.com/cosmos/cosmos-sdk/codec"
 	"github.com/cosmos/cosmos-sdk/telemetry"
 	sdk "github.com/cosmos/cosmos-sdk/types"
@@ -62,14 +63,15 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha
 	}
 
 	// initialize states with a correct header
-	app.setState(execModeFinalize, initHeader)
-	app.setState(execModeCheck, initHeader)
+	app.stateManager.SetState(execModeFinalize, app.cms, initHeader, app.logger, app.streamingManager)
+	app.stateManager.SetState(execModeCheck, app.cms, initHeader, app.logger, app.streamingManager)
+	finalizeState := app.stateManager.GetState(execModeFinalize)
 
 	// Store the consensus params in the BaseApp's param store. Note, this must be
 	// done after the finalizeBlockState and context have been set as it's persisted
 	// to state.
 	if req.ConsensusParams != nil {
-		err := app.StoreConsensusParams(app.finalizeBlockState.Context(), *req.ConsensusParams)
+		err := app.StoreConsensusParams(finalizeState.Context(), *req.ConsensusParams)
 		if err != nil {
 			return nil, err
 		}
@@ -81,13 +83,14 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha
 		// handler, the block height is zero by default. However, after Commit is called
 		// the height needs to reflect the true block height.
 		initHeader.Height = req.InitialHeight
-		app.checkState.SetContext(app.checkState.Context().WithBlockHeader(initHeader).
+		checkState := app.stateManager.GetState(execModeCheck)
+		checkState.SetContext(checkState.Context().WithBlockHeader(initHeader).
 			WithHeaderInfo(coreheader.Info{
 				ChainID: req.ChainId,
 				Height:  req.InitialHeight,
 				Time:    req.Time,
 			}))
-		app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockHeader(initHeader).
+		finalizeState.SetContext(finalizeState.Context().WithBlockHeader(initHeader).
 			WithHeaderInfo(coreheader.Info{
 				ChainID: req.ChainId,
 				Height:  req.InitialHeight,
@@ -100,9 +103,9 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha
 	}
 
 	// add block gas meter for any genesis transactions (allow infinite gas)
-	app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()))
+	finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()))
 
-	res, err := app.abciHandlers.InitChainer(app.finalizeBlockState.Context(), req)
+	res, err := app.abciHandlers.InitChainer(finalizeState.Context(), req)
 	if err != nil {
 		return nil, err
 	}
@@ -336,9 +339,9 @@ func (app *BaseApp) ApplySnapshotChunk(req *abci.RequestApplySnapshotChunk) (*ab
 // and only the AnteHandler is executed. State is persisted to the BaseApp's
 // internal CheckTx state if the AnteHandler passes. Otherwise, the ResponseCheckTx
 // will contain relevant error information. Regardless of tx execution outcome,
-// the ResponseCheckTx will contain relevant gas execution context.
+// the ResponseCheckTx will contain the relevant gas execution context.
 func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) {
-	var mode execMode
+	var mode sdk.ExecMode
 
 	switch req.Type {
 	case abci.CheckTxType_New:
@@ -352,14 +355,14 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er
 	}
 
 	if app.abciHandlers.CheckTxHandler == nil {
-		gInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
+		gasInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
 		if err != nil {
-			return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace), nil
+			return sdkerrors.ResponseCheckTxWithEvents(err, gasInfo.GasWanted, gasInfo.GasUsed, anteEvents, app.trace), nil
 		}
 
 		return &abci.ResponseCheckTx{
-			GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints?
-			GasUsed:   int64(gInfo.GasUsed),   // TODO: Should type accept unsigned ints?
+			GasWanted: int64(gasInfo.GasWanted), // TODO: Should type accept unsigned ints?
+			GasUsed:   int64(gasInfo.GasUsed),   // TODO: Should type accept unsigned ints?
 			Log:       result.Log,
 			Data:      result.Data,
 			Events:    sdk.MarkEventsToIndex(result.Events, app.indexEvents),
@@ -402,7 +405,7 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc
 		NextValidatorsHash: req.NextValidatorsHash,
 		AppHash:            app.LastCommitID().Hash,
 	}
-	app.setState(execModePrepareProposal, header)
+	app.stateManager.SetState(execModePrepareProposal, app.cms, header, app.logger, app.streamingManager)
 
 	// CometBFT must never call PrepareProposal with a height of 0.
 	//
@@ -411,7 +414,8 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc
 		return nil, errors.New("PrepareProposal called with invalid height")
 	}
 
-	app.prepareProposalState.SetContext(app.getContextForProposal(app.prepareProposalState.Context(), req.Height).
+	prepareProposalState := app.stateManager.GetState(execModePrepareProposal)
+	prepareProposalState.SetContext(app.getContextForProposal(prepareProposalState.Context(), req.Height).
 		WithVoteInfos(toVoteInfo(req.LocalLastCommit.Votes)). // this is a set of votes that are not finalized yet, wait for commit
 		WithBlockHeight(req.Height).
 		WithBlockTime(req.Time).
@@ -424,9 +428,9 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc
 			Time:    req.Time,
 		}))
 
-	app.prepareProposalState.SetContext(app.prepareProposalState.Context().
-		WithConsensusParams(app.GetConsensusParams(app.prepareProposalState.Context())).
-		WithBlockGasMeter(app.getBlockGasMeter(app.prepareProposalState.Context())))
+	prepareProposalState.SetContext(prepareProposalState.Context().
+		WithConsensusParams(app.GetConsensusParams(prepareProposalState.Context())).
+		WithBlockGasMeter(app.getBlockGasMeter(prepareProposalState.Context())))
 
 	defer func() {
 		if err := recover(); err != nil {
@@ -441,7 +445,7 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc
 		}
 	}()
 
-	resp, err = app.abciHandlers.PrepareProposalHandler(app.prepareProposalState.Context(), req)
+	resp, err = app.abciHandlers.PrepareProposalHandler(prepareProposalState.Context(), req)
 	if err != nil {
 		app.logger.Error("failed to prepare proposal", "height", req.Height, "time", req.Time, "err", err)
 		return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil
@@ -486,7 +490,7 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc
 		NextValidatorsHash: req.NextValidatorsHash,
 		AppHash:            app.LastCommitID().Hash,
 	}
-	app.setState(execModeProcessProposal, header)
+	app.stateManager.SetState(execModeProcessProposal, app.cms, header, app.logger, app.streamingManager)
 
 	// Since the application can get access to FinalizeBlock state and write to it,
 	// we must be sure to reset it in case ProcessProposal timeouts and is called
@@ -496,10 +500,11 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc
 	if req.Height > app.initialHeight {
 		// abort any running OE
 		app.optimisticExec.Abort()
-		app.setState(execModeFinalize, header)
+		app.stateManager.SetState(execModeFinalize, app.cms, header, app.logger, app.streamingManager)
 	}
 
-	app.processProposalState.SetContext(app.getContextForProposal(app.processProposalState.Context(), req.Height).
+	processProposalState := app.stateManager.GetState(execModeProcessProposal)
+	processProposalState.SetContext(app.getContextForProposal(processProposalState.Context(), req.Height).
 		WithVoteInfos(req.ProposedLastCommit.Votes). // this is a set of votes that are not finalized yet, wait for commit
 		WithBlockHeight(req.Height).
 		WithBlockTime(req.Time).
@@ -513,9 +518,9 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc
 			Time:    req.Time,
 		}))
 
-	app.processProposalState.SetContext(app.processProposalState.Context().
-		WithConsensusParams(app.GetConsensusParams(app.processProposalState.Context())).
-		WithBlockGasMeter(app.getBlockGasMeter(app.processProposalState.Context())))
+	processProposalState.SetContext(processProposalState.Context().
+		WithConsensusParams(app.GetConsensusParams(processProposalState.Context())).
+		WithBlockGasMeter(app.getBlockGasMeter(processProposalState.Context())))
 
 	defer func() {
 		if err := recover(); err != nil {
@@ -530,7 +535,7 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc
 		}
 	}()
 
-	resp, err = app.abciHandlers.ProcessProposalHandler(app.processProposalState.Context(), req)
+	resp, err = app.abciHandlers.ProcessProposalHandler(processProposalState.Context(), req)
 	if err != nil {
 		app.logger.Error("failed to process proposal", "height", req.Height, "time", req.Time, "hash", fmt.Sprintf("%X", req.Hash), "err", err)
 		return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil
@@ -570,7 +575,7 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
 	// finalizeBlockState context, otherwise we don't get the uncommitted data
 	// from InitChain.
 	if req.Height == app.initialHeight {
-		ctx, _ = app.finalizeBlockState.Context().CacheContext()
+		ctx, _ = app.stateManager.GetState(execModeFinalize).Context().CacheContext()
 	} else {
 		emptyHeader := cmtproto.Header{ChainID: app.chainID, Height: req.Height}
 		ms := app.cms.CacheMultiStore()
@@ -645,7 +650,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
 	// finalizeBlockState context, otherwise we don't get the uncommitted data
 	// from InitChain.
 	if req.Height == app.initialHeight {
-		ctx, _ = app.finalizeBlockState.Context().CacheContext()
+		ctx, _ = app.stateManager.GetState(execModeFinalize).Context().CacheContext()
 	} else {
 		emptyHeader := cmtproto.Header{ChainID: app.chainID, Height: req.Height}
 		ms := app.cms.CacheMultiStore()
@@ -700,7 +705,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
 
 // internalFinalizeBlock executes the block, called by the Optimistic
 // Execution flow or by the FinalizeBlock ABCI method. The context received is
-// only used to handle early cancellation, for anything related to state app.finalizeBlockState.Context()
+// only used to handle early cancellation, for anything related to state app.stateManager.GetState(execModeFinalize).Context()
 // must be used.
 func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
 	var events []abci.Event
@@ -731,12 +736,14 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
 	// finalizeBlockState should be set on InitChain or ProcessProposal. If it is
 	// nil, it means we are replaying this block and we need to set the state here
 	// given that during block replay ProcessProposal is not executed by CometBFT.
-	if app.finalizeBlockState == nil {
-		app.setState(execModeFinalize, header)
+	finalizeState := app.stateManager.GetState(execModeFinalize)
+	if finalizeState == nil {
+		app.stateManager.SetState(execModeFinalize, app.cms, header, app.logger, app.streamingManager)
+		finalizeState = app.stateManager.GetState(execModeFinalize)
 	}
 
 	// Context is now updated with Header information.
-	app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().
+	finalizeState.SetContext(finalizeState.Context().
 		WithBlockHeader(header).
 		WithHeaderHash(req.Hash).
 		WithHeaderInfo(coreheader.Info{
@@ -746,7 +753,7 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
 			Hash:    req.Hash,
 			AppHash: app.LastCommitID().Hash,
 		}).
-		WithConsensusParams(app.GetConsensusParams(app.finalizeBlockState.Context())).
+		WithConsensusParams(app.GetConsensusParams(finalizeState.Context())).
 		WithVoteInfos(req.DecidedLastCommit.Votes).
 		WithExecMode(sdk.ExecModeFinalize).
 		WithCometInfo(cometInfo{
@@ -757,11 +764,11 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
 		}))
 
 	// GasMeter must be set after we get a context with updated consensus params.
-	gasMeter := app.getBlockGasMeter(app.finalizeBlockState.Context())
-	app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(gasMeter))
+	gasMeter := app.getBlockGasMeter(finalizeState.Context())
+	finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(gasMeter))
 
-	if app.checkState != nil {
-		app.checkState.SetContext(app.checkState.Context().
+	if checkState := app.stateManager.GetState(execModeCheck); checkState != nil {
+		checkState.SetContext(checkState.Context().
 			WithBlockGasMeter(gasMeter).
 			WithHeaderHash(req.Hash))
 	}
@@ -790,8 +797,8 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
 	events = append(events, beginBlock.Events...)
 
 	// Reset the gas meter so that the AnteHandlers aren't required to
-	gasMeter = app.getBlockGasMeter(app.finalizeBlockState.Context())
-	app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(gasMeter))
+	gasMeter = app.getBlockGasMeter(finalizeState.Context())
+	finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(gasMeter))
 
 	// Iterate over all raw transactions in the proposal and attempt to execute
 	// them, gathering the execution results.
@@ -828,11 +835,11 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
 		txResults = append(txResults, response)
 	}
 
-	if app.finalizeBlockState.ms.TracingEnabled() {
-		app.finalizeBlockState.ms = app.finalizeBlockState.ms.SetTracingContext(nil).(storetypes.CacheMultiStore)
+	if finalizeState.MultiStore.TracingEnabled() {
+		finalizeState.MultiStore = finalizeState.MultiStore.SetTracingContext(nil).(storetypes.CacheMultiStore)
 	}
 
-	endBlock, err := app.endBlock(app.finalizeBlockState.Context())
+	endBlock, err := app.endBlock(finalizeState.Context())
 	if err != nil {
 		return nil, err
 	}
@@ -846,7 +853,7 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
 	}
 
 	events = append(events, endBlock.Events...)
-	cp := app.GetConsensusParams(app.finalizeBlockState.Context())
+	cp := app.GetConsensusParams(finalizeState.Context())
 
 	return &abci.ResponseFinalizeBlock{
 		Events:                events,
@@ -873,7 +880,7 @@ func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.Res
 		}
 		// call the streaming service hooks with the FinalizeBlock messages
 		for _, streamingListener := range app.streamingManager.ABCIListeners {
-			if err := streamingListener.ListenFinalizeBlock(app.finalizeBlockState.Context(), *req, *res); err != nil {
+			if err := streamingListener.ListenFinalizeBlock(app.stateManager.GetState(execModeFinalize).Context(), *req, *res); err != nil {
 				app.logger.Error("ListenFinalizeBlock listening hook failed", "height", req.Height, "err", err)
 			}
 		}
@@ -895,7 +902,7 @@ func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.Res
 		}
 
 		// if it was aborted, we need to reset the state
-		app.finalizeBlockState = nil
+		app.stateManager.ClearState(execModeFinalize)
 		app.optimisticExec.Reset()
 	}
 
@@ -934,11 +941,12 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error {
 // against that height and gracefully halt if it matches the latest committed
 // height.
 func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
-	header := app.finalizeBlockState.Context().BlockHeader()
+	finalizeState := app.stateManager.GetState(execModeFinalize)
+	header := finalizeState.Context().BlockHeader()
 	retainHeight := app.GetBlockRetentionHeight(header.Height)
 
 	if app.abciHandlers.Precommiter != nil {
-		app.abciHandlers.Precommiter(app.finalizeBlockState.Context())
+		app.abciHandlers.Precommiter(finalizeState.Context())
 	}
 
 	rms, ok := app.cms.(*rootmulti.Store)
@@ -954,7 +962,7 @@ func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
 
 	abciListeners := app.streamingManager.ABCIListeners
 	if len(abciListeners) > 0 {
-		ctx := app.finalizeBlockState.Context()
+		ctx := finalizeState.Context()
 		blockHeight := ctx.BlockHeight()
 		changeSet := app.cms.PopStateCache()
 
@@ -969,12 +977,12 @@ func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
 	//
 	// NOTE: This is safe because CometBFT holds a lock on the mempool for
 	// Commit. Use the header from this latest block.
-	app.setState(execModeCheck, header)
+	app.stateManager.SetState(execModeCheck, app.cms, header, app.logger, app.streamingManager)
 
-	app.finalizeBlockState = nil
+	app.stateManager.ClearState(execModeFinalize)
 
 	if app.abciHandlers.PrepareCheckStater != nil {
-		app.abciHandlers.PrepareCheckStater(app.checkState.Context())
+		app.abciHandlers.PrepareCheckStater(app.stateManager.GetState(execModeCheck).Context())
 	}
 
 	// The SnapshotIfApplicable method will create the snapshot by starting the goroutine
@@ -992,7 +1000,7 @@ func (app *BaseApp) workingHash() []byte {
 	// Write the FinalizeBlock state into branched storage and commit the MultiStore.
 	// The write to the FinalizeBlock state writes all state transitions to the root
 	// MultiStore (app.cms) so when Commit() is called it persists those values.
-	app.finalizeBlockState.ms.Write()
+	app.stateManager.GetState(execModeFinalize).MultiStore.Write()
 
 	// Get the hash of all writes in order to return the apphash to the comet in finalizeBlock.
 	commitHash := app.cms.WorkingHash()
@@ -1139,7 +1147,7 @@ func (app *BaseApp) FilterPeerByID(info string) *abci.ResponseQuery {
 // access any state changes made in InitChain.
 func (app *BaseApp) getContextForProposal(ctx sdk.Context, height int64) sdk.Context {
 	if height == app.initialHeight {
-		ctx, _ = app.finalizeBlockState.Context().CacheContext()
+		ctx, _ = app.stateManager.GetState(execModeFinalize).Context().CacheContext()
 
 		// clear all context data set during InitChain to avoid inconsistent behavior
 		ctx = ctx.WithBlockHeader(cmtproto.Header{}).WithHeaderInfo(coreheader.Info{})
@@ -1199,26 +1207,26 @@ func checkNegativeHeight(height int64) error {
 
 // CreateQueryContext creates a new sdk.Context for a query, taking as args
 // the block height and whether the query needs a proof or not.
-func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, error) {
-	return app.CreateQueryContextWithCheckHeader(height, prove, true)
+func (bapp *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, error) {
+	return bapp.CreateQueryContextWithCheckHeader(height, prove, true)
 }
 
 // CreateQueryContextWithCheckHeader creates a new sdk.Context for a query, taking as args
 // the block height, whether the query needs a proof or not, and whether to check the header or not.
-func (app *BaseApp) CreateQueryContextWithCheckHeader(height int64, prove, checkHeader bool) (sdk.Context, error) {
+func (bapp *BaseApp) CreateQueryContextWithCheckHeader(height int64, prove, checkHeader bool) (sdk.Context, error) {
 	if err := checkNegativeHeight(height); err != nil {
 		return sdk.Context{}, err
 	}
 
 	// use custom query multi-store if provided
-	qms := app.qms
+	qms := bapp.qms
 	if qms == nil {
-		qms = app.cms.(storetypes.MultiStore)
+		qms = bapp.cms.(storetypes.MultiStore)
 	}
 
 	lastBlockHeight := qms.LatestVersion()
 	if lastBlockHeight == 0 {
-		return sdk.Context{}, errorsmod.Wrapf(sdkerrors.ErrInvalidHeight, "%s is not ready; please wait for first block", app.Name())
+		return sdk.Context{}, errorsmod.Wrapf(sdkerrors.ErrInvalidHeight, "%s is not ready; please wait for first block", bapp.Name())
 	}
 
 	if height > lastBlockHeight {
@@ -1239,13 +1247,13 @@ func (app *BaseApp) CreateQueryContextWithCheckHeader(height int64, prove, check
 
 	var header *cmtproto.Header
 	isLatest := height == 0
-	for _, state := range []*state{
-		app.checkState,
-		app.finalizeBlockState,
+	for _, appState := range []*state.State{
+		bapp.stateManager.GetState(execModeCheck),
+		bapp.stateManager.GetState(execModeFinalize),
 	} {
-		if state != nil {
+		if appState != nil {
 			// branch the commit multi-store for safety
-			h := state.Context().BlockHeader()
+			h := appState.Context().BlockHeader()
 			if isLatest {
 				lastBlockHeight = qms.LatestVersion()
 			}
@@ -1279,14 +1287,14 @@ func (app *BaseApp) CreateQueryContextWithCheckHeader(height int64, prove, check
 	}
 
 	// branch the commit multi-store for safety
-	ctx := sdk.NewContext(cacheMS, *header, true, app.logger).
-		WithMinGasPrices(app.minGasPrices).
-		WithGasMeter(storetypes.NewGasMeter(app.queryGasLimit)).
+	ctx := sdk.NewContext(cacheMS, *header, true, bapp.logger).
+		WithMinGasPrices(bapp.gasConfig.MinGasPrices).
+		WithGasMeter(storetypes.NewGasMeter(bapp.gasConfig.QueryGasLimit)).
 		WithBlockHeader(*header).
 		WithBlockHeight(height)
 
 	if !isLatest {
-		rms, ok := app.cms.(*rootmulti.Store)
+		rms, ok := bapp.cms.(*rootmulti.Store)
 		if ok {
 			cInfo, err := rms.GetCommitInfo(height)
 			if cInfo != nil && err == nil {
@@ -1349,11 +1357,11 @@ func (app *BaseApp) GetBlockRetentionHeight(commitHeight int64) int64 {
 	var retentionHeight int64
 
 	// Define the number of blocks needed to protect against misbehaving validators
-	// which allows light clients to operate safely. Note, we piggy back of the
+	// which allows light clients to operate safely. Note, we piggyback of the
 	// evidence parameters instead of computing an estimated number of blocks based
 	// on the unbonding period and block commitment time as the two should be
 	// equivalent.
-	cp := app.GetConsensusParams(app.finalizeBlockState.Context())
+	cp := app.GetConsensusParams(app.stateManager.GetState(execModeFinalize).Context())
 	if cp.Evidence != nil && cp.Evidence.MaxAgeNumBlocks > 0 {
 		retentionHeight = commitHeight - cp.Evidence.MaxAgeNumBlocks
 	}
diff --git a/baseapp/abci_test.go b/baseapp/abci_test.go
index 1e1cfff98572..f23b274f23b9 100644
--- a/baseapp/abci_test.go
+++ b/baseapp/abci_test.go
@@ -11,6 +11,7 @@ import (
 	"math/rand"
 	"strconv"
 	"strings"
+	"sync/atomic"
 	"testing"
 	"time"
 
@@ -2535,3 +2536,51 @@ func TestFinalizeBlockDeferResponseHandle(t *testing.T) {
 	require.Empty(t, res)
 	require.NotEmpty(t, err)
 }
+
+func TestABCI_Race_Commit_Query(t *testing.T) {
+	suite := NewBaseAppSuite(t, baseapp.SetChainID("test-chain-id"))
+	app := suite.baseApp
+
+	_, err := app.InitChain(&abci.RequestInitChain{
+		ChainId:         "test-chain-id",
+		ConsensusParams: &cmtproto.ConsensusParams{Block: &cmtproto.BlockParams{MaxGas: 5000000}},
+		InitialHeight:   1,
+	})
+	require.NoError(t, err)
+	_, err = app.Commit()
+	require.NoError(t, err)
+
+	counter := atomic.Uint64{}
+	counter.Store(0)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	queryCreator := func() {
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			default:
+				_, err := app.CreateQueryContextWithCheckHeader(0, false, false)
+				require.NoError(t, err)
+
+				counter.Add(1)
+			}
+		}
+	}
+
+	for i := 0; i < 100; i++ {
+		go queryCreator()
+	}
+
+	for i := 0; i < 1000; i++ {
+		_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: app.LastBlockHeight() + 1})
+		require.NoError(t, err)
+
+		_, err = app.Commit()
+		require.NoError(t, err)
+	}
+
+	cancel()
+
+	require.Equal(t, int64(1001), app.GetContextForCheckTx(nil).BlockHeight())
+}
diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go
index 60558dcaa8f0..c7ebc956102b 100644
--- a/baseapp/baseapp.go
+++ b/baseapp/baseapp.go
@@ -17,7 +17,6 @@ import (
 	"github.com/cosmos/gogoproto/proto"
 	protov2 "google.golang.org/protobuf/proto"
 
-	"cosmossdk.io/core/header"
 	errorsmod "cosmossdk.io/errors"
 	"cosmossdk.io/log"
 	"cosmossdk.io/store"
@@ -25,7 +24,9 @@ import (
 	"cosmossdk.io/store/snapshots"
 	storetypes "cosmossdk.io/store/types"
 
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
 	"github.com/cosmos/cosmos-sdk/baseapp/oe"
+	"github.com/cosmos/cosmos-sdk/baseapp/state"
 	"github.com/cosmos/cosmos-sdk/codec"
 	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
 	servertypes "github.com/cosmos/cosmos-sdk/server/types"
@@ -37,8 +38,6 @@ import (
 )
 
 type (
-	execMode uint8
-
 	// StoreLoader defines a customizable function to control how we load the
 	// CommitMultiStore from disk. This is useful for state migration, when
 	// loading a datastore written with an older version of the software. In
@@ -48,14 +47,14 @@ type (
 )
 
 const (
-	execModeCheck               execMode = iota // Check a transaction
-	execModeReCheck                             // Recheck a (pending) transaction after a commit
-	execModeSimulate                            // Simulate a transaction
-	execModePrepareProposal                     // Prepare a block proposal
-	execModeProcessProposal                     // Process a block proposal
-	execModeVoteExtension                       // Extend or verify a pre-commit vote
-	execModeVerifyVoteExtension                 // Verify a vote extension
-	execModeFinalize                            // Finalize a block proposal
+	execModeCheck               = sdk.ExecModeCheck               // Check a transaction
+	execModeReCheck             = sdk.ExecModeReCheck             // Recheck a (pending) transaction after a commit
+	execModeSimulate            = sdk.ExecModeSimulate            // Simulate a transaction
+	execModePrepareProposal     = sdk.ExecModePrepareProposal     // Prepare a block proposal
+	execModeProcessProposal     = sdk.ExecModeProcessProposal     // Process a block proposal
+	execModeVoteExtension       = sdk.ExecModeVoteExtension       // Extend or verify a pre-commit vote
+	execModeVerifyVoteExtension = sdk.ExecModeVerifyVoteExtension // Verify a vote extension
+	execModeFinalize            = sdk.ExecModeFinalize            // Finalize a block proposal
 )
 
 var _ servertypes.ABCI = (*BaseApp)(nil)
@@ -90,29 +89,7 @@ type BaseApp struct {
 	// manages snapshots, i.e. dumps of app state at certain intervals
 	snapshotManager *snapshots.Manager
 
-	// volatile states:
-	//
-	// - checkState is set on InitChain and reset on Commit
-	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
-	// on Commit.
-	//
-	// - checkState: Used for CheckTx, which is set based on the previous block's
-	// state. This state is never committed.
-	//
-	// - 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.
-	//
-	// - processProposalState: Used for ProcessProposal, which is set based on the
-	// the previous block's state. This state is never committed. In case of
-	// multiple rounds, the state is always reset to the previous block's state.
-	//
-	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
-	// previous block's state. This state is committed.
-	checkState           *state
-	prepareProposalState *state
-	processProposalState *state
-	finalizeBlockState   *state
+	stateManager *state.Manager
 
 	// An inter-block write-through cache provided to the context during the ABCI
 	// FinalizeBlock call.
@@ -122,12 +99,8 @@ type BaseApp struct {
 	// application parameter store.
 	paramStore ParamStore
 
-	// queryGasLimit defines the maximum gas for queries; unbounded if 0.
-	queryGasLimit uint64
-
-	// The minimum gas prices a validator is willing to accept for processing a
-	// transaction. This is mainly used for DoS and spam prevention.
-	minGasPrices sdk.DecCoins
+	// gasConfig contains node-level gas configuration.
+	gasConfig config.GasConfig
 
 	// initialHeight is the initial height at which we start the BaseApp
 	initialHeight int64
@@ -207,7 +180,7 @@ func NewBaseApp(
 		txDecoder:        txDecoder,
 		fauxMerkleMode:   false,
 		sigverifyTx:      true,
-		queryGasLimit:    math.MaxUint64,
+		gasConfig:        config.GasConfig{QueryGasLimit: math.MaxUint64},
 	}
 
 	for _, option := range options {
@@ -254,6 +227,8 @@ func NewBaseApp(
 		}
 	}
 
+	app.stateManager = state.NewManager(app.gasConfig)
+
 	return app
 }
 
@@ -431,17 +406,21 @@ func (app *BaseApp) Init() error {
 		return errors.New("commit multi-store must not be nil")
 	}
 
+	if app.stateManager == nil {
+		return errors.New("state manager must not be nil")
+	}
+
 	emptyHeader := cmtproto.Header{ChainID: app.chainID}
 
 	// needed for the export command which inits from store but never calls initchain
-	app.setState(execModeCheck, emptyHeader)
+	app.stateManager.SetState(execModeCheck, app.cms, emptyHeader, app.logger, app.streamingManager)
 	app.Seal()
 
 	return app.cms.GetPruning().Validate()
 }
 
 func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
-	app.minGasPrices = gasPrices
+	app.gasConfig.MinGasPrices = gasPrices
 }
 
 func (app *BaseApp) setHaltHeight(haltHeight uint64) {
@@ -478,43 +457,6 @@ 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 }
 
-// setState sets the BaseApp's state for the corresponding mode with a branched
-// multi-store (i.e. a CacheMultiStore) and a new Context with the same
-// multi-store branch, and provided header.
-func (app *BaseApp) setState(mode execMode, h cmtproto.Header) {
-	ms := app.cms.CacheMultiStore()
-	headerInfo := header.Info{
-		Height:  h.Height,
-		Time:    h.Time,
-		ChainID: h.ChainID,
-		AppHash: h.AppHash,
-	}
-	baseState := &state{
-		ms: ms,
-		ctx: sdk.NewContext(ms, h, false, app.logger).
-			WithStreamingManager(app.streamingManager).
-			WithHeaderInfo(headerInfo),
-	}
-
-	switch mode {
-	case execModeCheck:
-		baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
-		app.checkState = baseState
-
-	case execModePrepareProposal:
-		app.prepareProposalState = baseState
-
-	case execModeProcessProposal:
-		app.processProposalState = baseState
-
-	case execModeFinalize:
-		app.finalizeBlockState = baseState
-
-	default:
-		panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
-	}
-}
-
 // SetCircuitBreaker sets the circuit breaker for the BaseApp.
 // The circuit breaker is checked on every message execution to verify if a transaction should be executed or not.
 func (app *BaseApp) SetCircuitBreaker(cb CircuitBreaker) {
@@ -635,22 +577,6 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error {
 	return nil
 }
 
-func (app *BaseApp) getState(mode execMode) *state {
-	switch mode {
-	case execModeFinalize:
-		return app.finalizeBlockState
-
-	case execModePrepareProposal:
-		return app.prepareProposalState
-
-	case execModeProcessProposal:
-		return app.processProposalState
-
-	default:
-		return app.checkState
-	}
-}
-
 func (app *BaseApp) getBlockGasMeter(ctx sdk.Context) storetypes.GasMeter {
 	if app.disableBlockGasMeter {
 		return noopGasMeter{}
@@ -664,11 +590,11 @@ func (app *BaseApp) getBlockGasMeter(ctx sdk.Context) storetypes.GasMeter {
 }
 
 // retrieve the context for the tx w/ txBytes and other memoized values.
-func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context {
+func (app *BaseApp) getContextForTx(mode sdk.ExecMode, txBytes []byte) sdk.Context {
 	app.mu.Lock()
 	defer app.mu.Unlock()
 
-	modeState := app.getState(mode)
+	modeState := app.stateManager.GetState(mode)
 	if modeState == nil {
 		panic(fmt.Sprintf("state is nil for mode %v", mode))
 	}
@@ -687,7 +613,7 @@ func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context {
 
 	if mode == execModeSimulate {
 		ctx, _ = ctx.CacheContext()
-		ctx = ctx.WithExecMode(sdk.ExecMode(execModeSimulate))
+		ctx = ctx.WithExecMode(execModeSimulate)
 	}
 
 	return ctx
@@ -700,11 +626,9 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context
 	msCache := ms.CacheMultiStore()
 	if msCache.TracingEnabled() {
 		msCache = msCache.SetTracingContext(
-			storetypes.TraceContext(
-				map[string]any{
-					"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
-				},
-			),
+			map[string]any{
+				"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
+			},
 		).(storetypes.CacheMultiStore)
 	}
 
@@ -714,7 +638,8 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context
 func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) {
 	var events []abci.Event
 	if app.abciHandlers.PreBlocker != nil {
-		ctx := app.finalizeBlockState.Context().WithEventManager(sdk.NewEventManager())
+		finalizeState := app.stateManager.GetState(execModeFinalize)
+		ctx := finalizeState.Context().WithEventManager(sdk.NewEventManager())
 		rsp, err := app.abciHandlers.PreBlocker(ctx, req)
 		if err != nil {
 			return nil, err
@@ -726,7 +651,7 @@ func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, erro
 			// GasMeter must be set after we get a context with updated consensus params.
 			gasMeter := app.getBlockGasMeter(ctx)
 			ctx = ctx.WithBlockGasMeter(gasMeter)
-			app.finalizeBlockState.SetContext(ctx)
+			finalizeState.SetContext(ctx)
 		}
 		events = ctx.EventManager().ABCIEvents()
 	}
@@ -740,7 +665,7 @@ func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, er
 	)
 
 	if app.abciHandlers.BeginBlocker != nil {
-		resp, err = app.abciHandlers.BeginBlocker(app.finalizeBlockState.Context())
+		resp, err = app.abciHandlers.BeginBlocker(app.stateManager.GetState(execModeFinalize).Context())
 		if err != nil {
 			return resp, err
 		}
@@ -802,7 +727,7 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) {
 	var endblock sdk.EndBlock
 
 	if app.abciHandlers.EndBlocker != nil {
-		eb, err := app.abciHandlers.EndBlocker(app.finalizeBlockState.Context())
+		eb, err := app.abciHandlers.EndBlocker(app.stateManager.GetState(execModeFinalize).Context())
 		if err != nil {
 			return endblock, err
 		}
@@ -831,7 +756,7 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) {
 // and execute successfully. An error is returned otherwise.
 // both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
 // passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
-func (app *BaseApp) runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
+func (app *BaseApp) runTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
 	// NOTE: GasWanted should be returned by the AnteHandler. GasUsed is
 	// determined by the GasMeter. We need access to the context to get the gas
 	// meter, so we initialize upfront.
@@ -1020,7 +945,7 @@ func (app *BaseApp) runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.G
 // and DeliverTx. An error is returned if any single message fails or if a
 // Handler does not exist for a given message route. Otherwise, a reference to a
 // Result is returned. The caller must not commit state if an error is returned.
-func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode execMode) (*sdk.Result, error) {
+func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode sdk.ExecMode) (*sdk.Result, error) {
 	events := sdk.EmptyEvents()
 	var msgResponses []*codectypes.Any
 
diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go
index 2813dfc3ea88..e941ee5c370d 100644
--- a/baseapp/baseapp_test.go
+++ b/baseapp/baseapp_test.go
@@ -5,7 +5,9 @@ import (
 	"context"
 	"crypto/sha256"
 	"fmt"
+	"io"
 	"math/rand"
+	"os"
 	"testing"
 	"time"
 
@@ -48,10 +50,9 @@ var (
 
 type (
 	BaseAppSuite struct {
-		baseApp   *baseapp.BaseApp
-		cdc       *codec.ProtoCodec
-		txConfig  client.TxConfig
-		logBuffer *bytes.Buffer
+		baseApp  *baseapp.BaseApp
+		cdc      *codec.ProtoCodec
+		txConfig client.TxConfig
 	}
 
 	SnapshotsConfig struct {
@@ -71,8 +72,7 @@ func NewBaseAppSuite(t *testing.T, opts ...func(*baseapp.BaseApp)) *BaseAppSuite
 
 	txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes)
 	db := dbm.NewMemDB()
-	logBuffer := new(bytes.Buffer)
-	logger := log.NewLogger(logBuffer, log.ColorOption(false))
+	logger := log.NewLogger(os.Stdout, log.ColorOption(false))
 
 	app := baseapp.NewBaseApp(t.Name(), logger, db, txConfig.TxDecoder(), opts...)
 	require.Equal(t, t.Name(), app.Name())
@@ -88,10 +88,9 @@ func NewBaseAppSuite(t *testing.T, opts ...func(*baseapp.BaseApp)) *BaseAppSuite
 	require.Nil(t, app.LoadLatestVersion())
 
 	return &BaseAppSuite{
-		baseApp:   app,
-		cdc:       cdc,
-		txConfig:  txConfig,
-		logBuffer: logBuffer,
+		baseApp:  app,
+		cdc:      cdc,
+		txConfig: txConfig,
 	}
 }
 
@@ -687,9 +686,30 @@ func TestBaseAppPostHandler(t *testing.T) {
 	tx = wonkyMsg(t, suite.txConfig, tx)
 	txBytes, err = suite.txConfig.TxEncoder()(tx)
 	require.NoError(t, err)
-	_, err = suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
+
+	output := captureStdout(t, func() {
+		_, err = suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
+		require.NoError(t, err)
+	})
+	// Check the captured output
+	require.NotContains(t, output, "panic recovered in runTx")
+}
+
+func captureStdout(t *testing.T, fn func()) string {
+	t.Helper()
+	oldStdout := os.Stdout
+	r, w, _ := os.Pipe()
+	os.Stdout = w
+
+	fn()
+
+	w.Close()
+	os.Stdout = oldStdout
+
+	var buf bytes.Buffer
+	_, err := io.Copy(&buf, r)
 	require.NoError(t, err)
-	require.NotContains(t, suite.logBuffer.String(), "panic recovered in runTx")
+	return buf.String()
 }
 
 func TestBaseAppPostHandlerErrorHandling(t *testing.T) {
diff --git a/baseapp/config/gas.go b/baseapp/config/gas.go
new file mode 100644
index 000000000000..2386733c9b9e
--- /dev/null
+++ b/baseapp/config/gas.go
@@ -0,0 +1,12 @@
+package config
+
+import sdk "github.com/cosmos/cosmos-sdk/types"
+
+type GasConfig struct {
+	// queryGasLimit defines the maximum gas for queries; unbounded if 0.
+	QueryGasLimit uint64
+
+	// The minimum gas prices a validator is willing to accept for processing a
+	// transaction. This is mainly used for DoS and spam prevention.
+	MinGasPrices sdk.DecCoins
+}
diff --git a/baseapp/options.go b/baseapp/options.go
index 2e48c6b6e2ae..eff6eabfac95 100644
--- a/baseapp/options.go
+++ b/baseapp/options.go
@@ -44,7 +44,7 @@ func SetQueryGasLimit(queryGasLimit uint64) func(*BaseApp) {
 		queryGasLimit = math.MaxUint64
 	}
 
-	return func(bapp *BaseApp) { bapp.queryGasLimit = queryGasLimit }
+	return func(bapp *BaseApp) { bapp.gasConfig.QueryGasLimit = queryGasLimit }
 }
 
 // SetHaltHeight returns a BaseApp option function that sets the halt block height.
diff --git a/baseapp/regression_test.go b/baseapp/regression_test.go
index 6e8265718db7..01018ddf16cb 100644
--- a/baseapp/regression_test.go
+++ b/baseapp/regression_test.go
@@ -9,12 +9,16 @@ import (
 	"cosmossdk.io/log"
 	"cosmossdk.io/store"
 	storemetrics "cosmossdk.io/store/metrics"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	"github.com/cosmos/cosmos-sdk/baseapp/state"
 )
 
 // Ensures that error checks are performed before sealing the app.
 // Please see https://github.com/cosmos/cosmos-sdk/issues/18726
 func TestNilCmsCheckBeforeSeal(t *testing.T) {
 	app := new(BaseApp)
+	app.stateManager = state.NewManager(config.GasConfig{})
 
 	// 1. Invoking app.Init with a nil cms MUST not seal the app
 	// and should return an error firstly, which can later be reversed.
diff --git a/baseapp/state/manager.go b/baseapp/state/manager.go
new file mode 100644
index 000000000000..fb8cb55e730f
--- /dev/null
+++ b/baseapp/state/manager.go
@@ -0,0 +1,137 @@
+package state
+
+import (
+	"fmt"
+	"sync"
+
+	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+
+	"cosmossdk.io/core/header"
+	"cosmossdk.io/log"
+	storetypes "cosmossdk.io/store/types"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type Manager struct {
+	// volatile states:
+	//
+	// - checkState is set on InitChain and reset on Commit
+	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
+	// on Commit.
+	//
+	// - checkState: Used for CheckTx, which is set based on the previous block's
+	// state. This state is never committed.
+	//
+	// - 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.
+	//
+	// - processProposalState: Used for ProcessProposal, which is set based on the
+	// the previous block's state. This state is never committed. In case of
+	// multiple rounds, the state is always reset to the previous block's state.
+	//
+	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
+	// previous block's state. This state is committed.
+	checkState           *State
+	prepareProposalState *State
+	processProposalState *State
+	finalizeBlockState   *State
+	stateMut             sync.RWMutex
+
+	gasConfig config.GasConfig
+}
+
+func NewManager(gasConfig config.GasConfig) *Manager {
+	return &Manager{
+		gasConfig: gasConfig,
+	}
+}
+
+func (mgr *Manager) GetState(mode sdk.ExecMode) *State {
+	mgr.stateMut.RLock()
+	defer mgr.stateMut.RUnlock()
+
+	switch mode {
+	case sdk.ExecModeFinalize:
+		return mgr.finalizeBlockState
+
+	case sdk.ExecModePrepareProposal:
+		return mgr.prepareProposalState
+
+	case sdk.ExecModeProcessProposal:
+		return mgr.processProposalState
+
+	default:
+		return mgr.checkState
+	}
+}
+
+// SetState sets the BaseApp's state for the corresponding mode with a branched
+// multi-store (i.e. a CacheMultiStore) and a new Context with the same
+// multi-store branch, and provided header.
+func (mgr *Manager) SetState(
+	mode sdk.ExecMode,
+	unbranchedStore storetypes.CommitMultiStore,
+	h cmtproto.Header,
+	logger log.Logger,
+	streamingManager storetypes.StreamingManager,
+) {
+	ms := unbranchedStore.CacheMultiStore()
+	headerInfo := header.Info{
+		Height:  h.Height,
+		Time:    h.Time,
+		ChainID: h.ChainID,
+		AppHash: h.AppHash,
+	}
+	baseState := NewState(
+		sdk.NewContext(ms, h, false, logger).
+			WithStreamingManager(streamingManager).
+			WithHeaderInfo(headerInfo),
+		ms,
+	)
+
+	mgr.stateMut.Lock()
+	defer mgr.stateMut.Unlock()
+
+	switch mode {
+	case sdk.ExecModeCheck:
+		baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(mgr.gasConfig.MinGasPrices))
+		mgr.checkState = baseState
+
+	case sdk.ExecModePrepareProposal:
+		mgr.prepareProposalState = baseState
+
+	case sdk.ExecModeProcessProposal:
+		mgr.processProposalState = baseState
+
+	case sdk.ExecModeFinalize:
+		mgr.finalizeBlockState = baseState
+
+	default:
+		panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
+	}
+}
+
+func (mgr *Manager) ClearState(mode sdk.ExecMode) {
+	mgr.stateMut.Lock()
+	defer mgr.stateMut.Unlock()
+
+	switch mode {
+	case sdk.ExecModeCheck:
+		mgr.checkState = nil
+
+	case sdk.ExecModePrepareProposal:
+		mgr.prepareProposalState = nil
+
+	case sdk.ExecModeProcessProposal:
+		mgr.processProposalState = nil
+
+	case sdk.ExecModeFinalize:
+		mgr.finalizeBlockState = nil
+
+	default:
+		panic(fmt.Sprintf("invalid runTxMode for clearState: %d", mode))
+	}
+}
diff --git a/baseapp/state.go b/baseapp/state/state.go
similarity index 54%
rename from baseapp/state.go
rename to baseapp/state/state.go
index 19ee4e7eaa40..7e3f2e425183 100644
--- a/baseapp/state.go
+++ b/baseapp/state/state.go
@@ -1,4 +1,4 @@
-package baseapp
+package state
 
 import (
 	"sync"
@@ -8,28 +8,35 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 )
 
-type state struct {
-	ms storetypes.CacheMultiStore
+type State struct {
+	MultiStore storetypes.CacheMultiStore
 
 	mtx sync.RWMutex
 	ctx sdk.Context
 }
 
+func NewState(ctx sdk.Context, ms storetypes.CacheMultiStore) *State {
+	return &State{
+		MultiStore: ms,
+		ctx:        ctx,
+	}
+}
+
 // CacheMultiStore calls and returns a CacheMultiStore on the state's underling
 // CacheMultiStore.
-func (st *state) CacheMultiStore() storetypes.CacheMultiStore {
-	return st.ms.CacheMultiStore()
+func (st *State) CacheMultiStore() storetypes.CacheMultiStore {
+	return st.MultiStore.CacheMultiStore()
 }
 
 // SetContext updates the state's context to the context provided.
-func (st *state) SetContext(ctx sdk.Context) {
+func (st *State) SetContext(ctx sdk.Context) {
 	st.mtx.Lock()
 	defer st.mtx.Unlock()
 	st.ctx = ctx
 }
 
 // Context returns the Context of the state.
-func (st *state) Context() sdk.Context {
+func (st *State) Context() sdk.Context {
 	st.mtx.RLock()
 	defer st.mtx.RUnlock()
 	return st.ctx
diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go
index b3aa396a0250..9cabad2e3232 100644
--- a/baseapp/test_helpers.go
+++ b/baseapp/test_helpers.go
@@ -54,17 +54,17 @@ func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.
 // SimWriteState is an entrypoint for simulations only. They are not executed during the normal ABCI finalize
 // block step but later. Therefor an extra call to the root multi-store (app.cms) is required to write the changes.
 func (app *BaseApp) SimWriteState() {
-	app.finalizeBlockState.ms.Write()
+	app.stateManager.GetState(execModeFinalize).MultiStore.Write()
 }
 
 // NewContextLegacy returns a new sdk.Context with the provided header
 func (app *BaseApp) NewContextLegacy(isCheckTx bool, header cmtproto.Header) sdk.Context {
 	if isCheckTx {
-		return sdk.NewContext(app.checkState.ms, header, true, app.logger).
-			WithMinGasPrices(app.minGasPrices)
+		return sdk.NewContext(app.stateManager.GetState(execModeCheck).MultiStore, header, true, app.logger).
+			WithMinGasPrices(app.gasConfig.MinGasPrices)
 	}
 
-	return sdk.NewContext(app.finalizeBlockState.ms, header, false, app.logger)
+	return sdk.NewContext(app.stateManager.GetState(execModeFinalize).MultiStore, header, false, app.logger)
 }
 
 // NewContext returns a new sdk.Context with a empty header
diff --git a/baseapp/utils_test.go b/baseapp/utils_test.go
index d2f9ce50d0e5..b447486d1962 100644
--- a/baseapp/utils_test.go
+++ b/baseapp/utils_test.go
@@ -28,6 +28,7 @@ import (
 	storetypes "cosmossdk.io/store/types"
 
 	"github.com/cosmos/cosmos-sdk/baseapp"
+	"github.com/cosmos/cosmos-sdk/baseapp/state"
 	baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil"
 	"github.com/cosmos/cosmos-sdk/client"
 	"github.com/cosmos/cosmos-sdk/codec"
@@ -323,18 +324,23 @@ func testLoadVersionHelper(t *testing.T, app *baseapp.BaseApp, expectedHeight in
 	require.Equal(t, expectedID, lastID)
 }
 
-func getCheckStateCtx(app *baseapp.BaseApp) sdk.Context {
+// note: we use reflection in this test because we do not want to make the baseapp state publicly available
+
+func reflectGetStateCtx(app *baseapp.BaseApp, mode sdk.ExecMode) sdk.Context {
 	v := reflect.ValueOf(app).Elem()
-	f := v.FieldByName("checkState")
+	f := v.FieldByName("stateManager")
 	rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
-	return rf.MethodByName("Context").Call(nil)[0].Interface().(sdk.Context)
+	arg := reflect.ValueOf(mode)
+	st := rf.MethodByName("GetState").Call([]reflect.Value{arg})[0].Interface().(*state.State)
+	return st.Context()
+}
+
+func getCheckStateCtx(app *baseapp.BaseApp) sdk.Context {
+	return reflectGetStateCtx(app, sdk.ExecModeCheck)
 }
 
 func getFinalizeBlockStateCtx(app *baseapp.BaseApp) sdk.Context {
-	v := reflect.ValueOf(app).Elem()
-	f := v.FieldByName("finalizeBlockState")
-	rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
-	return rf.MethodByName("Context").Call(nil)[0].Interface().(sdk.Context)
+	return reflectGetStateCtx(app, sdk.ExecModeFinalize)
 }
 
 func parseTxMemo(t *testing.T, tx sdk.Tx) (counter int64, failOnAnte bool) {
diff --git a/go.mod b/go.mod
index f9aeaba1cb3c..c4532487209c 100644
--- a/go.mod
+++ b/go.mod
@@ -221,6 +221,8 @@ require (
 // 	<temporary replace>
 // )
 
+replace cosmossdk.io/store => ./store
+
 // Below are the long-lived replace of the Cosmos SDK
 replace (
 	// use cosmos fork of keyring
diff --git a/go.sum b/go.sum
index 950cf281975b..8976076189aa 100644
--- a/go.sum
+++ b/go.sum
@@ -630,8 +630,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.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o=
-cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A=
 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=
diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go
index b9987455097a..672559421e29 100644
--- a/store/rootmulti/store.go
+++ b/store/rootmulti/store.go
@@ -10,6 +10,7 @@ import (
 	"sort"
 	"strings"
 	"sync"
+	"sync/atomic"
 
 	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
 	dbm "github.com/cosmos/cosmos-db"
@@ -59,7 +60,7 @@ func keysFromStoreKeyMap[V any](m map[types.StoreKey]V) []types.StoreKey {
 type Store struct {
 	db                  dbm.DB
 	logger              log.Logger
-	lastCommitInfo      *types.CommitInfo
+	lastCommitInfo      atomic.Pointer[types.CommitInfo]
 	pruningManager      *pruning.Manager
 	iavlCacheSize       int
 	iavlDisableFastNode bool
@@ -296,7 +297,7 @@ func (rs *Store) loadVersion(ver int64, upgrades *types.StoreUpgrades) error {
 		}
 	}
 
-	rs.lastCommitInfo = cInfo
+	rs.lastCommitInfo.Store(cInfo)
 	rs.stores = newStores
 
 	// load any snapshot heights we missed from disk to be pruned on the next run
@@ -445,7 +446,8 @@ func (rs *Store) LatestVersion() int64 {
 
 // LastCommitID implements Committer/CommitStore.
 func (rs *Store) LastCommitID() types.CommitID {
-	if rs.lastCommitInfo == nil {
+	info := rs.lastCommitInfo.Load()
+	if info == nil {
 		emptyHash := sha256.Sum256([]byte{})
 		appHash := emptyHash[:]
 		return types.CommitID{
@@ -453,22 +455,22 @@ func (rs *Store) LastCommitID() types.CommitID {
 			Hash:    appHash, // set empty apphash to sha256([]byte{}) if info is nil
 		}
 	}
-	if len(rs.lastCommitInfo.CommitID().Hash) == 0 {
+	if len(info.CommitID().Hash) == 0 {
 		emptyHash := sha256.Sum256([]byte{})
 		appHash := emptyHash[:]
 		return types.CommitID{
-			Version: rs.lastCommitInfo.Version,
+			Version: info.Version,
 			Hash:    appHash, // set empty apphash to sha256([]byte{}) if hash is nil
 		}
 	}
 
-	return rs.lastCommitInfo.CommitID()
+	return info.CommitID()
 }
 
 // Commit implements Committer/CommitStore.
 func (rs *Store) Commit() types.CommitID {
 	var previousHeight, version int64
-	if rs.lastCommitInfo.GetVersion() == 0 && rs.initialVersion > 1 {
+	if cInfo := rs.lastCommitInfo.Load(); (cInfo == nil || cInfo.Version == 0) && rs.initialVersion > 1 {
 		// This case means that no commit has been made in the store, we
 		// start from initialVersion.
 		version = rs.initialVersion
@@ -478,7 +480,11 @@ func (rs *Store) Commit() types.CommitID {
 		// case we increment the version from there,
 		// - or there was no previous commit, and initial version was not set,
 		// in which case we start at version 1.
-		previousHeight = rs.lastCommitInfo.GetVersion()
+		if cInfo != nil {
+			previousHeight = cInfo.Version
+		} else {
+			previousHeight = 0
+		}
 		version = previousHeight + 1
 	}
 
@@ -486,9 +492,11 @@ func (rs *Store) Commit() types.CommitID {
 		rs.logger.Debug("commit header and version mismatch", "header_height", rs.commitHeader.Height, "version", version)
 	}
 
-	rs.lastCommitInfo = commitStores(version, rs.stores, rs.removalMap)
-	rs.lastCommitInfo.Timestamp = rs.commitHeader.Time
-	defer rs.flushMetadata(rs.db, version, rs.lastCommitInfo)
+	cInfo := commitStores(version, rs.stores, rs.removalMap)
+	cInfo.Timestamp = rs.commitHeader.Time
+	rs.lastCommitInfo.Store(cInfo)
+
+	defer rs.flushMetadata(rs.db, version, cInfo)
 
 	// remove remnants of removed stores
 	for sk := range rs.removalMap {
@@ -511,7 +519,7 @@ func (rs *Store) Commit() types.CommitID {
 
 	return types.CommitID{
 		Version: version,
-		Hash:    rs.lastCommitInfo.Hash(),
+		Hash:    cInfo.Hash(),
 	}
 }
 
@@ -762,8 +770,9 @@ func (rs *Store) Query(req *types.RequestQuery) (*types.ResponseQuery, error) {
 	// Otherwise, we query for the commit info from disk.
 	var commitInfo *types.CommitInfo
 
-	if res.Height == rs.lastCommitInfo.Version {
-		commitInfo = rs.lastCommitInfo
+	cInfo := rs.lastCommitInfo.Load()
+	if res.Height == cInfo.Version {
+		commitInfo = cInfo
 	} else {
 		commitInfo, err = rs.GetCommitInfo(res.Height)
 		if err != nil {


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🔭 Outside diff range comments (2)</summary><blockquote>

<details>
<summary>baseapp/baseapp.go (1)</summary><blockquote>

`605-633`: _🛠️ Refactor suggestion_

**Lock scope larger than necessary**

`getContextForTx` holds `app.mu` while:

1. Fetching the `State`.
2. Copying and mutating the `sdk.Context` (`WithTxBytes`, `WithGasMeter`, …).

The second part is CPU-only work and does not touch any `BaseApp` fields, so it can occur outside the critical section, shrinking contention under concurrent tx execution:

```diff
- app.mu.Lock()
- modeState := app.stateManager.GetState(mode)
- ...
- ctx := modeState.Context().
-     WithTxBytes(txBytes).
-     WithGasMeter(storetypes.NewInfiniteGasMeter())
- app.mu.Unlock()
+ app.mu.Lock()
+ modeState := app.stateManager.GetState(mode)
+ app.mu.Unlock()
+
+ ctx := modeState.Context().
+     WithTxBytes(txBytes).
+     WithGasMeter(storetypes.NewInfiniteGasMeter())
```

</blockquote></details>
<details>
<summary>baseapp/abci.go (1)</summary><blockquote>

`883-887`: _⚠️ Potential issue_

**Possible nil-pointer when OE aborted before FinalizeBlock listeners fire**

`ListenFinalizeBlock` dereferences `app.stateManager.GetState(execModeFinalize)` without checking the result.  
When the optimistic executor aborts earlier in this method (`app.optimisticExec.AbortIfNeeded`), the `finalize` state is cleared a few lines later (905-907).  
In the very small window between those two calls, another goroutine could already have called `ClearState`, leaving `GetState(execModeFinalize)` as `nil` here and causing a panic.

```diff
-        for _, streamingListener := range app.streamingManager.ABCIListeners {
-            if err := streamingListener.ListenFinalizeBlock(app.stateManager.GetState(execModeFinalize).Context(), *req, *res); err != nil {
+        fs := app.stateManager.GetState(execModeFinalize)
+        if fs == nil {
+            return res, fmt.Errorf("finalize state unexpectedly cleared (optimistic execution abort?)")
+        }
+        for _, streamingListener := range app.streamingManager.ABCIListeners {
+            if err := streamingListener.ListenFinalizeBlock(fs.Context(), *req, *res); err != nil {
                 app.logger.Error("ListenFinalizeBlock listening hook failed", "height", req.Height, "err", err)
             }
         }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>baseapp/state/manager.go (1)</summary><blockquote>

`132-152`: **`ClearState` mirrors the same omission**

For consistency with `SetState`, `ClearState` should clear `checkState` when
mode is `ExecModeReCheck` or `ExecModeSimulate`; otherwise, calls with those
modes will panic.

```diff
 case ExecModeCheck, ExecModeReCheck, ExecModeSimulate:
     mgr.checkState = nil
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>baseapp/state/state.go (2)</summary><blockquote>

`25-29`: **Duplicate wording in doc-string**

Comment reads “CacheMultiStore calls and returns a CacheMultiStore on the state's underling CacheMultiStore.” – the final “CacheMultiStore” is redundant and makes the sentence hard to read.

```diff
-// CacheMultiStore calls and returns a CacheMultiStore on the state's underling
-// CacheMultiStore.
+// CacheMultiStore returns a branched cache from the State's underlying
+// MultiStore.
```

---

`11-16`: **Consider keeping `MultiStore` unexported**

`MultiStore` is now exported on the `State` struct.  Exposing it allows any caller to mutate or replace the store without the protection of the mutex, circumventing the concurrency guarantees you just added for `ctx`.  Most use-cases only need the safe helper (`CacheMultiStore`) anyway.

```diff
-type State struct {
-    MultiStore storetypes.CacheMultiStore
+type State struct {
+    multiStore storetypes.CacheMultiStore // keep unexported
```

and add a trivial getter if necessary.  
This keeps write-access encapsulated while preserving read-access.

</blockquote></details>
<details>
<summary>baseapp/utils_test.go (1)</summary><blockquote>

`329-336`: **Guard against reflection panics**

`FieldByName("stateManager")` returns a zero `reflect.Value` if the struct layout ever changes, which would make `NewAt` panic.  
Add a quick validity check to fail the test gracefully instead of taking down the whole test binary:

```diff
-   f := v.FieldByName("stateManager")
-   rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
+   f := v.FieldByName("stateManager")
+   require.True(t, f.IsValid(), "stateManager field not found – BaseApp layout changed?")
+   rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
```

Also consider replacing the unsafe trick with `f.Interface().(*state.Manager)` now that the field is a pointer; this removes the need for `unsafe` entirely.

</blockquote></details>
<details>
<summary>baseapp/state/manager.go (1)</summary><blockquote>

`96-108`: **Context lacks gas meter for non-Check modes**

`SetState` configures `MinGasPrices` for Check-Tx contexts but never sets an
initial gas meter for other modes. Several code paths (e.g. vote-extension)
expect a non-nil gas meter and will panic if `GasConsumed()` is called.

Consider initialising a default infinite gas meter for non-Check contexts here
to avoid surprises.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d1bb4298868106e88d2250d378032ea14ec4efc3 and 16d7c9949f1d04ee619cf3022d1dfd0fe1d5650f.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `go.mod` is excluded by `!**/*.mod`
* `go.sum` is excluded by `!**/*.sum`, `!**/*.sum`

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `CHANGELOG.md` (1 hunks)
* `baseapp/abci.go` (32 hunks)
* `baseapp/abci_test.go` (2 hunks)
* `baseapp/baseapp.go` (13 hunks)
* `baseapp/baseapp_test.go` (5 hunks)
* `baseapp/config/gas.go` (1 hunks)
* `baseapp/options.go` (1 hunks)
* `baseapp/regression_test.go` (1 hunks)
* `baseapp/state/manager.go` (1 hunks)
* `baseapp/state/state.go` (2 hunks)
* `baseapp/test_helpers.go` (1 hunks)
* `baseapp/utils_test.go` (2 hunks)
* `store/rootmulti/store.go` (7 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>baseapp/options.go (1)</summary><blockquote>

<details>
<summary>baseapp/baseapp.go (1)</summary>

* `BaseApp` (65-176)

</details>

</blockquote></details>
<details>
<summary>baseapp/baseapp_test.go (3)</summary><blockquote>

<details>
<summary>baseapp/baseapp.go (1)</summary>

* `BaseApp` (65-176)

</details>
<details>
<summary>log/logger.go (1)</summary>

* `NewLogger` (93-133)

</details>
<details>
<summary>log/options.go (1)</summary>

* `ColorOption` (58-62)

</details>

</blockquote></details>
<details>
<summary>baseapp/state/state.go (1)</summary><blockquote>

<details>
<summary>store/types/store.go (2)</summary>

* `MultiStore` (115-147)
* `CacheMultiStore` (150-153)

</details>

</blockquote></details>
<details>
<summary>baseapp/utils_test.go (2)</summary><blockquote>

<details>
<summary>baseapp/baseapp.go (1)</summary>

* `BaseApp` (65-176)

</details>
<details>
<summary>baseapp/state/manager.go (3)</summary>

* `ExecMode` (18-18)
* `ExecModeCheck` (22-22)
* `ExecModeFinalize` (29-29)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (16)</summary>

* GitHub Check: test-system
* GitHub Check: tests (03)
* GitHub Check: tests (02)
* GitHub Check: tests (01)
* GitHub Check: tests (00)
* GitHub Check: test-x-upgrade
* GitHub Check: test-sim-nondeterminism
* GitHub Check: test-e2e
* GitHub Check: test-integration
* GitHub Check: build (arm64)
* GitHub Check: Analyze
* GitHub Check: build (amd64)
* GitHub Check: golangci-lint
* GitHub Check: main
* GitHub Check: Gosec
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (24)</summary><blockquote>

<details>
<summary>CHANGELOG.md (1)</summary>

`87-87`: **Clear and informative changelog entry**

The changelog entry accurately describes the race condition fix between `Commit` and `CreateQueryContext` operations by adding mutex locks for state and making `lastCommitInfo` atomic. This is a good improvement for thread safety.

</details>
<details>
<summary>baseapp/options.go (1)</summary>

`47-47`: **Update to use new gasConfig structure**

The change correctly updates the `SetQueryGasLimit` function to modify the `QueryGasLimit` field within the newly introduced `gasConfig` struct rather than directly modifying a field on BaseApp. This aligns with the refactoring to centralize gas-related configuration.

</details>
<details>
<summary>baseapp/config/gas.go (1)</summary>

`1-12`: **Good design for gas configuration encapsulation**

Creating a dedicated `GasConfig` struct to centralize gas-related configuration is a clean approach. This encapsulation helps maintain a single source of truth for gas parameters that can be shared across different components of the system, improving maintainability.

</details>
<details>
<summary>baseapp/regression_test.go (1)</summary>

`12-14`: **Proper initialization of stateManager in test**

The test has been properly updated to initialize the new `stateManager` field with an empty `GasConfig` before running `app.Init()`. This ensures the test remains compatible with the refactored state management approach that uses mutexes to prevent race conditions.



Also applies to: 21-21

</details>
<details>
<summary>baseapp/test_helpers.go (3)</summary>

`57-57`: **Updates to use new state management abstraction**

The code now accesses the multi-store through the new centralized state manager, improving thread safety by using the proper abstraction layer.

---

`63-64`: **Refactored state and config access**

The change properly uses the state manager to access the check state's multi-store and gets minimum gas prices from the new centralized gas configuration. This follows the improved architecture pattern for state management.

---

`67-67`: **Consistent use of state manager for finalize state**

This change maintains consistency with the new state management approach, ensuring that all state access goes through the centralized state manager.

</details>
<details>
<summary>baseapp/baseapp_test.go (6)</summary>

`9-10`: **Added imports for new standard output capture functionality**

Added imports for `io` and `os` to support the new stdout capture mechanism used in tests.

---

`52-56`: **Simplified BaseAppSuite struct**

Removed the `logBuffer` field from the struct, moving away from buffered logging toward direct output capture.

---

`75-75`: **Improved logger initialization**

Changed logger initialization to write to `os.Stdout` directly with color disabled, which provides clearer test output and simplifies the suite setup.

---

`90-94`: **Simplified BaseAppSuite construction**

The suite construction no longer includes a log buffer field, streamlining the object initialization.

---

`690-696`: **Added output capture for panic testing**

Implemented a better approach for testing panic recovery by capturing stdout output directly, which provides more reliable test verification than checking log buffers.

---

`698-713`: **Clean implementation of stdout capture utility**

This new helper function correctly captures and returns stdout output during a function execution, properly handling pipe creation and cleanup. This is a good pattern for testing output-producing code.

</details>
<details>
<summary>baseapp/abci_test.go (2)</summary>

`14-14`: **Added atomic package for concurrency testing**

This import is necessary for the new concurrency stress test that verifies thread safety between commit and query operations.

---

`2540-2586`: **Added critical concurrency stress test**

This test effectively verifies that the new state management system correctly handles concurrent query context creation during commit operations. It's an excellent addition to prevent race conditions.

Key strengths:
1. Creates 100 concurrent goroutines that stress the query path
2. Performs 1000 block commits in parallel
3. Uses atomic counter to track operations
4. Properly cleans up with context cancellation

This test will help ensure the race condition fix remains effective in future changes.

</details>
<details>
<summary>store/rootmulti/store.go (7)</summary>

`13-13`: **Added atomic package import for thread-safe operations**

This import enables the use of atomic operations for thread-safe access to shared state.

---

`63-63`: **Made lastCommitInfo thread-safe using atomic.Pointer**

This critical change addresses the root cause of the race condition by ensuring that reads and writes to lastCommitInfo are atomic operations. This prevents data races when one goroutine is reading while another is writing to this field.

---

`300-300`: **Thread-safe update of lastCommitInfo**

Using the atomic Store method to update lastCommitInfo ensures visibility of the update to all goroutines and prevents race conditions during concurrent access.

---

`449-468`: **Improved thread safety in LastCommitID method**

The LastCommitID method now safely loads lastCommitInfo using atomic operations and properly handles the nil case. This prevents race conditions when concurrent queries and commits are happening.

---

`473-487`: **Thread-safe version determination in Commit method**

The Commit method now safely accesses the previous commit info using atomic operations and properly handles nil cases. This ensures correct version calculation even under concurrent operations.

---

`497-498`: **Atomic store of new commit info**

Storing the new commit info atomically ensures all goroutines will see a consistent state after commit operations. This is critical for preventing race conditions.

---

`773-775`: **Thread-safe commit info retrieval in Query method**

Query now safely loads lastCommitInfo using atomic operations, ensuring that queries run concurrently with commits can safely access this information.

</details>
<details>
<summary>baseapp/baseapp.go (1)</summary>

`421-429`: **Verify that every execution mode gets initialised before first use**

`Init()` only seeds state for `execModeCheck`, but later code (e.g. `deliverTx`, `beginBlock`, `preBlock`) immediately calls `stateManager.GetState(execModeFinalize)` and others.  
If `FinalizeBlock` or tests call these paths before the corresponding `SetState`, `GetState` returns `nil` and the application panics.

Please confirm that each mode is populated *before* any call site can run, or add a defensive `SetState` during `Init()` for all modes:

```diff
- app.stateManager.SetState(execModeCheck, app.cms, emptyHeader, app.logger, app.streamingManager)
+ for _, m := range []execMode{
+     execModeCheck,
+     execModeFinalize,          // add others as required
+ } {
+     app.stateManager.SetState(m, app.cms, emptyHeader, app.logger, app.streamingManager)
+ }
```

</details>
<details>
<summary>baseapp/abci.go (1)</summary>

`1000-1004`: **Concurrent read–write hazard on `MultiStore.Write()`**

`workingHash()` calls `finalizeState.MultiStore.Write()` without any explicit synchronisation with
`Commit()` which concurrently executes `app.cms.Commit()`.  
Although `CacheMultiStore.Write()` is typically safe, mixing writes and commits from different goroutines could re-introduce the very race this PR tries to solve.

Confirm that `workingHash()` is only reached from the same goroutine that will later invoke `Commit()`.  
If not, protect the call with the same mutex used inside `state.Manager`, or move the write into `Commit()` before `cms.Commit()`.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T17:13:52Z
user: login: technicallyty
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T17:37:27Z
user: login: aljo242
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T17:55:55Z
user: login: aljo242
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T17:19:44Z
user: login: technicallyty
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T18:15:12Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T19:16:44Z
user: login: technicallyty
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-13T00:12:28Z

Review comments:
diff_hunk: @@ -441,17 +418,21 @@ func (app *BaseApp) Init() error {
 		return errors.New("commit multi-store must not be nil")
 	}
 
+	if app.stateManager == nil {
+		return errors.New("state manager must not be nil")
+	}
+
 	emptyHeader := cmtproto.Header{ChainID: app.chainID}
 
 	// needed for the export command which inits from store but never calls initchain
-	app.setState(execModeCheck, emptyHeader)
+	app.stateManager.SetState(execModeCheck, app.cms, emptyHeader, app.logger, app.streamingManager)
 	app.Seal()
 
 	return app.cms.GetPruning().Validate()
 }
 
 func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
-	app.minGasPrices = gasPrices
+	app.gasConfig.MinGasPrices = gasPrices
 }
path: baseapp/baseapp.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`setMinGasPrices` is unsynchronised**

`setMinGasPrices` mutates `app.gasConfig` without holding `app.mu`, unlike most other setters.  
If called after the app starts serving ABCI requests this could race with concurrent gas checks.

Add the mutex to keep writes safe:

```diff
func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
-    app.gasConfig.MinGasPrices = gasPrices
+    app.mu.Lock()
+    defer app.mu.Unlock()
+    app.gasConfig.MinGasPrices = gasPrices
}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
    app.mu.Lock()
    defer app.mu.Unlock()
    app.gasConfig.MinGasPrices = gasPrices
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In baseapp/baseapp.go around lines 434 to 436, the setMinGasPrices method modifies app.gasConfig.MinGasPrices without acquiring the app.mu mutex, risking concurrent write conflicts during ABCI request handling. To fix this, wrap the assignment inside a lock and unlock sequence using app.mu.Lock() before the assignment and app.mu.Unlock() after, ensuring thread-safe mutation of gasConfig.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T17:13:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -934,11 +941,12 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error {
 // against that height and gracefully halt if it matches the latest committed
 // height.
 func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
-	header := app.finalizeBlockState.Context().BlockHeader()
+	finalizeState := app.stateManager.GetState(execModeFinalize)
+	header := finalizeState.Context().BlockHeader()
 	retainHeight := app.GetBlockRetentionHeight(header.Height)
 
 	if app.precommiter != nil {
-		app.precommiter(app.finalizeBlockState.Context())
+		app.precommiter(finalizeState.Context())
 	}
path: baseapp/abci.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`Commit()` can panic if `finalizeState` was cleared**

`finalizeState := app.stateManager.GetState(execModeFinalize)` is blindly dereferenced in the next line.  
If OE aborted in `FinalizeBlock`, `ClearState(execModeFinalize)` is already invoked (see 905-907), so `finalizeState` may be `nil`, leading to a panic at `finalizeState.Context()`.

Add a guard to fail fast instead of crashing:

```diff
- finalizeState := app.stateManager.GetState(execModeFinalize)
- header := finalizeState.Context().BlockHeader()
+ finalizeState := app.stateManager.GetState(execModeFinalize)
+ if finalizeState == nil {
+     return nil, fmt.Errorf("commit called without an active finalize state – possible race with OE abort")
+ }
+ header := finalizeState.Context().BlockHeader()
```



> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In baseapp/abci.go around lines 944 to 950, the code dereferences finalizeState without checking if it is nil, which can cause a panic if finalizeState was cleared earlier. Add a nil check for finalizeState after retrieving it with GetState(execModeFinalize) and return or handle the error gracefully if it is nil, to prevent a crash when calling finalizeState.Context().
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T17:13:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,152 @@
+package state
+
+import (
+	"fmt"
+	"sync"
+
+	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+
+	"cosmossdk.io/core/header"
+	"cosmossdk.io/log"
+	storetypes "cosmossdk.io/store/types"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type (
+	ExecMode uint8
+)
+
+const (
+	ExecModeCheck               = ExecMode(sdk.ExecModeCheck)               // Check a transaction
+	ExecModeReCheck             = ExecMode(sdk.ExecModeReCheck)             // Recheck a (pending) transaction after a commit
+	ExecModeSimulate            = ExecMode(sdk.ExecModeSimulate)            // Simulate a transaction
+	ExecModePrepareProposal     = ExecMode(sdk.ExecModePrepareProposal)     // Prepare a block proposal
+	ExecModeProcessProposal     = ExecMode(sdk.ExecModeProcessProposal)     // Process a block proposal
+	ExecModeVoteExtension       = ExecMode(sdk.ExecModeVoteExtension)       // Extend or verify a pre-commit vote
+	ExecModeVerifyVoteExtension = ExecMode(sdk.ExecModeVerifyVoteExtension) // Verify a vote extension
+	ExecModeFinalize            = ExecMode(sdk.ExecModeFinalize)            // Finalize a block proposal
+)
+
+type Manager struct {
+	// volatile states:
+	//
+	// - checkState is set on InitChain and reset on Commit
+	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
+	// on Commit.
+	//
+	// - checkState: Used for CheckTx, which is set based on the previous block's
+	// state. This state is never committed.
+	//
+	// - 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.
+	//
+	// - processProposalState: Used for ProcessProposal, which is set based on the
+	// the previous block's state. This state is never committed. In case of
+	// multiple rounds, the state is always reset to the previous block's state.
+	//
+	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
+	// previous block's state. This state is committed.
+	checkState           *State
+	prepareProposalState *State
+	processProposalState *State
+	finalizeBlockState   *State
+	stateMut             sync.RWMutex
+
+	gasConfig config.GasConfig
+}
+
+func NewManager(gasConfig config.GasConfig) *Manager {
+	return &Manager{
+		gasConfig: gasConfig,
+	}
+}
+
+func (mgr *Manager) GetState(mode ExecMode) *State {
+	mgr.stateMut.RLock()
+	defer mgr.stateMut.RUnlock()
+
+	switch mode {
+	case ExecModeFinalize:
+		return mgr.finalizeBlockState
+
+	case ExecModePrepareProposal:
+		return mgr.prepareProposalState
+
+	case ExecModeProcessProposal:
+		return mgr.processProposalState
+
+	default:
+		return mgr.checkState
+	}
+}
+
+// SetState sets the BaseApp's state for the corresponding mode with a branched
+// multi-store (i.e. a CacheMultiStore) and a new Context with the same
+// multi-store branch, and provided header.
+func (mgr *Manager) SetState(
+	mode ExecMode,
+	unbranchedStore storetypes.CommitMultiStore,
+	h cmtproto.Header,
+	logger log.Logger,
+	streamingManager storetypes.StreamingManager,
+) {
+	ms := unbranchedStore.CacheMultiStore()
+	headerInfo := header.Info{
+		Height:  h.Height,
+		Time:    h.Time,
+		ChainID: h.ChainID,
+		AppHash: h.AppHash,
+	}
+	baseState := NewState(
+		sdk.NewContext(ms, h, false, logger).
+			WithStreamingManager(streamingManager).
+			WithHeaderInfo(headerInfo),
+		ms,
+	)
+
+	mgr.stateMut.Lock()
+	defer mgr.stateMut.Unlock()
+
+	switch mode {
+	case ExecModeCheck:
+		baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(mgr.gasConfig.MinGasPrices))
+		mgr.checkState = baseState
+
+	case ExecModePrepareProposal:
+		mgr.prepareProposalState = baseState
+
+	case ExecModeProcessProposal:
+		mgr.processProposalState = baseState
+
+	case ExecModeFinalize:
+		mgr.finalizeBlockState = baseState
+
+	default:
+		panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
+	}
+}
path: baseapp/state/manager.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`SetState` panics for `ExecModeReCheck` and `ExecModeSimulate`**

`ExecModeReCheck` and `ExecModeSimulate` are defined in the const block but are
not handled in the switch, causing a panic if they are ever used.  
Even if they are not currently invoked, future code might, and the panic will
be hard to trace.

Add explicit cases mapping them to the Check-Tx branch.

```diff
 case ExecModeCheck, ExecModeReCheck, ExecModeSimulate:
     baseState.SetContext(baseState.Context().
         WithIsCheckTx(true).
         WithMinGasPrices(mgr.gasConfig.MinGasPrices))
     mgr.checkState = baseState
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	switch mode {
	case ExecModeCheck, ExecModeReCheck, ExecModeSimulate:
		baseState.SetContext(
			baseState.Context().
				WithIsCheckTx(true).
				WithMinGasPrices(mgr.gasConfig.MinGasPrices),
		)
		mgr.checkState = baseState

	case ExecModePrepareProposal:
		mgr.prepareProposalState = baseState

	case ExecModeProcessProposal:
		mgr.processProposalState = baseState

	case ExecModeFinalize:
		mgr.finalizeBlockState = baseState

	default:
		panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
	}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In baseapp/state/manager.go around lines 113 to 130, the switch statement in SetState does not handle ExecModeReCheck and ExecModeSimulate, causing a panic if these modes are used. Add explicit case branches for ExecModeReCheck and ExecModeSimulate that set the state similarly to ExecModeCheck, by calling baseState.SetContext with WithIsCheckTx(true) and WithMinGasPrices, and assigning baseState to mgr.checkState. This will prevent panics and ensure these modes are handled consistently.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T17:13:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,152 @@
+package state
+
+import (
+	"fmt"
+	"sync"
+
+	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+
+	"cosmossdk.io/core/header"
+	"cosmossdk.io/log"
+	storetypes "cosmossdk.io/store/types"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type (
+	ExecMode uint8
+)
+
+const (
+	ExecModeCheck               = ExecMode(sdk.ExecModeCheck)               // Check a transaction
+	ExecModeReCheck             = ExecMode(sdk.ExecModeReCheck)             // Recheck a (pending) transaction after a commit
+	ExecModeSimulate            = ExecMode(sdk.ExecModeSimulate)            // Simulate a transaction
+	ExecModePrepareProposal     = ExecMode(sdk.ExecModePrepareProposal)     // Prepare a block proposal
+	ExecModeProcessProposal     = ExecMode(sdk.ExecModeProcessProposal)     // Process a block proposal
+	ExecModeVoteExtension       = ExecMode(sdk.ExecModeVoteExtension)       // Extend or verify a pre-commit vote
+	ExecModeVerifyVoteExtension = ExecMode(sdk.ExecModeVerifyVoteExtension) // Verify a vote extension
+	ExecModeFinalize            = ExecMode(sdk.ExecModeFinalize)            // Finalize a block proposal
+)
+
+type Manager struct {
+	// volatile states:
+	//
+	// - checkState is set on InitChain and reset on Commit
+	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
+	// on Commit.
+	//
+	// - checkState: Used for CheckTx, which is set based on the previous block's
+	// state. This state is never committed.
+	//
+	// - 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.
+	//
+	// - processProposalState: Used for ProcessProposal, which is set based on the
+	// the previous block's state. This state is never committed. In case of
+	// multiple rounds, the state is always reset to the previous block's state.
+	//
+	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
+	// previous block's state. This state is committed.
+	checkState           *State
+	prepareProposalState *State
+	processProposalState *State
+	finalizeBlockState   *State
+	stateMut             sync.RWMutex
+
+	gasConfig config.GasConfig
+}
+
+func NewManager(gasConfig config.GasConfig) *Manager {
+	return &Manager{
+		gasConfig: gasConfig,
+	}
+}
+
+func (mgr *Manager) GetState(mode ExecMode) *State {
+	mgr.stateMut.RLock()
+	defer mgr.stateMut.RUnlock()
+
+	switch mode {
+	case ExecModeFinalize:
+		return mgr.finalizeBlockState
+
+	case ExecModePrepareProposal:
+		return mgr.prepareProposalState
+
+	case ExecModeProcessProposal:
+		return mgr.processProposalState
+
+	default:
+		return mgr.checkState
+	}
+}
path: baseapp/state/manager.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Default branch silently aliases unrelated modes to `checkState`**

`GetState` falls through to `checkState` for *any* mode not explicitly handled.
That means callers passing `ExecModeVoteExtension`, `ExecModeVerifyVoteExtension`,
`ExecModeReCheck`, or an unknown value will all receive the Check-Tx state
without noticing.

Returning `nil` (or better, an error) for unsupported modes would avoid subtle
mis-use.

```diff
-    default:
-        return mgr.checkState
+    case ExecModeCheck, ExecModeReCheck, ExecModeSimulate:
+        return mgr.checkState
+    default:
+        return nil
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
func (mgr *Manager) GetState(mode ExecMode) *State {
	mgr.stateMut.RLock()
	defer mgr.stateMut.RUnlock()

	switch mode {
	case ExecModeFinalize:
		return mgr.finalizeBlockState

	case ExecModePrepareProposal:
		return mgr.prepareProposalState

	case ExecModeProcessProposal:
		return mgr.processProposalState

	case ExecModeCheck, ExecModeReCheck, ExecModeSimulate:
		return mgr.checkState

	default:
		return nil
	}
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In baseapp/state/manager.go around lines 67 to 84, the GetState method currently returns mgr.checkState for any ExecMode not explicitly handled, which can cause unrelated modes to silently receive the wrong state. Modify the default case to return nil or an error to clearly indicate unsupported modes, preventing misuse by callers passing unknown or unsupported ExecMode values.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T17:13:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,7 +38,7 @@ import (
 )
 
 type (
-	execMode uint8
+	execMode = state.ExecMode
path: baseapp/baseapp.go
user: login: technicallyty
body: can't we just use the ExecMode type from `types` everywhere? never really understood why this has so many aliases
created_at: 2025-05-07T17:31:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,152 @@
+package state
+
+import (
+	"fmt"
+	"sync"
+
+	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+
+	"cosmossdk.io/core/header"
+	"cosmossdk.io/log"
+	storetypes "cosmossdk.io/store/types"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type (
+	ExecMode uint8
+)
+
+const (
+	ExecModeCheck               = ExecMode(sdk.ExecModeCheck)               // Check a transaction
+	ExecModeReCheck             = ExecMode(sdk.ExecModeReCheck)             // Recheck a (pending) transaction after a commit
+	ExecModeSimulate            = ExecMode(sdk.ExecModeSimulate)            // Simulate a transaction
+	ExecModePrepareProposal     = ExecMode(sdk.ExecModePrepareProposal)     // Prepare a block proposal
+	ExecModeProcessProposal     = ExecMode(sdk.ExecModeProcessProposal)     // Process a block proposal
+	ExecModeVoteExtension       = ExecMode(sdk.ExecModeVoteExtension)       // Extend or verify a pre-commit vote
+	ExecModeVerifyVoteExtension = ExecMode(sdk.ExecModeVerifyVoteExtension) // Verify a vote extension
+	ExecModeFinalize            = ExecMode(sdk.ExecModeFinalize)            // Finalize a block proposal
+)
+
+type Manager struct {
+	// volatile states:
+	//
+	// - checkState is set on InitChain and reset on Commit
+	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
+	// on Commit.
+	//
+	// - checkState: Used for CheckTx, which is set based on the previous block's
+	// state. This state is never committed.
+	//
+	// - 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.
+	//
+	// - processProposalState: Used for ProcessProposal, which is set based on the
+	// the previous block's state. This state is never committed. In case of
+	// multiple rounds, the state is always reset to the previous block's state.
+	//
+	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
+	// previous block's state. This state is committed.
+	checkState           *State
+	prepareProposalState *State
+	processProposalState *State
+	finalizeBlockState   *State
+	stateMut             sync.RWMutex
+
+	gasConfig config.GasConfig
+}
+
+func NewManager(gasConfig config.GasConfig) *Manager {
+	return &Manager{
+		gasConfig: gasConfig,
+	}
+}
path: baseapp/state/manager.go
user: login: technicallyty
body: the min gas prices value can be set on baseapp without the app being `sealed` which implies this value could be dynamic. i think we should use the value from the app when we need it, rather than storing it once and using the same value.

its entirely possible that devs or validators have a custom node impl that has dynamic minGasPrices.
created_at: 2025-05-07T17:34:57Z
author_association: MEMBER
diff_hunk: @@ -37,7 +38,7 @@ import (
 )
 
 type (
-	execMode uint8
+	execMode = state.ExecMode
path: baseapp/baseapp.go
user: login: aljo242
body: no we should use types you're right

created_at: 2025-05-07T17:55:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,152 @@
+package state
+
+import (
+	"fmt"
+	"sync"
+
+	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+
+	"cosmossdk.io/core/header"
+	"cosmossdk.io/log"
+	storetypes "cosmossdk.io/store/types"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type (
+	ExecMode uint8
+)
+
+const (
+	ExecModeCheck               = ExecMode(sdk.ExecModeCheck)               // Check a transaction
+	ExecModeReCheck             = ExecMode(sdk.ExecModeReCheck)             // Recheck a (pending) transaction after a commit
+	ExecModeSimulate            = ExecMode(sdk.ExecModeSimulate)            // Simulate a transaction
+	ExecModePrepareProposal     = ExecMode(sdk.ExecModePrepareProposal)     // Prepare a block proposal
+	ExecModeProcessProposal     = ExecMode(sdk.ExecModeProcessProposal)     // Process a block proposal
+	ExecModeVoteExtension       = ExecMode(sdk.ExecModeVoteExtension)       // Extend or verify a pre-commit vote
+	ExecModeVerifyVoteExtension = ExecMode(sdk.ExecModeVerifyVoteExtension) // Verify a vote extension
+	ExecModeFinalize            = ExecMode(sdk.ExecModeFinalize)            // Finalize a block proposal
+)
+
+type Manager struct {
+	// volatile states:
+	//
+	// - checkState is set on InitChain and reset on Commit
+	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
+	// on Commit.
+	//
+	// - checkState: Used for CheckTx, which is set based on the previous block's
+	// state. This state is never committed.
+	//
+	// - 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.
+	//
+	// - processProposalState: Used for ProcessProposal, which is set based on the
+	// the previous block's state. This state is never committed. In case of
+	// multiple rounds, the state is always reset to the previous block's state.
+	//
+	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
+	// previous block's state. This state is committed.
+	checkState           *State
+	prepareProposalState *State
+	processProposalState *State
+	finalizeBlockState   *State
+	stateMut             sync.RWMutex
+
+	gasConfig config.GasConfig
+}
+
+func NewManager(gasConfig config.GasConfig) *Manager {
+	return &Manager{
+		gasConfig: gasConfig,
+	}
+}
path: baseapp/state/manager.go
user: login: aljo242
body: do you think it makes sense to just let folks set it once before being `sealed`? thats a semantic difference but probably makes more sense imo.  

The only time you change your min gas prices is when you edit your app toml or with a flag and restart your app

created_at: 2025-05-08T17:19:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,152 @@
+package state
+
+import (
+	"fmt"
+	"sync"
+
+	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+
+	"cosmossdk.io/core/header"
+	"cosmossdk.io/log"
+	storetypes "cosmossdk.io/store/types"
+
+	"github.com/cosmos/cosmos-sdk/baseapp/config"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type (
+	ExecMode uint8
+)
+
+const (
+	ExecModeCheck               = ExecMode(sdk.ExecModeCheck)               // Check a transaction
+	ExecModeReCheck             = ExecMode(sdk.ExecModeReCheck)             // Recheck a (pending) transaction after a commit
+	ExecModeSimulate            = ExecMode(sdk.ExecModeSimulate)            // Simulate a transaction
+	ExecModePrepareProposal     = ExecMode(sdk.ExecModePrepareProposal)     // Prepare a block proposal
+	ExecModeProcessProposal     = ExecMode(sdk.ExecModeProcessProposal)     // Process a block proposal
+	ExecModeVoteExtension       = ExecMode(sdk.ExecModeVoteExtension)       // Extend or verify a pre-commit vote
+	ExecModeVerifyVoteExtension = ExecMode(sdk.ExecModeVerifyVoteExtension) // Verify a vote extension
+	ExecModeFinalize            = ExecMode(sdk.ExecModeFinalize)            // Finalize a block proposal
+)
+
+type Manager struct {
+	// volatile states:
+	//
+	// - checkState is set on InitChain and reset on Commit
+	// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
+	// on Commit.
+	//
+	// - checkState: Used for CheckTx, which is set based on the previous block's
+	// state. This state is never committed.
+	//
+	// - 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.
+	//
+	// - processProposalState: Used for ProcessProposal, which is set based on the
+	// the previous block's state. This state is never committed. In case of
+	// multiple rounds, the state is always reset to the previous block's state.
+	//
+	// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
+	// previous block's state. This state is committed.
+	checkState           *State
+	prepareProposalState *State
+	processProposalState *State
+	finalizeBlockState   *State
+	stateMut             sync.RWMutex
+
+	gasConfig config.GasConfig
+}
+
+func NewManager(gasConfig config.GasConfig) *Manager {
+	return &Manager{
+		gasConfig: gasConfig,
+	}
+}
path: baseapp/state/manager.go
user: login: technicallyty
body: yeah you're right nvm, when i looked yesterday i thought it was a setter, but its just an initialization option
created_at: 2025-05-08T18:15:12Z
author_association: MEMBER
diff_hunk: @@ -352,14 +355,14 @@
 	}
 
 	if app.abciHandlers.CheckTxHandler == nil {
-		gInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
+		gasInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
 		if err != nil {
-			return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace), nil
+			return sdkerrors.ResponseCheckTxWithEvents(err, gasInfo.GasWanted, gasInfo.GasUsed, anteEvents, app.trace), nil
 		}
 
 		return &abci.ResponseCheckTx{
-			GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints?
-			GasUsed:   int64(gInfo.GasUsed),   // TODO: Should type accept unsigned ints?
+			GasWanted: int64(gasInfo.GasWanted), // TODO: Should type accept unsigned ints?
path: baseapp/abci.go
user: login: github-advanced-security[bot]
body: ## integer overflow conversion uint64 -> uint32

integer overflow conversion uint64 -> int64

[Show more details](https://github.com/cosmos/cosmos-sdk/security/code-scanning/23583)
created_at: 2025-05-08T19:16:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -352,14 +355,14 @@
 	}
 
 	if app.abciHandlers.CheckTxHandler == nil {
-		gInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
+		gasInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
 		if err != nil {
-			return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace), nil
+			return sdkerrors.ResponseCheckTxWithEvents(err, gasInfo.GasWanted, gasInfo.GasUsed, anteEvents, app.trace), nil
 		}
 
 		return &abci.ResponseCheckTx{
-			GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints?
-			GasUsed:   int64(gInfo.GasUsed),   // TODO: Should type accept unsigned ints?
+			GasWanted: int64(gasInfo.GasWanted), // TODO: Should type accept unsigned ints?
+			GasUsed:   int64(gasInfo.GasUsed),   // TODO: Should type accept unsigned ints?
path: baseapp/abci.go
user: login: github-advanced-security[bot]
body: ## integer overflow conversion uint64 -> uint32

integer overflow conversion uint64 -> int64

[Show more details](https://github.com/cosmos/cosmos-sdk/security/code-scanning/23582)
created_at: 2025-05-08T19:16:44Z
author_association: CONTRIBUTOR

PR comments:
user: login: ironbird-prod[bot]
created_at: 2025-05-02T14:35:18Z
body: <details><summary>Ironbird - launch a network</summary>
To use Ironbird, you can use the following commands:
  
- /ironbird start <chain> <loadtest> OR /ironbird start <chain> --load-test-config=<yaml> - Launch a testnet with the specified chain and load test configuration.
- /ironbird chains - List of chain images that ironbird can use to spin-up testnet
- /ironbird loadtests - List of load test modes that ironbird can run against testnet
</details>
<details><summary>Custom Load Test Configuration</summary>
You can provide a custom load test configuration using the `--load-test-config=` flag:
  
```
/ironbird start cosmos --load-test-config={
  "block_gas_limit_target": 0.75,
  "num_of_blocks": 50,
  "msgs": [
    {"weight": 0.3, "type": "MsgSend"},
    {"weight": 0.3, "type": "MsgMultiSend"},
	{"weight": 0.4, "type": "MsgArr", "ContainedType": "MsgSend", "NumMsgs": 3300}
  ]
}
```
Use `/ironbird loadtests` to see more examples.
  
</details>
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-02T17:13:48Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This update introduces a centralized, thread-safe state management system for the BaseApp by refactoring multiple direct state fields into a unified `stateManager` abstraction. The `stateManager` handles different execution modes and protects state access with mutexes, improving concurrency safety. The change also introduces a `GasConfig` struct for gas-related configuration, replacing separate fields. All state access patterns in BaseApp and associated test helpers are updated to use the new manager. Additionally, the commit info in the multi-store is now managed atomically, and a new concurrency stress test is added to detect race conditions between commit and query operations.

## Changes

| File(s) | Change Summary |
|---------|---------------|
| `baseapp/state/manager.go`, `baseapp/state/state.go` | Introduced new `Manager` and `State` types for thread-safe, multi-mode state management; exported relevant fields and methods; moved package from `baseapp` to `state`. |
| `baseapp/baseapp.go`, `baseapp/abci.go`, `baseapp/options.go`, `baseapp/test_helpers.go` | Refactored BaseApp to use `stateManager` for all state access; removed direct state fields and related methods; updated gas configuration to use new `GasConfig` struct. |
| `baseapp/config/gas.go` | Added new `GasConfig` struct for encapsulating gas-related configuration. |
| `baseapp/abci_test.go` | Added a new concurrency test (`TestABCI_Race_Commit_Query`) to detect race conditions between commit and query context creation. |
| `baseapp/baseapp_test.go` | Updated logging and error output verification to use `captureStdout` helper instead of a buffer; removed `logBuffer` from test suite. |
| `baseapp/regression_test.go` | Ensured `stateManager` is initialized before running regression test. |
| `baseapp/utils_test.go` | Refactored test helpers to use reflection on `stateManager` for context retrieval, centralizing logic. |
| `store/rootmulti/store.go` | Updated `lastCommitInfo` to use `atomic.Pointer` for thread-safe access; refactored all reads/writes to use atomic methods. |
| `CHANGELOG.md` | Added entry documenting mutex and atomic improvements for state and commit info. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant BaseApp
    participant StateManager
    participant Store

    Client->>BaseApp: InitChain/FinalizeBlock/Commit/CreateQueryContext
    BaseApp->>StateManager: SetState/GetState/ClearState (by ExecMode)
    StateManager->>Store: Create branched store (SetState)
    StateManager-->>BaseApp: Return State (with context & store)
    BaseApp->>Store: Commit/Query (using atomic lastCommitInfo)
    Store-->>BaseApp: Commit result / Query result
    BaseApp-->>Client: Response
```

```mermaid
sequenceDiagram
    participant Test
    participant BaseApp
    participant StateManager
    participant Store

    par Commit Loop
        Test->>BaseApp: FinalizeBlock
        BaseApp->>StateManager: SetState(execModeFinalize)
        StateManager->>Store: Create branched store
        StateManager-->>BaseApp: Return State
        BaseApp->>Store: Commit (atomic lastCommitInfo)
    end
    par Query Goroutines
        loop many times
            Test->>BaseApp: CreateQueryContext
            BaseApp->>StateManager: GetState(execModeCheck)
            StateManager-->>BaseApp: Return State
            BaseApp-->>Test: Query Context
        end
    end
```


</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=cosmos/cosmos-sdk&utm_content=24655):

> ‼️ **IMPORTANT**
> Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR