From 53a86a144adc61f658e0866989f417387756ccbb Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Tue, 10 Dec 2019 11:14:22 -0800 Subject: [PATCH] [quorum] Optimize counting up of already voted voters (#2000) * [quorum] Optimize counting up of already voted voters * [quorum] By Mask cache of computation not needed - talked w/Chao --- consensus/consensus_service.go | 2 +- consensus/quorum/one-node-one-vote.go | 11 ++- consensus/quorum/one-node-staked-vote.go | 119 +++++++++++++---------- consensus/quorum/quorum.go | 12 ++- consensus/view_change.go | 4 +- consensus/votepower/roster.go | 4 - 6 files changed, 85 insertions(+), 67 deletions(-) diff --git a/consensus/consensus_service.go b/consensus/consensus_service.go index e85e2fb5e..7213b7b53 100644 --- a/consensus/consensus_service.go +++ b/consensus/consensus_service.go @@ -197,7 +197,7 @@ func (consensus *Consensus) ResetState() { consensus.blockHash = [32]byte{} consensus.blockHeader = []byte{} consensus.block = []byte{} - consensus.Decider.Reset([]quorum.Phase{quorum.Prepare, quorum.Commit}) + consensus.Decider.ResetPrepareAndCommitVotes() members := consensus.Decider.Participants() prepareBitmap, _ := bls_cosi.NewMask(members, nil) commitBitmap, _ := bls_cosi.NewMask(members, nil) diff --git a/consensus/quorum/one-node-one-vote.go b/consensus/quorum/one-node-one-vote.go index e2d067487..5d6277b92 100644 --- a/consensus/quorum/one-node-one-vote.go +++ b/consensus/quorum/one-node-one-vote.go @@ -44,7 +44,8 @@ func (v *uniformVoteWeight) IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool { return false } utils.Logger().Debug(). - Msgf("[IsQuorumAchievedByMask] have enough voting power: need %+v, have %+v", threshold, currentTotalPower) + Msgf("[IsQuorumAchievedByMask] have enough voting power: need %+v, have %+v", + threshold, currentTotalPower) return true } @@ -130,3 +131,11 @@ func (v *uniformVoteWeight) AmIMemberOfCommitee() bool { } return false } + +func (v *uniformVoteWeight) ResetPrepareAndCommitVotes() { + v.reset([]Phase{Prepare, Commit}) +} + +func (v *uniformVoteWeight) ResetViewChangeVotes() { + v.reset([]Phase{ViewChange}) +} diff --git a/consensus/quorum/one-node-staked-vote.go b/consensus/quorum/one-node-staked-vote.go index 4d069b234..1efbab2a6 100644 --- a/consensus/quorum/one-node-staked-vote.go +++ b/consensus/quorum/one-node-staked-vote.go @@ -19,9 +19,6 @@ var ( totalShare = numeric.MustNewDecFromStr("1.00") ) -// TODO Test the case where we have 33 nodes, 68/33 will give precision hell and it should trigger -// the 100% mismatch err. - // TallyResult is the result of when we calculate voting power, // recall that it happens to us at epoch change type TallyResult struct { @@ -29,12 +26,24 @@ type TallyResult struct { theirPercent numeric.Dec } +type voteBox struct { + voters map[shard.BlsPublicKey]struct{} + currentTotal numeric.Dec +} + +type box struct { + Prepare *voteBox + Commit *voteBox + ViewChange *voteBox +} + type stakedVoteWeight struct { SignatureReader DependencyInjectionWriter DependencyInjectionReader slash.ThresholdDecider - roster votepower.Roster + roster votepower.Roster + ballotBox box } // Policy .. @@ -83,39 +92,56 @@ func (v *stakedVoteWeight) IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool { func (v *stakedVoteWeight) computeCurrentTotalPower(p Phase) (*numeric.Dec, error) { w := shard.BlsPublicKey{} members := v.Participants() - currentTotalPower := numeric.ZeroDec() + ballot := func() *voteBox { + switch p { + case Prepare: + return v.ballotBox.Prepare + case Commit: + return v.ballotBox.Commit + case ViewChange: + return v.ballotBox.ViewChange + default: + // Should not happen + return nil + } + }() for i := range members { - if v.ReadSignature(p, members[i]) != nil { + w.FromLibBLSPublicKey(members[i]) + if _, didVote := ballot.voters[w]; !didVote && + v.ReadSignature(p, members[i]) != nil { err := w.FromLibBLSPublicKey(members[i]) if err != nil { return nil, err } - currentTotalPower = currentTotalPower.Add( + ballot.currentTotal = ballot.currentTotal.Add( v.roster.Voters[w].EffectivePercent, ) + ballot.voters[w] = struct{}{} } } - return ¤tTotalPower, nil + + return &ballot.currentTotal, nil } // ComputeTotalPowerByMask computes the total power indicated by bitmap mask func (v *stakedVoteWeight) computeTotalPowerByMask(mask *bls_cosi.Mask) *numeric.Dec { - currentTotalPower := numeric.ZeroDec() pubKeys := mask.Publics - for _, key := range pubKeys { - w := shard.BlsPublicKey{} - err := w.FromLibBLSPublicKey(key) + w := shard.BlsPublicKey{} + currentTotal := numeric.ZeroDec() + + for i := range pubKeys { + err := w.FromLibBLSPublicKey(pubKeys[i]) if err != nil { return nil } - if enabled, err := mask.KeyEnabled(key); err == nil && enabled { - currentTotalPower = currentTotalPower.Add( + if enabled, err := mask.KeyEnabled(pubKeys[i]); err == nil && enabled { + currentTotal = currentTotal.Add( v.roster.Voters[w].EffectivePercent, ) } } - return ¤tTotalPower + return ¤tTotal } // QuorumThreshold .. @@ -135,43 +161,6 @@ func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool { return reached.GTE(ninetyPercent) } -// Award .. -// func (v *stakedVoteWeight) Award( -// Pie numeric.Dec, -// earners []common.Address, -// hook func(earner common.Address, due *big.Int), -// ) numeric.Dec { -// payout := big.NewInt(0) -// last := big.NewInt(0) -// count := big.NewInt(int64(len(earners))) -// // proportional := map[common.Address]numeric.Dec{} - -// for _, voter := range v.voters { -// if voter.isHarmonyNode == false { -// // proportional[details.earningAccount] = details.effective.QuoTruncate( -// // v.stakedTotal, -// // ) -// } -// } -// // TODO Finish implementing this logic w/Chao - -// for i := range earners { -// cur := big.NewInt(0) - -// cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count) - -// diff := big.NewInt(0).Sub(cur, last) - -// // hook(common.Address(account), diff) - -// payout = big.NewInt(0).Add(payout, diff) - -// last = cur -// } - -// return payout -// } - var ( errSumOfVotingPowerNotOne = errors.New("sum of total votes do not sum to 100 percent") errSumOfOursAndTheirsNotOne = errors.New( @@ -183,7 +172,8 @@ func (v *stakedVoteWeight) SetVoters( staked shard.SlotList, ) (*TallyResult, error) { s, _ := v.ShardIDProvider()() - v.Reset([]Phase{Prepare, Commit, ViewChange}) + v.ResetPrepareAndCommitVotes() + v.ResetViewChangeVotes() roster, err := votepower.Compute(staked) if err != nil { @@ -283,3 +273,26 @@ func (v *stakedVoteWeight) AmIMemberOfCommitee() bool { _, ok := v.roster.Voters[w] return ok } + +func newBox() *voteBox { + return &voteBox{map[shard.BlsPublicKey]struct{}{}, numeric.ZeroDec()} +} + +func newBallotBox() box { + return box{ + Prepare: newBox(), + Commit: newBox(), + ViewChange: newBox(), + } +} + +func (v *stakedVoteWeight) ResetPrepareAndCommitVotes() { + v.reset([]Phase{Prepare, Commit}) + v.ballotBox.Prepare = newBox() + v.ballotBox.Commit = newBox() +} + +func (v *stakedVoteWeight) ResetViewChangeVotes() { + v.reset([]Phase{ViewChange}) + v.ballotBox.ViewChange = newBox() +} diff --git a/consensus/quorum/quorum.go b/consensus/quorum/quorum.go index dd5177af7..973473c6a 100644 --- a/consensus/quorum/quorum.go +++ b/consensus/quorum/quorum.go @@ -76,7 +76,7 @@ type SignatoryTracker interface { AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign) // Caller assumes concurrency protection SignersCount(Phase) int64 - Reset([]Phase) + reset([]Phase) } // SignatureReader .. @@ -114,10 +114,12 @@ type Decider interface { SetVoters(shard.SlotList) (*TallyResult, error) Policy() Policy IsQuorumAchieved(Phase) bool - IsQuorumAchievedByMask(*bls_cosi.Mask) bool + IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool QuorumThreshold() numeric.Dec AmIMemberOfCommitee() bool IsRewardThresholdAchieved() bool + ResetPrepareAndCommitVotes() + ResetViewChangeVotes() } // These maps represent the signatories (validators), keys are BLS public keys @@ -216,7 +218,7 @@ func (s *cIdentities) AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign } } -func (s *cIdentities) Reset(ps []Phase) { +func (s *cIdentities) reset(ps []Phase) { for i := range ps { switch m := map[string]*bls.Sign{}; ps[i] { case Prepare: @@ -311,13 +313,13 @@ func NewDecider(p Policy) Decider { c.DependencyInjectionWriter, c.DependencyInjectionReader, c, } case SuperMajorityStake: - roster := votepower.NewRoster() return &stakedVoteWeight{ c.SignatureReader, c.DependencyInjectionWriter, c.DependencyInjectionWriter.(DependencyInjectionReader), c.SignatureReader.(slash.ThresholdDecider), - *roster, + *votepower.NewRoster(), + newBallotBox(), } default: // Should not be possible diff --git a/consensus/view_change.go b/consensus/view_change.go index e9159fcf8..b7c669d95 100644 --- a/consensus/view_change.go +++ b/consensus/view_change.go @@ -94,12 +94,10 @@ func (consensus *Consensus) ResetViewChangeState() { consensus.bhpSigs = map[uint64]map[string]*bls.Sign{} consensus.nilSigs = map[uint64]map[string]*bls.Sign{} consensus.viewIDSigs = map[uint64]map[string]*bls.Sign{} - consensus.bhpBitmap = map[uint64]*bls_cosi.Mask{} consensus.nilBitmap = map[uint64]*bls_cosi.Mask{} consensus.viewIDBitmap = map[uint64]*bls_cosi.Mask{} - - consensus.Decider.Reset([]quorum.Phase{quorum.ViewChange}) + consensus.Decider.ResetViewChangeVotes() } func createTimeout() map[TimeoutType]*utils.Timeout { diff --git a/consensus/votepower/roster.go b/consensus/votepower/roster.go index 7b2c2e1d0..7519422dd 100644 --- a/consensus/votepower/roster.go +++ b/consensus/votepower/roster.go @@ -162,9 +162,6 @@ func Compute(staked shard.SlotList) (*Roster, error) { theirPercentage = theirPercentage.Add(member.EffectivePercent) lastStakedVoter = &member } else { // Our node - // TODO See the todo on where this called in one-node-staked-vote, - // need to have these two values of our - // percentage and hmy percentage sum to 1 member.EffectivePercent = HarmonysShare.Quo(ourCount) ourPercentage = ourPercentage.Add(member.EffectivePercent) } @@ -198,7 +195,6 @@ func Compute(staked shard.SlotList) (*Roster, error) { roster.OurVotingPowerTotalPercentage = ourPercentage roster.TheirVotingPowerTotalPercentage = theirPercentage - return roster, nil }