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
co-authored by Facundo Medica
parent 9e2abf8ef4
commit 86ca92a5cf
6 changed files with 75 additions and 21 deletions
+11 -6
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
+57
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(
-9
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