Skip to content
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: add more test cases verifying voter sampling #105

Merged
merged 17 commits into from
Jul 20, 2020
Merged
37 changes: 37 additions & 0 deletions consensus/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"testing"
"time"

"github.com/tendermint/tendermint/crypto"

"github.com/go-kit/kit/log/term"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -132,6 +134,12 @@ func incrementHeight(vss ...*validatorStub) {
}
}

func incrementHeightByMap(vssMap map[crypto.PubKey]*validatorStub) {
for _, vs := range vssMap {
vs.Height++
}
}

func incrementRound(vss ...*validatorStub) {
for _, vs := range vss {
vs.Round++
Expand Down Expand Up @@ -222,6 +230,15 @@ func signAddVotes(
addVotes(to, votes...)
}

func getValidatorBeingNotVoter(cs *State) *types.Validator {
for _, val := range cs.Validators.Validators {
if !cs.Voters.HasAddress(val.Address) {
return val
}
}
return nil
}

func validatePrevote(t *testing.T, cs *State, round int, privVal *validatorStub, blockHash []byte) {
prevotes := cs.Votes.Prevotes(round)
pubKey, err := privVal.GetPubKey()
Expand Down Expand Up @@ -429,6 +446,26 @@ func randStateWithVoterParams(nValidators int, voterParams *types.VoterParams) (
return cs, vss
}

func randStateWithVoterParamsWithApp(nValidators int, voterParams *types.VoterParams, testName string) (
*State, []*validatorStub) {
// Get State
state, privVals := randGenesisState(nValidators, false, 10, voterParams)
state.LastProofHash = []byte{2}

vss := make([]*validatorStub, nValidators)

app := newPersistentKVStoreWithPath(path.Join(config.DBDir(), testName))
cs := newState(state, privVals[0], app)

for i := 0; i < nValidators; i++ {
vss[i] = newValidatorStub(privVals[i], i)
}
// since cs1 starts at 1
incrementHeight(vss[1:]...)

return cs, vss
}

func theOthers(index int) int {
const theOtherIndex = math.MaxInt32
return theOtherIndex - index
Expand Down
260 changes: 260 additions & 0 deletions consensus/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"testing"
"time"

"github.com/tendermint/tendermint/abci/example/kvstore"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -15,6 +17,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
tmrand "github.com/tendermint/tendermint/libs/rand"
mempl "github.com/tendermint/tendermint/mempool"
p2pmock "github.com/tendermint/tendermint/p2p/mock"
"github.com/tendermint/tendermint/types"
)
Expand Down Expand Up @@ -46,6 +49,9 @@ CatchupSuite
* TestCatchup - if we might be behind and we've seen any 2/3 prevotes, round skip to new round, precommit, or prevote
HaltSuite
x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we should still commit
VoterSamplingSuite
x * TestStateFullRoundWithSelectedVoter - voter sampling version of TestStateFullRound2
x * TestStateAllVoterToSelectedVoter - selected voters increase under the situation that validators change from 5 to 24

*/

Expand Down Expand Up @@ -1997,3 +2003,257 @@ func TestStateFullRoundWithSelectedVoter(t *testing.T) {
// we're going to roll right into new height
ensureNewRound(newRoundCh, height+1, 0)
}

func ensureVotingPowerOfVoteSet(t *testing.T, voteSet *types.VoteSet, votingPower int64) {
assert.True(t, voteSet.GetSum() == votingPower)
}

func TestStateBadVoterWithSelectedVoter(t *testing.T) {
// if validators are 9, then selected voters are 4+
// if one of 4+ voters does not vote, the consensus state does not progress to next step
// making him having 1/3 + 1 voting power of total
cs, vss := randStateWithVoterParams(9, &types.VoterParams{
VoterElectionThreshold: 5,
MaxTolerableByzantinePercentage: 20,
ElectionPrecision: 5})

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

// a validator being not a voter votes instead of a voter
notVoter := getValidatorBeingNotVoter(cs)
if notVoter == nil {
panic("invalid test case: cannot find a validator being not a voter")
}

nonMyIndex := 0
myPub, _ := cs.privValidator.GetPubKey()
for i, v := range cs.Voters.Voters {
if !myPub.Equals(v.PubKey) {
nonMyIndex = i
break
}
}

// make the invalid voter having voting power of 1/3+1 of total
cs.Voters.Voters[nonMyIndex].VotingPower =
(cs.Voters.TotalVotingPower()-cs.Voters.Voters[nonMyIndex].VotingPower)/2 + 1

voters := cs.Voters.Copy()

// make a voter having invalid pub key
voters.Voters[nonMyIndex] = &types.Validator{
PubKey: notVoter.PubKey,
Address: notVoter.Address,
StakingPower: cs.Voters.Voters[nonMyIndex].StakingPower,
VotingPower: cs.Voters.Voters[nonMyIndex].VotingPower,
}

vss[0].Height = 1 // this is needed because of `incrementHeight(vss[1:]...)` of randStateWithVoterParams()
vssMap := makeVssMap(vss)
height, round := cs.Height, cs.Round

voteCh := subscribeUnBuffered(cs.eventBus, types.EventQueryVote)
propCh := subscribe(cs.eventBus, types.EventQueryCompleteProposal)
newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound)
newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)

startTestRound(cs, height, round)

// height 1
ensureNewRound(newRoundCh, height, round)
privPubKey, _ := cs.privValidator.GetPubKey()
if !cs.isProposer(privPubKey.Address()) {
blockID := proposeBlock(t, cs, round, vssMap)
ensureProposal(propCh, height, round, blockID)
} else {
ensureNewProposal(propCh, height, round)
}

propBlock := cs.GetRoundState().ProposalBlock

voterPrivVals := votersPrivVals(voters, vssMap)
signAddVotes(cs, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(types.BlockPartSizeBytes).Header(),
voterPrivVals...)

sumVotingPower := int64(0)
for i := range voterPrivVals { // one failed
if i == nonMyIndex {
continue
}
ensurePrevote(voteCh, height, round) // wait for prevote
sumVotingPower += voters.Voters[i].VotingPower
}

// ensure we didn't get a vote for Voters[nonMyIndex]
ensureVotingPowerOfVoteSet(t, cs.Votes.Prevotes(round), sumVotingPower)
assert.False(t, cs.Votes.Prevotes(round).HasTwoThirdsMajority())

// add remain vote
voterPrivVals = votersPrivVals(cs.Voters, vssMap)
signAddVotes(cs, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(types.BlockPartSizeBytes).Header(),
voterPrivVals[nonMyIndex:nonMyIndex+1]...)

ensurePrevote(voteCh, height, round)

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

for range voterPrivVals {
ensurePrecommit(voteCh, height, round) // wait for precommit
}

ensureNewBlock(newBlockCh, height)

// height 2
incrementHeight(vss...)

ensureNewRound(newRoundCh, height+1, 0)

height = cs.Height
privPubKey, _ = cs.privValidator.GetPubKey()
if !cs.isProposer(privPubKey.Address()) {
blockID := proposeBlock(t, cs, round, vssMap)
ensureProposal(propCh, height, round, blockID)
} else {
ensureNewProposal(propCh, height, round)
}

propBlock = cs.GetRoundState().ProposalBlock
voterPrivVals = votersPrivVals(cs.Voters, vssMap)

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

for range voterPrivVals {
ensurePrevote(voteCh, height, round) // wait for prevote
}

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

for range voterPrivVals {
ensurePrecommit(voteCh, height, round) // wait for precommit
}

ensureNewBlock(newBlockCh, height)

// we're going to roll right into new height
ensureNewRound(newRoundCh, height+1, 0)
}

func addValidator(cs *State, vssMap map[crypto.PubKey]*validatorStub, height int64) {
newVal, privVal := types.RandValidator(false, 10)
valPubKeyABCI := types.TM2PB.PubKey(newVal.PubKey)
newValidatorTx := kvstore.MakeValSetChangeTx(valPubKeyABCI, 10)
_ = assertMempool(cs.txNotifier).CheckTx(newValidatorTx, nil, mempl.TxInfo{})
vssMap[newVal.PubKey] = newValidatorStub(privVal, len(vssMap)+1)
vssMap[newVal.PubKey].Height = height
}

func TestStateAllVoterToSelectedVoter(t *testing.T) {
startValidators := 5
cs, vss := randStateWithVoterParamsWithApp(startValidators, &types.VoterParams{
VoterElectionThreshold: int32(startValidators),
MaxTolerableByzantinePercentage: 20,
ElectionPrecision: 2},
"TestStateAllVoterToSelectedVoter")
vss[0].Height = 1 // this is needed because of `incrementHeight(vss[1:]...)` of randStateWithVoterParams()
vssMap := makeVssMap(vss)
height, round := cs.Height, cs.Round

voteCh := subscribeUnBuffered(cs.eventBus, types.EventQueryVote)
propCh := subscribe(cs.eventBus, types.EventQueryCompleteProposal)
newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound)
newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)

