Vote Extensions

In this section you will learn how a node can ExtendVote and VerifyVote on the forum application.

ExtendVote

The ExtendVote method allows applications to extend the pre-commit vote with arbitrary data. This allows applications to force their validators to do more than just validate blocks within consensus.

When a validator is preparing to send a pre-commit vote, it first calls ExtendVote. The application then returns a blob of data called a vote extension. This data is opaque to the consensus algorithm but can contain application-specific information.

The validator then sends both the pre-commit vote and the vote extension together to other validators. Other validators also call ExtendVote to generate their own vote extensions.

When a validator receives a pre-commit vote with an attached vote extension, it calls VerifyVoteExtension to validate the vote extension. If valid, the validator includes the vote in its tally.

The proposer of the next block will receive all vote extensions in PrepareProposalRequest.

This allows validators to have access to all vote extensions at the next height. They can then use the data in the vote extensions to inform the transactions that make it into the next block.

Following is the code for the ExtendVote function:

// ExtendVote returns curse words as vote extensions
func (app *ForumApp) ExtendVote(_ context.Context, _ *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) {
	app.logger.Info("Executing Application ExtendVote")

	return &abci.ExtendVoteResponse{VoteExtension: []byte(app.CurseWords)}, nil
}

Explanation of code:

ExtendVote function takes two parameters: a context.Context and a pointer to an abci.ExtendVoteRequest struct. It returns a pointer to an ExtendVoteResponse struct and an error.

The method implementation simply returns a new instance of ExtendVoteResponse with the VoteExtension field set to the value of app.CurseWords. The app.CurseWords is expected to be a byte array containing the vote extension data.

The ExtendVoteResponse struct is used to encapsulate the response data for the ExtendVote method. By setting the VoteExtension field, the method includes the application-specific vote extension data in the response.

In this implementation, the ExtendVote method in the ForumApp application returns the application-specific vote extension data stored in the app.CurseWords variable.

Tip: The vote extensions are opaque to the consensus algorithm but visible to the application, allowing for a variety of use cases like price oracles, encrypted mempools, and threshold cryptography.

VerifyVoteExtension

The VerifyVoteExtension method allows applications to verify the VoteExtension data attached to each pre-commit message.

When a validator is preparing to send a pre-commit vote, it first calls ExtendVote to generate a VoteExtension. This VoteExtension is broadcast along with the pre-commit vote.

Other validators also call ExtendVote to generate their own vote extensions. However, not all validators will generate the same vote extension.

When a validator receives a pre-commit vote with an attached vote extension, it calls VerifyVoteExtension to validate the vote extension.

If the vote extension is successfully verified, the pre-commit vote is included in the tally. If validation fails, the entire pre-commit message is ignored.

Following is the blurb of code for the VerifyVoteExtension function:

// VerifyVoteExtension verifies the vote extensions and ensure they include the curse words
// It will not be called for extensions generated by this validator.
func (app *ForumApp) VerifyVoteExtension(_ context.Context, req *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
	app.logger.Info("Executing Application VerifyVoteExtension")

	if _, ok := app.valAddrToPubKeyMap[string(req.ValidatorAddress)]; !ok {
		// we do not have a validator with this address mapped; this should never happen
		return nil, errors.New("unknown validator")
	}

	curseWords := strings.Split(string(req.VoteExtension), "|")
	if hasDuplicateWords(curseWords) {
		return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT}, nil
	}

	// ensure vote extension curse words limit has not been exceeded
	if len(curseWords) > CurseWordsLimitVE {
		return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT}, nil
	}
	return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT}, nil
}

Explanation of code:

VerifyVoteExtension function takes two parameters: a context.Context and a pointer to an VerifyVoteExtensionRequest object. It returns a pointer to an VerifyVoteExtensionResponse object and an error.

The implementation checks if the validator address provided in the request (req.ValidatorAddress) is mapped to a public key in the app.valAddrToPubKeyMap. If the validator address is not found in the map, it returns an error indicating an “unknown validator”. This check ensures that the validator making the request is recognized by the application.

The method splits the VoteExtension field of the request (req.VoteExtension) into individual words using the strings.Split function. The separator used is the pipe character (|). The resulting words are stored in the curseWords slice.

The implementation creates a temporary map called tmpCurseWordMap to verify that there are no duplicate words in the curseWords slice and to check if the validator is trying to cheat by including the same word multiple times.

If the length of the tmpCurseWordMap is less than the length of the curseWords slice, it means that there are duplicate words in the extension. In this case, the method returns a response with a status of abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT, indicating that the vote extension is rejected.

If the length of the curseWords slic is greater than the maximum number of cursor words allowed in vote extensions (CurseWordsLimitVE = 10), it is not permitted. In this case, the method returns a response with a status of abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT, indicating that the vote extension is rejected.

If there are no duplicate words in the extension, the method returns a response with a status of abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT, indicating that the vote extension is accepted.

Tip: Verified vote extensions can be persisted by the application. For example, the application could store data derived from the vote extensions.


In the next session, you will find the entire implementation of the Forum Application in the app.go file.

Decorative Orb