Revision c9997fd322da5b794b011091946c228eda74155e authored by Simon Kirillov on 23 March 2022, 09:47:05 UTC, committed by Simon Kirillov on 23 March 2022, 09:47:05 UTC
1 parent ef8276a
Raw File
vote.go
package types

import (
	"bytes"
	"errors"
	"fmt"
	"time"

	"github.com/tendermint/tendermint/crypto"
	"github.com/tendermint/tendermint/internal/libs/protoio"
	tmbytes "github.com/tendermint/tendermint/libs/bytes"
	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)

const (
	nilVoteStr string = "nil-Vote"
)

var (
	ErrVoteUnexpectedStep            = errors.New("unexpected step")
	ErrVoteInvalidValidatorIndex     = errors.New("invalid validator index")
	ErrVoteInvalidValidatorAddress   = errors.New("invalid validator address")
	ErrVoteInvalidSignature          = errors.New("invalid signature")
	ErrVoteInvalidBlockHash          = errors.New("invalid block hash")
	ErrVoteNonDeterministicSignature = errors.New("non-deterministic signature")
	ErrVoteNil                       = errors.New("nil vote")
	ErrVoteInvalidExtension          = errors.New("invalid vote extension")
)

type ErrVoteConflictingVotes struct {
	VoteA *Vote
	VoteB *Vote
}

func (err *ErrVoteConflictingVotes) Error() string {
	return fmt.Sprintf("conflicting votes from validator %X", err.VoteA.ValidatorAddress)
}

func NewConflictingVoteError(vote1, vote2 *Vote) *ErrVoteConflictingVotes {
	return &ErrVoteConflictingVotes{
		VoteA: vote1,
		VoteB: vote2,
	}
}

// Address is hex bytes.
type Address = crypto.Address

// VoteExtensionToSign is a subset of VoteExtension
// that is signed by the validators private key
type VoteExtensionToSign struct {
	AppDataToSign []byte `json:"app_data_to_sign"`
}

func (ext VoteExtensionToSign) ToProto() *tmproto.VoteExtensionToSign {
	if ext.IsEmpty() {
		return nil
	}
	return &tmproto.VoteExtensionToSign{
		AppDataToSign: ext.AppDataToSign,
	}
}

func VoteExtensionToSignFromProto(pext *tmproto.VoteExtensionToSign) VoteExtensionToSign {
	if pext == nil {
		return VoteExtensionToSign{}
	}
	return VoteExtensionToSign{
		AppDataToSign: pext.AppDataToSign,
	}
}

func (ext VoteExtensionToSign) IsEmpty() bool {
	return len(ext.AppDataToSign) == 0
}

// BytesPacked returns a bytes-packed representation for
// debugging and human identification. This function should
// not be used for any logical operations.
func (ext VoteExtensionToSign) BytesPacked() []byte {
	res := []byte{}
	res = append(res, ext.AppDataToSign...)
	return res
}

// ToVoteExtension constructs a VoteExtension from a VoteExtensionToSign
func (ext VoteExtensionToSign) ToVoteExtension() VoteExtension {
	return VoteExtension{
		AppDataToSign: ext.AppDataToSign,
	}
}

// VoteExtension is a set of data provided by the application
// that is additionally included in the vote
type VoteExtension struct {
	AppDataToSign             []byte `json:"app_data_to_sign"`
	AppDataSelfAuthenticating []byte `json:"app_data_self_authenticating"`
}

// ToSign constructs a VoteExtensionToSign from a VoteExtenstion
func (ext VoteExtension) ToSign() VoteExtensionToSign {
	return VoteExtensionToSign{
		AppDataToSign: ext.AppDataToSign,
	}
}

// BytesPacked returns a bytes-packed representation for
// debugging and human identification. This function should
// not be used for any logical operations.
func (ext VoteExtension) BytesPacked() []byte {
	res := []byte{}
	res = append(res, ext.AppDataToSign...)
	res = append(res, ext.AppDataSelfAuthenticating...)
	return res
}

