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()
+31 -29
View File
@@ -70,7 +70,7 @@ func TestBlockValidateBasic(t *testing.T) {
{"Make Block w/ proposer Addr", func(blk *Block) { blk.ProposerAddress = valSet.GetProposer().Address }, false},
{"Negative Height", func(blk *Block) { blk.Height = -1 }, true},
{"Remove 1/2 the commits", func(blk *Block) {
blk.LastCommit.Precommits = commit.Precommits[:commit.Size()/2]
blk.LastCommit.Signatures = commit.Signatures[:commit.Size()/2]
blk.LastCommit.hash = nil // clear hash or change wont be noticed
}, true},
{"Remove LastCommitHash", func(blk *Block) { blk.LastCommitHash = []byte("something else") }, true},
@@ -124,7 +124,7 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) {
ev := NewMockGoodEvidence(h, 0, valSet.Validators[0].Address)
evList := []Evidence{ev}
partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(1024)
partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512)
assert.NotNil(t, partSet)
assert.Equal(t, 3, partSet.Total())
}
@@ -167,23 +167,29 @@ func TestBlockString(t *testing.T) {
}
func makeBlockIDRandom() BlockID {
blockHash := make([]byte, tmhash.Size)
partSetHash := make([]byte, tmhash.Size)
var (
blockHash = make([]byte, tmhash.Size)
partSetHash = make([]byte, tmhash.Size)
)
rand.Read(blockHash) //nolint: gosec
rand.Read(partSetHash) //nolint: gosec
blockPartsHeader := PartSetHeader{123, partSetHash}
return BlockID{blockHash, blockPartsHeader}
return BlockID{blockHash, PartSetHeader{123, partSetHash}}
}
func makeBlockID(hash []byte, partSetSize int, partSetHash []byte) BlockID {
var (
h = make([]byte, tmhash.Size)
psH = make([]byte, tmhash.Size)
)
copy(h, hash)
copy(psH, partSetHash)
return BlockID{
Hash: hash,
Hash: h,
PartsHeader: PartSetHeader{
Total: partSetSize,
Hash: partSetHash,
Hash: psH,
},
}
}
var nilBytes []byte
@@ -205,8 +211,8 @@ func TestCommit(t *testing.T) {
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
require.NoError(t, err)
assert.Equal(t, h-1, commit.Height())
assert.Equal(t, 1, commit.Round())
assert.Equal(t, h-1, commit.Height)
assert.Equal(t, 1, commit.Round)
assert.Equal(t, PrecommitType, SignedMsgType(commit.Type()))
if commit.Size() <= 0 {
t.Fatalf("commit %v has a zero or negative size: %d", commit, commit.Size())
@@ -226,11 +232,9 @@ func TestCommitValidateBasic(t *testing.T) {
expectErr bool
}{
{"Random Commit", func(com *Commit) {}, false},
{"Nil precommit", func(com *Commit) { com.Precommits[0] = nil }, false},
{"Incorrect signature", func(com *Commit) { com.Precommits[0].Signature = []byte{0} }, false},
{"Incorrect type", func(com *Commit) { com.Precommits[0].Type = PrevoteType }, true},
{"Incorrect height", func(com *Commit) { com.Precommits[0].Height = int64(100) }, true},
{"Incorrect round", func(com *Commit) { com.Precommits[0].Round = 100 }, true},
{"Incorrect signature", func(com *Commit) { com.Signatures[0].Signature = []byte{0} }, false},
{"Incorrect height", func(com *Commit) { com.Height = int64(-100) }, true},
{"Incorrect round", func(com *Commit) { com.Round = -100 }, true},
}
for _, tc := range testCases {
tc := tc
@@ -444,13 +448,13 @@ func TestCommitToVoteSet(t *testing.T) {
}
}
func TestCommitToVoteSetWithVotesForAnotherBlockOrNilBlock(t *testing.T) {
func TestCommitToVoteSetWithVotesForNilBlock(t *testing.T) {
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
blockID3 := makeBlockID([]byte("blockhash3"), 10000, []byte("partshash"))
height := int64(3)
round := 1
const (
height = int64(3)
round = 0
)
type commitVoteTest struct {
blockIDs []BlockID
@@ -460,16 +464,11 @@ func TestCommitToVoteSetWithVotesForAnotherBlockOrNilBlock(t *testing.T) {
}
testCases := []commitVoteTest{
{[]BlockID{blockID, blockID2, blockID3}, []int{8, 1, 1}, 10, true},
{[]BlockID{blockID, blockID2, blockID3}, []int{67, 20, 13}, 100, true},
{[]BlockID{blockID, blockID2, blockID3}, []int{1, 1, 1}, 3, false},
{[]BlockID{blockID, blockID2, blockID3}, []int{3, 1, 1}, 5, false},
{[]BlockID{blockID, {}}, []int{67, 33}, 100, true},
{[]BlockID{blockID, blockID2, {}}, []int{10, 5, 5}, 20, false},
}
for _, tc := range testCases {
voteSet, valSet, vals := randVoteSet(height-1, 1, PrecommitType, tc.numValidators, 1)
voteSet, valSet, vals := randVoteSet(height-1, round, PrecommitType, tc.numValidators, 1)
vi := 0
for n := range tc.blockIDs {
@@ -485,11 +484,14 @@ func TestCommitToVoteSetWithVotesForAnotherBlockOrNilBlock(t *testing.T) {
Timestamp: tmtime.Now(),
}
_, err := signAddVote(vals[vi], vote, voteSet)
added, err := signAddVote(vals[vi], vote, voteSet)
assert.NoError(t, err)
assert.True(t, added)
vi++
}
}
if tc.valid {
commit := voteSet.MakeCommit() // panics without > 2/3 valid votes
assert.NotNil(t, commit)
@@ -508,7 +510,7 @@ func TestSignedHeaderValidateBasic(t *testing.T) {
h := Header{
Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64},
ChainID: chainID,
Height: commit.Height(),
Height: commit.Height,
Time: timestamp,
LastBlockID: commit.BlockID,
LastCommitHash: commit.Hash(),
+6 -6
View File
@@ -10,9 +10,9 @@ type (
Actual int64
}
// ErrInvalidCommitPrecommits is returned when we encounter a commit where
// the number of precommits doesn't match the number of validators.
ErrInvalidCommitPrecommits struct {
// ErrInvalidCommitSignatures is returned when we encounter a commit where
// the number of signatures doesn't match the number of validators.
ErrInvalidCommitSignatures struct {
Expected int
Actual int
}
@@ -29,13 +29,13 @@ func (e ErrInvalidCommitHeight) Error() string {
return fmt.Sprintf("Invalid commit -- wrong height: %v vs %v", e.Expected, e.Actual)
}
func NewErrInvalidCommitPrecommits(expected, actual int) ErrInvalidCommitPrecommits {
return ErrInvalidCommitPrecommits{
func NewErrInvalidCommitSignatures(expected, actual int) ErrInvalidCommitSignatures {
return ErrInvalidCommitSignatures{
Expected: expected,
Actual: actual,
}
}
func (e ErrInvalidCommitPrecommits) Error() string {
func (e ErrInvalidCommitSignatures) Error() string {
return fmt.Sprintf("Invalid commit -- wrong set size: %v vs %v", e.Expected, e.Actual)
}
+55 -69
View File
@@ -14,18 +14,17 @@ import (
cmn "github.com/tendermint/tendermint/libs/common"
)
// MaxTotalVotingPower - the maximum allowed total voting power.
// It needs to be sufficiently small to, in all cases:
// 1. prevent clipping in incrementProposerPriority()
// 2. let (diff+diffMax-1) not overflow in IncrementProposerPriority()
// (Proof of 1 is tricky, left to the reader).
// It could be higher, but this is sufficiently large for our purposes,
// and leaves room for defensive purposes.
// PriorityWindowSizeFactor - is a constant that when multiplied with the total voting power gives
// the maximum allowed distance between validator priorities.
const (
MaxTotalVotingPower = int64(math.MaxInt64) / 8
// MaxTotalVotingPower - the maximum allowed total voting power.
// It needs to be sufficiently small to, in all cases:
// 1. prevent clipping in incrementProposerPriority()
// 2. let (diff+diffMax-1) not overflow in IncrementProposerPriority()
// (Proof of 1 is tricky, left to the reader).
// It could be higher, but this is sufficiently large for our purposes,
// and leaves room for defensive purposes.
MaxTotalVotingPower = int64(math.MaxInt64) / 8
// PriorityWindowSizeFactor - is a constant that when multiplied with the total voting power gives
// the maximum allowed distance between validator priorities.
PriorityWindowSizeFactor = 2
)
@@ -67,12 +66,13 @@ func NewValidatorSet(valz []*Validator) *ValidatorSet {
return vals
}
// Nil or empty validator sets are invalid.
// IsNilOrEmpty returns true if validator set is nil or empty.
func (vals *ValidatorSet) IsNilOrEmpty() bool {
return vals == nil || len(vals.Validators) == 0
}
// Increment ProposerPriority and update the proposer on a copy, and return it.
// CopyIncrementProposerPriority increments ProposerPriority and update the
// proposer on a copy, and return it.
func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet {
copy := vals.Copy()
copy.IncrementProposerPriority(times)
@@ -106,6 +106,7 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) {
vals.Proposer = proposer
}
// RescalePriorities ...
func (vals *ValidatorSet) RescalePriorities(diffMax int64) {
if vals.IsNilOrEmpty() {
panic("empty validator set")
@@ -177,9 +178,8 @@ func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 {
diff := max - min
if diff < 0 {
return -1 * diff
} else {
return diff
}
return diff
}
func (vals *ValidatorSet) getValWithMostPriority() *Validator {
@@ -594,22 +594,21 @@ func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error {
return vals.updateWithChangeSet(changes, true)
}
// VerifyCommit verifies that +2/3 of the validator set signed this commit.
func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
if vals.Size() != len(commit.Precommits) {
return NewErrInvalidCommitPrecommits(vals.Size(), len(commit.Precommits))
// VerifyCommit verifies +2/3 of the set had signed the given commit.
func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID,
height int64, commit *Commit) error {
if vals.Size() != len(commit.Signatures) {
return NewErrInvalidCommitSignatures(vals.Size(), len(commit.Signatures))
}
if err := vals.verifyCommitBasic(commit, height, blockID); err != nil {
return err
}
talliedVotingPower := int64(0)
for idx, precommit := range commit.Precommits {
// skip absent and nil votes
// NOTE: do we want to check the validity of votes
// for nil?
if precommit == nil {
continue // OK, some precommits can be missing.
for idx, commitSig := range commit.Signatures {
if commitSig.Absent() {
continue // OK, some signatures can be absent.
}
// The vals and commit have a 1-to-1 correspondance.
@@ -617,17 +616,17 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i
val := vals.Validators[idx]
// Validate signature.
precommitSignBytes := commit.VoteSignBytes(chainID, idx)
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return fmt.Errorf("invalid commit -- invalid signature: %v", precommit)
voteSignBytes := commit.VoteSignBytes(chainID, idx)
if !val.PubKey.VerifyBytes(voteSignBytes, commitSig.Signature) {
return fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature)
}
// Good precommit!
if blockID.Equals(precommit.BlockID) {
// Good!
if blockID.Equals(commitSig.BlockID(commit.BlockID)) {
talliedVotingPower += val.VotingPower
}
// else {
// It's OK that the BlockID doesn't match. We include stray
// precommits to measure validator availability.
// signatures (~votes for nil) to measure validator availability.
// }
}
@@ -680,40 +679,31 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin
// Check old voting power.
oldVotingPower := int64(0)
seen := map[int]bool{}
round := commit.Round()
for idx, precommit := range commit.Precommits {
if precommit == nil {
continue
}
if precommit.Height != height {
return errors.Errorf("blocks don't match - %d vs %d", round, precommit.Round)
}
if precommit.Round != round {
return errors.Errorf("invalid commit -- wrong round: %v vs %v", round, precommit.Round)
}
if precommit.Type != PrecommitType {
return errors.Errorf("invalid commit -- not precommit @ index %v", idx)
for idx, commitSig := range commit.Signatures {
if commitSig.Absent() {
continue // OK, some signatures can be absent.
}
// See if this validator is in oldVals.
oldIdx, val := oldVals.GetByAddress(precommit.ValidatorAddress)
oldIdx, val := oldVals.GetByAddress(commitSig.ValidatorAddress)
if val == nil || seen[oldIdx] {
continue // missing or double vote...
}
seen[oldIdx] = true
// Validate signature.
precommitSignBytes := commit.VoteSignBytes(chainID, idx)
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return errors.Errorf("invalid commit -- invalid signature: %v", precommit)
voteSignBytes := commit.VoteSignBytes(chainID, idx)
if !val.PubKey.VerifyBytes(voteSignBytes, commitSig.Signature) {
return errors.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature)
}
// Good precommit!
if blockID.Equals(precommit.BlockID) {
// Good!
if blockID.Equals(commitSig.BlockID(commit.BlockID)) {
oldVotingPower += val.VotingPower
}
// else {
// It's OK that the BlockID doesn't match. We include stray
// precommits to measure validator availability.
// signatures (~votes for nil) to measure validator availability.
// }
}
@@ -740,31 +730,28 @@ func (vals *ValidatorSet) VerifyCommitTrusting(chainID string, blockID BlockID,
}
talliedVotingPower := int64(0)
for idx, precommit := range commit.Precommits {
// skip absent and nil votes
// NOTE: do we want to check the validity of votes
// for nil?
if precommit == nil {
continue
for idx, commitSig := range commit.Signatures {
if commitSig.Absent() {
continue // OK, some signatures can be absent.
}
// We don't know the validators that committed this block, so we have to
// check for each vote if its validator is already known.
_, val := vals.GetByAddress(precommit.ValidatorAddress)
_, val := vals.GetByAddress(commitSig.ValidatorAddress)
if val != nil {
// Validate signature.
precommitSignBytes := commit.VoteSignBytes(chainID, idx)
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return fmt.Errorf("invalid commit -- invalid signature: %v", precommit)
voteSignBytes := commit.VoteSignBytes(chainID, idx)
if !val.PubKey.VerifyBytes(voteSignBytes, commitSig.Signature) {
return errors.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature)
}
// Good precommit!
if blockID.Equals(precommit.BlockID) {
// Good!
if blockID.Equals(commitSig.BlockID(commit.BlockID)) {
talliedVotingPower += val.VotingPower
}
// else {
// It's OK that the BlockID doesn't match. We include stray
// precommits to measure validator availability.
// signatures (~votes for nil) to measure validator availability.
// }
}
}
@@ -782,8 +769,8 @@ func (vals *ValidatorSet) verifyCommitBasic(commit *Commit, height int64, blockI
if err := commit.ValidateBasic(); err != nil {
return err
}
if height != commit.Height() {
return NewErrInvalidCommitHeight(height, commit.Height())
if height != commit.Height {
return NewErrInvalidCommitHeight(height, commit.Height)
}
if !blockID.Equals(commit.BlockID) {
return fmt.Errorf("invalid commit -- wrong block ID: want %v, got %v",
@@ -797,7 +784,6 @@ func (vals *ValidatorSet) verifyCommitBasic(commit *Commit, height int64, blockI
// IsErrTooMuchChange returns true if err is related to changes in validator
// set exceeding max limit.
// TODO: remove
func IsErrTooMuchChange(err error) bool {
_, ok := errors.Cause(err).(ErrTooMuchChange)
return ok
@@ -819,7 +805,7 @@ func (vals *ValidatorSet) String() string {
return vals.StringIndented("")
}
// String
// StringIndented returns an intended string representation of ValidatorSet.
func (vals *ValidatorSet) StringIndented(indent string) string {
if vals == nil {
return "nil-ValidatorSet"
@@ -844,7 +830,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string {
//-------------------------------------
// Implements sort for sorting validators by address.
// Sort validators by address.
// ValidatorsByAddress is used to sort validators by address.
type ValidatorsByAddress []*Validator
func (valz ValidatorsByAddress) Len() int {
+14 -8
View File
@@ -602,9 +602,12 @@ func TestValidatorSetVerifyCommit(t *testing.T) {
v1 := NewValidator(pubKey, 1000)
vset := NewValidatorSet([]*Validator{v1})
chainID := "mychainID"
blockID := BlockID{Hash: []byte("hello")}
height := int64(5)
// good
var (
chainID = "mychainID"
blockID = makeBlockIDRandom()
height = int64(5)
)
vote := &Vote{
ValidatorAddress: v1.Address,
ValidatorIndex: 0,
@@ -617,12 +620,15 @@ func TestValidatorSetVerifyCommit(t *testing.T) {
sig, err := privKey.Sign(vote.SignBytes(chainID))
assert.NoError(t, err)
vote.Signature = sig
commit := NewCommit(blockID, []*CommitSig{vote.CommitSig()})
commit := NewCommit(vote.Height, vote.Round, blockID, []CommitSig{vote.CommitSig()})
badChainID := "notmychainID"
badBlockID := BlockID{Hash: []byte("goodbye")}
badHeight := height + 1
badCommit := NewCommit(blockID, []*CommitSig{nil})
// bad
var (
badChainID = "notmychainID"
badBlockID = BlockID{Hash: []byte("goodbye")}
badHeight = height + 1
badCommit = NewCommit(badHeight, 0, blockID, []CommitSig{{BlockIDFlag: BlockIDFlagAbsent}})
)
// test some error cases
// TODO: test more cases!
+20 -5
View File
@@ -57,13 +57,27 @@ type Vote struct {
}
// CommitSig converts the Vote to a CommitSig.
// If the Vote is nil, the CommitSig will be nil.
func (vote *Vote) CommitSig() *CommitSig {
func (vote *Vote) CommitSig() CommitSig {
if vote == nil {
return 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,
}
cs := CommitSig(*vote)
return &cs
}
func (vote *Vote) SignBytes(chainID string) []byte {
@@ -83,6 +97,7 @@ func (vote *Vote) String() string {
if vote == nil {
return nilVoteStr
}
var typeString string
switch vote.Type {
case PrevoteType:
+14 -6
View File
@@ -98,20 +98,23 @@ func (voteSet *VoteSet) ChainID() string {
return voteSet.chainID
}
func (voteSet *VoteSet) Height() int64 {
// Implements VoteSetReader.
func (voteSet *VoteSet) GetHeight() int64 {
if voteSet == nil {
return 0
}
return voteSet.height
}
func (voteSet *VoteSet) Round() int {
// Implements VoteSetReader.
func (voteSet *VoteSet) GetRound() int {
if voteSet == nil {
return -1
}
return voteSet.round
}
// Implements VoteSetReader.
func (voteSet *VoteSet) Type() byte {
if voteSet == nil {
return 0x00
@@ -119,6 +122,7 @@ func (voteSet *VoteSet) Type() byte {
return byte(voteSet.type_)
}
// Implements VoteSetReader.
func (voteSet *VoteSet) Size() int {
if voteSet == nil {
return 0
@@ -335,6 +339,7 @@ func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error {
return nil
}
// Implements VoteSetReader.
func (voteSet *VoteSet) BitArray() *cmn.BitArray {
if voteSet == nil {
return nil
@@ -358,6 +363,7 @@ func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *cmn.BitArray {
}
// NOTE: if validator has conflicting votes, returns "canonical" vote
// Implements VoteSetReader.
func (voteSet *VoteSet) GetByIndex(valIndex int) *Vote {
if voteSet == nil {
return nil
@@ -389,6 +395,7 @@ func (voteSet *VoteSet) HasTwoThirdsMajority() bool {
return voteSet.maj23 != nil
}
// Implements VoteSetReader.
func (voteSet *VoteSet) IsCommit() bool {
if voteSet == nil {
return false
@@ -556,11 +563,12 @@ func (voteSet *VoteSet) MakeCommit() *Commit {
}
// For every validator, get the precommit
commitSigs := make([]*CommitSig, len(voteSet.votes))
commitSigs := make([]CommitSig, len(voteSet.votes))
for i, v := range voteSet.votes {
commitSigs[i] = v.CommitSig()
}
return NewCommit(*voteSet.maj23, commitSigs)
return NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs)
}
//--------------------------------------------------------------------------------
@@ -607,8 +615,8 @@ func (vs *blockVotes) getByIndex(index int) *Vote {
// Common interface between *consensus.VoteSet and types.Commit
type VoteSetReader interface {
Height() int64
Round() int
GetHeight() int64
GetRound() int
Type() byte
Size() int
BitArray() *cmn.BitArray
+15 -4
View File
@@ -522,16 +522,27 @@ func TestMakeCommit(t *testing.T) {
}
}
// The 9th voted for nil.
{
addr := privValidators[8].GetPubKey().Address()
vote := withValidator(voteProto, addr, 8)
vote.BlockID = BlockID{}
_, err := signAddVote(privValidators[8], vote, voteSet)
if err != nil {
t.Error(err)
}
}
commit := voteSet.MakeCommit()
// Commit should have 10 elements
if len(commit.Precommits) != 10 {
t.Errorf("commit Precommits should have the same number of precommits as validators")
if len(commit.Signatures) != 10 {
t.Errorf("expected commit to include %d elems, got %d", 10, len(commit.Signatures))
}
// Ensure that Commit precommits are ordered.
// Ensure that Commit is good.
if err := commit.ValidateBasic(); err != nil {
t.Errorf("error in Commit.ValidateBasic(): %v", err)
}
}
-14
View File
@@ -8,7 +8,6 @@ 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"
@@ -45,19 +44,6 @@ 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")