diff --git a/consensus/common_test.go b/consensus/common_test.go index 14957eee8..499d03bd4 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -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" @@ -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++ @@ -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() @@ -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 diff --git a/consensus/state_test.go b/consensus/state_test.go index 58ebba323..7e45f887a 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/tendermint/tendermint/abci/example/kvstore" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -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" ) @@ -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 */ @@ -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) + } +} diff --git a/libs/rand/sampling.go b/libs/rand/sampling.go index 4d7f85a0b..69e15612c 100644 --- a/libs/rand/sampling.go +++ b/libs/rand/sampling.go @@ -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 } diff --git a/libs/rand/sampling_test.go b/libs/rand/sampling_test.go index e7b261907..0cf544c0b 100644 --- a/libs/rand/sampling_test.go +++ b/libs/rand/sampling_test.go @@ -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 { diff --git a/state/state_test.go b/state/state_test.go index 72357306b..c56db259d 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -281,7 +281,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } } -func isSameVoterSet(t *testing.T, a, b *types.VoterSet) { +func mustBeSameVoterSet(t *testing.T, a, b *types.VoterSet) { assert.True(t, a.Size() == b.Size(), "VoterSet size is different") for i, v := range a.Voters { assert.True(t, bytes.Equal(v.PubKey.Bytes(), b.Voters[i].PubKey.Bytes()), @@ -291,7 +291,7 @@ func isSameVoterSet(t *testing.T, a, b *types.VoterSet) { } } -func isSameValidatorSet(t *testing.T, a, b *types.ValidatorSet) { +func mustBeSameValidatorSet(t *testing.T, a, b *types.ValidatorSet) { assert.True(t, a.Size() == b.Size(), "ValidatorSet size is different") for i, v := range a.Validators { assert.True(t, bytes.Equal(v.PubKey.Bytes(), b.Validators[i].PubKey.Bytes()), @@ -337,14 +337,14 @@ func TestLoadAndSaveVoters(t *testing.T) { for i := int64(1); i <= int64(lastHeight); i++ { voterSet, err := sm.LoadVoters(db, i, voterParam) assert.NoError(t, err, "LoadVoters should succeed") - isSameVoterSet(t, voters[i-1], voterSet) + mustBeSameVoterSet(t, voters[i-1], voterSet) validatorSet, err := sm.LoadValidators(db, i) assert.NoError(t, err, "LoadValidators should succeed") - isSameValidatorSet(t, validators[i-1], validatorSet) + mustBeSameValidatorSet(t, validators[i-1], validatorSet) } validatorSet, err := sm.LoadValidators(db, int64(lastHeight+1)) assert.NoError(t, err, "LoadValidators should succeed") - isSameValidatorSet(t, validators[lastHeight], validatorSet) + mustBeSameValidatorSet(t, validators[lastHeight], validatorSet) } func TestProposerFrequency(t *testing.T) { @@ -959,10 +959,18 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) require.Equal(t, int64(0), state.LastBlockHeight) + + state.VoterParams = &types.VoterParams{ + VoterElectionThreshold: 3, + MaxTolerableByzantinePercentage: 20, + ElectionPrecision: 5, + } state.Validators = genValSet(valSetSize) state.Validators.SelectProposer([]byte{}, 1, 0) state.NextValidators = state.Validators.Copy() state.NextValidators.SelectProposer([]byte{}, 2, 0) + state.Voters = types.SelectVoter(state.Validators, state.LastProofHash, state.VoterParams) + sm.SaveState(stateDB, state) _, valOld := state.Validators.GetByIndex(0) @@ -992,6 +1000,10 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { if index < 0 { t.Fatal("expected to find old validator") } + // verify voters + voterSetOf1, err := sm.LoadVoters(stateDB, nextHeight, state.VoterParams) + assert.NoError(t, err) + mustBeSameVoterSet(t, state.Voters, voterSetOf1) // Load nextheight+1, it should be the new pubkey. v1, err := sm.LoadValidators(stateDB, nextHeight+1) @@ -1002,6 +1014,8 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { if index < 0 { t.Fatal("expected to find newly added validator") } + _, err = sm.LoadVoters(stateDB, nextHeight+1, state.VoterParams) + assert.Error(t, err, sm.ErrNoProofHashForHeight{}) } func TestStateMakeBlock(t *testing.T) { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 4d53c8638..b18ef2d64 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -306,10 +306,14 @@ func randValidator(totalStakingPower int64) *Validator { func randValidatorSet(numValidators int) *ValidatorSet { validators := make([]*Validator, numValidators) - totalStakingPower := int64(0) + totalStakingPower := int64(numValidators) // to depend for total staking power to be over MaxTotalStakingPower for i := 0; i < numValidators; i++ { validators[i] = randValidator(totalStakingPower) totalStakingPower += validators[i].StakingPower + if totalStakingPower >= MaxTotalStakingPower { + // the remainder must have 1 of staking power + totalStakingPower = MaxTotalStakingPower - 1 + } } return NewValidatorSet(validators) } diff --git a/types/vote_set.go b/types/vote_set.go index 1b343f3e5..1825bffe0 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -94,6 +94,16 @@ func NewVoteSet(chainID string, height int64, round int, signedMsgType SignedMsg } } +// used only test +func (voteSet *VoteSet) GetSum() int64 { + if voteSet == nil { + return 0 + } + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + return voteSet.sum +} + func (voteSet *VoteSet) ChainID() string { return voteSet.chainID } diff --git a/types/voter_set_test.go b/types/voter_set_test.go index 8608400ab..e7a4b8399 100644 --- a/types/voter_set_test.go +++ b/types/voter_set_test.go @@ -4,6 +4,8 @@ import ( "math" "testing" + "github.com/tendermint/tendermint/libs/rand" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -247,6 +249,27 @@ func TestCalVotersNum(t *testing.T) { assert.Panics(t, func() { CalNumOfVoterToElect(total, 1.1, 0.9999) }) } +func TestCalNumOfVoterToElect(t *testing.T) { + // result of CalNumOfVoterToElect(1, 0.2, 0.99999) ~ CalNumOfVoterToElect(260, 0.2, 0.99999) + result := []int64{1, 1, 1, 1, 4, 4, 4, 4, 4, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10, 13, + 13, 13, 13, 13, 16, 16, 16, 16, 16, 19, 19, 19, 19, 19, 22, 22, 22, 22, 22, 25, + 25, 25, 25, 25, 28, 28, 28, 28, 28, 31, 31, 31, 31, 31, 34, 34, 34, 34, 34, 37, + 37, 37, 37, 37, 40, 40, 40, 40, 40, 43, 43, 43, 43, 43, 46, 46, 46, 46, 46, 49, + 49, 49, 49, 49, 52, 52, 52, 52, 49, 55, 52, 52, 52, 52, 55, 55, 55, 55, 55, 58, + 58, 58, 58, 58, 61, 61, 58, 58, 58, 61, 61, 61, 61, 61, 64, 64, 64, 64, 61, 67, + 67, 64, 64, 64, 67, 67, 67, 67, 67, 70, 70, 70, 67, 67, 70, 70, 70, 70, 70, 73, + 73, 73, 70, 70, 73, 73, 73, 73, 73, 76, 76, 76, 76, 73, 79, 76, 76, 76, 76, 79, + 79, 79, 76, 76, 79, 79, 79, 79, 79, 82, 82, 82, 79, 79, 82, 82, 82, 82, 82, 85, + 85, 82, 82, 82, 85, 85, 85, 85, 85, 88, 88, 85, 85, 85, 88, 88, 88, 88, 85, 88, + 88, 88, 88, 88, 91, 91, 88, 88, 88, 91, 91, 91, 91, 88, 94, 91, 91, 91, 91, 94, + 94, 94, 91, 91, 94, 94, 94, 94, 94, 97, 94, 94, 94, 94, 97, 97, 97, 94, 94, 97, + 97, 97, 97, 97, 100, 97, 97, 97, 97, 100, 100, 100, 97, 97, 100, 100, 100, 100, 97, 103} + + for i := 1; i <= len(result); i++ { + assert.True(t, CalNumOfVoterToElect(int64(i), 0.2, 0.99999) == result[i-1]) + } +} + func makeByzantine(valSet *ValidatorSet, rate float64) map[string]bool { result := make(map[string]bool) byzantinePower := int64(0) @@ -372,3 +395,46 @@ func TestVoterSetProtoBuf(t *testing.T) { } } } + +func testVotingPower(t *testing.T, valSet *ValidatorSet) { + voterParams := &VoterParams{ + VoterElectionThreshold: 100, + MaxTolerableByzantinePercentage: 20, + ElectionPrecision: 2, + } + + voterSetNoSampling := SelectVoter(valSet, []byte{0}, voterParams) + for _, v := range voterSetNoSampling.Voters { + assert.True(t, v.StakingPower == v.VotingPower) + } + + for i := 90; i > 50; i-- { + voterParams.VoterElectionThreshold = int32(i) + voterSetSampling := SelectVoter(valSet, []byte{0}, voterParams) + allSame := true + for _, v := range voterSetSampling.Voters { + if v.StakingPower != v.VotingPower { + allSame = false + break + } + } + assert.False(t, allSame) + assert.True(t, valSet.TotalStakingPower() > voterSetSampling.TotalVotingPower()) + // total voting power can not be less than total staking power - precisionForSelection(1000) + assert.True(t, valSet.TotalStakingPower()-voterSetSampling.TotalVotingPower() <= 1000) + } +} + +func TestVotingPower(t *testing.T) { + testVotingPower(t, randValidatorSet(100)) + vals := make([]*Validator, 100) + for i := 0; i < len(vals); i++ { + vals[i] = newValidator(rand.Bytes(32), 100) + } + testVotingPower(t, NewValidatorSet(vals)) + vals2 := make([]*Validator, 100) + for i := 0; i < len(vals2); i++ { + vals2[i] = newValidator(rand.Bytes(32), MaxTotalStakingPower/100) + } + testVotingPower(t, NewValidatorSet(vals2)) +}