// Vote represents a prevote, precommit, or commit vote from validators for
// consensus.
type Vote struct {
	Type             tmproto.SignedMsgType `json:"type"`
	Height           int64                 `json:"height,string"`
	Round            int32                 `json:"round"`    // assume there will not be greater than 2_147_483_647 rounds
	BlockID          BlockID               `json:"block_id"` // zero if vote is nil.
	Timestamp        time.Time             `json:"timestamp"`
	ValidatorAddress Address               `json:"validator_address"`
	ValidatorIndex   int32                 `json:"validator_index"`
	Signature        []byte                `json:"signature"`
	VoteExtension    VoteExtension         `json:"vote_extension"`
}

// CommitSig converts the Vote to a CommitSig.
func (vote *Vote) CommitSig() CommitSig {
	if vote == nil {
		return NewCommitSigAbsent()
	}

	var blockIDFlag BlockIDFlag
	switch {
	case vote.BlockID.IsComplete():
		blockIDFlag = BlockIDFlagCommit
	case vote.BlockID.IsNil():
		blockIDFlag = BlockIDFlagNil
	default:
		panic(fmt.Sprintf("Invalid vote %v - expected BlockID to be either empty or complete", vote))
	}

	return CommitSig{
		BlockIDFlag:      blockIDFlag,
		ValidatorAddress: vote.ValidatorAddress,
		Timestamp:        vote.Timestamp,
		Signature:        vote.Signature,
		VoteExtension:    vote.VoteExtension.ToSign(),
	}
}

// VoteSignBytes returns the proto-encoding of the canonicalized Vote, for
// signing. Panics is the marshaling fails.
//
// The encoded Protobuf message is varint length-prefixed (using MarshalDelimited)
// for backwards-compatibility with the Amino encoding, due to e.g. hardware
// devices that rely on this encoding.
//
// See CanonicalizeVote
func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte {
	pb := CanonicalizeVote(chainID, vote)
	bz, err := protoio.MarshalDelimited(&pb)
	if err != nil {
		panic(err)
	}

	return bz
}

func (vote *Vote) Copy() *Vote {
	voteCopy := *vote
	voteCopy.VoteExtension = vote.VoteExtension.Copy()
	return &voteCopy
}

// String returns a string representation of Vote.
//
// 1. validator index
// 2. first 6 bytes of validator address
// 3. height
// 4. round,
// 5. type byte
// 6. type string
// 7. first 6 bytes of block hash
// 8. first 6 bytes of signature
// 9. first 6 bytes of vote extension
// 10. timestamp
func (vote *Vote) String() string {
	if vote == nil {
		return nilVoteStr
	}

	var typeString string
	switch vote.Type {
	case tmproto.PrevoteType:
		typeString = "Prevote"
	case tmproto.PrecommitType:
		typeString = "Precommit"
	default:
		panic("Unknown vote type")
	}

	return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X %X @ %s}",
		vote.ValidatorIndex,
		tmbytes.Fingerprint(vote.ValidatorAddress),
		vote.Height,
		vote.Round,
		vote.Type,
		typeString,
		tmbytes.Fingerprint(vote.BlockID.Hash),
		tmbytes.Fingerprint(vote.Signature),
		tmbytes.Fingerprint(vote.VoteExtension.BytesPacked()),
		CanonicalTime(vote.Timestamp),
	)
}

func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
	if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) {
		return ErrVoteInvalidValidatorAddress
	}
	v := vote.ToProto()
	if !pubKey.VerifySignature(VoteSignBytes(chainID, v), vote.Signature) {
		return ErrVoteInvalidSignature
	}
	return nil
}

