Skip to content

Commit

Permalink
fix(consensus): send decided vote for previous round on query vote (p…
Browse files Browse the repository at this point in the history
  • Loading branch information
b00f authored Nov 7, 2024
1 parent eb0adf3 commit cc18fa3
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 61 deletions.
7 changes: 4 additions & 3 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,13 +514,14 @@ func (cs *consensus) HandleQueryVote(height uint32, round int16) *vote.Vote {
votes := []*vote.Vote{}
switch {
case round < cs.round:
// Past round: Only broadcast cp:decided votes
vs := cs.log.CPDecidedVoteSet(round)
// A validator requests votes for past rounds.
// Sending cp:decide for the last round helps them advance to the current round.
vs := cs.log.CPDecidedVoteSet(cs.round - 1)
votes = append(votes, vs.AllVotes()...)

case round == cs.round:
// Current round
m := cs.log.RoundMessages(round)
m := cs.log.RoundMessages(cs.round)
votes = append(votes, m.AllVotes()...)

case round > cs.round:
Expand Down
132 changes: 80 additions & 52 deletions consensus/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,46 @@ func (td *testData) makeProposal(t *testing.T, height uint32, round int16) *prop
return prop
}

func (td *testData) makeMainVoteCertificate(t *testing.T,
height uint32, round, cpRound int16,
) *certificate.VoteCertificate {
t.Helper()

// === make valid certificate
preVoteCommitters := []int32{}
preVoteSigs := []*bls.Signature{}
for i, val := range td.consP.validators {
preVoteJust := &vote.JustInitYes{}
preVote := vote.NewCPPreVote(hash.UndefHash, height, round,
cpRound, vote.CPValueYes, preVoteJust, val.Address())
sbPreVote := preVote.SignBytes()

preVoteCommitters = append(preVoteCommitters, val.Number())
preVoteSigs = append(preVoteSigs, td.valKeys[i].Sign(sbPreVote))
}
preVoteAggSig := bls.SignatureAggregate(preVoteSigs...)
certPreVote := certificate.NewVoteCertificate(height, round)
certPreVote.SetSignature(preVoteCommitters, []int32{}, preVoteAggSig)

mainVoteCommitters := []int32{}
mainVoteSigs := []*bls.Signature{}
for i, val := range td.consP.validators {
mainVoteJust := &vote.JustMainVoteNoConflict{
QCert: certPreVote,
}
mainVote := vote.NewCPMainVote(hash.UndefHash, height, round, cpRound, vote.CPValueYes, mainVoteJust, val.Address())
sbMainVote := mainVote.SignBytes()

mainVoteCommitters = append(mainVoteCommitters, val.Number())
mainVoteSigs = append(mainVoteSigs, td.valKeys[i].Sign(sbMainVote))
}
mainVoteAggSig := bls.SignatureAggregate(mainVoteSigs...)
certMainVote := certificate.NewVoteCertificate(height, round)
certMainVote.SetSignature(mainVoteCommitters, []int32{}, mainVoteAggSig)

return certMainVote
}

func TestStart(t *testing.T) {
td := setup(t)

Expand Down Expand Up @@ -578,68 +618,56 @@ func TestHandleQueryVote(t *testing.T) {
td := setup(t)

td.enterNewHeight(td.consP)
assert.Nil(t, td.consP.HandleQueryVote(1, 0))
cpRound := int16(0)
height := uint32(1)
assert.Nil(t, td.consP.HandleQueryVote(height, 0))

// === make valid certificate
preVoteCommitters := []int32{}
preVoteSigs := []*bls.Signature{}
for index, val := range td.consP.validators {
preVoteJust := &vote.JustInitYes{}
preVote := vote.NewCPPreVote(hash.UndefHash, 1, 0, cpRound, vote.CPValueYes, preVoteJust, val.Address())
sbPreVote := preVote.SignBytes()

preVoteCommitters = append(preVoteCommitters, val.Number())
preVoteSigs = append(preVoteSigs, td.valKeys[index].Sign(sbPreVote))
}
preVoteAggSig := bls.SignatureAggregate(preVoteSigs...)
certPreVote := certificate.NewVoteCertificate(1, 0)
certPreVote.SetSignature(preVoteCommitters, []int32{}, preVoteAggSig)

mainVoteCommitters := []int32{}
mainVoteSigs := []*bls.Signature{}
for index, val := range td.consP.validators {
mainVoteJust := &vote.JustMainVoteNoConflict{
QCert: certPreVote,
}
mainVote := vote.NewCPMainVote(hash.UndefHash, 1, 0, cpRound, vote.CPValueYes, mainVoteJust, val.Address())
sbMainVote := mainVote.SignBytes()
// Add some votes for Round 0
td.addCPDecidedVote(td.consP, hash.UndefHash, height, 0, vote.CPValueYes,
&vote.JustDecided{QCert: td.makeMainVoteCertificate(t, height, 0, cpRound)}, tIndexY)

mainVoteCommitters = append(mainVoteCommitters, val.Number())
mainVoteSigs = append(mainVoteSigs, td.valKeys[index].Sign(sbMainVote))
}
mainVoteAggSig := bls.SignatureAggregate(mainVoteSigs...)
certMainVote := certificate.NewVoteCertificate(1, 0)
certMainVote.SetSignature(mainVoteCommitters, []int32{}, mainVoteAggSig)
// ====

// round 0
td.addPrepareVote(td.consP, td.RandHash(), 1, 0, tIndexX)
td.addPrepareVote(td.consP, td.RandHash(), 1, 0, tIndexY)
td.addCPPreVote(td.consP, hash.UndefHash, 1, 0, vote.CPValueYes,
// Add some votes for Round 1
td.enterNextRound(td.consP)
td.addPrepareVote(td.consP, td.RandHash(), height, 1, tIndexX)
td.addCPPreVote(td.consP, hash.UndefHash, height, 1, vote.CPValueYes,
&vote.JustInitYes{}, tIndexY)
td.addCPMainVote(td.consP, hash.UndefHash, 1, 0, vote.CPValueYes,
&vote.JustMainVoteNoConflict{QCert: certPreVote}, tIndexY)
td.addCPDecidedVote(td.consP, hash.UndefHash, 1, 0, vote.CPValueYes,
&vote.JustDecided{QCert: certMainVote}, tIndexY)

assert.NotNil(t, td.consP.HandleQueryVote(1, 0))
td.addCPDecidedVote(td.consP, hash.UndefHash, height, 1, vote.CPValueYes,
&vote.JustDecided{QCert: td.makeMainVoteCertificate(t, height, 1, cpRound)}, tIndexY)

// Round 1
// Add some votes for Round 2
td.enterNextRound(td.consP)
td.addPrepareVote(td.consP, td.RandHash(), 1, 1, tIndexY)
td.addPrepareVote(td.consP, td.RandHash(), height, 2, tIndexY)

rndVote0 := td.consP.HandleQueryVote(1, 0)
assert.Equal(t, vote.VoteTypeCPDecided, rndVote0.Type(), "should send the decided vote for the previous round")
t.Run("Query vote for round 0: should send the decided vote for the round 1", func(t *testing.T) {
rndVote := td.consP.HandleQueryVote(height, 0)
assert.Equal(t, vote.VoteTypeCPDecided, rndVote.Type())
assert.Equal(t, height, rndVote.Height())
assert.Equal(t, int16(1), rndVote.Round())
})

rndVote1 := td.consP.HandleQueryVote(1, 1)
assert.Equal(t, vote.VoteTypePrepare, rndVote1.Type(), "should send the prepare vote for the current round")
t.Run("Query vote for round 1: should send the decided vote for the round 1", func(t *testing.T) {
rndVote := td.consP.HandleQueryVote(height, 1)
assert.Equal(t, vote.VoteTypeCPDecided, rndVote.Type())
assert.Equal(t, height, rndVote.Height())
assert.Equal(t, int16(1), rndVote.Round())
})

rndVote2 := td.consP.HandleQueryVote(1, 2)
assert.Nil(t, rndVote2, "should not send a vote for the next round")
t.Run("Query vote for round 2: should send the prepare vote for the current round", func(t *testing.T) {
rndVote := td.consP.HandleQueryVote(height, 2)
assert.Equal(t, vote.VoteTypePrepare, rndVote.Type())
assert.Equal(t, height, rndVote.Height())
assert.Equal(t, int16(2), rndVote.Round())
})

rndVote4 := td.consP.HandleQueryVote(2, 0)
assert.Nil(t, rndVote4, "should not have a vote for the next height")
t.Run("Query vote for round 3: should not send a vote for the next round", func(t *testing.T) {
rndVote := td.consP.HandleQueryVote(height, 3)
assert.Nil(t, rndVote)
})

t.Run("Query vote for height 2: should not send a vote for the next height", func(t *testing.T) {
rndVote := td.consP.HandleQueryVote(height+1, 0)
assert.Nil(t, rndVote)
})
}

func TestHandleQueryProposal(t *testing.T) {
Expand Down
12 changes: 6 additions & 6 deletions consensus/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (*changeProposer) checkCPValue(vote *vote.Vote, allowedValues ...vote.CPVal
}
}

func (cp *changeProposer) checkJustInitZero(just vote.Just, blockHash hash.Hash) error {
func (cp *changeProposer) checkJustInitNo(just vote.Just, blockHash hash.Hash) error {
j, ok := just.(*vote.JustInitNo)
if !ok {
return InvalidJustificationError{
Expand All @@ -56,7 +56,7 @@ func (cp *changeProposer) checkJustInitZero(just vote.Just, blockHash hash.Hash)
return nil
}

func (*changeProposer) checkJustInitOne(just vote.Just) error {
func (*changeProposer) checkJustInitYes(just vote.Just) error {
_, ok := just.(*vote.JustInitYes)
if !ok {
return InvalidJustificationError{
Expand Down Expand Up @@ -150,12 +150,12 @@ func (cp *changeProposer) checkJustMainVoteConflict(just vote.Just,
}

if cpRound == 0 {
err := cp.checkJustInitZero(j.JustNo, blockHash)
err := cp.checkJustInitNo(j.JustNo, blockHash)
if err != nil {
return err
}

err = cp.checkJustInitOne(j.JustYes)
err = cp.checkJustInitYes(j.JustYes)
if err != nil {
return err
}
Expand Down Expand Up @@ -201,15 +201,15 @@ func (cp *changeProposer) checkJustPreVote(vte *vote.Vote) error {
return err
}

return cp.checkJustInitZero(just, vte.BlockHash())
return cp.checkJustInitNo(just, vte.BlockHash())

case vote.JustTypeInitYes:
err := cp.checkCPValue(vte, vote.CPValueYes)
if err != nil {
return err
}

return cp.checkJustInitOne(just)
return cp.checkJustInitYes(just)
default:
return InvalidJustificationError{
JustType: just.Type(),
Expand Down

0 comments on commit cc18fa3

Please sign in to comment.