H. They are part of ABCI 2.0 and are available starting from CometBFT v0.38 and Cosmos SDK v0.50.
Enabling vote extensions
Vote extensions are controlled by theVoteExtensionsEnableHeight consensus parameter. At the configured height, CometBFT begins calling ExtendVote and VerifyVoteExtension on every validator. Extensions produced at height H are available to the block proposer at height H+1 via PrepareProposal.
To check whether vote extensions are active in a handler:
ConsensusParams().Abci is a pointer and must be nil-checked before use.
ExtendVote
The Cosmos SDK definesExtendVoteHandler:
app.go via baseapp.SetExtendVoteHandler (defined in baseapp/options.go):
ExtendVoteHandler is set, it must return a non-nil VoteExtension. An empty byte slice is valid.
ExtendVote is called only on the local validator and does not need to be deterministic. Common uses include:
- Submitting prices for an oracle
- Sharing encryption shares for an encrypted mempool
VerifyVoteExtension
The SDK definesVerifyVoteExtensionHandler:
app.go:
VerifyVoteExtension is called on every validator for every peer’s pre-commit. It must be deterministic — the same extension must produce the same result on every validator. If an application defines ExtendVoteHandler, it should also define a VerifyVoteExtensionHandler.
Always validate the size of incoming extensions in this handler.
Validating vote extension signatures
Before processing vote extensions inPrepareProposal or ProcessProposal, validate that they are properly signed. The SDK provides baseapp.ValidateVoteExtensions for this:
ValidateVoteExtensions verifies that each vote extension in the commit is correctly signed by its validator. valStore is a baseapp.ValidatorStore, an interface with a single method:
ValidateVoteExtensions in both PrepareProposal (on req.LocalLastCommit) and ProcessProposal (on the ExtendedCommitInfo recovered from the injected transaction) before trusting any extension data.
Vote extension propagation
Vote extensions from heightH are provided only to the block proposer at height H+1 via req.LocalLastCommit in PrepareProposal. They are not provided to other validators during ProcessProposal.
If all validators need to use extension data at H+1, the proposer must inject it into the block proposal. Since the Txs field in PrepareProposal is a [][]byte, any byte slice — including a serialized extensions summary — can be prepended to the proposal:
FinalizeBlock ignores any byte slice that does not implement sdk.Tx, so injected extensions are safely skipped during message execution.
For more details on propagation design, see the ABCI 2.0 ADR.
Recovery via PreBlocker
The SDK’sPreBlocker runs before any message execution in FinalizeBlock. Use it to recover injected vote extensions and make the results available to modules during the block:
app.go (see baseapp/options.go):
sdk.PreBlocker type is defined in types/abci.go:
PreBlocker is available to all BeginBlock and message handlers in the same block.