fix(baseapp): align block header when querying with latest height (#24055)

This commit is contained in:
Tyler 2025-03-19 18:13:44 -07:00 committed by GitHub
parent 2a67b267c8
commit e8ac0f580e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 128 additions and 30 deletions

View File

@ -67,6 +67,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/bank) [#23836](https://github.com/cosmos/cosmos-sdk/pull/23836) Fix `DenomMetadata` rpc allow value with slashes.
* (query) [87d3a43](https://github.com/cosmos/cosmos-sdk/commit/87d3a432af95f4cf96aa02351ed5fcc51cca6e7b) Fix collection filtered pagination.
* (sims) [#23952](https://github.com/cosmos/cosmos-sdk/pull/23952) Use liveness matrix for validator sign status in sims
* (baseapp) [#24055](https://github.com/cosmos/cosmos-sdk/pull/24055) Align block header when query with latest height.
## [v0.50.12](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.50.12) - 2025-02-20

View File

@ -1188,9 +1188,15 @@ func checkNegativeHeight(height int64) error {
return nil
}
// createQueryContext creates a new sdk.Context for a query, taking as args
// 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)
}
// 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) {
if err := checkNegativeHeight(height); err != nil {
return sdk.Context{}, err
}
@ -1214,12 +1220,7 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
)
}
// when a client did not provide a query height, manually inject the latest
if height == 0 {
height = lastBlockHeight
}
if height <= 1 && prove {
if height == 1 && prove {
return sdk.Context{},
errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
@ -1227,33 +1228,63 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
)
}
var header *cmtproto.Header
isLatest := height == 0
for _, state := range []*state{
app.checkState,
app.finalizeBlockState,
} {
if state != nil {
// branch the commit multi-store for safety
h := state.Context().BlockHeader()
if isLatest {
lastBlockHeight = qms.LatestVersion()
}
if !checkHeader || !isLatest || isLatest && h.Height == lastBlockHeight {
header = &h
break
}
}
}
if header == nil {
return sdk.Context{},
errorsmod.Wrapf(
sdkerrors.ErrInvalidHeight,
"context did not contain latest block height in either check state or finalize block state (%d)", lastBlockHeight,
)
}
// when a client did not provide a query height, manually inject the latest
if isLatest {
height = lastBlockHeight
}
cacheMS, err := qms.CacheMultiStoreWithVersion(height)
if err != nil {
return sdk.Context{},
errorsmod.Wrapf(
sdkerrors.ErrInvalidRequest,
sdkerrors.ErrNotFound,
"failed to load state at height %d; %s (latest height: %d)", height, err, lastBlockHeight,
)
}
// branch the commit multi-store for safety
header := app.checkState.Context().BlockHeader()
ctx := sdk.NewContext(cacheMS, header, true, app.logger).
ctx := sdk.NewContext(cacheMS, *header, true, app.logger).
WithMinGasPrices(app.minGasPrices).
WithGasMeter(storetypes.NewGasMeter(app.queryGasLimit)).
WithBlockHeader(header).
WithBlockHeader(*header).
WithBlockHeight(height)
if height != lastBlockHeight {
if !isLatest {
rms, ok := app.cms.(*rootmulti.Store)
if ok {
cInfo, err := rms.GetCommitInfo(height)
if cInfo != nil && err == nil {
ctx = ctx.WithBlockTime(cInfo.Timestamp)
ctx = ctx.WithBlockHeight(height).WithBlockTime(cInfo.Timestamp)
}
}
}
return ctx, nil
}

View File

@ -686,20 +686,7 @@ func TestBaseAppPostHandler(t *testing.T) {
// - https://github.com/cosmos/cosmos-sdk/issues/7662
func TestABCI_CreateQueryContext(t *testing.T) {
t.Parallel()
db := dbm.NewMemDB()
name := t.Name()
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
_, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)
_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)
app := getQueryBaseapp(t)
testCases := []struct {
name string
@ -709,7 +696,7 @@ func TestABCI_CreateQueryContext(t *testing.T) {
expErr bool
}{
{"valid height", 2, 2, true, false},
{"valid height with different initial height", 2, 1, true, false},
{"valid height with different initial height", 2, 1, true, true},
{"future height", 10, 10, true, true},
{"negative height, prove=true", -1, -1, true, true},
{"negative height, prove=false", -1, -1, false, true},
@ -723,7 +710,11 @@ func TestABCI_CreateQueryContext(t *testing.T) {
})
require.NoError(t, err)
}
ctx, err := app.CreateQueryContext(tc.height, tc.prove)
height := tc.height
if tc.height > tc.headerHeight {
height = 0
}
ctx, err := app.CreateQueryContext(height, tc.prove)
if tc.expErr {
require.Error(t, err)
} else {
@ -734,6 +725,81 @@ func TestABCI_CreateQueryContext(t *testing.T) {
}
}
func TestABCI_CreateQueryContextWithCheckHeader(t *testing.T) {
t.Parallel()
app := getQueryBaseapp(t)
var height int64 = 2
var headerHeight int64 = 1
testCases := []struct {
checkHeader bool
expErr bool
}{
{true, true},
{false, false},
}
for _, tc := range testCases {
t.Run("valid height with different initial height", func(t *testing.T) {
_, err := app.InitChain(&abci.RequestInitChain{
InitialHeight: headerHeight,
})
require.NoError(t, err)
ctx, err := app.CreateQueryContextWithCheckHeader(0, true, tc.checkHeader)
if tc.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, height, ctx.BlockHeight())
}
})
}
}
func TestABCI_CreateQueryContext_Before_Set_CheckState(t *testing.T) {
t.Parallel()
db := dbm.NewMemDB()
name := t.Name()
var height int64 = 2
var headerHeight int64 = 1
t.Run("valid height with different initial height", func(t *testing.T) {
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
_, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)
_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
require.NoError(t, err)
var queryCtx *sdk.Context
var queryCtxErr error
app.SetStreamingManager(storetypes.StreamingManager{
ABCIListeners: []storetypes.ABCIListener{
&mockABCIListener{
ListenCommitFn: func(context.Context, abci.ResponseCommit, []*storetypes.StoreKVPair) error {
qCtx, qErr := app.CreateQueryContext(0, true)
queryCtx = &qCtx
queryCtxErr = qErr
return nil
},
},
},
})
_, err = app.Commit()
require.NoError(t, err)
require.NoError(t, queryCtxErr)
require.Equal(t, height, queryCtx.BlockHeight())
_, err = app.InitChain(&abci.RequestInitChain{
InitialHeight: headerHeight,
})
require.NoError(t, err)
})
}
func TestSetMinGasPrices(t *testing.T) {
minGasPrices := sdk.DecCoins{sdk.NewInt64DecCoin("stake", 5000)}
suite := NewBaseAppSuite(t, baseapp.SetMinGasPrices(minGasPrices.String()))