diff --git a/CHANGELOG.md b/CHANGELOG.md index 34caada19e..ff3c13a9a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/baseapp/abci.go b/baseapp/abci.go index 67ddc5ded2..64796076e0 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -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 } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index f847009e8e..629b39be87 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -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()))