Skip to content

Commit

Permalink
Merge pull request #124 from line/feat/voting
Browse files Browse the repository at this point in the history
feat: Make voter electing staisfy finality
  • Loading branch information
kukugi authored Oct 13, 2020
2 parents c392d08 + b1348de commit 01beee1
Show file tree
Hide file tree
Showing 14 changed files with 544 additions and 513 deletions.
19 changes: 3 additions & 16 deletions consensus/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1924,8 +1924,7 @@ func proposeBlock(t *testing.T, cs *State, round int, vssMap map[crypto.PubKey]*
func TestStateFullRoundWithSelectedVoter(t *testing.T) {
cs, vss := randStateWithVoterParams(10, &types.VoterParams{
VoterElectionThreshold: 5,
MaxTolerableByzantinePercentage: 20,
ElectionPrecision: 2})
MaxTolerableByzantinePercentage: 20})
vss[0].Height = 1 // this is needed because of `incrementHeight(vss[1:]...)` of randStateWithVoterParams()
vssMap := makeVssMap(vss)
height, round := cs.Height, cs.Round
Expand Down Expand Up @@ -2014,8 +2013,7 @@ func TestStateBadVoterWithSelectedVoter(t *testing.T) {
// making him having 1/3 + 1 voting power of total
cs, vss := randStateWithVoterParams(9, &types.VoterParams{
VoterElectionThreshold: 5,
MaxTolerableByzantinePercentage: 20,
ElectionPrecision: 5})
MaxTolerableByzantinePercentage: 20})

assert.True(t, cs.Voters.Size() >= 4)