// add first validator
addValidator(cs, vssMap, height)

startTestRound(cs, height, round)

// height 1
ensureNewRound(newRoundCh, height, round)
privPubKey, _ := cs.privValidator.GetPubKey()
if !cs.isProposer(privPubKey.Address()) {
blockID := proposeBlock(t, cs, round, vssMap)
ensureProposal(propCh, height, round, blockID)
} else {
ensureNewProposal(propCh, height, round)
}

propBlock := cs.GetRoundState().ProposalBlock
voters := cs.Voters
voterPrivVals := votersPrivVals(voters, vssMap)
signAddVotes(cs, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(types.BlockPartSizeBytes).Header(),
voterPrivVals...)

for range voterPrivVals {
ensurePrevote(voteCh, height, round) // wait for prevote
}

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

// add sescond validator
addValidator(cs, vssMap, height)

for range voterPrivVals {
ensurePrecommit(voteCh, height, round) // wait for precommit
}

ensureNewBlock(newBlockCh, height)

incrementHeightByMap(vssMap)

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()
if !cs.isProposer(privPubKey.Address()) {
blockID := proposeBlock(t, cs, round, vssMap)
ensureProposal(propCh, height, round, blockID)
} else {
ensureNewProposal(propCh, height, round)
}

propBlock = cs.GetRoundState().ProposalBlock
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...)

