fix: Allow VerifyVoteExtensions without ExtendVote (backport #17251) (#17254)

Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>
This commit is contained in:
mergify[bot] 2023-08-01 19:45:43 +00:00 committed by GitHub
parent 9e2abf8ef4
commit 86ca92a5cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 75 additions and 21 deletions

View File

@ -51,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes
* (baseapp) [#17251](https://github.com/cosmos/cosmos-sdk/pull/17251) VerifyVoteExtensions and ExtendVote initialize their own contexts/states, allowing VerifyVoteExtensions being called without ExtendVote.
* (x/auth) [#17209](https://github.com/cosmos/cosmos-sdk/pull/17209) Internal error on AccountInfo when account's public key is not set.
* (baseapp) [#17159](https://github.com/cosmos/cosmos-sdk/pull/17159) Validators can propose blocks that exceed the gas limit.
* (x/group) [#17146](https://github.com/cosmos/cosmos-sdk/pull/17146) Rename x/group legacy ORM package's error codespace from "orm" to "legacy_orm", preventing collisions with ORM's error codespace "orm".

View File

@ -548,7 +548,8 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
// Always reset state given that ExtendVote and VerifyVoteExtension can timeout
// and be called again in a subsequent round.
emptyHeader := cmtproto.Header{ChainID: app.chainID, Height: req.Height}
app.setState(execModeVoteExtension, emptyHeader)
ms := app.cms.CacheMultiStore()
ctx := sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
if app.extendVote == nil {
return nil, errors.New("application ExtendVote handler not set")
@ -556,14 +557,14 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
// If vote extensions are not enabled, as a safety precaution, we return an
// error.
cp := app.GetConsensusParams(app.voteExtensionState.ctx)
cp := app.GetConsensusParams(ctx)
extsEnabled := cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
if !extsEnabled {
return nil, fmt.Errorf("vote extensions are not enabled; unexpected call to ExtendVote at height %d", req.Height)
}
app.voteExtensionState.ctx = app.voteExtensionState.ctx.
ctx = ctx.
WithConsensusParams(cp).
WithBlockGasMeter(storetypes.NewInfiniteGasMeter()).
WithBlockHeight(req.Height).
@ -588,7 +589,7 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
}
}()
resp, err = app.extendVote(app.voteExtensionState.ctx, req)
resp, err = app.extendVote(ctx, req)
if err != nil {
app.logger.Error("failed to extend vote", "height", req.Height, "error", err)
return &abci.ResponseExtendVote{VoteExtension: []byte{}}, nil
@ -608,9 +609,13 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
return nil, errors.New("application VerifyVoteExtension handler not set")
}
emptyHeader := cmtproto.Header{ChainID: app.chainID, Height: req.Height}
ms := app.cms.CacheMultiStore()
ctx := sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
// If vote extensions are not enabled, as a safety precaution, we return an
// error.
cp := app.GetConsensusParams(app.voteExtensionState.ctx)
cp := app.GetConsensusParams(ctx)
extsEnabled := cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
if !extsEnabled {
@ -631,7 +636,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
}
}()
resp, err = app.verifyVoteExt(app.voteExtensionState.ctx, req)
resp, err = app.verifyVoteExt(ctx, req)
if err != nil {
app.logger.Error("failed to verify vote extension", "height", req.Height, "error", err)
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_REJECT}, nil

View File

@ -364,6 +364,63 @@ func TestABCI_ExtendVote(t *testing.T) {
require.Equal(t, abci.ResponseVerifyVoteExtension_REJECT, vres.Status)
}
// TestABCI_OnlyVerifyVoteExtension makes sure we can call VerifyVoteExtension
// without having called ExtendVote before.
func TestABCI_OnlyVerifyVoteExtension(t *testing.T) {
name := t.Name()
db := dbm.NewMemDB()
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
// do some kind of verification here
expectedVoteExt := "foo" + hex.EncodeToString(req.Hash) + strconv.FormatInt(req.Height, 10)
if !bytes.Equal(req.VoteExtension, []byte(expectedVoteExt)) {
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_REJECT}, nil
}
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil
})
app.SetParamStore(&paramStore{db: dbm.NewMemDB()})
_, err := app.InitChain(
&abci.RequestInitChain{
InitialHeight: 1,
ConsensusParams: &cmtproto.ConsensusParams{
Abci: &cmtproto.ABCIParams{
VoteExtensionsEnableHeight: 200,
},
},
},
)
require.NoError(t, err)
// Verify Vote Extensions
_, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 123, VoteExtension: []byte("1234567")})
require.ErrorContains(t, err, "vote extensions are not enabled")
// First vote on the first enabled height
vres, err := app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 200, Hash: []byte("thehash"), VoteExtension: []byte("foo74686568617368200")})
require.NoError(t, err)
require.Equal(t, abci.ResponseVerifyVoteExtension_ACCEPT, vres.Status)
vres, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 1000, Hash: []byte("thehash"), VoteExtension: []byte("foo746865686173681000")})
require.NoError(t, err)
require.Equal(t, abci.ResponseVerifyVoteExtension_ACCEPT, vres.Status)
// Reject because it's just some random bytes
vres, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
require.NoError(t, err)
require.Equal(t, abci.ResponseVerifyVoteExtension_REJECT, vres.Status)
// Reject because the verification failed (no error)
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
return nil, errors.New("some error")
})
vres, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
require.NoError(t, err)
require.Equal(t, abci.ResponseVerifyVoteExtension_REJECT, vres.Status)
}
func TestABCI_GRPCQuery(t *testing.T) {
grpcQueryOpt := func(bapp *baseapp.BaseApp) {
testdata.RegisterQueryServer(

View File

@ -104,11 +104,6 @@ type BaseApp struct {
// 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.
//
// - voteExtensionState: Used for ExtendVote and VerifyVoteExtension, which is
// set based on 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.
//
// - 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.
@ -118,7 +113,6 @@ type BaseApp struct {
checkState *state
prepareProposalState *state
processProposalState *state
voteExtensionState *state
finalizeBlockState *state
// An inter-block write-through cache provided to the context during the ABCI
@ -475,9 +469,6 @@ func (app *BaseApp) setState(mode execMode, header cmtproto.Header) {
case execModeProcessProposal:
app.processProposalState = baseState
case execModeVoteExtension:
app.voteExtensionState = baseState
case execModeFinalize:
app.finalizeBlockState = baseState

View File

@ -105,10 +105,11 @@ type ExtendVoteHandler func(sdk.Context, abci.RequestExtendVote) abci.ResponseEx
type VerifyVoteExtensionHandler func(sdk.Context, abci.RequestVerifyVoteExtension) abci.ResponseVerifyVoteExtension
```
A new execution state, `voteExtensionState`, will be introduced and provided as
the `Context` that is supplied to both handlers. It will contain relevant metadata
such as the block height and block hash. Note, `voteExtensionState` is never
committed and will exist as ephemeral state only in the context of a single block.
An ephemeral context and state will be supplied to both handlers. The
context will contain relevant metadata such as the block height and block hash.
The state will be a cached version of the committed state of the application and
will be discarded after the execution of the handler, this means that both handlers
get a fresh state view and no changes made to it will be written.
If an application decides to implement `ExtendVoteHandler`, it must return a
non-nil `ResponseExtendVote.VoteExtension`.

View File

@ -90,7 +90,6 @@ Then, parameters used to define [volatile states](#state-updates) (i.e. cached s
[`Commit`](#commit) and gets re-initialized on `FinalizeBlock`.
* `processProposalState`: This state is updated during [`ProcessProposal`](#process-proposal).
* `prepareProposalState`: This state is updated during [`PrepareProposal`](#prepare-proposal).
* `voteExtensionState`: This state is updated during [`ExtendVote`](#extendvote) & [`VerifyVoteExtension`](#verifyvoteextension).
Finally, a few more important parameters:
@ -130,7 +129,7 @@ Naturally, developers can add additional `options` based on their application's
## State Updates
The `BaseApp` maintains four primary volatile states and a root or main state. The main state
is the canonical state of the application and the volatile states, `checkState`, `prepareProposalState`, `processProposalState`, `voteExtensionState` and `finalizeBlockState`
is the canonical state of the application and the volatile states, `checkState`, `prepareProposalState`, `processProposalState` and `finalizeBlockState`
are used to handle state transitions in-between the main state made during [`Commit`](#commit).
Internally, there is only a single `CommitMultiStore` which we refer to as the main or root state.