From 36d6ffd9088ee27e555b3d11fbdb4c2dbe33ad2e Mon Sep 17 00:00:00 2001 From: mantre Date: Mon, 29 Mar 2021 02:10:21 +0800 Subject: [PATCH] Keep undef vvotes --- consensus/consensus.go | 5 +- consensus/pending_votes/vote_set.go | 41 +++++--- consensus/pending_votes/vote_set_test.go | 118 ++++++++++++++++++----- consensus/precommit.go | 14 +-- state/state.go | 2 +- 5 files changed, 124 insertions(+), 56 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 5d5c9d678..bb98d8043 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -149,12 +149,9 @@ func (cs *consensus) addVote(v *vote.Vote) error { } added, err := cs.pendingVotes.AddVote(v) - if err != nil { - return err - } if !added { // we probably have this vote - return nil + return err } round := v.Round() diff --git a/consensus/pending_votes/vote_set.go b/consensus/pending_votes/vote_set.go index cc391958d..625949fc7 100644 --- a/consensus/pending_votes/vote_set.go +++ b/consensus/pending_votes/vote_set.go @@ -38,10 +38,10 @@ func NewVoteSet(height int, round int, voteType vote.VoteType, validators []*val } } -func (vs *VoteSet) Type() vote.VoteType { return vs.voteType } -func (vs *VoteSet) Height() int { return vs.height } -func (vs *VoteSet) Round() int { return vs.round } -func (vs *VoteSet) Power() int64 { return vs.accumulatedPower } +func (vs *VoteSet) Type() vote.VoteType { return vs.voteType } +func (vs *VoteSet) Height() int { return vs.height } +func (vs *VoteSet) Round() int { return vs.round } +func (vs *VoteSet) AccumulatedPower() int64 { return vs.accumulatedPower } func (vs *VoteSet) Len() int { sum := 0 @@ -106,21 +106,20 @@ func (vs *VoteSet) AddVote(vote *vote.Vote) (bool, error) { anotherVote, ok := bv.votes[signer] if ok { - // A possible scenario: - // A peer doesn't have a proposal, he votes for undef. - // Later he receives the proposal, so he vote again. - // We should ignore undef vote + if anotherVote.BlockHash().IsUndef() { - // Remove undef vote and replace it with new vote + // A possible scenario: + // A peer doesn't have a proposal, it votes for null. + // Later it receives the proposal, so it votes again. duplicated = true } else if vote.BlockHash().IsUndef() { - // Because of network latency, we might receive undef vote after block vote. - // Ignore undef vote in this case. - return false, nil + // A possible scenario: + // Because of network latency, we might receive null_vote after block_vote. + duplicated = true } else if anotherVote.BlockHash() != blockHash { // Duplicated vote: // 1- Same signer - // 2- Block hashes are not undef and different + // 2- Block hashes are not null and they are different // // We report an error but keep both votes // @@ -134,12 +133,14 @@ func (vs *VoteSet) AddVote(vote *vote.Vote) (bool, error) { added := bv.addVote(vote) if added { bv.power += val.Power() - if vs.hasQuorum(bv.power) { - vs.quorumBlock = &blockHash - } if !duplicated { vs.accumulatedPower += val.Power() } + if vs.hasQuorum(bv.power) { + if vs.quorumBlock == nil || vs.quorumBlock.IsUndef() { + vs.quorumBlock = &blockHash + } + } } return added, err @@ -156,6 +157,14 @@ func (vs *VoteSet) QuorumBlock() *crypto.Hash { return vs.quorumBlock } +func (vs *VoteSet) HasOneThirdOfTotalPower(hash crypto.Hash) bool { + bv := vs.blockVotes[hash] + if bv == nil { + return false + } + return bv.power > (vs.totalPower * 1 / 3) +} + func (vs *VoteSet) ToCertificate() *block.Certificate { if vs.voteType != vote.VoteTypePrecommit { return nil diff --git a/consensus/pending_votes/vote_set_test.go b/consensus/pending_votes/vote_set_test.go index adcda9c33..793187332 100644 --- a/consensus/pending_votes/vote_set_test.go +++ b/consensus/pending_votes/vote_set_test.go @@ -77,33 +77,33 @@ func TestDuplicateVote(t *testing.T) { h2 := crypto.GenerateTestHash() vs := NewVoteSet(1, 0, vote.VoteTypePrepare, committee.Validators()) - undefVote := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[0].Address()) + nullVote := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[0].Address()) correctVote := vote.NewVote(vote.VoteTypePrepare, 1, 0, h1, signers[0].Address()) duplicatedVote := vote.NewVote(vote.VoteTypePrepare, 1, 0, h2, signers[0].Address()) // sign the votes - signers[0].SignMsg(undefVote) + signers[0].SignMsg(nullVote) signers[0].SignMsg(correctVote) signers[0].SignMsg(duplicatedVote) - added, err := vs.AddVote(undefVote) + added, err := vs.AddVote(nullVote) assert.True(t, added) // ok assert.NoError(t, err) - added, err = vs.AddVote(undefVote) + added, err = vs.AddVote(nullVote) assert.False(t, added) // added before assert.NoError(t, err) assert.Equal(t, vs.Len(), 1) - assert.Equal(t, vs.Power(), int64(1000)) // First validator's stake + assert.Equal(t, vs.AccumulatedPower(), int64(1000)) // First validator's stake added, err = vs.AddVote(correctVote) - assert.True(t, added) // ok - assert.NoError(t, err) // - assert.Equal(t, vs.Len(), 2) // undef + vote - assert.Equal(t, vs.Power(), int64(1000)) // First validator's stake + assert.True(t, added) // ok + assert.NoError(t, err) // + assert.Equal(t, vs.Len(), 2) // null_vote + block_vote + assert.Equal(t, vs.AccumulatedPower(), int64(1000)) // First validator's stake - // Again add undef vote - added, err = vs.AddVote(undefVote) + // Again add null_vote + added, err = vs.AddVote(nullVote) assert.False(t, added) // added before assert.NoError(t, err) assert.Equal(t, vs.Len(), 2) @@ -116,11 +116,11 @@ func TestDuplicateVote(t *testing.T) { bv1 := vs.blockVotes[h1] bv2 := vs.blockVotes[h2] bv3 := vs.blockVotes[crypto.UndefHash] - assert.Equal(t, vs.Len(), 3) // - assert.Equal(t, vs.Power(), int64(1000)) // - assert.Equal(t, bv1.power, int64(1000)) // - assert.Equal(t, bv2.power, int64(1000)) // - assert.Equal(t, bv3.power, int64(1000)) // + assert.Equal(t, vs.Len(), 3) // + assert.Equal(t, vs.AccumulatedPower(), int64(1000)) // + assert.Equal(t, bv1.power, int64(1000)) // + assert.Equal(t, bv2.power, int64(1000)) // + assert.Equal(t, bv3.power, int64(1000)) // } func TestQuorum(t *testing.T) { @@ -200,7 +200,7 @@ func TestPower(t *testing.T) { assert.True(t, vs.HasQuorum()) assert.True(t, vs.QuorumBlock().IsUndef()) assert.Equal(t, vs.Len(), 3) - assert.Equal(t, vs.Power(), int64(1000+1500+2500)) + assert.Equal(t, vs.AccumulatedPower(), int64(1000+1500+2500)) ok, _ = vs.AddVote(v4) assert.True(t, ok) @@ -220,7 +220,7 @@ func TestPower(t *testing.T) { // Check accumulated power assert.True(t, vs.HasQuorum()) assert.True(t, vs.QuorumBlock().IsUndef()) - assert.Equal(t, vs.Power(), int64(1000+1500+2500)) + assert.Equal(t, vs.AccumulatedPower(), int64(1000+1500+2500)) assert.Equal(t, vs.Len(), 4) // Add more votes @@ -237,7 +237,7 @@ func TestPower(t *testing.T) { assert.True(t, vs.HasQuorum()) assert.Equal(t, vs.QuorumBlock(), &h1) - assert.Equal(t, vs.Power(), int64(1000+1500+2500)) + assert.Equal(t, vs.AccumulatedPower(), int64(1000+1500+2500)) assert.Equal(t, vs.Len(), 6) // Check previous votes @@ -252,12 +252,13 @@ func TestAllVotes(t *testing.T) { vs := NewVoteSet(1, 0, vote.VoteTypePrecommit, committee.Validators()) - h1 := crypto.GenerateTestHash() - v1 := vote.NewVote(vote.VoteTypePrecommit, 1, 0, crypto.UndefHash, signers[0].Address()) - v2 := vote.NewVote(vote.VoteTypePrecommit, 1, 0, h1, signers[1].Address()) + v1 := vote.NewVote(vote.VoteTypePrecommit, 1, 0, crypto.GenerateTestHash(), signers[0].Address()) + v2 := vote.NewVote(vote.VoteTypePrecommit, 1, 0, crypto.UndefHash, signers[0].Address()) + v3 := vote.NewVote(vote.VoteTypePrecommit, 1, 0, crypto.GenerateTestHash(), signers[1].Address()) signers[0].SignMsg(v1) - signers[1].SignMsg(v2) + signers[0].SignMsg(v2) + signers[1].SignMsg(v3) assert.Equal(t, vs.Len(), 0) assert.Empty(t, vs.AllVotes()) @@ -266,8 +267,77 @@ func TestAllVotes(t *testing.T) { assert.True(t, ok) ok, _ = vs.AddVote(v2) assert.True(t, ok) + ok, _ = vs.AddVote(v3) + assert.True(t, ok) - assert.Equal(t, vs.Len(), 2) + assert.Equal(t, vs.Len(), 3) assert.Contains(t, vs.AllVotes(), v1) assert.Contains(t, vs.AllVotes(), v2) + assert.Contains(t, vs.AllVotes(), v3) +} + +func TestUpdateQuoromBlock(t *testing.T) { + committee, signers := setupCommittee(t, 1000, 1000, 2500, 2000) + + vs := NewVoteSet(1, 0, vote.VoteTypePrepare, committee.Validators()) + + h1 := crypto.GenerateTestHash() + v1 := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[3].Address()) + v2 := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[2].Address()) + v3 := vote.NewVote(vote.VoteTypePrepare, 1, 0, h1, signers[3].Address()) + v4 := vote.NewVote(vote.VoteTypePrepare, 1, 0, h1, signers[2].Address()) + v5 := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[1].Address()) + + signers[3].SignMsg(v1) + signers[2].SignMsg(v2) + signers[3].SignMsg(v3) + signers[2].SignMsg(v4) + signers[1].SignMsg(v5) + + ok, _ := vs.AddVote(v1) + assert.True(t, ok) + assert.False(t, vs.HasQuorum()) + assert.Nil(t, vs.QuorumBlock()) + + ok, _ = vs.AddVote(v2) + assert.True(t, ok) + assert.True(t, vs.HasQuorum()) + assert.Equal(t, vs.QuorumBlock(), &crypto.UndefHash) + + ok, _ = vs.AddVote(v3) + assert.True(t, ok) + assert.True(t, vs.HasQuorum()) + assert.Equal(t, vs.QuorumBlock(), &crypto.UndefHash) + + ok, _ = vs.AddVote(v4) + assert.True(t, ok) + assert.True(t, vs.HasQuorum()) + assert.Equal(t, vs.QuorumBlock(), &h1) + + ok, _ = vs.AddVote(v5) + assert.True(t, ok) + assert.True(t, vs.HasQuorum()) + assert.Equal(t, vs.QuorumBlock(), &h1) +} + +func TestHasOneThirdOfTotalPower(t *testing.T) { + committee, signers := setupCommittee(t, 1000, 1500, 2500, 2000) + + vs := NewVoteSet(1, 0, vote.VoteTypePrepare, committee.Validators()) + + v1 := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[0].Address()) + v2 := vote.NewVote(vote.VoteTypePrepare, 1, 0, crypto.UndefHash, signers[1].Address()) + + signers[0].SignMsg(v1) + signers[1].SignMsg(v2) + + assert.False(t, vs.HasOneThirdOfTotalPower(crypto.UndefHash)) + + ok, _ := vs.AddVote(v1) + assert.True(t, ok) + assert.False(t, vs.HasOneThirdOfTotalPower(crypto.UndefHash)) + + ok, _ = vs.AddVote(v2) + assert.True(t, ok) + assert.True(t, vs.HasOneThirdOfTotalPower(crypto.UndefHash)) } diff --git a/consensus/precommit.go b/consensus/precommit.go index ab88ebe0c..5884705d9 100644 --- a/consensus/precommit.go +++ b/consensus/precommit.go @@ -32,23 +32,15 @@ func (cs *consensus) enterPrecommit(round int) { // We have a valid proposal, but there is no consensus about it // // If we are behind the partition, it might be easy to find it here - // There should be some null-votes here - // If weight of null-votes are greather tha `1f` (`f` stands for faulty) + // There should be some null-votes here. + // If weight of null-votes are greather than `1f` (`f` stands for faulty) // Then we broadcast our proposal and return here // // Note: Byzantine node might send different valid proposals to different nodes // cs.logger.Info("Precommit: Some peers don't have proposal yet.") - votes := prepares.AllVotes() - count := 0 - for _, v := range votes { - if v.BlockHash().IsUndef() { - count++ - } - } - - if count > len(votes)/3 { + if prepares.HasOneThirdOfTotalPower(crypto.UndefHash) { cs.logger.Debug("Precommit: Broadcst proposal.", "proposal", roundProposal) cs.broadcastProposal(roundProposal) return diff --git a/state/state.go b/state/state.go index c787ec8f2..316cb8e61 100644 --- a/state/state.go +++ b/state/state.go @@ -252,7 +252,7 @@ func (st *state) createSubsidyTx(fee int64) *tx.Tx { seq := acc.Sequence() + 1 amt := calcBlockSubsidy(st.lastInfo.BlockHeight()+1, st.params.SubsidyReductionInterval) - tx := tx.NewMintbaseTx(stamp, seq, st.mintbaseAddr, amt+fee, "hello!") + tx := tx.NewMintbaseTx(stamp, seq, st.mintbaseAddr, amt+fee, "") return tx }