mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-10 14:00:33 +00:00
380 lines
9.9 KiB
Go
380 lines
9.9 KiB
Go
package metadata
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/merkle"
|
|
"github.com/tendermint/tendermint/libs/bits"
|
|
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
|
tmmath "github.com/tendermint/tendermint/libs/math"
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
)
|
|
|
|
var (
|
|
// MaxSignatureSize is a maximum allowed signature size for the Proposal
|
|
// and Vote.
|
|
// XXX: secp256k1 does not have Size nor MaxSize defined.
|
|
MaxSignatureSize = tmmath.MaxInt(ed25519.SignatureSize, 64)
|
|
)
|
|
|
|
//-------------------------------------
|
|
|
|
const (
|
|
// Max size of commit without any commitSigs -> 82 for BlockID, 8 for Height, 4 for Round.
|
|
MaxCommitOverheadBytes int64 = 94
|
|
// Commit sig size is made up of 64 bytes for the signature, 20 bytes for the address,
|
|
// 1 byte for the flag and 14 bytes for the timestamp
|
|
MaxCommitSigBytes int64 = 109
|
|
)
|
|
|
|
// CommitSig is a part of the Vote included in a Commit.
|
|
type CommitSig struct {
|
|
BlockIDFlag BlockIDFlag `json:"block_id_flag"`
|
|
ValidatorAddress crypto.Address `json:"validator_address"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Signature []byte `json:"signature"`
|
|
}
|
|
|
|
// NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit.
|
|
func NewCommitSigForBlock(signature []byte, valAddr crypto.Address, ts time.Time) CommitSig {
|
|
return CommitSig{
|
|
BlockIDFlag: BlockIDFlagCommit,
|
|
ValidatorAddress: valAddr,
|
|
Timestamp: ts,
|
|
Signature: signature,
|
|
}
|
|
}
|
|
|
|
func MaxCommitBytes(valCount int) int64 {
|
|
// From the repeated commit sig field
|
|
var protoEncodingOverhead int64 = 2
|
|
return MaxCommitOverheadBytes + ((MaxCommitSigBytes + protoEncodingOverhead) * int64(valCount))
|
|
}
|
|
|
|
// NewCommitSigAbsent returns new CommitSig with BlockIDFlagAbsent. Other
|
|
// fields are all empty.
|
|
func NewCommitSigAbsent() CommitSig {
|
|
return CommitSig{
|
|
BlockIDFlag: BlockIDFlagAbsent,
|
|
}
|
|
}
|
|
|
|
// ForBlock returns true if CommitSig is for the block.
|
|
func (cs CommitSig) ForBlock() bool {
|
|
return cs.BlockIDFlag == BlockIDFlagCommit
|
|
}
|
|
|
|
// Absent returns true if CommitSig is absent.
|
|
func (cs CommitSig) Absent() bool {
|
|
return cs.BlockIDFlag == BlockIDFlagAbsent
|
|
}
|
|
|
|
// CommitSig returns a string representation of CommitSig.
|
|
//
|
|
// 1. first 6 bytes of signature
|
|
// 2. first 6 bytes of validator address
|
|
// 3. block ID flag
|
|
// 4. timestamp
|
|
func (cs CommitSig) String() string {
|
|
return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}",
|
|
tmbytes.Fingerprint(cs.Signature),
|
|
tmbytes.Fingerprint(cs.ValidatorAddress),
|
|
cs.BlockIDFlag,
|
|
CanonicalTime(cs.Timestamp))
|
|
}
|
|
|
|
// BlockID returns the Commit's BlockID if CommitSig indicates signing,
|
|
// otherwise - empty BlockID.
|
|
func (cs CommitSig) BlockID(commitBlockID BlockID) BlockID {
|
|
var blockID BlockID
|
|
switch cs.BlockIDFlag {
|
|
case BlockIDFlagAbsent:
|
|
blockID = BlockID{}
|
|
case BlockIDFlagCommit:
|
|
blockID = commitBlockID
|
|
case BlockIDFlagNil:
|
|
blockID = BlockID{}
|
|
default:
|
|
panic(fmt.Sprintf("Unknown BlockIDFlag: %v", cs.BlockIDFlag))
|
|
}
|
|
return blockID
|
|
}
|
|
|
|
// ValidateBasic performs basic validation.
|
|
func (cs CommitSig) ValidateBasic() error {
|
|
switch cs.BlockIDFlag {
|
|
case BlockIDFlagAbsent:
|
|
case BlockIDFlagCommit:
|
|
case BlockIDFlagNil:
|
|
default:
|
|
return fmt.Errorf("unknown BlockIDFlag: %v", cs.BlockIDFlag)
|
|
}
|
|
|
|
switch cs.BlockIDFlag {
|
|
case BlockIDFlagAbsent:
|
|
if len(cs.ValidatorAddress) != 0 {
|
|
return errors.New("validator address is present")
|
|
}
|
|
if !cs.Timestamp.IsZero() {
|
|
return errors.New("time is present")
|
|
}
|
|
if len(cs.Signature) != 0 {
|
|
return errors.New("signature is present")
|
|
}
|
|
default:
|
|
if len(cs.ValidatorAddress) != crypto.AddressSize {
|
|
return fmt.Errorf("expected ValidatorAddress size to be %d bytes, got %d bytes",
|
|
crypto.AddressSize,
|
|
len(cs.ValidatorAddress),
|
|
)
|
|
}
|
|
// NOTE: Timestamp validation is subtle and handled elsewhere.
|
|
if len(cs.Signature) == 0 {
|
|
return errors.New("signature is missing")
|
|
}
|
|
if len(cs.Signature) > MaxSignatureSize {
|
|
return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ToProto converts CommitSig to protobuf
|
|
func (cs *CommitSig) ToProto() *tmproto.CommitSig {
|
|
if cs == nil {
|
|
return nil
|
|
}
|
|
|
|
return &tmproto.CommitSig{
|
|
BlockIdFlag: tmproto.BlockIDFlag(cs.BlockIDFlag),
|
|
ValidatorAddress: cs.ValidatorAddress,
|
|
Timestamp: cs.Timestamp,
|
|
Signature: cs.Signature,
|
|
}
|
|
}
|
|
|
|
// FromProto sets a protobuf CommitSig to the given pointer.
|
|
// It returns an error if the CommitSig is invalid.
|
|
func (cs *CommitSig) FromProto(csp tmproto.CommitSig) error {
|
|
|
|
cs.BlockIDFlag = BlockIDFlag(csp.BlockIdFlag)
|
|
cs.ValidatorAddress = csp.ValidatorAddress
|
|
cs.Timestamp = csp.Timestamp
|
|
cs.Signature = csp.Signature
|
|
|
|
return cs.ValidateBasic()
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
// Commit contains the evidence that a block was committed by a set of validators.
|
|
// NOTE: Commit is empty for height 1, but never nil.
|
|
type Commit struct {
|
|
// NOTE: The signatures are in order of address to preserve the bonded
|
|
// ValidatorSet order.
|
|
// Any peer with a block can gossip signatures by index with a peer without
|
|
// recalculating the active ValidatorSet.
|
|
Height int64 `json:"height"`
|
|
Round int32 `json:"round"`
|
|
BlockID BlockID `json:"block_id"`
|
|
Signatures []CommitSig `json:"signatures"`
|
|
|
|
// Memoized in first call to corresponding method.
|
|
// NOTE: can't memoize in constructor because constructor isn't used for
|
|
// unmarshaling.
|
|
hash tmbytes.HexBytes
|
|
bitArray *bits.BitArray
|
|
}
|
|
|
|
// NewCommit returns a new Commit.
|
|
func NewCommit(height int64, round int32, blockID BlockID, commitSigs []CommitSig) *Commit {
|
|
return &Commit{
|
|
Height: height,
|
|
Round: round,
|
|
BlockID: blockID,
|
|
Signatures: commitSigs,
|
|
}
|
|
}
|
|
|
|
// Type returns the vote type of the commit, which is always VoteTypePrecommit
|
|
// Implements VoteSetReader.
|
|
func (commit *Commit) Type() byte {
|
|
return byte(tmproto.PrecommitType)
|
|
}
|
|
|
|
// GetHeight returns height of the commit.
|
|
// Implements VoteSetReader.
|
|
func (commit *Commit) GetHeight() int64 {
|
|
return commit.Height
|
|
}
|
|
|
|
// GetRound returns height of the commit.
|
|
// Implements VoteSetReader.
|
|
func (commit *Commit) GetRound() int32 {
|
|
return commit.Round
|
|
}
|
|
|
|
// Size returns the number of signatures in the commit.
|
|
// Implements VoteSetReader.
|
|
func (commit *Commit) Size() int {
|
|
if commit == nil {
|
|
return 0
|
|
}
|
|
return len(commit.Signatures)
|
|
}
|
|
|
|
// ClearCache removes the saved hash. This is predominantly used for testing.
|
|
func (commit *Commit) ClearCache() {
|
|
commit.hash = nil
|
|
commit.bitArray = nil
|
|
}
|
|
|
|
// BitArray returns a BitArray of which validators voted for BlockID or nil in this commit.
|
|
// Implements VoteSetReader.
|
|
func (commit *Commit) BitArray() *bits.BitArray {
|
|
if commit.bitArray == nil {
|
|
commit.bitArray = bits.NewBitArray(len(commit.Signatures))
|
|
for i, commitSig := range commit.Signatures {
|
|
// TODO: need to check the BlockID otherwise we could be counting conflicts,
|
|
// not just the one with +2/3 !
|
|
commit.bitArray.SetIndex(i, !commitSig.Absent())
|
|
}
|
|
}
|
|
return commit.bitArray
|
|
}
|
|
|
|
// IsCommit returns true if there is at least one signature.
|
|
// Implements VoteSetReader.
|
|
func (commit *Commit) IsCommit() bool {
|
|
return len(commit.Signatures) != 0
|
|
}
|
|
|
|
// ValidateBasic performs basic validation that doesn't involve state data.
|
|
// Does not actually check the cryptographic signatures.
|
|
func (commit *Commit) ValidateBasic() error {
|
|
if commit.Height < 0 {
|
|
return errors.New("negative Height")
|
|
}
|
|
if commit.Round < 0 {
|
|
return errors.New("negative Round")
|
|
}
|
|
|
|
if commit.Height >= 1 {
|
|
if commit.BlockID.IsZero() {
|
|
return errors.New("commit cannot be for nil block")
|
|
}
|
|
|
|
if len(commit.Signatures) == 0 {
|
|
return errors.New("no signatures in commit")
|
|
}
|
|
for i, commitSig := range commit.Signatures {
|
|
if err := commitSig.ValidateBasic(); err != nil {
|
|
return fmt.Errorf("wrong CommitSig #%d: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Hash returns the hash of the commit
|
|
func (commit *Commit) Hash() tmbytes.HexBytes {
|
|
if commit == nil {
|
|
return nil
|
|
}
|
|
if commit.hash == nil {
|
|
bs := make([][]byte, len(commit.Signatures))
|
|
for i, commitSig := range commit.Signatures {
|
|
pbcs := commitSig.ToProto()
|
|
bz, err := pbcs.Marshal()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
bs[i] = bz
|
|
}
|
|
commit.hash = merkle.HashFromByteSlices(bs)
|
|
}
|
|
return commit.hash
|
|
}
|
|
|
|
// StringIndented returns a string representation of the commit.
|
|
func (commit *Commit) StringIndented(indent string) string {
|
|
if commit == nil {
|
|
return "nil-Commit"
|
|
}
|
|
commitSigStrings := make([]string, len(commit.Signatures))
|
|
for i, commitSig := range commit.Signatures {
|
|
commitSigStrings[i] = commitSig.String()
|
|
}
|
|
return fmt.Sprintf(`Commit{
|
|
%s Height: %d
|
|
%s Round: %d
|
|
%s BlockID: %v
|
|
%s Signatures:
|
|
%s %v
|
|
%s}#%v`,
|
|
indent, commit.Height,
|
|
indent, commit.Round,
|
|
indent, commit.BlockID,
|
|
indent,
|
|
indent, strings.Join(commitSigStrings, "\n"+indent+" "),
|
|
indent, commit.hash)
|
|
}
|
|
|
|
// ToProto converts Commit to protobuf
|
|
func (commit *Commit) ToProto() *tmproto.Commit {
|
|
if commit == nil {
|
|
return nil
|
|
}
|
|
|
|
c := new(tmproto.Commit)
|
|
sigs := make([]tmproto.CommitSig, len(commit.Signatures))
|
|
for i := range commit.Signatures {
|
|
sigs[i] = *commit.Signatures[i].ToProto()
|
|
}
|
|
c.Signatures = sigs
|
|
|
|
c.Height = commit.Height
|
|
c.Round = commit.Round
|
|
c.BlockID = commit.BlockID.ToProto()
|
|
|
|
return c
|
|
}
|
|
|
|
// FromProto sets a protobuf Commit to the given pointer.
|
|
// It returns an error if the commit is invalid.
|
|
func CommitFromProto(cp *tmproto.Commit) (*Commit, error) {
|
|
if cp == nil {
|
|
return nil, errors.New("nil Commit")
|
|
}
|
|
|
|
var (
|
|
commit = new(Commit)
|
|
)
|
|
|
|
bi, err := BlockIDFromProto(&cp.BlockID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sigs := make([]CommitSig, len(cp.Signatures))
|
|
for i := range cp.Signatures {
|
|
if err := sigs[i].FromProto(cp.Signatures[i]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
commit.Signatures = sigs
|
|
|
|
commit.Height = cp.Height
|
|
commit.Round = cp.Round
|
|
commit.BlockID = *bi
|
|
|
|
return commit, commit.ValidateBasic()
|
|
}
|