-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: Write a test case to verify a new voter sampling #129
Changes from 1 commit
ab45ea1
be3651a
6463e5f
b31c6da
fbb1a18
55a485d
11980dc
e041312
e83038b
45c1c22
ae5b44b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -784,6 +784,9 @@ func sortVoters(candidates []*voter) []*voter { | |
sort.Slice(temp, func(i, j int) bool { | ||
bigA := new(big.Int).Mul(big.NewInt(temp[i].val.VotingPower), big.NewInt(temp[j].val.StakingPower)) | ||
bigB := new(big.Int).Mul(big.NewInt(temp[j].val.VotingPower), big.NewInt(temp[i].val.StakingPower)) | ||
if bigA.Cmp(bigB) == 0 { | ||
return bytes.Compare(temp[i].val.Address, temp[j].val.Address) == -1 | ||
} | ||
return bigA.Cmp(bigB) == 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, it would be better 👍 |
||
}) | ||
return temp | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,17 +3,19 @@ package types | |
import ( | ||
"bytes" | ||
"math" | ||
"math/big" | ||
s "sort" | ||
"strconv" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/tendermint/tendermint/crypto" | ||
"github.com/tendermint/tendermint/crypto/merkle" | ||
"github.com/tendermint/tendermint/libs/rand" | ||
tmtime "github.com/tendermint/tendermint/types/time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
tmtime "github.com/tendermint/tendermint/types/time" | ||
) | ||
|
||
func countZeroStakingPower(vals []*Validator) int { | ||
|
@@ -268,84 +270,6 @@ func TestCalNumOfVoterToElect(t *testing.T) { | |
} | ||
} | ||
|
||
func makeByzantine(valSet *ValidatorSet, rate float64) map[string]bool { | ||
result := make(map[string]bool) | ||
byzantinePower := int64(0) | ||
threshold := int64(float64(valSet.TotalStakingPower()) * rate) | ||
for _, v := range valSet.Validators { | ||
if byzantinePower+v.StakingPower > threshold { | ||
break | ||
} | ||
result[v.Address.String()] = true | ||
byzantinePower += v.StakingPower | ||
} | ||
return result | ||
} | ||
|
||
func byzantinesPower(voters []*Validator, byzantines map[string]bool) int64 { | ||
power := int64(0) | ||
for _, v := range voters { | ||
if byzantines[v.Address.String()] { | ||
power += v.VotingPower | ||
} | ||
} | ||
return power | ||
} | ||
|
||
func countByzantines(voters []*Validator, byzantines map[string]bool) int { | ||
count := 0 | ||
for _, v := range voters { | ||
if byzantines[v.Address.String()] { | ||
count++ | ||
} | ||
} | ||
return count | ||
} | ||
|
||
func electVotersForLoop(t *testing.T, hash []byte, valSet *ValidatorSet, privMap map[string]PrivValidator, | ||
byzantines map[string]bool, loopCount int, byzantinePercent, accuracy int32) { | ||
byzantineFault := 0 | ||
totalVoters := 0 | ||
totalByzantines := 0 | ||
for i := 0; i < loopCount; i++ { | ||
voterSet := SelectVoter(valSet, hash, &VoterParams{1, byzantinePercent}) | ||
byzantineThreshold := int64(float64(voterSet.TotalVotingPower())*0.33) + 1 | ||
if byzantinesPower(voterSet.Voters, byzantines) >= byzantineThreshold { | ||
byzantineFault++ | ||
} | ||
totalVoters += voterSet.Size() | ||
totalByzantines += countByzantines(voterSet.Voters, byzantines) | ||
proposer := valSet.SelectProposer(hash, int64(i), 0) | ||
message := MakeRoundHash(hash, int64(i), 0) | ||
proof, _ := privMap[proposer.Address.String()].GenerateVRFProof(message) | ||
pubKey, _ := privMap[proposer.Address.String()].GetPubKey() | ||
hash, _ = pubKey.VRFVerify(proof, message) | ||
} | ||
t.Logf("voters=%d, fault=%d, avg byzantines=%f", | ||
totalVoters/loopCount, byzantineFault, float64(totalByzantines)/float64(loopCount)) | ||
assert.True(t, float64(byzantineFault) < float64(loopCount)) | ||
} | ||
|
||
func TestCalVotersNum2(t *testing.T) { | ||
t.Skip("take too much time and no longer using accuracy") | ||
valSet, privMap := randValidatorSetWithMinMax(100, 100, 10000) | ||
byzantinePercent := int32(20) | ||
byzantines := makeByzantine(valSet, float64(byzantinePercent)/100) | ||
genDoc := &GenesisDoc{ | ||
GenesisTime: tmtime.Now(), | ||
ChainID: "tendermint-test", | ||
Validators: toGenesisValidators(valSet.Validators), | ||
} | ||
hash := genDoc.Hash() | ||
|
||
loopCount := 1000 | ||
electVotersForLoop(t, hash, valSet, privMap, byzantines, loopCount, byzantinePercent, 1) | ||
electVotersForLoop(t, hash, valSet, privMap, byzantines, loopCount, byzantinePercent, 2) | ||
electVotersForLoop(t, hash, valSet, privMap, byzantines, loopCount, byzantinePercent, 3) | ||
electVotersForLoop(t, hash, valSet, privMap, byzantines, loopCount, byzantinePercent, 4) | ||
electVotersForLoop(t, hash, valSet, privMap, byzantines, loopCount, byzantinePercent, 5) | ||
} | ||
|
||
func TestVoterSetProtoBuf(t *testing.T) { | ||
_, voterSet, _ := RandVoterSet(10, 100) | ||
_, voterSet2, _ := RandVoterSet(10, 100) | ||
|
@@ -719,6 +643,19 @@ func sameVoters(c1 []*Validator, c2 []*Validator) bool { | |
} | ||
|
||
func TestElectVotersNonDup(t *testing.T) { | ||
for n := 100; n <= 100000; n *= 10 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is |
||
validators := newValidatorSet(100, func(i int) int64 { | ||
return int64(100 * (i + 1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about to use random staking power? |
||
}) | ||
validators.updateTotalStakingPower() | ||
|
||
winners := electVotersNonDup(validators.Copy(), 0, 30) | ||
|
||
assert.True(t, isByzantine(winners, validators.totalStakingPower, 30)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although the code was written earlier, the 356 line of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And use following to calculate
like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And is |
||
} | ||
} | ||
|
||
func TestElectVotersNonDupStaticVotingPower(t *testing.T) { | ||
candidates := newValidatorSet(5, func(i int) int64 { return 10 }) | ||
expectedVotingPower := []int64{ | ||
13, | ||
|
@@ -755,3 +692,257 @@ func TestElectVoter(t *testing.T) { | |
moveWinnerToLast(candidates, idx) | ||
} | ||
} | ||
|
||
func TestElectVotersNonDupWithDifferentSeed(t *testing.T) { | ||
validators := newValidatorSet(1000, func(i int) int64 { | ||
return 1 | ||
}) | ||
|
||
voters1 := electVotersNonDup(validators.Copy(), 1234, 20) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This cannot be proved by a single case. We'd better test for many cases using random. |
||
voters2 := electVotersNonDup(validators.Copy(), 4321, 20) | ||
|
||
assert.False(t, sameVoters(voters1, voters2)) | ||
} | ||
|
||
func TestElectVotersNonDupValidatorsNotSorting(t *testing.T) { | ||
validators := newValidatorSet(1000, func(i int) int64 { | ||
return int64(i + 1) | ||
}) | ||
|
||
shuffled := validators.Copy() | ||
for i := range shuffled.Validators { | ||
r := rand.Intn(len(shuffled.Validators)) | ||
shuffled.Validators[i], shuffled.Validators[r] = shuffled.Validators[r], shuffled.Validators[i] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I didn't know swapping was possible without temp variables. Good! |
||
} | ||
|
||
winners := electVotersNonDup(validators, 0, 30) | ||
shuffledWinners := electVotersNonDup(shuffled, 0, 30) | ||
|
||
assert.True(t, sameVoters(winners, shuffledWinners)) | ||
} | ||
|
||
func TestElectVotersNonDupVotingPower(t *testing.T) { | ||
validators := newValidatorSet(100, func(i int) int64 { | ||
return 1000 | ||
}) | ||
|
||
winners := electVotersNonDup(validators, 0, 30) | ||
|
||
winPoints := make([]*big.Int, 0) | ||
total := int64(0) | ||
totalWinPoint := new(big.Int) | ||
for n := 0; n < len(winners); n++ { | ||
for i := range winPoints { | ||
winPoint := big.NewInt(validators.totalStakingPower - total + 1000) | ||
winPoint.Div(big.NewInt(1000*precisionForSelection), winPoint) | ||
totalWinPoint.Add(totalWinPoint, winPoint) | ||
winPoints[i] = new(big.Int).Add(winPoints[i], winPoint) | ||
} | ||
winPoints = append(winPoints, big.NewInt(1000)) | ||
totalWinPoint.Add(totalWinPoint, big.NewInt(1000)) | ||
total += 1000 | ||
} | ||
|
||
for i, w := range winners { | ||
winPoint := new(big.Int).Mul(winPoints[i], big.NewInt(precisionForSelection)) | ||
votingPower := new(big.Int).Div(winPoint, totalWinPoint) | ||
votingPower.Mul(votingPower, big.NewInt(validators.totalStakingPower)) | ||
votingPower.Div(votingPower, big.NewInt(precisionCorrectionForSelection)) | ||
|
||
assert.True(t, w.VotingPower == votingPower.Int64()) | ||
} | ||
} | ||
|
||
func TestElectVotersNonDupWithOverflow(t *testing.T) { | ||
expectedPanic := "Total staking power should be guarded to not exceed" | ||
validators := newValidatorSet(101, func(i int) int64 { | ||
return math.MaxInt64 / 100 | ||
}) | ||
|
||
defer func() { | ||
pnc := recover() | ||
if pncStr, ok := pnc.(string); ok { | ||
assert.True(t, strings.HasPrefix(pncStr, expectedPanic)) | ||
} else { | ||
t.Fatal("panic expected, but doesn't panic") | ||
} | ||
}() | ||
electVotersNonDup(validators, 0, 30) | ||
} | ||
|
||
func TestElectVotersNonDupDistribution(t *testing.T) { | ||
validators := newValidatorSet(100, func(i int) int64 { | ||
return 1000 | ||
}) | ||
scores := make(map[string]int) | ||
for i := 0; i < 100000; i++ { | ||
//hash is distributed well | ||
hash := merkle.SimpleHashFromByteSlices([][]byte{ | ||
[]byte(strconv.Itoa(i)), | ||
}) | ||
seed := hashToSeed(hash) | ||
winners := electVotersNonDup(validators.Copy(), seed, 1) | ||
scores[winners[0].Address.String()]++ | ||
} | ||
|
||
for _, v := range scores { | ||
assert.True(t, v >= 900 && v <= 1100) | ||
} | ||
} | ||
|
||
func TestElectVoterPanic(t *testing.T) { | ||
|
||
validators := newValidatorSet(10, func(i int) int64 { return int64(i + 1) }) | ||
total := int64(0) | ||
for _, val := range validators.Validators { | ||
total += val.StakingPower | ||
} | ||
seed := uint64(0) | ||
|
||
candidates := validators.Validators | ||
|
||
//vote when there is no candidates | ||
expectedResult := "Cannot find random sample." | ||
defer func() { | ||
pnc := recover() | ||
if pncStr, ok := pnc.(string); ok { | ||
assert.True(t, strings.HasPrefix(pncStr, expectedResult)) | ||
} else { | ||
t.Fatal("panic expected, but doesn't panic") | ||
} | ||
|
||
}() | ||
for i := 0; i < 11; i++ { | ||
idx, winner := electVoter(&seed, candidates, i, total) | ||
total -= winner.StakingPower | ||
moveWinnerToLast(candidates, idx) | ||
} | ||
} | ||
|
||
func newVotersWithRandomVotingPowerDescending(max, numerator, stakingPower int64) []*voter { | ||
voters := make([]*voter, 0) | ||
|
||
// random voters descending | ||
random := int64(0) | ||
for votingPower := max; votingPower > 0; votingPower -= random { | ||
random = rand.Int63n(max/numerator) + 1 | ||
voters = append(voters, &voter{ | ||
val: &Validator{ | ||
StakingPower: stakingPower, | ||
VotingPower: votingPower, | ||
}, | ||
}) | ||
} | ||
return voters | ||
} | ||
|
||
func TestSortVoters(t *testing.T) { | ||
for n := int64(0); n < 100; n++ { | ||
|
||
// random voters descending | ||
voters := newVotersWithRandomVotingPowerDescending(100000, 100, 10) | ||
|
||
//shuffle the voters | ||
shuffled := make([]*voter, len(voters)) | ||
copy(shuffled, voters) | ||
for i := range shuffled { | ||
target := rand.Intn(len(shuffled) - 1) | ||
shuffled[i], shuffled[target] = shuffled[target], shuffled[i] | ||
} | ||
|
||
sorted := sortVoters(shuffled) | ||
for i := range sorted { | ||
assert.True(t, sorted[i].val.VotingPower == voters[i].val.VotingPower) | ||
} | ||
} | ||
} | ||
|
||
func TestSortVotersWithSameValue(t *testing.T) { | ||
for n := 0; n < 100; n++ { | ||
|
||
voters := make([]*voter, 0) | ||
|
||
// random voters descending | ||
random := int64(0) | ||
n := 0 | ||
for votingPower := int64(100000); votingPower > 0; votingPower -= random { | ||
random = rand.Int63n(100000/100) + 1 | ||
voters = append(voters, &voter{ | ||
val: &Validator{ | ||
StakingPower: 10, | ||
VotingPower: votingPower, | ||
Address: []byte(strconv.Itoa(n)), | ||
}, | ||
}) | ||
voters = append(voters, &voter{ | ||
val: &Validator{ | ||
StakingPower: 10, | ||
VotingPower: votingPower, | ||
Address: []byte(strconv.Itoa(n + 1)), | ||
}, | ||
}) | ||
n += 2 | ||
} | ||
|
||
//shuffle the voters | ||
shuffled := make([]*voter, len(voters)) | ||
copy(shuffled, voters) | ||
for i := range shuffled { | ||
target := rand.Intn(len(shuffled) - 1) | ||
shuffled[i], shuffled[target] = shuffled[target], shuffled[i] | ||
} | ||
|
||
sorted := sortVoters(shuffled) | ||
for i := range sorted { | ||
a := sorted[i].val | ||
b := voters[i].val | ||
assert.True(t, bytes.Equal(a.Address, b.Address)) | ||
assert.True(t, a.VotingPower == b.VotingPower) | ||
} | ||
} | ||
} | ||
|
||
func TestCountVoters(t *testing.T) { | ||
for n := int64(0); n < 100; n++ { | ||
tolerableByzantinePercent := int64(30) | ||
|
||
// random voters descending | ||
voters := newVotersWithRandomVotingPowerDescending(100000, 100, 1000) | ||
totalStakingPower := int64(1000 * len(voters)) | ||
|
||
tolerableByzantinePower := totalStakingPower * tolerableByzantinePercent / 100 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about to make a separate function for calculating |
||
if totalStakingPower*tolerableByzantinePercent%100 > 0 { | ||
tolerableByzantinePower++ | ||
} | ||
|
||
result := countVoters(voters, tolerableByzantinePower) | ||
topFVotersStakingPower := int64(0) | ||
topFVotersVotingPower := int64(0) | ||
prev := int64(0) | ||
for _, v := range voters { | ||
prev = topFVotersStakingPower | ||
topFVotersStakingPower += v.val.StakingPower | ||
topFVotersVotingPower += v.val.VotingPower | ||
if topFVotersVotingPower == result { | ||
break | ||
} | ||
} | ||
|
||
assert.True(t, topFVotersVotingPower == result) | ||
assert.True(t, prev < tolerableByzantinePower) | ||
assert.True(t, topFVotersStakingPower >= tolerableByzantinePower) | ||
} | ||
} | ||
|
||
func TestMoveWinnerToLast(t *testing.T) { | ||
validators := newValidatorSet(10, func(i int) int64 { | ||
return int64(i + 1) | ||
}) | ||
|
||
target := validators.Validators[3] | ||
nextOfTarget := validators.Validators[4] | ||
moveWinnerToLast(validators.Validators, 3) | ||
assert.True(t, target == validators.Validators[9]) | ||
assert.True(t, nextOfTarget == validators.Validators[3]) | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct to multiply
temp[i].val.VotingPower
bytemp[j].val.StakingPower
instead oftemp[i]
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is for comparing votingPower(i) / stakingPower(i) and votingPower(j) / stakingPower(j). so compare that way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it will be right. This code is for comparing two fractions using the fact
a/b > c/d iff a*d > b*c
.