mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-09 14:43:19 +00:00
Introduce CommitSig alias for Vote in Commit (#3245)
* types: memoize height/round in commit instead of first vote * types: commit.ValidateBasic in VerifyCommit * types: new CommitSig alias for Vote In preparation for reducing the redundancy in Commits, we introduce the CommitSig as an alias for Vote. This is non-breaking on the protocol, and minor breaking on the Go API, as Commit now contains a list of CommitSig instead of Vote. * remove dependence on ToVote * update some comments * fix tests * fix tests * fixes from review
This commit is contained in:
@@ -477,39 +477,77 @@ 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
|
||||
|
||||
// String returns the underlying Vote.String()
|
||||
func (cs *CommitSig) String() string {
|
||||
return cs.toVote().String()
|
||||
}
|
||||
|
||||
// toVote converts the CommitSig to a vote.
|
||||
// Once CommitSig has fewer fields than vote,
|
||||
// converting to a Vote will require more information.
|
||||
func (cs *CommitSig) toVote() *Vote {
|
||||
if cs == nil {
|
||||
return nil
|
||||
}
|
||||
v := Vote(*cs)
|
||||
return &v
|
||||
}
|
||||
|
||||
// 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 []*Vote `json:"precommits"`
|
||||
BlockID BlockID `json:"block_id"`
|
||||
Precommits []*CommitSig `json:"precommits"`
|
||||
|
||||
// Volatile
|
||||
firstPrecommit *Vote
|
||||
hash cmn.HexBytes
|
||||
bitArray *cmn.BitArray
|
||||
height int64
|
||||
round int
|
||||
hash cmn.HexBytes
|
||||
bitArray *cmn.BitArray
|
||||
}
|
||||
|
||||
// FirstPrecommit returns the first non-nil precommit in the commit.
|
||||
// If all precommits are nil, it returns an empty precommit with height 0.
|
||||
func (commit *Commit) FirstPrecommit() *Vote {
|
||||
// VoteSignBytes constructs the SignBytes for the given CommitSig.
|
||||
// The only unique part of the SignBytes is the Timestamp - all other fields
|
||||
// signed over are otherwise the same for all validators.
|
||||
func (commit *Commit) VoteSignBytes(chainID string, cs *CommitSig) []byte {
|
||||
return cs.toVote().SignBytes(chainID)
|
||||
}
|
||||
|
||||
// memoizeHeightRound memoizes the height and round of the commit using
|
||||
// the first non-nil vote.
|
||||
func (commit *Commit) memoizeHeightRound() {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if commit.firstPrecommit != nil {
|
||||
return commit.firstPrecommit
|
||||
if commit.height > 0 {
|
||||
return
|
||||
}
|
||||
for _, precommit := range commit.Precommits {
|
||||
if precommit != nil {
|
||||
commit.firstPrecommit = precommit
|
||||
return precommit
|
||||
commit.height = precommit.Height
|
||||
commit.round = precommit.Round
|
||||
return
|
||||
}
|
||||
}
|
||||
return &Vote{
|
||||
Type: PrecommitType,
|
||||
}
|
||||
}
|
||||
|
||||
// ToVote converts a CommitSig to a Vote.
|
||||
// If the CommitSig is nil, the Vote will be nil.
|
||||
// When CommitSig is reduced to contain fewer fields,
|
||||
// this will need access to the ValidatorSet to properly
|
||||
// reconstruct the vote.
|
||||
func (commit *Commit) ToVote(cs *CommitSig) *Vote {
|
||||
return cs.toVote()
|
||||
}
|
||||
|
||||
// Height returns the height of the commit
|
||||
@@ -517,7 +555,8 @@ func (commit *Commit) Height() int64 {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return 0
|
||||
}
|
||||
return commit.FirstPrecommit().Height
|
||||
commit.memoizeHeightRound()
|
||||
return commit.height
|
||||
}
|
||||
|
||||
// Round returns the round of the commit
|
||||
@@ -525,7 +564,8 @@ func (commit *Commit) Round() int {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return 0
|
||||
}
|
||||
return commit.FirstPrecommit().Round
|
||||
commit.memoizeHeightRound()
|
||||
return commit.round
|
||||
}
|
||||
|
||||
// Type returns the vote type of the commit, which is always VoteTypePrecommit
|
||||
@@ -554,12 +594,13 @@ func (commit *Commit) BitArray() *cmn.BitArray {
|
||||
return commit.bitArray
|
||||
}
|
||||
|
||||
// GetByIndex returns the vote corresponding to a given validator index
|
||||
// GetByIndex returns the vote corresponding to a given validator index.
|
||||
// Implements VoteSetReader.
|
||||
func (commit *Commit) GetByIndex(index int) *Vote {
|
||||
return commit.Precommits[index]
|
||||
return commit.Precommits[index].toVote()
|
||||
}
|
||||
|
||||
// IsCommit returns true if there is at least one vote
|
||||
// IsCommit returns true if there is at least one vote.
|
||||
func (commit *Commit) IsCommit() bool {
|
||||
return len(commit.Precommits) != 0
|
||||
}
|
||||
|
||||
@@ -198,7 +198,6 @@ func TestCommit(t *testing.T) {
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, commit.FirstPrecommit())
|
||||
assert.Equal(t, h-1, commit.Height())
|
||||
assert.Equal(t, 1, commit.Round())
|
||||
assert.Equal(t, PrecommitType, SignedMsgType(commit.Type()))
|
||||
|
||||
@@ -368,6 +368,10 @@ func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
||||
|
||||
// Verify that +2/3 of the set had signed the given signBytes.
|
||||
func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
|
||||
|
||||
if err := commit.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
if vals.Size() != len(commit.Precommits) {
|
||||
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits))
|
||||
}
|
||||
@@ -380,24 +384,14 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i
|
||||
}
|
||||
|
||||
talliedVotingPower := int64(0)
|
||||
round := commit.Round()
|
||||
|
||||
for idx, precommit := range commit.Precommits {
|
||||
if precommit == nil {
|
||||
continue // OK, some precommits can be missing.
|
||||
}
|
||||
if precommit.Height != height {
|
||||
return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height)
|
||||
}
|
||||
if precommit.Round != round {
|
||||
return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round)
|
||||
}
|
||||
if precommit.Type != PrecommitType {
|
||||
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx)
|
||||
}
|
||||
_, val := vals.GetByIndex(idx)
|
||||
// Validate signature.
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
precommitSignBytes := commit.VoteSignBytes(chainID, precommit)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
@@ -481,7 +475,7 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin
|
||||
seen[idx] = true
|
||||
|
||||
// Validate signature.
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
precommitSignBytes := commit.VoteSignBytes(chainID, precommit)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return cmn.NewError("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
|
||||
@@ -565,7 +565,7 @@ func TestValidatorSetVerifyCommit(t *testing.T) {
|
||||
vote.Signature = sig
|
||||
commit := &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: []*Vote{vote},
|
||||
Precommits: []*CommitSig{vote.CommitSig()},
|
||||
}
|
||||
|
||||
badChainID := "notmychainID"
|
||||
@@ -573,7 +573,7 @@ func TestValidatorSetVerifyCommit(t *testing.T) {
|
||||
badHeight := height + 1
|
||||
badCommit := &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: []*Vote{nil},
|
||||
Precommits: []*CommitSig{nil},
|
||||
}
|
||||
|
||||
// test some error cases
|
||||
|
||||
@@ -59,6 +59,16 @@ type Vote struct {
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
// CommitSig converts the Vote to a CommitSig.
|
||||
// If the Vote is nil, the CommitSig will be nil.
|
||||
func (vote *Vote) CommitSig() *CommitSig {
|
||||
if vote == nil {
|
||||
return nil
|
||||
}
|
||||
cs := CommitSig(*vote)
|
||||
return &cs
|
||||
}
|
||||
|
||||
func (vote *Vote) SignBytes(chainID string) []byte {
|
||||
bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeVote(chainID, vote))
|
||||
if err != nil {
|
||||
|
||||
@@ -541,11 +541,13 @@ func (voteSet *VoteSet) MakeCommit() *Commit {
|
||||
}
|
||||
|
||||
// For every validator, get the precommit
|
||||
votesCopy := make([]*Vote, len(voteSet.votes))
|
||||
copy(votesCopy, voteSet.votes)
|
||||
commitSigs := make([]*CommitSig, len(voteSet.votes))
|
||||
for i, v := range voteSet.votes {
|
||||
commitSigs[i] = v.CommitSig()
|
||||
}
|
||||
return &Commit{
|
||||
BlockID: *voteSet.maj23,
|
||||
Precommits: votesCopy,
|
||||
Precommits: commitSigs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
@@ -43,6 +44,19 @@ func exampleVote(t byte) *Vote {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that Vote and CommitSig have the same encoding.
|
||||
// This ensures using CommitSig isn't a breaking change.
|
||||
// This test will fail and can be removed once CommitSig contains only sigs and
|
||||
// timestamps.
|
||||
func TestVoteEncoding(t *testing.T) {
|
||||
vote := examplePrecommit()
|
||||
commitSig := vote.CommitSig()
|
||||
cdc := amino.NewCodec()
|
||||
bz1 := cdc.MustMarshalBinaryBare(vote)
|
||||
bz2 := cdc.MustMarshalBinaryBare(commitSig)
|
||||
assert.Equal(t, bz1, bz2)
|
||||
}
|
||||
|
||||
func TestVoteSignable(t *testing.T) {
|
||||
vote := examplePrecommit()
|
||||
signBytes := vote.SignBytes("test_chain_id")
|
||||
|
||||
Reference in New Issue
Block a user