- 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
-
- // 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"`
- Extension []byte `json:"extension"`
- ExtensionSignature []byte `json:"extension_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 if 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. 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.Extension),
- 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
- }
- if vote.Extension != nil && !pubKey.VerifySignature(vote.Extension, vote.ExtensionSignature) {
- 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)
- }
-
- if len(vote.Extension) > 0 && len(vote.ExtensionSignature) == 0 {
- return errors.New("vote extension signature is missing")
- }
-
- 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,
- Extension: vote.Extension,
- ExtensionSignature: vote.ExtensionSignature,
- }
- }
-
- 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
- }
-
- // 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.Extension = pv.Extension
- vote.ExtensionSignature = pv.ExtensionSignature
-
- return vote, vote.ValidateBasic()
- }
|