Skip to content

Commit

Permalink
fix(baseapp): align block header when query with latest height (#21003)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmsqe authored Oct 7, 2024
1 parent 38662ec commit 6cc6a6c
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i
* (sims) [#21952](https://github.com/cosmos/cosmos-sdk/pull/21952) Use liveness matrix for validator sign status in sims
* (sims) [#21906](https://github.com/cosmos/cosmos-sdk/pull/21906) Skip sims test when running dry on validators
* (cli) [#21919](https://github.com/cosmos/cosmos-sdk/pull/21919) Query address-by-acc-num by account_id instead of id.
* (baseapp) [#21003](https://github.com/cosmos/cosmos-sdk/pull/21003) Align block header when query with latest height.

### API Breaking Changes

Expand Down
52 changes: 42 additions & 10 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (app *BaseApp) Info(_ *abci.InfoRequest) (*abci.InfoResponse, error) {
lastCommitID := app.cms.LastCommitID()
appVersion := InitialAppVersion
if lastCommitID.Version > 0 {
ctx, err := app.CreateQueryContext(lastCommitID.Version, false)
ctx, err := app.CreateQueryContextWithCheckHeader(lastCommitID.Version, false, false)
if err != nil {
return nil, fmt.Errorf("failed creating query context: %w", err)
}
Expand Down Expand Up @@ -1222,6 +1222,12 @@ 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)
}

// 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
}
Expand All @@ -1245,19 +1251,46 @@ 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 > 0 && height <= 1 && prove {
return sdk.Context{},
errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"cannot query with proof when height <= 1; please provide a valid height",
)
}

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,
"header height in all state context is not latest height (%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{},
Expand All @@ -1275,10 +1308,10 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
ChainID: app.chainID,
Height: height,
}).
WithBlockHeader(app.checkState.Context().BlockHeader()).
WithBlockHeader(*header).
WithBlockHeight(height)

if height != lastBlockHeight {
if !isLatest {
rms, ok := app.cms.(*rootmulti.Store)
if ok {
cInfo, err := rms.GetCommitInfo(height)
Expand All @@ -1287,7 +1320,6 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
}
}
}

return ctx, nil
}

Expand Down
109 changes: 94 additions & 15 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,26 +696,26 @@ func TestBaseAppPostHandler(t *testing.T) {
require.NotContains(t, suite.logBuffer.String(), "panic recovered in runTx")
}

type mockABCIListener struct {
ListenCommitFn func(context.Context, abci.CommitResponse, []*storetypes.StoreKVPair) error
}

func (m mockABCIListener) ListenFinalizeBlock(_ context.Context, _ abci.FinalizeBlockRequest, _ abci.FinalizeBlockResponse) error {
return nil
}

func (m *mockABCIListener) ListenCommit(ctx context.Context, commit abci.CommitResponse, pairs []*storetypes.StoreKVPair) error {
return m.ListenCommitFn(ctx, commit, pairs)
}

// Test and ensure that invalid block heights always cause errors.
// See issues:
// - https://github.com/cosmos/cosmos-sdk/issues/11220
// - https://github.com/cosmos/cosmos-sdk/issues/7662
func TestABCI_CreateQueryContext(t *testing.T) {
t.Parallel()
app := getQueryBaseapp(t)

db := coretesting.NewMemDB()
name := t.Name()
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)

_, err := app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)

_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 2})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)
testCases := []struct {
name string
height int64
Expand All @@ -724,7 +724,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},
Expand All @@ -738,7 +738,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 {
Expand All @@ -749,6 +753,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.InitChainRequest{
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 := coretesting.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.FinalizeBlockRequest{Height: 1})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)

_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{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.CommitResponse, []*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.InitChainRequest{
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()))
Expand Down

0 comments on commit 6cc6a6c

Please sign in to comment.