Skip to content

Commit cb5b8cf

Browse files
fix: Allow VerifyVoteExtensions without ExtendVote (#17251)
1 parent 1d09c96 commit cb5b8cf

File tree

6 files changed

+75
-21
lines changed

6 files changed

+75
-21
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
5151

5252
### Bug Fixes
5353

54+
* (baseapp) [#17251](https://github.com/cosmos/cosmos-sdk/pull/17251) VerifyVoteExtensions and ExtendVote initialize their own contexts/states, allowing VerifyVoteExtensions being called without ExtendVote.
5455
* (x/auth) [#17209](https://github.com/cosmos/cosmos-sdk/pull/17209) Internal error on AccountInfo when account's public key is not set.
5556
* (baseapp) [#17159](https://github.com/cosmos/cosmos-sdk/pull/17159) Validators can propose blocks that exceed the gas limit.
5657
* (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".

baseapp/abci.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -548,22 +548,23 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
548548
// Always reset state given that ExtendVote and VerifyVoteExtension can timeout
549549
// and be called again in a subsequent round.
550550
emptyHeader := cmtproto.Header{ChainID: app.chainID, Height: req.Height}
551-
app.setState(execModeVoteExtension, emptyHeader)
551+
ms := app.cms.CacheMultiStore()
552+
ctx := sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
552553

553554
if app.extendVote == nil {
554555
return nil, errors.New("application ExtendVote handler not set")
555556
}
556557

557558
// If vote extensions are not enabled, as a safety precaution, we return an
558559
// error.
559-
cp := app.GetConsensusParams(app.voteExtensionState.ctx)
560+
cp := app.GetConsensusParams(ctx)
560561

561562
extsEnabled := cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
562563
if !extsEnabled {
563564
return nil, fmt.Errorf("vote extensions are not enabled; unexpected call to ExtendVote at height %d", req.Height)
564565
}
565566

566-
app.voteExtensionState.ctx = app.voteExtensionState.ctx.
567+
ctx = ctx.
567568
WithConsensusParams(cp).
568569
WithBlockGasMeter(storetypes.NewInfiniteGasMeter()).
569570
WithBlockHeight(req.Height).
@@ -588,7 +589,7 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
588589
}
589590
}()
590591

591-
resp, err = app.extendVote(app.voteExtensionState.ctx, req)
592+
resp, err = app.extendVote(ctx, req)
592593
if err != nil {
593594
app.logger.Error("failed to extend vote", "height", req.Height, "error", err)
594595
return &abci.ResponseExtendVote{VoteExtension: []byte{}}, nil
@@ -608,9 +609,13 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
608609
return nil, errors.New("application VerifyVoteExtension handler not set")
609610
}
610611

612+
emptyHeader := cmtproto.Header{ChainID: app.chainID, Height: req.Height}
613+
ms := app.cms.CacheMultiStore()
614+
ctx := sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
615+
611616
// If vote extensions are not enabled, as a safety precaution, we return an
612617
// error.
613-
cp := app.GetConsensusParams(app.voteExtensionState.ctx)
618+
cp := app.GetConsensusParams(ctx)
614619

615620
extsEnabled := cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
616621
if !extsEnabled {
@@ -631,7 +636,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
631636
}
632637
}()
633638

634-
resp, err = app.verifyVoteExt(app.voteExtensionState.ctx, req)
639+
resp, err = app.verifyVoteExt(ctx, req)
635640
if err != nil {
636641
app.logger.Error("failed to verify vote extension", "height", req.Height, "error", err)
637642
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_REJECT}, nil

baseapp/abci_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,63 @@ func TestABCI_ExtendVote(t *testing.T) {
364364
require.Equal(t, abci.ResponseVerifyVoteExtension_REJECT, vres.Status)
365365
}
366366

367+
// TestABCI_OnlyVerifyVoteExtension makes sure we can call VerifyVoteExtension
368+
// without having called ExtendVote before.
369+
func TestABCI_OnlyVerifyVoteExtension(t *testing.T) {
370+
name := t.Name()
371+
db := dbm.NewMemDB()
372+
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
373+
374+
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
375+
// do some kind of verification here
376+
expectedVoteExt := "foo" + hex.EncodeToString(req.Hash) + strconv.FormatInt(req.Height, 10)
377+
if !bytes.Equal(req.VoteExtension, []byte(expectedVoteExt)) {
378+
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_REJECT}, nil
379+
}
380+
381+
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil
382+
})
383+
384+
app.SetParamStore(&paramStore{db: dbm.NewMemDB()})
385+
_, err := app.InitChain(
386+
&abci.RequestInitChain{
387+
InitialHeight: 1,
388+
ConsensusParams: &cmtproto.ConsensusParams{
389+
Abci: &cmtproto.ABCIParams{
390+
VoteExtensionsEnableHeight: 200,
391+
},
392+
},
393+
},
394+
)
395+
require.NoError(t, err)
396+
397+
// Verify Vote Extensions
398+
_, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 123, VoteExtension: []byte("1234567")})
399+
require.ErrorContains(t, err, "vote extensions are not enabled")
400+
401+
// First vote on the first enabled height
402+
vres, err := app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 200, Hash: []byte("thehash"), VoteExtension: []byte("foo74686568617368200")})
403+
require.NoError(t, err)
404+
require.Equal(t, abci.ResponseVerifyVoteExtension_ACCEPT, vres.Status)
405+
406+
vres, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 1000, Hash: []byte("thehash"), VoteExtension: []byte("foo746865686173681000")})
407+
require.NoError(t, err)
408+
require.Equal(t, abci.ResponseVerifyVoteExtension_ACCEPT, vres.Status)
409+
410+
// Reject because it's just some random bytes
411+
vres, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
412+
require.NoError(t, err)
413+
require.Equal(t, abci.ResponseVerifyVoteExtension_REJECT, vres.Status)
414+
415+
// Reject because the verification failed (no error)
416+
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
417+
return nil, errors.New("some error")
418+
})
419+
vres, err = app.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
420+
require.NoError(t, err)
421+
require.Equal(t, abci.ResponseVerifyVoteExtension_REJECT, vres.Status)
422+
}
423+
367424
func TestABCI_GRPCQuery(t *testing.T) {
368425
grpcQueryOpt := func(bapp *baseapp.BaseApp) {
369426
testdata.RegisterQueryServer(

baseapp/baseapp.go

-9
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,6 @@ type BaseApp struct {
104104
// previous block's state. This state is never committed. In case of multiple
105105
// consensus rounds, the state is always reset to the previous block's state.
106106
//
107-
// - voteExtensionState: Used for ExtendVote and VerifyVoteExtension, which is
108-
// set based on the previous block's state. This state is never committed. In
109-
// case of multiple rounds, the state is always reset to the previous block's
110-
// state.
111-
//
112107
// - processProposalState: Used for ProcessProposal, which is set based on the
113108
// the previous block's state. This state is never committed. In case of
114109
// multiple rounds, the state is always reset to the previous block's state.
@@ -118,7 +113,6 @@ type BaseApp struct {
118113
checkState *state
119114
prepareProposalState *state
120115
processProposalState *state
121-
voteExtensionState *state
122116
finalizeBlockState *state
123117

124118
// 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) {
475469
case execModeProcessProposal:
476470
app.processProposalState = baseState
477471

478-
case execModeVoteExtension:
479-
app.voteExtensionState = baseState
480-
481472
case execModeFinalize:
482473
app.finalizeBlockState = baseState
483474

docs/architecture/adr-064-abci-2.0.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,11 @@ type ExtendVoteHandler func(sdk.Context, abci.RequestExtendVote) abci.ResponseEx
106106
type VerifyVoteExtensionHandler func(sdk.Context, abci.RequestVerifyVoteExtension) abci.ResponseVerifyVoteExtension
107107
```
108108

109-
A new execution state, `voteExtensionState`, will be introduced and provided as
110-
the `Context` that is supplied to both handlers. It will contain relevant metadata
111-
such as the block height and block hash. Note, `voteExtensionState` is never
112-
committed and will exist as ephemeral state only in the context of a single block.
109+
An ephemeral context and state will be supplied to both handlers. The
110+
context will contain relevant metadata such as the block height and block hash.
111+
The state will be a cached version of the committed state of the application and
112+
will be discarded after the execution of the handler, this means that both handlers
113+
get a fresh state view and no changes made to it will be written.
113114

114115
If an application decides to implement `ExtendVoteHandler`, it must return a
115116
non-nil `ResponseExtendVote.VoteExtension`.

docs/docs/core/00-baseapp.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ Then, parameters used to define [volatile states](#state-updates) (i.e. cached s
9090
[`Commit`](#commit) and gets re-initialized on `FinalizeBlock`.
9191
* `processProposalState`: This state is updated during [`ProcessProposal`](#process-proposal).
9292
* `prepareProposalState`: This state is updated during [`PrepareProposal`](#prepare-proposal).
93-
* `voteExtensionState`: This state is updated during [`ExtendVote`](#extendvote) & [`VerifyVoteExtension`](#verifyvoteextension).
9493

9594
Finally, a few more important parameters:
9695

@@ -130,7 +129,7 @@ Naturally, developers can add additional `options` based on their application's
130129
## State Updates
131130

132131
The `BaseApp` maintains four primary volatile states and a root or main state. The main state
133-
is the canonical state of the application and the volatile states, `checkState`, `prepareProposalState`, `processProposalState`, `voteExtensionState` and `finalizeBlockState`
132+
is the canonical state of the application and the volatile states, `checkState`, `prepareProposalState`, `processProposalState` and `finalizeBlockState`
134133
are used to handle state transitions in-between the main state made during [`Commit`](#commit).
135134

136135
Internally, there is only a single `CommitMultiStore` which we refer to as the main or root state.

0 commit comments

Comments
 (0)