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") ) 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 // 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"` } // 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, } } // 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 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. 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 @ %s}", vote.ValidatorIndex, tmbytes.Fingerprint(vote.ValidatorAddress), vote.Height, vote.Round, vote.Type, typeString, tmbytes.Fingerprint(vote.BlockID.Hash), tmbytes.Fingerprint(vote.Signature), 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) } return nil } // 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, } } // 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 return vote, vote.ValidateBasic() }