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 }