types: change Commit to consist of just signatures (#4146)

* types: change `Commit` to consist of just signatures

These are final changes towards removing votes from commit and leaving
only signatures (see ADR-25)

Fixes #1648

* bring back TestCommitToVoteSetWithVotesForAnotherBlockOrNilBlock

+ add absent flag to Vote to indicate that it's for another block

* encode nil votes as CommitSig with BlockIDFlagAbsent

+ make Commit#Precommits array of non-pointers
because precommit will never be nil

* add NewCommitSigAbsent and Absent() funcs

* uncomment validation in CommitSig#ValidateBasic

* add comments to ValidatorSet funcs

* add a changelog entry

* break instead of continue

continue does not make sense in these cases

* types: rename Commit#Precommits to Signatures

* swagger: fix /commit response

* swagger: change block_id_flag type

* fix merge conflicts
This commit is contained in:
Anton Kaliaev
2019-11-26 14:10:38 +04:00
committed by GitHub
parent fb8b00f1d8
commit ad715fe966
30 changed files with 531 additions and 425 deletions
+181 -127
View File
@@ -73,7 +73,7 @@ func (b *Block) ValidateBasic() error {
return errors.New("nil LastCommit")
}
if err := b.LastCommit.ValidateBasic(); err != nil {
return fmt.Errorf("wrong LastCommit")
return fmt.Errorf("wrong LastCommit: %v", err)
}
}
if err := ValidateHash(b.LastCommitHash); err != nil {
@@ -434,27 +434,112 @@ func (h *Header) StringIndented(indent string) string {
//-------------------------------------
// CommitSig is a vote included in a Commit.
// For now, it is identical to a vote,
// but in the future it will contain fewer fields
// to eliminate the redundancy in commits.
// See https://github.com/tendermint/tendermint/issues/1648.
type CommitSig Vote
// BlockIDFlag indicates which BlockID the signature is for.
type BlockIDFlag byte
// String returns the underlying Vote.String()
func (cs *CommitSig) String() string {
return cs.toVote().String()
const (
// BlockIDFlagAbsent - no vote was received from a validator.
BlockIDFlagAbsent BlockIDFlag = iota + 1
// BlockIDFlagCommit - voted for the Commit.BlockID.
BlockIDFlagCommit
// BlockIDFlagNil - voted for nil.
BlockIDFlagNil
)
// CommitSig is a part of the Vote included in a Commit.
type CommitSig struct {
BlockIDFlag BlockIDFlag `json:"block_id_flag"`
ValidatorAddress Address `json:"validator_address"`
Timestamp time.Time `json:"timestamp"`
Signature []byte `json:"signature"`
}
// toVote converts the CommitSig to a vote.
// TODO: deprecate for #1648. Converting to Vote will require
// access to ValidatorSet.
func (cs *CommitSig) toVote() *Vote {
if cs == nil {
return nil
// NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit.
func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time) CommitSig {
return CommitSig{
BlockIDFlag: BlockIDFlagCommit,
ValidatorAddress: valAddr,
Timestamp: ts,
Signature: signature,
}
v := Vote(*cs)
return &v
}
// NewCommitSigAbsent returns new CommitSig with BlockIDFlagAbsent. Other
// fields are all empty.
func NewCommitSigAbsent() CommitSig {
return CommitSig{
BlockIDFlag: BlockIDFlagAbsent,
}
}
// Absent returns true if CommitSig is absent.
func (cs CommitSig) Absent() bool {
return cs.BlockIDFlag == BlockIDFlagAbsent
}
func (cs CommitSig) String() string {
return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}",
cmn.Fingerprint(cs.Signature),
cmn.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
}
//-------------------------------------
@@ -462,40 +547,40 @@ func (cs *CommitSig) toVote() *Vote {
// 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 Precommits are in order of address to preserve the bonded ValidatorSet order.
// Any peer with a block can gossip precommits by index with a peer without recalculating the
// active ValidatorSet.
BlockID BlockID `json:"block_id"`
Precommits []*CommitSig `json:"precommits"`
// 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 int `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
height int64
round int
// Memoized in first call to corresponding method.
// NOTE: can't memoize in constructor because constructor isn't used for
// unmarshaling.
hash cmn.HexBytes
bitArray *cmn.BitArray
}
// NewCommit returns a new Commit with the given blockID and precommits.
// TODO: memoize ValidatorSet in constructor so votes can be easily reconstructed
// from CommitSig after #1648.
func NewCommit(blockID BlockID, precommits []*CommitSig) *Commit {
// NewCommit returns a new Commit.
func NewCommit(height int64, round int, blockID BlockID, commitSigs []CommitSig) *Commit {
return &Commit{
Height: height,
Round: round,
BlockID: blockID,
Precommits: precommits,
Signatures: commitSigs,
}
}
// Construct a VoteSet from the Commit and validator set. Panics
// if precommits from the commit can't be added to the voteset.
// CommitToVoteSet constructs a VoteSet from the Commit and validator set.
// Panics if signatures from the commit can't be added to the voteset.
// Inverse of VoteSet.MakeCommit().
func CommitToVoteSet(chainID string, commit *Commit, vals *ValidatorSet) *VoteSet {
height, round, typ := commit.Height(), commit.Round(), PrecommitType
voteSet := NewVoteSet(chainID, height, round, typ, vals)
for idx, precommit := range commit.Precommits {
if precommit == nil {
continue
voteSet := NewVoteSet(chainID, commit.Height, commit.Round, PrecommitType, vals)
for idx, commitSig := range commit.Signatures {
if commitSig.Absent() {
continue // OK, some precommits can be missing.
}
added, err := voteSet.AddVote(commit.GetVote(idx))
if !added || err != nil {
@@ -509,21 +594,12 @@ func CommitToVoteSet(chainID string, commit *Commit, vals *ValidatorSet) *VoteSe
// Returns nil if the precommit at valIdx is nil.
// Panics if valIdx >= commit.Size().
func (commit *Commit) GetVote(valIdx int) *Vote {
commitSig := commit.Precommits[valIdx]
if commitSig == nil {
return nil
}
// NOTE: this commitSig might be for a nil blockID,
// so we can't just use commit.BlockID here.
// For #1648, CommitSig will need to indicate what BlockID it's for !
blockID := commitSig.BlockID
commit.memoizeHeightRound()
commitSig := commit.Signatures[valIdx]
return &Vote{
Type: PrecommitType,
Height: commit.height,
Round: commit.round,
BlockID: blockID,
Height: commit.Height,
Round: commit.Round,
BlockID: commitSig.BlockID(commit.BlockID),
Timestamp: commitSig.Timestamp,
ValidatorAddress: commitSig.ValidatorAddress,
ValidatorIndex: valIdx,
@@ -539,58 +615,42 @@ func (commit *Commit) VoteSignBytes(chainID string, valIdx int) []byte {
return commit.GetVote(valIdx).SignBytes(chainID)
}
// memoizeHeightRound memoizes the height and round of the commit using
// the first non-nil vote.
// Should be called before any attempt to access `commit.height` or `commit.round`.
func (commit *Commit) memoizeHeightRound() {
if len(commit.Precommits) == 0 {
return
}
if commit.height > 0 {
return
}
for _, precommit := range commit.Precommits {
if precommit != nil {
commit.height = precommit.Height
commit.round = precommit.Round
return
}
}
}
// Height returns the height of the commit
func (commit *Commit) Height() int64 {
commit.memoizeHeightRound()
return commit.height
}
// Round returns the round of the commit
func (commit *Commit) Round() int {
commit.memoizeHeightRound()
return commit.round
}
// Type returns the vote type of the commit, which is always VoteTypePrecommit
// Implements VoteSetReader.
func (commit *Commit) Type() byte {
return byte(PrecommitType)
}
// Size returns the number of votes in the commit
// 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() int {
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.Precommits)
return len(commit.Signatures)
}
// BitArray returns a BitArray of which validators voted in this commit
// BitArray returns a BitArray of which validators voted for BlockID or nil in this commit.
// Implements VoteSetReader.
func (commit *Commit) BitArray() *cmn.BitArray {
if commit.bitArray == nil {
commit.bitArray = cmn.NewBitArray(len(commit.Precommits))
for i, precommit := range commit.Precommits {
commit.bitArray = cmn.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, precommit != nil)
commit.bitArray.SetIndex(i, !commitSig.Absent())
}
}
return commit.bitArray
@@ -603,44 +663,35 @@ func (commit *Commit) GetByIndex(valIdx int) *Vote {
return commit.GetVote(valIdx)
}
// IsCommit returns true if there is at least one vote.
// IsCommit returns true if there is at least one signature.
// Implements VoteSetReader.
func (commit *Commit) IsCommit() bool {
return len(commit.Precommits) != 0
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.BlockID.IsZero() {
return errors.New("commit cannot be for nil block")
}
if len(commit.Precommits) == 0 {
return errors.New("no precommits in commit")
}
height, round := commit.Height(), commit.Round()
// Validate the precommits.
for _, precommit := range commit.Precommits {
// It's OK for precommits to be missing.
if precommit == nil {
continue
}
// Ensure that all votes are precommits.
if precommit.Type != PrecommitType {
return fmt.Errorf("invalid commit vote. Expected precommit, got %v",
precommit.Type)
}
// Ensure that all heights are the same.
if precommit.Height != height {
return fmt.Errorf("invalid commit precommit height. Expected %v, got %v",
height, precommit.Height)
}
// Ensure that all rounds are the same.
if precommit.Round != round {
return fmt.Errorf("invalid commit precommit round. Expected %v, got %v",
round, precommit.Round)
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
}
@@ -650,9 +701,9 @@ func (commit *Commit) Hash() cmn.HexBytes {
return nil
}
if commit.hash == nil {
bs := make([][]byte, len(commit.Precommits))
for i, precommit := range commit.Precommits {
bs[i] = cdcEncode(precommit)
bs := make([][]byte, len(commit.Signatures))
for i, commitSig := range commit.Signatures {
bs[i] = cdcEncode(commitSig)
}
commit.hash = merkle.SimpleHashFromByteSlices(bs)
}
@@ -664,18 +715,22 @@ func (commit *Commit) StringIndented(indent string) string {
if commit == nil {
return "nil-Commit"
}
precommitStrings := make([]string, len(commit.Precommits))
for i, precommit := range commit.Precommits {
precommitStrings[i] = precommit.String()
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 Precommits:
%s Signatures:
%s %v
%s}#%v`,
indent, commit.Height,
indent, commit.Round,
indent, commit.BlockID,
indent,
indent, strings.Join(precommitStrings, "\n"+indent+" "),
indent, strings.Join(commitSigStrings, "\n"+indent+" "),
indent, commit.hash)
}
@@ -695,7 +750,6 @@ type SignedHeader struct {
// sure to use a Verifier to validate the signatures actually provide a
// significantly strong proof for this header's validity.
func (sh SignedHeader) ValidateBasic(chainID string) error {
// Make sure the header is consistent with the commit.
if sh.Header == nil {
return errors.New("signedHeader missing header")
@@ -710,9 +764,9 @@ func (sh SignedHeader) ValidateBasic(chainID string) error {
sh.ChainID, chainID)
}
// Check Height.
if sh.Commit.Height() != sh.Height {
if sh.Commit.Height != sh.Height {
return fmt.Errorf("signedHeader header and commit height mismatch: %v vs %v",
sh.Height, sh.Commit.Height())
sh.Height, sh.Commit.Height)
}
// Check Hash.
hhash := sh.Hash()