Expand Down Expand Up @@ -2154,8 +2152,7 @@ func TestStateAllVoterToSelectedVoter(t *testing.T) {
startValidators := 5
cs, vss := randStateWithVoterParamsWithApp(startValidators, &types.VoterParams{
VoterElectionThreshold: int32(startValidators),
MaxTolerableByzantinePercentage: 20,
ElectionPrecision: 2},
MaxTolerableByzantinePercentage: 20},
"TestStateAllVoterToSelectedVoter")
vss[0].Height = 1 // this is needed because of `incrementHeight(vss[1:]...)` of randStateWithVoterParams()
vssMap := makeVssMap(vss)
Expand Down Expand Up @@ -2208,13 +2205,6 @@ func TestStateAllVoterToSelectedVoter(t *testing.T) {
ensureNewRound(newRoundCh, height+1, 0)

endHeight := 20
voterCount := make([]int, endHeight-1)
for i := 0; i < len(voterCount); i++ {
voterCount[i] = int(types.CalNumOfVoterToElect(int64(startValidators+i), 0.2, 0.99))
if voterCount[i] < startValidators {
voterCount[i] = startValidators
}
}
for i := 2; i <= endHeight; i++ { // height 2~10
height = cs.Height
privPubKey, _ = cs.privValidator.GetPubKey()
Expand All @@ -2229,9 +2219,6 @@ func TestStateAllVoterToSelectedVoter(t *testing.T) {
voters = cs.Voters
voterPrivVals = votersPrivVals(voters, vssMap)

// verify voters count
assert.True(t, voters.Size() == voterCount[i-2])

signAddVotes(cs, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(types.BlockPartSizeBytes).Header(),
voterPrivVals...)

Expand Down
96 changes: 2 additions & 94 deletions libs/rand/sampling.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
type Candidate interface {
Priority() uint64
LessThan(other Candidate) bool
SetWinPoint(winPoint int64)
}

// Select a specified number of candidates randomly from the candidate set based on each priority. This function is
Expand All @@ -32,7 +31,7 @@ func RandomSamplingWithPriority(
thresholds := make([]uint64, sampleSize)
for i := 0; i < sampleSize; i++ {
// calculating [gross weights] × [(0,1] random number]
thresholds[i] = randomThreshold(&seed, totalPriority)
thresholds[i] = RandomThreshold(&seed, totalPriority)
}
s.Slice(thresholds, func(i, j int) bool { return thresholds[i] < thresholds[j] })

Expand Down Expand Up @@ -65,114 +64,23 @@ func RandomSamplingWithPriority(
totalPriority, actualTotalPriority, seed, sampleSize, undrawn, undrawn, thresholds[undrawn], len(candidates)))
}

func moveWinnerToLast(candidates []Candidate, winner int) {
winnerCandidate := candidates[winner]
copy(candidates[winner:], candidates[winner+1:])
candidates[len(candidates)-1] = winnerCandidate
}

const uint64Mask = uint64(0x7FFFFFFFFFFFFFFF)

// precisionForSelection is a value to be corrected to increase precision when calculating voting power as an integer.
const precisionForSelection = uint64(1000)

// precisionCorrectionForSelection is a value corrected for accuracy of voting power
const precisionCorrectionForSelection = uint64(1000)

var divider *big.Int

func init() {
divider = big.NewInt(int64(uint64Mask))
divider.Add(divider, big.NewInt(1))
}

func randomThreshold(seed *uint64, total uint64) uint64 {
func RandomThreshold(seed *uint64, total uint64) uint64 {
totalBig := new(big.Int).SetUint64(total)
a := new(big.Int).SetUint64(nextRandom(seed) & uint64Mask)
a.Mul(a, totalBig)
a.Div(a, divider)
return a.Uint64()
}

func RandomSamplingWithoutReplacement(
seed uint64, candidates []Candidate, minSamplingCount int) (winners []Candidate) {

if len(candidates) < minSamplingCount {
panic(fmt.Sprintf("The number of candidates(%d) cannot be less minSamplingCount %d",
len(candidates), minSamplingCount))
}

totalPriority := sumTotalPriority(candidates)
candidates = sort(candidates)
winnersPriority := uint64(0)
losersPriorities := make([]uint64, len(candidates))
winnerNum := 0
for winnerNum < minSamplingCount {
if totalPriority-winnersPriority == 0 {
// it's possible if some candidates have zero priority
// if then, we can't elect voter any more; we should holt electing not to fall in infinity loop
break
}
threshold := randomThreshold(&seed, totalPriority-winnersPriority)
cumulativePriority := uint64(0)
found := false
for i, candidate := range candidates[:len(candidates)-winnerNum] {
if threshold < cumulativePriority+candidate.Priority() {
moveWinnerToLast(candidates, i)
winnersPriority += candidate.Priority()
losersPriorities[winnerNum] = totalPriority - winnersPriority
winnerNum++
found = true
break
}
cumulativePriority += candidate.Priority()
}

if !found {
panic(fmt.Sprintf("Cannot find random sample. winnerNum=%d, minSamplingCount=%d, "+
"winnersPriority=%d, totalPriority=%d, threshold=%d",
winnerNum, minSamplingCount, winnersPriority, totalPriority, threshold))
}
}
correction := new(big.Int).SetUint64(totalPriority)
correction = correction.Mul(correction, new(big.Int).SetUint64(precisionForSelection))
compensationProportions := make([]big.Int, winnerNum)
for i := winnerNum - 2; i >= 0; i-- {
additionalCompensation := new(big.Int).Div(correction, new(big.Int).SetUint64(losersPriorities[i]))
compensationProportions[i].Add(&compensationProportions[i+1], additionalCompensation)
}
winners = candidates[len(candidates)-winnerNum:]
winPoints := make([]*big.Int, len(winners))
totalWinPoint := new(big.Int)
for i, winner := range winners {
winPoints[i] = new(big.Int).SetUint64(winner.Priority())
winPoints[i].Mul(winPoints[i], &compensationProportions[i])
winPoints[i].Add(winPoints[i], correction)
totalWinPoint.Add(totalWinPoint, winPoints[i])
}
recalibration := new(big.Int).Div(correction, new(big.Int).SetUint64(precisionCorrectionForSelection))
for i, winner := range winners {
winPoint := new(big.Int)
winPoint.Mul(recalibration, winPoints[i])
winPoint.Div(winPoint, totalWinPoint)
winner.SetWinPoint(winPoint.Int64())
}
return winners
}

// sumTotalPriority calculate the sum of all candidate's priority(weight)
// and the sum should be less then or equal to MaxUint64
// TODO We need to check the total weight doesn't over MaxUint64 in somewhere not here.
func sumTotalPriority(candidates []Candidate) (sum uint64) {
for _, candi := range candidates {
sum += candi.Priority()
}
if sum == 0 {
panic("all candidates have zero priority")
}
return
}

// SplitMix64
// http://xoshiro.di.unimi.it/splitmix64.c
//
Expand Down
Loading

0 comments on commit 01beee1

Please sign in to comment.