// ValidateBasic performs basic validation.
func (vote *Vote) ValidateBasic() error {
	if !IsVoteTypeValid(vote.Type) {
		return errors.New("invalid Type")
	}

	if vote.Height < 0 {
		return errors.New("negative Height")
	}

	if vote.Round < 0 {
		return errors.New("negative Round")
	}

	// NOTE: Timestamp validation is subtle and handled elsewhere.

	if err := vote.BlockID.ValidateBasic(); err != nil {
		return fmt.Errorf("wrong BlockID: %w", err)
	}

	// BlockID.ValidateBasic would not err if we for instance have an empty hash but a
	// non-empty PartsSetHeader:
	if !vote.BlockID.IsNil() && !vote.BlockID.IsComplete() {
		return fmt.Errorf("blockID must be either empty or complete, got: %v", vote.BlockID)
	}

	if len(vote.ValidatorAddress) != crypto.AddressSize {
		return fmt.Errorf("expected ValidatorAddress size to be %d bytes, got %d bytes",
			crypto.AddressSize,
			len(vote.ValidatorAddress),
		)
	}
	if vote.ValidatorIndex < 0 {
		return errors.New("negative ValidatorIndex")
	}
	if len(vote.Signature) == 0 {
		return errors.New("signature is missing")
	}

	if len(vote.Signature) > MaxSignatureSize {
		return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
	}

	// XXX: add length verification for vote extension?

	return nil
}

func (ext VoteExtension) Copy() VoteExtension {
	res := VoteExtension{
		AppDataToSign:             ext.AppDataToSign,
		AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating,
	}
	return res
}

func (ext VoteExtension) IsEmpty() bool {
	if len(ext.AppDataToSign) != 0 {
		return false
	}
	if len(ext.AppDataSelfAuthenticating) != 0 {
		return false
	}
	return true
}

func (ext VoteExtension) ToProto() *tmproto.VoteExtension {
	if ext.IsEmpty() {
		return nil
	}

	return &tmproto.VoteExtension{
		AppDataToSign:             ext.AppDataToSign,
		AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating,
	}
}

// ToProto converts the handwritten type to proto generated type
// return type, nil if everything converts safely, otherwise nil, error
func (vote *Vote) ToProto() *tmproto.Vote {
	if vote == nil {
		return nil
	}

	return &tmproto.Vote{
		Type:             vote.Type,
		Height:           vote.Height,
		Round:            vote.Round,
		BlockID:          vote.BlockID.ToProto(),
		Timestamp:        vote.Timestamp,
		ValidatorAddress: vote.ValidatorAddress,
		ValidatorIndex:   vote.ValidatorIndex,
		Signature:        vote.Signature,
		VoteExtension:    vote.VoteExtension.ToProto(),
	}
}

func VotesToProto(votes []*Vote) []*tmproto.Vote {
	if votes == nil {
		return nil
	}

	res := make([]*tmproto.Vote, 0, len(votes))
	for _, vote := range votes {
		v := vote.ToProto()
		// protobuf crashes when serializing "repeated" fields with nil elements
		if v != nil {
			res = append(res, v)
		}
	}
	return res
}

func VoteExtensionFromProto(pext *tmproto.VoteExtension) VoteExtension {
	ext := VoteExtension{}
	if pext != nil {
		ext.AppDataToSign = pext.AppDataToSign
		ext.AppDataSelfAuthenticating = pext.AppDataSelfAuthenticating
	}
	return ext
}

// FromProto converts a proto generetad type to a handwritten type
// return type, nil if everything converts safely, otherwise nil, error
func VoteFromProto(pv *tmproto.Vote) (*Vote, error) {
	if pv == nil {
		return nil, errors.New("nil vote")
	}

	blockID, err := BlockIDFromProto(&pv.BlockID)
	if err != nil {
		return nil, err
	}

	vote := new(Vote)
	vote.Type = pv.Type
	vote.Height = pv.Height
	vote.Round = pv.Round
	vote.BlockID = *blockID
	vote.Timestamp = pv.Timestamp
	vote.ValidatorAddress = pv.ValidatorAddress
	vote.ValidatorIndex = pv.ValidatorIndex
	vote.Signature = pv.Signature
	vote.VoteExtension = VoteExtensionFromProto(pv.VoteExtension)

	return vote, vote.ValidateBasic()
}
back to top