mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-09 06:33:16 +00:00
Add package jsontypes that implements a subset of the custom libs/json package. Specifically it handles encoding and decoding of interface types wrapped in "tagged" JSON objects. It omits the deep reflection on arbitrary types, preserving only the handling of type tags wrapper encoding. - Register interface types (Evidence, PubKey, PrivKey) for tagged encoding. - Update the existing implementations to satisfy the type. - Register those types with the jsontypes registry. - Add string tags to 64-bit integer fields where needed. - Add marshalers to structs that export interface-typed fields.
247 lines
6.5 KiB
Go
247 lines
6.5 KiB
Go
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.IsZero():
|
|
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.IsZero() && !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()
|
|
}
|