for range voterPrivVals {
ensurePrevote(voteCh, height, round) // wait for prevote
}

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

// add next validator
addValidator(cs, vssMap, height)

for range voterPrivVals {
ensurePrecommit(voteCh, height, round) // wait for precommit
}

ensureNewBlock(newBlockCh, height)

incrementHeightByMap(vssMap)

// we're going to roll right into new height
ensureNewRound(newRoundCh, height+1, 0)
}
}
18 changes: 12 additions & 6 deletions libs/rand/sampling.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,20 @@ func RandomSamplingWithoutReplacement(
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 = correction + winner.Priority() * compensationProportions[i]
winPoint := new(big.Int).SetUint64(winner.Priority())
winPoint.Mul(winPoint, &compensationProportions[i])
winPoint.Add(winPoint, correction)

winner.SetWinPoint(winPoint.Div(winPoint, recalibration).Int64())
winPoint := new(big.Int)
winPoint.Mul(recalibration, winPoints[i])
winPoint.Div(winPoint, totalWinPoint)
winner.SetWinPoint(winPoint.Int64())
}
return winners
}
Expand Down
1 change: 0 additions & 1 deletion libs/rand/sampling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ func TestRandomSamplingWithoutReplacementOverflow(t *testing.T) {
assert.True(t, element.winPoint <= lastWinPoint)
lastWinPoint = element.winPoint
}
assert.Equal(t, lastWinPoint, int64(precisionForSelection))
}

func accumulateAndResetReward(candidate []Candidate, acc []uint64) uint64 {
Expand Down
Loading