Skip to content

Commit

Permalink
Keep undef vvotes
Browse files Browse the repository at this point in the history
  • Loading branch information
themantre committed Apr 1, 2021
1 parent ca987ab commit 36d6ffd
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 56 deletions.
5 changes: 1 addition & 4 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
41 changes: 25 additions & 16 deletions consensus/pending_votes/vote_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
//
Expand All @@ -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
Expand All @@ -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
Expand Down
118 changes: 94 additions & 24 deletions consensus/pending_votes/vote_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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())
Expand All @@ -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))
}
14 changes: 3 additions & 11 deletions consensus/precommit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 36d6ffd

Please sign in to comment.