Skip to content

Commit

Permalink
Add validation at the beginning of func:RandomSamplingWithPriority (#496
Browse files Browse the repository at this point in the history
)

* Add validation at the beginning of func:RandomSamplingWithPriority

* Fix RandomSamplingWithPriority call during test with different total priority and actual priority

* Modify panic message

* Remove TODO comment
  • Loading branch information
Mdaiki0730 authored Nov 11, 2022
1 parent a2effec commit 0681636
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 12 deletions.
44 changes: 35 additions & 9 deletions libs/rand/sampling.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type Candidate interface {
func RandomSamplingWithPriority(
seed uint64, candidates []Candidate, sampleSize int, totalPriority uint64) (samples []Candidate) {

// This step is performed if and only if the parameter is invalid. The reasons are as stated in the message:
err := checkInvalidPriority(candidates, totalPriority)
if err != nil {
panic(err)
}

// generates a random selection threshold for candidates' cumulative priority
thresholds := make([]uint64, sampleSize)
for i := 0; i < sampleSize; i++ {
Expand All @@ -50,15 +56,10 @@ func RandomSamplingWithPriority(
cumulativePriority += candidate.Priority()
}

// This step is performed if and only if the parameter is invalid. The reasons are as stated in the message:
actualTotalPriority := uint64(0)
for i := 0; i < len(candidates); i++ {
actualTotalPriority += candidates[i].Priority()
}
panic(fmt.Sprintf("Either the given candidate is an empty set, the actual cumulative priority is zero,"+
" or the total priority is less than the actual one; totalPriority=%d, actualTotalPriority=%d,"+
" seed=%d, sampleSize=%d, undrawn=%d, threshold[%d]=%d, len(candidates)=%d",
totalPriority, actualTotalPriority, seed, sampleSize, undrawn, undrawn, thresholds[undrawn], len(candidates)))
// We're assuming you never get to this code
panic(fmt.Sprintf("Cannot select samples; "+
"totalPriority=%d, seed=%d, sampleSize=%d, undrawn=%d, threshold[%d]=%d, len(candidates)=%d",
totalPriority, seed, sampleSize, undrawn, undrawn, thresholds[undrawn], len(candidates)))
}

const uint64Mask = uint64(0x7FFFFFFFFFFFFFFF)
Expand Down Expand Up @@ -93,3 +94,28 @@ func nextRandom(rand *uint64) uint64 {
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
return z ^ (z >> 31)
}

func checkInvalidPriority(candidates []Candidate, totalPriority uint64) error {
actualTotalPriority := uint64(0)
for i := 0; i < len(candidates); i++ {
actualTotalPriority += candidates[i].Priority()
}

if len(candidates) == 0 {
return fmt.Errorf("candidates is empty; "+
"totalPriority=%d, actualTotalPriority=%d, len(candidates)=%d",
totalPriority, actualTotalPriority, len(candidates))

} else if totalPriority == 0 || actualTotalPriority == 0 {
return fmt.Errorf("either total priority or actual priority is zero; "+
"totalPriority=%d, actualTotalPriority=%d, len(candidates)=%d",
totalPriority, actualTotalPriority, len(candidates))

} else if actualTotalPriority != totalPriority {
return fmt.Errorf("total priority not equal to actual priority; "+
"totalPriority=%d, actualTotalPriority=%d, len(candidates)=%d",
totalPriority, actualTotalPriority, len(candidates))

}
return nil
}
14 changes: 11 additions & 3 deletions libs/rand/sampling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ func (e *Element) VotingPower() uint64 { return e.votingPower }
func TestRandomSamplingWithPriority(t *testing.T) {
candidates := newCandidates(100, func(i int) uint64 { return uint64(i) })

elected := RandomSamplingWithPriority(0, candidates, 10, uint64(len(candidates)))
elected := RandomSamplingWithPriority(0, candidates, 10, calculateTotalPriority(candidates))
if len(elected) != 10 {
t.Errorf(fmt.Sprintf("unexpected sample size: %d", len(elected)))
}

// ----
// The same result can be obtained for the same input.
others := newCandidates(100, func(i int) uint64 { return uint64(i) })
secondTimeElected := RandomSamplingWithPriority(0, others, 10, uint64(len(others)))
secondTimeElected := RandomSamplingWithPriority(0, others, 10, calculateTotalPriority(others))
if len(elected) != len(secondTimeElected) || !sameCandidates(elected, secondTimeElected) {
t.Errorf(fmt.Sprintf("undeterministic: %+v != %+v", elected, others))
}
Expand All @@ -56,7 +56,7 @@ func TestRandomSamplingWithPriority(t *testing.T) {
candidates = newCandidates(100, func(i int) uint64 { return 1 })
counts := make([]int, len(candidates))
for i := 0; i < 100000; i++ {
elected = RandomSamplingWithPriority(uint64(i), candidates, 10, uint64(len(candidates)))
elected = RandomSamplingWithPriority(uint64(i), candidates, 10, calculateTotalPriority(candidates))
for _, e := range elected {
counts[e.(*Element).id]++
}
Expand Down Expand Up @@ -199,3 +199,11 @@ func calculateMeanAndVariance(values []float64) (mean float64, variance float64)
variance = sum2 / float64(len(values))
return
}

func calculateTotalPriority(candidates []Candidate) uint64 {
totalPriority := uint64(0)
for _, candidate := range candidates {
totalPriority += candidate.Priority()
}
return totalPriority
}

0 comments on commit 0681636

Please sign in to comment.