- 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) (res []*tmproto.Vote) {
- if votes == nil {
- return nil
- }
-
- res = make([]*tmproto.Vote, len(votes))
- for i, vote := range votes {
- res[i] = vote.ToProto()
- }
- return
- }
-
- 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()
- }
|