Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(baseapp): align block header when query with latest height #21003

Merged
merged 10 commits into from
Oct 7, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i
* [#19851](https://github.com/cosmos/cosmos-sdk/pull/19851) Fix some places in which we call Remove inside a Walk (x/staking and x/gov).
* [#20939](https://github.com/cosmos/cosmos-sdk/pull/20939) Fix collection reverse iterator to include `pagination.key` in the result.
* (client/grpc) [#20969](https://github.com/cosmos/cosmos-sdk/pull/20969) Fix `node.NewQueryServer` method not setting `cfg`.
* (baseapp) [#21003](https://github.com/cosmos/cosmos-sdk/pull/21003) Align block header when query with latest height.

### API Breaking Changes

Expand Down
26 changes: 24 additions & 2 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,15 +1245,37 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
)
}

// branch the commit multi-store for safety
var header *cmtproto.Header
for _, state := range []*state{
app.checkState,
app.finalizeBlockState,
} {
if state != nil {
// branch the commit multi-store for safety
h := state.Context().BlockHeader()
if h.Height == height || 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,
)
}

ctx := sdk.NewContext(cacheMS, true, app.logger).
WithMinGasPrices(app.minGasPrices).
WithGasMeter(storetypes.NewGasMeter(app.queryGasLimit)).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: height,
}).
WithBlockHeader(app.checkState.Context().BlockHeader()).
WithBlockHeader(*header).
WithBlockHeight(height)

if height != lastBlockHeight {
Expand Down
72 changes: 58 additions & 14 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,26 +688,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)
}
Comment on lines +703 to +709
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Inconsistent method receiver types in mockABCIListener methods

The methods ListenFinalizeBlock and ListenCommit of mockABCIListener use different receiver types: ListenFinalizeBlock uses a value receiver (m mockABCIListener), whereas ListenCommit uses a pointer receiver (m *mockABCIListener). According to Go best practices and the Uber Go Style Guide, methods on the same type should have consistent receiver types to avoid confusion and potential bugs. Consider changing ListenFinalizeBlock to use a pointer receiver for consistency.

Apply this diff to fix the inconsistent receiver:

-func (m mockABCIListener) ListenFinalizeBlock(_ context.Context, _ abci.FinalizeBlockRequest, _ abci.FinalizeBlockResponse) error {
+func (m *mockABCIListener) ListenFinalizeBlock(_ context.Context, _ abci.FinalizeBlockRequest, _ abci.FinalizeBlockResponse) error {
📝 Committable suggestion

‼️ 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.

Suggested change
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)
}
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 := dbm.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 @@ -716,7 +716,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 @@ -741,6 +741,50 @@ func TestABCI_CreateQueryContext(t *testing.T) {
}
}

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.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(height, 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
Loading