From b7b923cc6bb6542186db4477ad1f197edd78b209 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 31 Oct 2014 18:35:38 -0700 Subject: [PATCH] Validation tests --- blocks/part_set.go | 17 ++- blocks/part_set_test.go | 4 +- blocks/vote.go | 5 + common/test/assert.go | 14 +++ consensus/pol_test.go | 79 ++++++++------ consensus/state_test.go | 33 +++--- consensus/test.go | 5 +- consensus/vote_set.go | 6 +- consensus/vote_set_test.go | 209 ++++++++++++++++++++++++++++++++----- state/account.go | 4 +- 10 files changed, 279 insertions(+), 97 deletions(-) create mode 100644 common/test/assert.go diff --git a/blocks/part_set.go b/blocks/part_set.go index fc5adb521..57491388b 100644 --- a/blocks/part_set.go +++ b/blocks/part_set.go @@ -84,20 +84,20 @@ func (part *Part) StringWithIndent(indent string) string { //------------------------------------- type PartSetHeader struct { - Hash []byte Total uint16 + Hash []byte } func ReadPartSetHeader(r io.Reader, n *int64, err *error) PartSetHeader { return PartSetHeader{ - Hash: ReadByteSlice(r, n, err), Total: ReadUInt16(r, n, err), + Hash: ReadByteSlice(r, n, err), } } func (psh PartSetHeader) WriteTo(w io.Writer) (n int64, err error) { - WriteByteSlice(w, psh.Hash, &n, &err) WriteUInt16(w, psh.Total, &n, &err) + WriteByteSlice(w, psh.Hash, &n, &err) return } @@ -110,15 +110,14 @@ func (psh PartSetHeader) String() string { } func (psh PartSetHeader) Equals(other PartSetHeader) bool { - return bytes.Equal(psh.Hash, other.Hash) && - psh.Total == other.Total + return psh.Total == other.Total && bytes.Equal(psh.Hash, other.Hash) } //------------------------------------- type PartSet struct { - hash []byte total uint16 + hash []byte mtx sync.Mutex parts []*Part @@ -148,8 +147,8 @@ func NewPartSetFromData(data []byte) *PartSet { parts[i].Trail = merkle.HashTrailForIndex(hashTree, i) } return &PartSet{ - hash: hashTree[len(hashTree)/2], total: uint16(total), + hash: hashTree[len(hashTree)/2], parts: parts, partsBitArray: partsBitArray, count: uint16(total), @@ -159,8 +158,8 @@ func NewPartSetFromData(data []byte) *PartSet { // Returns an empty PartSet ready to be populated. func NewPartSetFromHeader(header PartSetHeader) *PartSet { return &PartSet{ - hash: header.Hash, total: header.Total, + hash: header.Hash, parts: make([]*Part, header.Total), partsBitArray: NewBitArray(uint(header.Total)), count: 0, @@ -172,8 +171,8 @@ func (ps *PartSet) Header() PartSetHeader { return PartSetHeader{} } else { return PartSetHeader{ - Hash: ps.hash, Total: ps.total, + Hash: ps.hash, } } } diff --git a/blocks/part_set_test.go b/blocks/part_set_test.go index 623c0f57a..60e929f73 100644 --- a/blocks/part_set_test.go +++ b/blocks/part_set_test.go @@ -25,7 +25,7 @@ func TestBasicPartSet(t *testing.T) { } // Test adding parts to a new partSet. - partSet2 := NewPartSetFromHeader(PartSetHeader{partSet.Hash(), partSet.Total()}) + partSet2 := NewPartSetFromHeader(partSet.Header()) for i := uint16(0); i < partSet.Total(); i++ { part := partSet.GetPart(i) @@ -65,7 +65,7 @@ func TestWrongTrail(t *testing.T) { partSet := NewPartSetFromData(data) // Test adding a part with wrong data. - partSet2 := NewPartSetFromHeader(PartSetHeader{partSet.Hash(), partSet.Total()}) + partSet2 := NewPartSetFromHeader(partSet.Header()) // Test adding a part with wrong trail. part := partSet.GetPart(0) diff --git a/blocks/vote.go b/blocks/vote.go index 48639dec7..07adece5f 100644 --- a/blocks/vote.go +++ b/blocks/vote.go @@ -62,6 +62,11 @@ func (v *Vote) SetSignature(sig Signature) { v.Signature = sig } +func (v *Vote) Copy() *Vote { + vCopy := *v + return &vCopy +} + func (v *Vote) String() string { var typeString string switch v.Type { diff --git a/common/test/assert.go b/common/test/assert.go new file mode 100644 index 000000000..a6ffed0ce --- /dev/null +++ b/common/test/assert.go @@ -0,0 +1,14 @@ +package test + +import ( + "testing" +) + +func AssertPanics(t *testing.T, msg string, f func()) { + defer func() { + if err := recover(); err == nil { + t.Errorf("Should have panic'd, but didn't: %v", msg) + } + }() + f() +} diff --git a/consensus/pol_test.go b/consensus/pol_test.go index 56841d576..7e8c23444 100644 --- a/consensus/pol_test.go +++ b/consensus/pol_test.go @@ -11,17 +11,19 @@ import ( // NOTE: see consensus/test.go for common test methods. func TestVerifyVotes(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 0, 10, 1) + height, round := uint32(1), uint16(0) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with -2/3 votes. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 0, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 0, Type: VoteTypePrevote, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round, Type: VoteTypePrevote, BlockHash: blockHash, } for i := 0; i < 6; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) pol.Votes = append(pol.Votes, vote.Signature) } @@ -32,6 +34,7 @@ func TestVerifyVotes(t *testing.T) { } // Make a POL with +2/3 votes. + vote := voteProto.Copy() privAccounts[7].Sign(vote) pol.Votes = append(pol.Votes, vote.Signature) @@ -42,17 +45,19 @@ func TestVerifyVotes(t *testing.T) { } func TestVerifyInvalidVote(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 0, 10, 1) + height, round := uint32(1), uint16(0) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with +2/3 votes with the wrong signature. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 0, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 0, Type: VoteTypePrevote, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round, Type: VoteTypePrevote, BlockHash: blockHash, } for i := 0; i < 7; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) // Mutate the signature. vote.Signature.Bytes[0] += byte(0x01) @@ -66,19 +71,21 @@ func TestVerifyInvalidVote(t *testing.T) { } func TestVerifyCommits(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 2, 10, 1) + height, round := uint32(1), uint16(2) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with +2/3 votes. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 2, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 1, Type: VoteTypeCommit, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round - 1, Type: VoteTypeCommit, BlockHash: blockHash, } for i := 0; i < 7; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) - pol.Commits = append(pol.Commits, RoundSignature{1, vote.Signature}) + pol.Commits = append(pol.Commits, RoundSignature{round - 1, vote.Signature}) } // Check that validation succeeds. @@ -88,21 +95,23 @@ func TestVerifyCommits(t *testing.T) { } func TestVerifyInvalidCommits(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 2, 10, 1) + height, round := uint32(1), uint16(2) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with +2/3 votes with the wrong signature. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 2, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 1, Type: VoteTypeCommit, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round - 1, Type: VoteTypeCommit, BlockHash: blockHash, } for i := 0; i < 7; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) // Mutate the signature. vote.Signature.Bytes[0] += byte(0x01) - pol.Commits = append(pol.Commits, RoundSignature{1, vote.Signature}) + pol.Commits = append(pol.Commits, RoundSignature{round - 1, vote.Signature}) } // Check that validation fails. @@ -112,19 +121,21 @@ func TestVerifyInvalidCommits(t *testing.T) { } func TestVerifyInvalidCommitRounds(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 2, 10, 1) + height, round := uint32(1), uint16(2) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with +2/3 commits for the current round. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 2, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 2, Type: VoteTypeCommit, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round, Type: VoteTypeCommit, BlockHash: blockHash, } for i := 0; i < 7; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) - pol.Commits = append(pol.Commits, RoundSignature{2, vote.Signature}) + pol.Commits = append(pol.Commits, RoundSignature{round, vote.Signature}) } // Check that validation fails. @@ -134,19 +145,21 @@ func TestVerifyInvalidCommitRounds(t *testing.T) { } func TestVerifyInvalidCommitRounds2(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 2, 10, 1) + height, round := uint32(1), uint16(2) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with +2/3 commits for future round. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 2, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 3, Type: VoteTypeCommit, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round + 1, Type: VoteTypeCommit, BlockHash: blockHash, } for i := 0; i < 7; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) - pol.Commits = append(pol.Commits, RoundSignature{3, vote.Signature}) + pol.Commits = append(pol.Commits, RoundSignature{round + 1, vote.Signature}) } // Check that validation fails. @@ -156,17 +169,19 @@ func TestVerifyInvalidCommitRounds2(t *testing.T) { } func TestReadWrite(t *testing.T) { - _, valSet, privAccounts := makeVoteSet(0, 0, 10, 1) + height, round := uint32(1), uint16(2) + _, valSet, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // Make a POL with +2/3 votes. blockHash := RandBytes(32) pol := &POL{ - Height: 0, Round: 0, BlockHash: blockHash, + Height: height, Round: round, BlockHash: blockHash, } - vote := &Vote{ - Height: 0, Round: 0, Type: VoteTypePrevote, BlockHash: blockHash, + voteProto := &Vote{ + Height: height, Round: round, Type: VoteTypePrevote, BlockHash: blockHash, } for i := 0; i < 7; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) pol.Votes = append(pol.Votes, vote.Signature) } diff --git a/consensus/state_test.go b/consensus/state_test.go index 645d2b567..2713407ba 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -6,6 +6,7 @@ import ( . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" + . "github.com/tendermint/tendermint/common/test" db_ "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/state" @@ -50,15 +51,6 @@ func makeConsensusState() (*ConsensusState, []*state.PrivAccount) { return cs, privAccounts } -func assertPanics(t *testing.T, msg string, f func()) { - defer func() { - if err := recover(); err == nil { - t.Errorf("Should have panic'd, but didn't: %v", msg) - } - }() - f() -} - //----------------------------------------------------------------------------- func TestSetupRound(t *testing.T) { @@ -131,17 +123,16 @@ func TestRunActionPropose(t *testing.T) { } } -func checkRoundState(t *testing.T, cs *ConsensusState, +func checkRoundState(t *testing.T, rs *RoundState, height uint32, round uint16, step RoundStep) { - rs := cs.GetRoundState() if rs.Height != height { - t.Errorf("cs.RoundState.Height should be %v, got %v", height, rs.Height) + t.Errorf("rs.Height should be %v, got %v", height, rs.Height) } if rs.Round != round { - t.Errorf("cs.RoundState.Round should be %v, got %v", round, rs.Round) + t.Errorf("rs.Round should be %v, got %v", round, rs.Round) } if rs.Step != step { - t.Errorf("cs.RoundState.Step should be %v, got %v", step, rs.Step) + t.Errorf("rs.Step should be %v, got %v", step, rs.Step) } } @@ -160,8 +151,8 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) { <-cs.NewStepCh() // TODO: test this value too. // Test RunActionPrecommit failures: - assertPanics(t, "Wrong height ", func() { cs.RunActionPrecommit(2, 0) }) - assertPanics(t, "Wrong round", func() { cs.RunActionPrecommit(1, 1) }) + AssertPanics(t, "Wrong height ", func() { cs.RunActionPrecommit(2, 0) }) + AssertPanics(t, "Wrong round", func() { cs.RunActionPrecommit(1, 1) }) cs.RunActionPrecommit(1, 0) <-cs.NewStepCh() // TODO: test this value too. if cs.Precommits.GetById(0) != nil { @@ -187,11 +178,11 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) { if cs.Precommits.GetById(0) == nil { t.Errorf("RunActionPrecommit should have succeeded") } - checkRoundState(t, cs, 1, 0, RoundStepPrecommit) + checkRoundState(t, cs.GetRoundState(), 1, 0, RoundStepPrecommit) // Test RunActionCommit failures: - assertPanics(t, "Wrong height ", func() { cs.RunActionCommit(2) }) - assertPanics(t, "Wrong round", func() { cs.RunActionCommit(1) }) + AssertPanics(t, "Wrong height ", func() { cs.RunActionCommit(2) }) + AssertPanics(t, "Wrong round", func() { cs.RunActionCommit(1) }) // Add at least +2/3 precommits. for i := 0; i < 7; i++ { @@ -212,7 +203,7 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) { if cs.Commits.GetById(0) == nil { t.Errorf("RunActionCommit should have succeeded") } - checkRoundState(t, cs, 1, 0, RoundStepCommit) + checkRoundState(t, cs.GetRoundState(), 1, 0, RoundStepCommit) // cs.CommitTime should still be zero if !cs.CommitTime.IsZero() { @@ -235,5 +226,5 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) { // Test TryFinalizeCommit: cs.TryFinalizeCommit(1) <-cs.NewStepCh() // TODO: test this value too. - checkRoundState(t, cs, 2, 0, RoundStepNewHeight) + checkRoundState(t, cs.GetRoundState(), 2, 0, RoundStepNewHeight) } diff --git a/consensus/test.go b/consensus/test.go index 647422045..8112c8023 100644 --- a/consensus/test.go +++ b/consensus/test.go @@ -1,7 +1,6 @@ package consensus import ( - . "github.com/tendermint/tendermint/blocks" "github.com/tendermint/tendermint/state" ) @@ -16,7 +15,7 @@ func makeValidator(id uint64, votingPower uint64) (*state.Validator, *state.Priv }, privAccount } -func makeVoteSet(height uint32, round uint16, numValidators int, votingPower uint64) (*VoteSet, *state.ValidatorSet, []*state.PrivAccount) { +func makeVoteSet(height uint32, round uint16, type_ byte, numValidators int, votingPower uint64) (*VoteSet, *state.ValidatorSet, []*state.PrivAccount) { vals := make([]*state.Validator, numValidators) privAccounts := make([]*state.PrivAccount, numValidators) for i := 0; i < numValidators; i++ { @@ -25,5 +24,5 @@ func makeVoteSet(height uint32, round uint16, numValidators int, votingPower uin privAccounts[i] = privAccount } valSet := state.NewValidatorSet(vals) - return NewVoteSet(height, round, VoteTypePrevote, valSet), valSet, privAccounts + return NewVoteSet(height, round, type_, valSet), valSet, privAccounts } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index c5a5ae9cf..7b8e2fd24 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -36,6 +36,9 @@ type VoteSet struct { // Constructs a new VoteSet struct used to accumulate votes for each round. func NewVoteSet(height uint32, round uint16, type_ byte, vset *state.ValidatorSet) *VoteSet { + if height == 0 { + panic("Cannot make VoteSet for height == 0, doesn't make sense.") + } if type_ == VoteTypeCommit && round != 0 { panic("Expected round 0 for commit vote set") } @@ -214,7 +217,7 @@ func (vs *VoteSet) MakePOL() *POL { func (vs *VoteSet) MakeValidation() Validation { if vs.type_ != VoteTypeCommit { - panic("Cannot MakeValidation() unless VoteSet.Type is VoteTypePrevote") + panic("Cannot MakeValidation() unless VoteSet.Type is VoteTypeCommit") } vs.mtx.Lock() defer vs.mtx.Unlock() @@ -224,6 +227,7 @@ func (vs *VoteSet) MakeValidation() Validation { rsigs := make([]RoundSignature, vs.vset.Size()) vs.vset.Iterate(func(index uint, val *state.Validator) bool { vote := vs.votes[val.Id] + if vote == nil { return false } diff --git a/consensus/vote_set_test.go b/consensus/vote_set_test.go index 274d4e711..428a06f2f 100644 --- a/consensus/vote_set_test.go +++ b/consensus/vote_set_test.go @@ -1,8 +1,11 @@ package consensus import ( + "bytes" + . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" + . "github.com/tendermint/tendermint/common/test" "testing" ) @@ -10,7 +13,8 @@ import ( // NOTE: see consensus/test.go for common test methods. func TestAddVote(t *testing.T) { - voteSet, _, privAccounts := makeVoteSet(0, 0, 10, 1) + height, round := uint32(1), uint16(0) + voteSet, _, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // t.Logf(">> %v", voteSet) @@ -25,7 +29,7 @@ func TestAddVote(t *testing.T) { t.Errorf("There should be no 2/3 majority") } - vote := &Vote{Height: 0, Round: 0, Type: VoteTypePrevote, BlockHash: nil} + vote := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: nil} privAccounts[0].Sign(vote) voteSet.Add(vote) @@ -42,11 +46,13 @@ func TestAddVote(t *testing.T) { } func Test2_3Majority(t *testing.T) { - voteSet, _, privAccounts := makeVoteSet(0, 0, 10, 1) + height, round := uint32(1), uint16(0) + voteSet, _, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // 6 out of 10 voted for nil. - vote := &Vote{Height: 0, Round: 0, Type: VoteTypePrevote, BlockHash: nil} + voteProto := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: nil} for i := 0; i < 6; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) voteSet.Add(vote) } @@ -56,30 +62,112 @@ func Test2_3Majority(t *testing.T) { } // 7th validator voted for some blockhash - vote.BlockHash = CRandBytes(32) - privAccounts[6].Sign(vote) - voteSet.Add(vote) - hash, header, ok = voteSet.TwoThirdsMajority() + { + vote := voteProto.Copy() + vote.BlockHash = CRandBytes(32) + privAccounts[6].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if hash != nil || !header.IsZero() || ok { + t.Errorf("There should be no 2/3 majority") + } + } + + // 8th validator voted for nil. + { + vote := voteProto.Copy() + vote.BlockHash = nil + privAccounts[7].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if hash != nil || !header.IsZero() || !ok { + t.Errorf("There should be 2/3 majority for nil") + } + } +} + +func Test2_3MajorityRedux(t *testing.T) { + height, round := uint32(1), uint16(0) + voteSet, _, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 100, 1) + + blockHash := CRandBytes(32) + blockPartsTotal := uint16(123) + blockParts := PartSetHeader{blockPartsTotal, CRandBytes(32)} + + // 66 out of 100 voted for nil. + voteProto := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: blockHash, BlockParts: blockParts} + for i := 0; i < 66; i++ { + vote := voteProto.Copy() + privAccounts[i].Sign(vote) + voteSet.Add(vote) + } + hash, header, ok := voteSet.TwoThirdsMajority() if hash != nil || !header.IsZero() || ok { t.Errorf("There should be no 2/3 majority") } - // 8th validator voted for nil. - vote.BlockHash = nil - privAccounts[7].Sign(vote) - voteSet.Add(vote) - hash, header, ok = voteSet.TwoThirdsMajority() - if hash != nil || !header.IsZero() || !ok { - t.Errorf("There should be 2/3 majority for nil") + // 67th validator voted for nil + { + vote := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: nil, BlockParts: PartSetHeader{}} + privAccounts[66].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if hash != nil || !header.IsZero() || ok { + t.Errorf("There should be no 2/3 majority: last vote added was nil") + } + } + + // 68th validator voted for a different BlockParts PartSetHeader + { + vote := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: blockHash, BlockParts: PartSetHeader{blockPartsTotal, CRandBytes(32)}} + privAccounts[67].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if hash != nil || !header.IsZero() || ok { + t.Errorf("There should be no 2/3 majority: last vote added had different PartSetHeader Hash") + } } + // 69th validator voted for different BlockParts Total + { + vote := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: blockHash, BlockParts: PartSetHeader{blockPartsTotal + 1, blockParts.Hash}} + privAccounts[68].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if hash != nil || !header.IsZero() || ok { + t.Errorf("There should be no 2/3 majority: last vote added had different PartSetHeader Total") + } + } + + // 70th validator voted for different BlockHash + { + vote := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: CRandBytes(32), BlockParts: blockParts} + privAccounts[69].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if hash != nil || !header.IsZero() || ok { + t.Errorf("There should be no 2/3 majority: last vote added had different BlockHash") + } + } + + // 71st validator voted for the right BlockHash & BlockParts + { + vote := voteProto.Copy() + privAccounts[70].Sign(vote) + voteSet.Add(vote) + hash, header, ok = voteSet.TwoThirdsMajority() + if !bytes.Equal(hash, blockHash) || !header.Equals(blockParts) || !ok { + t.Errorf("There should be 2/3 majority") + } + } } func TestBadVotes(t *testing.T) { - voteSet, _, privAccounts := makeVoteSet(1, 0, 10, 1) + height, round := uint32(1), uint16(0) + voteSet, _, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // val0 votes for nil. - vote := &Vote{Height: 1, Round: 0, Type: VoteTypePrevote, BlockHash: nil} + vote := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: nil} privAccounts[0].Sign(vote) added, err := voteSet.Add(vote) if !added || err != nil { @@ -87,7 +175,7 @@ func TestBadVotes(t *testing.T) { } // val0 votes again for some block. - vote = &Vote{Height: 1, Round: 0, Type: VoteTypePrevote, BlockHash: CRandBytes(32)} + vote = &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: CRandBytes(32)} privAccounts[0].Sign(vote) added, err = voteSet.Add(vote) if added || err == nil { @@ -95,7 +183,7 @@ func TestBadVotes(t *testing.T) { } // val1 votes on another height - vote = &Vote{Height: 0, Round: 0, Type: VoteTypePrevote, BlockHash: nil} + vote = &Vote{Height: height + 1, Round: round, Type: VoteTypePrevote, BlockHash: nil} privAccounts[1].Sign(vote) added, err = voteSet.Add(vote) if added { @@ -103,7 +191,7 @@ func TestBadVotes(t *testing.T) { } // val2 votes on another round - vote = &Vote{Height: 1, Round: 1, Type: VoteTypePrevote, BlockHash: nil} + vote = &Vote{Height: height, Round: round + 1, Type: VoteTypePrevote, BlockHash: nil} privAccounts[2].Sign(vote) added, err = voteSet.Add(vote) if added { @@ -111,7 +199,7 @@ func TestBadVotes(t *testing.T) { } // val3 votes of another type. - vote = &Vote{Height: 1, Round: 0, Type: VoteTypePrecommit, BlockHash: nil} + vote = &Vote{Height: height, Round: round, Type: VoteTypePrecommit, BlockHash: nil} privAccounts[3].Sign(vote) added, err = voteSet.Add(vote) if added { @@ -120,11 +208,13 @@ func TestBadVotes(t *testing.T) { } func TestAddCommitsToPrevoteVotes(t *testing.T) { - voteSet, _, privAccounts := makeVoteSet(1, 5, 10, 1) + height, round := uint32(2), uint16(5) + voteSet, _, privAccounts := makeVoteSet(height, round, VoteTypePrevote, 10, 1) // val0, val1, val2, val3, val4, val5 vote for nil. - vote := &Vote{Height: 1, Round: 5, Type: VoteTypePrevote, BlockHash: nil} + voteProto := &Vote{Height: height, Round: round, Type: VoteTypePrevote, BlockHash: nil} for i := 0; i < 6; i++ { + vote := voteProto.Copy() privAccounts[i].Sign(vote) voteSet.Add(vote) } @@ -134,7 +224,7 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { } // Attempt to add a commit from val6 at a previous height - vote = &Vote{Height: 0, Round: 5, Type: VoteTypeCommit, BlockHash: nil} + vote := &Vote{Height: height - 1, Round: round, Type: VoteTypeCommit, BlockHash: nil} privAccounts[6].Sign(vote) added, _ := voteSet.Add(vote) if added { @@ -142,7 +232,7 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { } // Attempt to add a commit from val6 at a later round - vote = &Vote{Height: 1, Round: 6, Type: VoteTypeCommit, BlockHash: nil} + vote = &Vote{Height: height, Round: round + 1, Type: VoteTypeCommit, BlockHash: nil} privAccounts[6].Sign(vote) added, _ = voteSet.Add(vote) if added { @@ -150,7 +240,7 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { } // Attempt to add a commit from val6 for currrent height/round. - vote = &Vote{Height: 1, Round: 5, Type: VoteTypeCommit, BlockHash: nil} + vote = &Vote{Height: height, Round: round, Type: VoteTypeCommit, BlockHash: nil} privAccounts[6].Sign(vote) added, err := voteSet.Add(vote) if added || err == nil { @@ -158,7 +248,7 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { } // Add commit from val6 at a previous round - vote = &Vote{Height: 1, Round: 4, Type: VoteTypeCommit, BlockHash: nil} + vote = &Vote{Height: height, Round: round - 1, Type: VoteTypeCommit, BlockHash: nil} privAccounts[6].Sign(vote) added, err = voteSet.Add(vote) if !added || err != nil { @@ -166,7 +256,7 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { } // Also add commit from val7 for previous round. - vote = &Vote{Height: 1, Round: 3, Type: VoteTypeCommit, BlockHash: nil} + vote = &Vote{Height: height, Round: round - 2, Type: VoteTypeCommit, BlockHash: nil} privAccounts[7].Sign(vote) added, err = voteSet.Add(vote) if !added || err != nil { @@ -180,3 +270,66 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { } } + +func TestMakeValidation(t *testing.T) { + height, round := uint32(1), uint16(0) + voteSet, _, privAccounts := makeVoteSet(height, round, VoteTypeCommit, 10, 1) + blockHash, blockParts := CRandBytes(32), PartSetHeader{123, CRandBytes(32)} + + // 6 out of 10 voted for some block. + voteProto := &Vote{Height: height, Round: round, Type: VoteTypeCommit, + BlockHash: blockHash, BlockParts: blockParts} + for i := 0; i < 6; i++ { + vote := voteProto.Copy() + privAccounts[i].Sign(vote) + voteSet.Add(vote) + } + + // MakeValidation should fail. + AssertPanics(t, "Doesn't have +2/3 majority", func() { voteSet.MakeValidation() }) + + // 7th voted for some other block. + { + vote := &Vote{Height: height, Round: round, Type: VoteTypeCommit, + BlockHash: CRandBytes(32), + BlockParts: PartSetHeader{123, CRandBytes(32)}} + privAccounts[6].Sign(vote) + voteSet.Add(vote) + } + + // The 8th voted like everyone else. + { + vote := voteProto.Copy() + privAccounts[7].Sign(vote) + voteSet.Add(vote) + } + + validation := voteSet.MakeValidation() + + // Validation should have 10 elements + if len(validation.Commits) != 10 { + t.Errorf("Validation Commits should have the same number of commits as validators") + } + + // Ensure that Validation commits are ordered. + for i, rsig := range validation.Commits { + if i < 6 || i == 7 { + if rsig.Round != round { + t.Errorf("Expected round %v but got %v", round, rsig.Round) + } + if rsig.SignerId != uint64(i) { + t.Errorf("Validation commit signer out of order. Expected %v, got %v", i, rsig.Signature) + } + vote := &Vote{Height: height, Round: rsig.Round, Type: VoteTypeCommit, + BlockHash: blockHash, BlockParts: blockParts, + Signature: rsig.Signature} + if !privAccounts[i].Verify(vote) { + t.Errorf("Validation commit did not verify") + } + } else { + if !rsig.IsZero() { + t.Errorf("Expected zero RoundSignature for the rest") + } + } + } +} diff --git a/state/account.go b/state/account.go index 9cd1a0082..ab200c874 100644 --- a/state/account.go +++ b/state/account.go @@ -168,7 +168,9 @@ func (pa *PrivAccount) SignBytes(msg []byte) Signature { } func (pa *PrivAccount) Sign(o Signable) { - o.SetSignature(Signature{}) // clear + if !o.GetSignature().IsZero() { + panic("Cannot sign: already signed") + } msg := BinaryBytes(o) sig := pa.SignBytes(msg) o.SetSignature(sig)