github.com/aakash4dev/cometbft@v0.38.2/types/vote.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/aakash4dev/cometbft/crypto"
    10  	cmtbytes "github.com/aakash4dev/cometbft/libs/bytes"
    11  	"github.com/aakash4dev/cometbft/libs/protoio"
    12  	cmtproto "github.com/aakash4dev/cometbft/proto/tendermint/types"
    13  )
    14  
    15  const (
    16  	nilVoteStr string = "nil-Vote"
    17  
    18  	// The maximum supported number of bytes in a vote extension.
    19  	MaxVoteExtensionSize int = 1024 * 1024
    20  )
    21  
    22  var (
    23  	ErrVoteUnexpectedStep            = errors.New("unexpected step")
    24  	ErrVoteInvalidValidatorIndex     = errors.New("invalid validator index")
    25  	ErrVoteInvalidValidatorAddress   = errors.New("invalid validator address")
    26  	ErrVoteInvalidSignature          = errors.New("invalid signature")
    27  	ErrVoteInvalidBlockHash          = errors.New("invalid block hash")
    28  	ErrVoteNonDeterministicSignature = errors.New("non-deterministic signature")
    29  	ErrVoteNil                       = errors.New("nil vote")
    30  	ErrVoteExtensionAbsent           = errors.New("vote extension absent")
    31  	ErrInvalidVoteExtension          = errors.New("invalid vote extension")
    32  )
    33  
    34  type ErrVoteConflictingVotes struct {
    35  	VoteA *Vote
    36  	VoteB *Vote
    37  }
    38  
    39  func (err *ErrVoteConflictingVotes) Error() string {
    40  	return fmt.Sprintf("conflicting votes from validator %X", err.VoteA.ValidatorAddress)
    41  }
    42  
    43  func NewConflictingVoteError(vote1, vote2 *Vote) *ErrVoteConflictingVotes {
    44  	return &ErrVoteConflictingVotes{
    45  		VoteA: vote1,
    46  		VoteB: vote2,
    47  	}
    48  }
    49  
    50  // Address is hex bytes.
    51  type Address = crypto.Address
    52  
    53  // Vote represents a prevote, precommit, or commit vote from validators for
    54  // consensus.
    55  type Vote struct {
    56  	Type               cmtproto.SignedMsgType `json:"type"`
    57  	Height             int64                  `json:"height"`
    58  	Round              int32                  `json:"round"`    // assume there will not be greater than 2_147_483_647 rounds
    59  	BlockID            BlockID                `json:"block_id"` // zero if vote is nil.
    60  	Timestamp          time.Time              `json:"timestamp"`
    61  	ValidatorAddress   Address                `json:"validator_address"`
    62  	ValidatorIndex     int32                  `json:"validator_index"`
    63  	Signature          []byte                 `json:"signature"`
    64  	Extension          []byte                 `json:"extension"`
    65  	ExtensionSignature []byte                 `json:"extension_signature"`
    66  }
    67  
    68  // VoteFromProto attempts to convert the given serialization (Protobuf) type to
    69  // our Vote domain type. No validation is performed on the resulting vote -
    70  // this is left up to the caller to decide whether to call ValidateBasic or
    71  // ValidateWithExtension.
    72  func VoteFromProto(pv *cmtproto.Vote) (*Vote, error) {
    73  	blockID, err := BlockIDFromProto(&pv.BlockID)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	return &Vote{
    79  		Type:               pv.Type,
    80  		Height:             pv.Height,
    81  		Round:              pv.Round,
    82  		BlockID:            *blockID,
    83  		Timestamp:          pv.Timestamp,
    84  		ValidatorAddress:   pv.ValidatorAddress,
    85  		ValidatorIndex:     pv.ValidatorIndex,
    86  		Signature:          pv.Signature,
    87  		Extension:          pv.Extension,
    88  		ExtensionSignature: pv.ExtensionSignature,
    89  	}, nil
    90  }
    91  
    92  // CommitSig converts the Vote to a CommitSig.
    93  func (vote *Vote) CommitSig() CommitSig {
    94  	if vote == nil {
    95  		return NewCommitSigAbsent()
    96  	}
    97  
    98  	var blockIDFlag BlockIDFlag
    99  	switch {
   100  	case vote.BlockID.IsComplete():
   101  		blockIDFlag = BlockIDFlagCommit
   102  	case vote.BlockID.IsZero():
   103  		blockIDFlag = BlockIDFlagNil
   104  	default:
   105  		panic(fmt.Sprintf("Invalid vote %v - expected BlockID to be either empty or complete", vote))
   106  	}
   107  
   108  	return CommitSig{
   109  		BlockIDFlag:      blockIDFlag,
   110  		ValidatorAddress: vote.ValidatorAddress,
   111  		Timestamp:        vote.Timestamp,
   112  		Signature:        vote.Signature,
   113  	}
   114  }
   115  
   116  // ExtendedCommitSig attempts to construct an ExtendedCommitSig from this vote.
   117  // Panics if either the vote extension signature is missing or if the block ID
   118  // is not either empty or complete.
   119  func (vote *Vote) ExtendedCommitSig() ExtendedCommitSig {
   120  	if vote == nil {
   121  		return NewExtendedCommitSigAbsent()
   122  	}
   123  
   124  	return ExtendedCommitSig{
   125  		CommitSig:          vote.CommitSig(),
   126  		Extension:          vote.Extension,
   127  		ExtensionSignature: vote.ExtensionSignature,
   128  	}
   129  }
   130  
   131  // VoteSignBytes returns the proto-encoding of the canonicalized Vote, for
   132  // signing. Panics if the marshaling fails.
   133  //
   134  // The encoded Protobuf message is varint length-prefixed (using MarshalDelimited)
   135  // for backwards-compatibility with the Amino encoding, due to e.g. hardware
   136  // devices that rely on this encoding.
   137  //
   138  // See CanonicalizeVote
   139  func VoteSignBytes(chainID string, vote *cmtproto.Vote) []byte {
   140  	pb := CanonicalizeVote(chainID, vote)
   141  	bz, err := protoio.MarshalDelimited(&pb)
   142  	if err != nil {
   143  		panic(err)
   144  	}
   145  
   146  	return bz
   147  }
   148  
   149  // VoteExtensionSignBytes returns the proto-encoding of the canonicalized vote
   150  // extension for signing. Panics if the marshaling fails.
   151  //
   152  // Similar to VoteSignBytes, the encoded Protobuf message is varint
   153  // length-prefixed for backwards-compatibility with the Amino encoding.
   154  func VoteExtensionSignBytes(chainID string, vote *cmtproto.Vote) []byte {
   155  	pb := CanonicalizeVoteExtension(chainID, vote)
   156  	bz, err := protoio.MarshalDelimited(&pb)
   157  	if err != nil {
   158  		panic(err)
   159  	}
   160  
   161  	return bz
   162  }
   163  
   164  func (vote *Vote) Copy() *Vote {
   165  	voteCopy := *vote
   166  	return &voteCopy
   167  }
   168  
   169  // String returns a string representation of Vote.
   170  //
   171  // 1. validator index
   172  // 2. first 6 bytes of validator address
   173  // 3. height
   174  // 4. round,
   175  // 5. type byte
   176  // 6. type string
   177  // 7. first 6 bytes of block hash
   178  // 8. first 6 bytes of signature
   179  // 9. first 6 bytes of vote extension
   180  // 10. timestamp
   181  func (vote *Vote) String() string {
   182  	if vote == nil {
   183  		return nilVoteStr
   184  	}
   185  
   186  	var typeString string
   187  	switch vote.Type {
   188  	case cmtproto.PrevoteType:
   189  		typeString = "Prevote"
   190  	case cmtproto.PrecommitType:
   191  		typeString = "Precommit"
   192  	default:
   193  		panic("Unknown vote type")
   194  	}
   195  
   196  	return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X %X @ %s}",
   197  		vote.ValidatorIndex,
   198  		cmtbytes.Fingerprint(vote.ValidatorAddress),
   199  		vote.Height,
   200  		vote.Round,
   201  		vote.Type,
   202  		typeString,
   203  		cmtbytes.Fingerprint(vote.BlockID.Hash),
   204  		cmtbytes.Fingerprint(vote.Signature),
   205  		cmtbytes.Fingerprint(vote.Extension),
   206  		CanonicalTime(vote.Timestamp),
   207  	)
   208  }
   209  
   210  func (vote *Vote) verifyAndReturnProto(chainID string, pubKey crypto.PubKey) (*cmtproto.Vote, error) {
   211  	if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) {
   212  		return nil, ErrVoteInvalidValidatorAddress
   213  	}
   214  	v := vote.ToProto()
   215  	if !pubKey.VerifySignature(VoteSignBytes(chainID, v), vote.Signature) {
   216  		return nil, ErrVoteInvalidSignature
   217  	}
   218  	return v, nil
   219  }
   220  
   221  // Verify checks whether the signature associated with this vote corresponds to
   222  // the given chain ID and public key. This function does not validate vote
   223  // extension signatures - to do so, use VerifyWithExtension instead.
   224  func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
   225  	_, err := vote.verifyAndReturnProto(chainID, pubKey)
   226  	return err
   227  }
   228  
   229  // VerifyVoteAndExtension performs the same verification as Verify, but
   230  // additionally checks whether the vote extension signature corresponds to the
   231  // given chain ID and public key. We only verify vote extension signatures for
   232  // precommits.
   233  func (vote *Vote) VerifyVoteAndExtension(chainID string, pubKey crypto.PubKey) error {
   234  	v, err := vote.verifyAndReturnProto(chainID, pubKey)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	// We only verify vote extension signatures for non-nil precommits.
   239  	if vote.Type == cmtproto.PrecommitType && !ProtoBlockIDIsNil(&v.BlockID) {
   240  		if len(vote.ExtensionSignature) == 0 {
   241  			return errors.New("expected vote extension signature")
   242  		}
   243  
   244  		extSignBytes := VoteExtensionSignBytes(chainID, v)
   245  		if !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
   246  			return ErrVoteInvalidSignature
   247  		}
   248  	}
   249  	return nil
   250  }
   251  
   252  // VerifyExtension checks whether the vote extension signature corresponds to the
   253  // given chain ID and public key.
   254  func (vote *Vote) VerifyExtension(chainID string, pubKey crypto.PubKey) error {
   255  	if vote.Type != cmtproto.PrecommitType || vote.BlockID.IsZero() {
   256  		return nil
   257  	}
   258  	v := vote.ToProto()
   259  	extSignBytes := VoteExtensionSignBytes(chainID, v)
   260  	if !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
   261  		return ErrVoteInvalidSignature
   262  	}
   263  	return nil
   264  }
   265  
   266  // ValidateBasic checks whether the vote is well-formed. It does not, however,
   267  // check vote extensions - for vote validation with vote extension validation,
   268  // use ValidateWithExtension.
   269  func (vote *Vote) ValidateBasic() error {
   270  	if !IsVoteTypeValid(vote.Type) {
   271  		return errors.New("invalid Type")
   272  	}
   273  
   274  	if vote.Height <= 0 {
   275  		return errors.New("negative or zero Height")
   276  	}
   277  
   278  	if vote.Round < 0 {
   279  		return errors.New("negative Round")
   280  	}
   281  
   282  	// NOTE: Timestamp validation is subtle and handled elsewhere.
   283  
   284  	if err := vote.BlockID.ValidateBasic(); err != nil {
   285  		return fmt.Errorf("wrong BlockID: %v", err)
   286  	}
   287  
   288  	// BlockID.ValidateBasic would not err if we for instance have an empty hash but a
   289  	// non-empty PartsSetHeader:
   290  	if !vote.BlockID.IsZero() && !vote.BlockID.IsComplete() {
   291  		return fmt.Errorf("blockID must be either empty or complete, got: %v", vote.BlockID)
   292  	}
   293  
   294  	if len(vote.ValidatorAddress) != crypto.AddressSize {
   295  		return fmt.Errorf("expected ValidatorAddress size to be %d bytes, got %d bytes",
   296  			crypto.AddressSize,
   297  			len(vote.ValidatorAddress),
   298  		)
   299  	}
   300  	if vote.ValidatorIndex < 0 {
   301  		return errors.New("negative ValidatorIndex")
   302  	}
   303  	if len(vote.Signature) == 0 {
   304  		return errors.New("signature is missing")
   305  	}
   306  
   307  	if len(vote.Signature) > MaxSignatureSize {
   308  		return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
   309  	}
   310  
   311  	// We should only ever see vote extensions in non-nil precommits, otherwise
   312  	// this is a violation of the specification.
   313  	// https://github.com/tendermint/tendermint/issues/8487
   314  	if vote.Type != cmtproto.PrecommitType || vote.BlockID.IsZero() {
   315  		if len(vote.Extension) > 0 {
   316  			return fmt.Errorf(
   317  				"unexpected vote extension; vote type %d, isNil %t",
   318  				vote.Type, vote.BlockID.IsZero(),
   319  			)
   320  		}
   321  		if len(vote.ExtensionSignature) > 0 {
   322  			return errors.New("unexpected vote extension signature")
   323  		}
   324  	}
   325  
   326  	if vote.Type == cmtproto.PrecommitType && !vote.BlockID.IsZero() {
   327  		// It's possible that this vote has vote extensions but
   328  		// they could also be disabled and thus not present thus
   329  		// we can't do all checks
   330  		if len(vote.ExtensionSignature) > MaxSignatureSize {
   331  			return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize)
   332  		}
   333  
   334  		// NOTE: extended votes should have a signature regardless of
   335  		// of whether there is any data in the extension or not however
   336  		// we don't know if extensions are enabled so we can only
   337  		// enforce the signature when extension size is not nil
   338  		if len(vote.ExtensionSignature) == 0 && len(vote.Extension) != 0 {
   339  			return fmt.Errorf("vote extension signature absent on vote with extension")
   340  		}
   341  	}
   342  
   343  	return nil
   344  }
   345  
   346  // EnsureExtension checks for the presence of extensions signature data
   347  // on precommit vote types.
   348  func (vote *Vote) EnsureExtension() error {
   349  	// We should always see vote extension signatures in non-nil precommits
   350  	if vote.Type != cmtproto.PrecommitType {
   351  		return nil
   352  	}
   353  	if vote.BlockID.IsZero() {
   354  		return nil
   355  	}
   356  	if len(vote.ExtensionSignature) > 0 {
   357  		return nil
   358  	}
   359  	return ErrVoteExtensionAbsent
   360  }
   361  
   362  // ToProto converts the handwritten type to proto generated type
   363  // return type, nil if everything converts safely, otherwise nil, error
   364  func (vote *Vote) ToProto() *cmtproto.Vote {
   365  	if vote == nil {
   366  		return nil
   367  	}
   368  
   369  	return &cmtproto.Vote{
   370  		Type:               vote.Type,
   371  		Height:             vote.Height,
   372  		Round:              vote.Round,
   373  		BlockID:            vote.BlockID.ToProto(),
   374  		Timestamp:          vote.Timestamp,
   375  		ValidatorAddress:   vote.ValidatorAddress,
   376  		ValidatorIndex:     vote.ValidatorIndex,
   377  		Signature:          vote.Signature,
   378  		Extension:          vote.Extension,
   379  		ExtensionSignature: vote.ExtensionSignature,
   380  	}
   381  }
   382  
   383  func VotesToProto(votes []*Vote) []*cmtproto.Vote {
   384  	if votes == nil {
   385  		return nil
   386  	}
   387  
   388  	res := make([]*cmtproto.Vote, 0, len(votes))
   389  	for _, vote := range votes {
   390  		v := vote.ToProto()
   391  		// protobuf crashes when serializing "repeated" fields with nil elements
   392  		if v != nil {
   393  			res = append(res, v)
   394  		}
   395  	}
   396  	return res
   397  }
   398  
   399  func SignAndCheckVote(
   400  	vote *Vote,
   401  	privVal PrivValidator,
   402  	chainID string,
   403  	extensionsEnabled bool,
   404  ) (bool, error) {
   405  	v := vote.ToProto()
   406  	if err := privVal.SignVote(chainID, v); err != nil {
   407  		// Failing to sign a vote has always been a recoverable error, this function keeps it that way
   408  		return true, err // true = recoverable
   409  	}
   410  	vote.Signature = v.Signature
   411  
   412  	isPrecommit := vote.Type == cmtproto.PrecommitType
   413  	if !isPrecommit && extensionsEnabled {
   414  		// Non-recoverable because the caller passed parameters that don't make sense
   415  		return false, fmt.Errorf("only Precommit votes may have extensions enabled; vote type: %d", vote.Type)
   416  	}
   417  
   418  	isNil := vote.BlockID.IsZero()
   419  	extSignature := (len(v.ExtensionSignature) > 0)
   420  	if extSignature == (!isPrecommit || isNil) {
   421  		// Non-recoverable because the vote is malformed
   422  		return false, fmt.Errorf(
   423  			"extensions must be present IFF vote is a non-nil Precommit; present %t, vote type %d, is nil %t",
   424  			extSignature,
   425  			vote.Type,
   426  			isNil,
   427  		)
   428  	}
   429  
   430  	vote.ExtensionSignature = nil
   431  	if extensionsEnabled {
   432  		vote.ExtensionSignature = v.ExtensionSignature
   433  	}
   434  	vote.Timestamp = v.Timestamp
   435  
   436  	return true, nil
   437  }