package types import ( "bytes" "context" "fmt" "math" "math/rand" "sort" "strings" "testing" "testing/quick" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" tmmath "github.com/tendermint/tendermint/libs/math" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) func TestValidatorSetBasic(t *testing.T) { // empty or nil validator lists are allowed, // but attempting to IncrementProposerPriority on them will panic. vset := NewValidatorSet([]*Validator{}) assert.Panics(t, func() { vset.IncrementProposerPriority(1) }) vset = NewValidatorSet(nil) assert.Panics(t, func() { vset.IncrementProposerPriority(1) }) assert.EqualValues(t, vset, vset.Copy()) assert.False(t, vset.HasAddress([]byte("some val"))) idx, val := vset.GetByAddress([]byte("some val")) assert.EqualValues(t, -1, idx) assert.Nil(t, val) addr, val := vset.GetByIndex(-100) assert.Nil(t, addr) assert.Nil(t, val) addr, val = vset.GetByIndex(0) assert.Nil(t, addr) assert.Nil(t, val) addr, val = vset.GetByIndex(100) assert.Nil(t, addr) assert.Nil(t, val) assert.Zero(t, vset.Size()) assert.Equal(t, int64(0), vset.TotalVotingPower()) assert.Nil(t, vset.GetProposer()) assert.Equal(t, []byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}, vset.Hash()) // add val = randModuloValidator(vset.TotalVotingPower()) assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) assert.True(t, vset.HasAddress(val.Address)) idx, _ = vset.GetByAddress(val.Address) assert.EqualValues(t, 0, idx) addr, _ = vset.GetByIndex(0) assert.Equal(t, []byte(val.Address), addr) assert.Equal(t, 1, vset.Size()) assert.Equal(t, val.VotingPower, vset.TotalVotingPower()) assert.NotNil(t, vset.Hash()) assert.NotPanics(t, func() { vset.IncrementProposerPriority(1) }) assert.Equal(t, val.Address, vset.GetProposer().Address) // update val = randModuloValidator(vset.TotalVotingPower()) assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) _, val = vset.GetByAddress(val.Address) val.VotingPower += 100 proposerPriority := val.ProposerPriority val.ProposerPriority = 0 assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) _, val = vset.GetByAddress(val.Address) assert.Equal(t, proposerPriority, val.ProposerPriority) } func TestValidatorSetValidateBasic(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() val, _, err := randValidator(ctx, false, 1) require.NoError(t, err) badVal := &Validator{} testCases := []struct { vals ValidatorSet err bool msg string }{ { vals: ValidatorSet{}, err: true, msg: "validator set is nil or empty", }, { vals: ValidatorSet{ Validators: []*Validator{}, }, err: true, msg: "validator set is nil or empty", }, { vals: ValidatorSet{ Validators: []*Validator{val}, }, err: true, msg: "proposer failed validate basic, error: nil validator", }, { vals: ValidatorSet{ Validators: []*Validator{badVal}, }, err: true, msg: "invalid validator #0: validator does not have a public key", }, { vals: ValidatorSet{ Validators: []*Validator{val}, Proposer: val, }, err: false, msg: "", }, } for _, tc := range testCases { err := tc.vals.ValidateBasic() if tc.err { if assert.Error(t, err) { assert.Equal(t, tc.msg, err.Error()) } } else { assert.NoError(t, err) } } } func TestCopy(t *testing.T) { vset := randModuloValidatorSet(10) vsetHash := vset.Hash() if len(vsetHash) == 0 { t.Fatalf("ValidatorSet had unexpected zero hash") } vsetCopy := vset.Copy() vsetCopyHash := vsetCopy.Hash() if !bytes.Equal(vsetHash, vsetCopyHash) { t.Fatalf("ValidatorSet copy had wrong hash. Orig: %X, Copy: %X", vsetHash, vsetCopyHash) } } // Test that IncrementProposerPriority requires positive times. func TestIncrementProposerPriorityPositiveTimes(t *testing.T) { vset := NewValidatorSet([]*Validator{ newValidator([]byte("foo"), 1000), newValidator([]byte("bar"), 300), newValidator([]byte("baz"), 330), }) assert.Panics(t, func() { vset.IncrementProposerPriority(-1) }) assert.Panics(t, func() { vset.IncrementProposerPriority(0) }) vset.IncrementProposerPriority(1) } func BenchmarkValidatorSetCopy(b *testing.B) { b.StopTimer() vset := NewValidatorSet([]*Validator{}) for i := 0; i < 1000; i++ { privKey := ed25519.GenPrivKey() pubKey := privKey.PubKey() val := NewValidator(pubKey, 10) err := vset.UpdateWithChangeSet([]*Validator{val}) require.NoError(b, err) } b.StartTimer() for i := 0; i < b.N; i++ { vset.Copy() } } //------------------------------------------------------------------- func TestProposerSelection1(t *testing.T) { vset := NewValidatorSet([]*Validator{ newValidator([]byte("foo"), 1000), newValidator([]byte("bar"), 300), newValidator([]byte("baz"), 330), }) var proposers []string for i := 0; i < 99; i++ { val := vset.GetProposer() proposers = append(proposers, string(val.Address)) vset.IncrementProposerPriority(1) } expected := `foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar` + ` foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar` + ` foo baz foo foo bar foo baz foo foo bar foo baz foo foo foo baz bar foo foo foo baz` + ` foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo` + ` foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo` if expected != strings.Join(proposers, " ") { t.Errorf("expected sequence of proposers was\n%v\nbut got \n%v", expected, strings.Join(proposers, " ")) } } func TestProposerSelection2(t *testing.T) { addr0 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} addr1 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} addr2 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} // when all voting power is same, we go in order of addresses val0, val1, val2 := newValidator(addr0, 100), newValidator(addr1, 100), newValidator(addr2, 100) valList := []*Validator{val0, val1, val2} vals := NewValidatorSet(valList) for i := 0; i < len(valList)*5; i++ { ii := (i) % len(valList) prop := vals.GetProposer() if !bytes.Equal(prop.Address, valList[ii].Address) { t.Fatalf("(%d): Expected %X. Got %X", i, valList[ii].Address, prop.Address) } vals.IncrementProposerPriority(1) } // One validator has more than the others, but not enough to propose twice in a row *val2 = *newValidator(addr2, 400) vals = NewValidatorSet(valList) // vals.IncrementProposerPriority(1) prop := vals.GetProposer() if !bytes.Equal(prop.Address, addr2) { t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address) } vals.IncrementProposerPriority(1) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr0) { t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address) } // One validator has more than the others, and enough to be proposer twice in a row *val2 = *newValidator(addr2, 401) vals = NewValidatorSet(valList) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr2) { t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address) } vals.IncrementProposerPriority(1) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr2) { t.Fatalf("Expected address with highest voting power to be second proposer. Got %X", prop.Address) } vals.IncrementProposerPriority(1) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr0) { t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address) } // each validator should be the proposer a proportional number of times val0, val1, val2 = newValidator(addr0, 4), newValidator(addr1, 5), newValidator(addr2, 3) valList = []*Validator{val0, val1, val2} propCount := make([]int, 3) vals = NewValidatorSet(valList) N := 1 for i := 0; i < 120*N; i++ { prop := vals.GetProposer() ii := prop.Address[19] propCount[ii]++ vals.IncrementProposerPriority(1) } if propCount[0] != 40*N { t.Fatalf( "Expected prop count for validator with 4/12 of voting power to be %d/%d. Got %d/%d", 40*N, 120*N, propCount[0], 120*N, ) } if propCount[1] != 50*N { t.Fatalf( "Expected prop count for validator with 5/12 of voting power to be %d/%d. Got %d/%d", 50*N, 120*N, propCount[1], 120*N, ) } if propCount[2] != 30*N { t.Fatalf( "Expected prop count for validator with 3/12 of voting power to be %d/%d. Got %d/%d", 30*N, 120*N, propCount[2], 120*N, ) } } func TestProposerSelection3(t *testing.T) { vset := NewValidatorSet([]*Validator{ newValidator([]byte("avalidator_address12"), 1), newValidator([]byte("bvalidator_address12"), 1), newValidator([]byte("cvalidator_address12"), 1), newValidator([]byte("dvalidator_address12"), 1), }) proposerOrder := make([]*Validator, 4) for i := 0; i < 4; i++ { // need to give all validators to have keys pk := ed25519.GenPrivKey().PubKey() vset.Validators[i].PubKey = pk proposerOrder[i] = vset.GetProposer() vset.IncrementProposerPriority(1) } // i for the loop // j for the times // we should go in order for ever, despite some IncrementProposerPriority with times > 1 var ( i int j int32 ) for ; i < 10000; i++ { got := vset.GetProposer().Address expected := proposerOrder[j%4].Address if !bytes.Equal(got, expected) { t.Fatalf(fmt.Sprintf("vset.Proposer (%X) does not match expected proposer (%X) for (%d, %d)", got, expected, i, j)) } // serialize, deserialize, check proposer b := vset.toBytes(t) vset = vset.fromBytes(t, b) computed := vset.GetProposer() // findGetProposer() if i != 0 { if !bytes.Equal(got, computed.Address) { t.Fatalf( fmt.Sprintf( "vset.Proposer (%X) does not match computed proposer (%X) for (%d, %d)", got, computed.Address, i, j, ), ) } } // times is usually 1 times := int32(1) mod := (rand.Int() % 5) + 1 if rand.Int()%mod > 0 { // sometimes its up to 5 times = (rand.Int31() % 4) + 1 } vset.IncrementProposerPriority(times) j += times } } func newValidator(address []byte, power int64) *Validator { return &Validator{Address: address, VotingPower: power} } func randPubKey() crypto.PubKey { pubKey := make(ed25519.PubKey, ed25519.PubKeySize) copy(pubKey, tmrand.Bytes(32)) return ed25519.PubKey(tmrand.Bytes(32)) } func randModuloValidator(totalVotingPower int64) *Validator { // this modulo limits the ProposerPriority/VotingPower to stay in the // bounds of MaxTotalVotingPower minus the already existing voting power: val := NewValidator(randPubKey(), int64(rand.Uint64()%uint64(MaxTotalVotingPower-totalVotingPower))) val.ProposerPriority = rand.Int63() % (MaxTotalVotingPower - totalVotingPower) return val } func randValidator(ctx context.Context, randPower bool, minPower int64) (*Validator, PrivValidator, error) { privVal := NewMockPV() votePower := minPower if randPower { votePower += int64(rand.Uint32()) } pubKey, err := privVal.GetPubKey(ctx) if err != nil { return nil, nil, err } val := NewValidator(pubKey, votePower) return val, privVal, nil } func randModuloValidatorSet(numValidators int) *ValidatorSet { validators := make([]*Validator, numValidators) totalVotingPower := int64(0) for i := 0; i < numValidators; i++ { validators[i] = randModuloValidator(totalVotingPower) totalVotingPower += validators[i].VotingPower } return NewValidatorSet(validators) } func (vals *ValidatorSet) toBytes(t *testing.T) []byte { pbvs, err := vals.ToProto() require.NoError(t, err) bz, err := pbvs.Marshal() require.NoError(t, err) return bz } func (vals *ValidatorSet) fromBytes(t *testing.T, b []byte) *ValidatorSet { pbvs := new(tmproto.ValidatorSet) err := pbvs.Unmarshal(b) require.NoError(t, err) vs, err := ValidatorSetFromProto(pbvs) require.NoError(t, err) return vs } //------------------------------------------------------------------- func TestValidatorSetTotalVotingPowerPanicsOnOverflow(t *testing.T) { // NewValidatorSet calls IncrementProposerPriority which calls TotalVotingPower() // which should panic on overflows: shouldPanic := func() { NewValidatorSet([]*Validator{ {Address: []byte("a"), VotingPower: math.MaxInt64, ProposerPriority: 0}, {Address: []byte("b"), VotingPower: math.MaxInt64, ProposerPriority: 0}, {Address: []byte("c"), VotingPower: math.MaxInt64, ProposerPriority: 0}, }) } assert.Panics(t, shouldPanic) } func TestAvgProposerPriority(t *testing.T) { // Create Validator set without calling IncrementProposerPriority: tcs := []struct { vs ValidatorSet want int64 }{ 0: {ValidatorSet{Validators: []*Validator{{ProposerPriority: 0}, {ProposerPriority: 0}, {ProposerPriority: 0}}}, 0}, 1: { ValidatorSet{ Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: 0}, {ProposerPriority: 0}}, }, math.MaxInt64 / 3, }, 2: { ValidatorSet{ Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: 0}}, }, math.MaxInt64 / 2, }, 3: { ValidatorSet{ Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: math.MaxInt64}}, }, math.MaxInt64, }, 4: { ValidatorSet{ Validators: []*Validator{{ProposerPriority: math.MinInt64}, {ProposerPriority: math.MinInt64}}, }, math.MinInt64, }, } for i, tc := range tcs { got := tc.vs.computeAvgProposerPriority() assert.Equal(t, tc.want, got, "test case: %v", i) } } func TestAveragingInIncrementProposerPriority(t *testing.T) { // Test that the averaging works as expected inside of IncrementProposerPriority. // Each validator comes with zero voting power which simplifies reasoning about // the expected ProposerPriority. tcs := []struct { vs ValidatorSet times int32 avg int64 }{ 0: {ValidatorSet{ Validators: []*Validator{ {Address: []byte("a"), ProposerPriority: 1}, {Address: []byte("b"), ProposerPriority: 2}, {Address: []byte("c"), ProposerPriority: 3}}}, 1, 2}, 1: {ValidatorSet{ Validators: []*Validator{ {Address: []byte("a"), ProposerPriority: 10}, {Address: []byte("b"), ProposerPriority: -10}, {Address: []byte("c"), ProposerPriority: 1}}}, // this should average twice but the average should be 0 after the first iteration // (voting power is 0 -> no changes) 11, 0}, 2: {ValidatorSet{ Validators: []*Validator{ {Address: []byte("a"), ProposerPriority: 100}, {Address: []byte("b"), ProposerPriority: -10}, {Address: []byte("c"), ProposerPriority: 1}}}, 1, 91 / 3}, } for i, tc := range tcs { // work on copy to have the old ProposerPriorities: newVset := tc.vs.CopyIncrementProposerPriority(tc.times) for _, val := range tc.vs.Validators { _, updatedVal := newVset.GetByAddress(val.Address) assert.Equal(t, updatedVal.ProposerPriority, val.ProposerPriority-tc.avg, "test case: %v", i) } } } func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { // Other than TestAveragingInIncrementProposerPriority this is a more complete test showing // how each ProposerPriority changes in relation to the validator's voting power respectively. // average is zero in each round: vp0 := int64(10) vp1 := int64(1) vp2 := int64(1) total := vp0 + vp1 + vp2 avg := (vp0 + vp1 + vp2 - total) / 3 vals := ValidatorSet{Validators: []*Validator{ {Address: []byte{0}, ProposerPriority: 0, VotingPower: vp0}, {Address: []byte{1}, ProposerPriority: 0, VotingPower: vp1}, {Address: []byte{2}, ProposerPriority: 0, VotingPower: vp2}}} tcs := []struct { vals *ValidatorSet wantProposerPrioritys []int64 times int32 wantProposer *Validator }{ 0: { vals.Copy(), []int64{ // Acumm+VotingPower-Avg: 0 + vp0 - total - avg, // mostest will be subtracted by total voting power (12) 0 + vp1, 0 + vp2}, 1, vals.Validators[0]}, 1: { vals.Copy(), []int64{ (0 + vp0 - total) + vp0 - total - avg, // this will be mostest on 2nd iter, too (0 + vp1) + vp1, (0 + vp2) + vp2}, 2, vals.Validators[0]}, // increment twice -> expect average to be subtracted twice 2: { vals.Copy(), []int64{ 0 + 3*(vp0-total) - avg, // still mostest 0 + 3*vp1, 0 + 3*vp2}, 3, vals.Validators[0]}, 3: { vals.Copy(), []int64{ 0 + 4*(vp0-total), // still mostest 0 + 4*vp1, 0 + 4*vp2}, 4, vals.Validators[0]}, 4: { vals.Copy(), []int64{ 0 + 4*(vp0-total) + vp0, // 4 iters was mostest 0 + 5*vp1 - total, // now this val is mostest for the 1st time (hence -12==totalVotingPower) 0 + 5*vp2}, 5, vals.Validators[1]}, 5: { vals.Copy(), []int64{ 0 + 6*vp0 - 5*total, // mostest again 0 + 6*vp1 - total, // mostest once up to here 0 + 6*vp2}, 6, vals.Validators[0]}, 6: { vals.Copy(), []int64{ 0 + 7*vp0 - 6*total, // in 7 iters this val is mostest 6 times 0 + 7*vp1 - total, // in 7 iters this val is mostest 1 time 0 + 7*vp2}, 7, vals.Validators[0]}, 7: { vals.Copy(), []int64{ 0 + 8*vp0 - 7*total, // mostest again 0 + 8*vp1 - total, 0 + 8*vp2}, 8, vals.Validators[0]}, 8: { vals.Copy(), []int64{ 0 + 9*vp0 - 7*total, 0 + 9*vp1 - total, 0 + 9*vp2 - total}, // mostest 9, vals.Validators[2]}, 9: { vals.Copy(), []int64{ 0 + 10*vp0 - 8*total, // after 10 iters this is mostest again 0 + 10*vp1 - total, // after 6 iters this val is "mostest" once and not in between 0 + 10*vp2 - total}, // in between 10 iters this val is "mostest" once 10, vals.Validators[0]}, 10: { vals.Copy(), []int64{ 0 + 11*vp0 - 9*total, 0 + 11*vp1 - total, // after 6 iters this val is "mostest" once and not in between 0 + 11*vp2 - total}, // after 10 iters this val is "mostest" once 11, vals.Validators[0]}, } for i, tc := range tcs { tc.vals.IncrementProposerPriority(tc.times) assert.Equal(t, tc.wantProposer.Address, tc.vals.GetProposer().Address, "test case: %v", i) for valIdx, val := range tc.vals.Validators { assert.Equal(t, tc.wantProposerPrioritys[valIdx], val.ProposerPriority, "test case: %v, validator: %v", i, valIdx) } } } func TestSafeAdd(t *testing.T) { f := func(a, b int64) bool { c, overflow := safeAdd(a, b) return overflow || (!overflow && c == a+b) } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestSafeAddClip(t *testing.T) { assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, 10)) assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, math.MaxInt64)) assert.EqualValues(t, math.MinInt64, safeAddClip(math.MinInt64, -10)) } func TestSafeSubClip(t *testing.T) { assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, 10)) assert.EqualValues(t, 0, safeSubClip(math.MinInt64, math.MinInt64)) assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, math.MaxInt64)) assert.EqualValues(t, math.MaxInt64, safeSubClip(math.MaxInt64, -10)) } //------------------------------------------------------------------- func TestEmptySet(t *testing.T) { var valList []*Validator valSet := NewValidatorSet(valList) assert.Panics(t, func() { valSet.IncrementProposerPriority(1) }) assert.Panics(t, func() { valSet.RescalePriorities(100) }) assert.Panics(t, func() { valSet.shiftByAvgProposerPriority() }) assert.Panics(t, func() { assert.Zero(t, computeMaxMinPriorityDiff(valSet)) }) valSet.GetProposer() // Add to empty set v1 := newValidator([]byte("v1"), 100) v2 := newValidator([]byte("v2"), 100) valList = []*Validator{v1, v2} assert.NoError(t, valSet.UpdateWithChangeSet(valList)) verifyValidatorSet(t, valSet) // Delete all validators from set v1 = newValidator([]byte("v1"), 0) v2 = newValidator([]byte("v2"), 0) delList := []*Validator{v1, v2} assert.Error(t, valSet.UpdateWithChangeSet(delList)) // Attempt delete from empty set assert.Error(t, valSet.UpdateWithChangeSet(delList)) } func TestUpdatesForNewValidatorSet(t *testing.T) { v1 := newValidator([]byte("v1"), 100) v2 := newValidator([]byte("v2"), 100) valList := []*Validator{v1, v2} valSet := NewValidatorSet(valList) verifyValidatorSet(t, valSet) // Verify duplicates are caught in NewValidatorSet() and it panics v111 := newValidator([]byte("v1"), 100) v112 := newValidator([]byte("v1"), 123) v113 := newValidator([]byte("v1"), 234) valList = []*Validator{v111, v112, v113} assert.Panics(t, func() { NewValidatorSet(valList) }) // Verify set including validator with voting power 0 cannot be created v1 = newValidator([]byte("v1"), 0) v2 = newValidator([]byte("v2"), 22) v3 := newValidator([]byte("v3"), 33) valList = []*Validator{v1, v2, v3} assert.Panics(t, func() { NewValidatorSet(valList) }) // Verify set including validator with negative voting power cannot be created v1 = newValidator([]byte("v1"), 10) v2 = newValidator([]byte("v2"), -20) v3 = newValidator([]byte("v3"), 30) valList = []*Validator{v1, v2, v3} assert.Panics(t, func() { NewValidatorSet(valList) }) } type testVal struct { name string power int64 } func permutation(valList []testVal) []testVal { if len(valList) == 0 { return nil } permList := make([]testVal, len(valList)) perm := rand.Perm(len(valList)) for i, v := range perm { permList[v] = valList[i] } return permList } func createNewValidatorList(testValList []testVal) []*Validator { valList := make([]*Validator, 0, len(testValList)) for _, val := range testValList { valList = append(valList, newValidator([]byte(val.name), val.power)) } return valList } func createNewValidatorSet(testValList []testVal) *ValidatorSet { return NewValidatorSet(createNewValidatorList(testValList)) } func valSetTotalProposerPriority(valSet *ValidatorSet) int64 { sum := int64(0) for _, val := range valSet.Validators { // mind overflow sum = safeAddClip(sum, val.ProposerPriority) } return sum } func verifyValidatorSet(t *testing.T, valSet *ValidatorSet) { // verify that the capacity and length of validators is the same assert.Equal(t, len(valSet.Validators), cap(valSet.Validators)) // verify that the set's total voting power has been updated tvp := valSet.totalVotingPower valSet.updateTotalVotingPower() expectedTvp := valSet.TotalVotingPower() assert.Equal(t, expectedTvp, tvp, "expected TVP %d. Got %d, valSet=%s", expectedTvp, tvp, valSet) // verify that validator priorities are centered valsCount := int64(len(valSet.Validators)) tpp := valSetTotalProposerPriority(valSet) assert.True(t, tpp < valsCount && tpp > -valsCount, "expected total priority in (-%d, %d). Got %d", valsCount, valsCount, tpp) // verify that priorities are scaled dist := computeMaxMinPriorityDiff(valSet) assert.True(t, dist <= PriorityWindowSizeFactor*tvp, "expected priority distance < %d. Got %d", PriorityWindowSizeFactor*tvp, dist) } func toTestValList(valList []*Validator) []testVal { testList := make([]testVal, len(valList)) for i, val := range valList { testList[i].name = string(val.Address) testList[i].power = val.VotingPower } return testList } func testValSet(nVals int, power int64) []testVal { vals := make([]testVal, nVals) for i := 0; i < nVals; i++ { vals[i] = testVal{fmt.Sprintf("v%d", i+1), power} } return vals } type valSetErrTestCase struct { startVals []testVal updateVals []testVal } func executeValSetErrTestCase(t *testing.T, idx int, tt valSetErrTestCase) { // create a new set and apply updates, keeping copies for the checks valSet := createNewValidatorSet(tt.startVals) valSetCopy := valSet.Copy() valList := createNewValidatorList(tt.updateVals) valListCopy := validatorListCopy(valList) err := valSet.UpdateWithChangeSet(valList) // for errors check the validator set has not been changed assert.Error(t, err, "test %d", idx) assert.Equal(t, valSet, valSetCopy, "test %v", idx) // check the parameter list has not changed assert.Equal(t, valList, valListCopy, "test %v", idx) } func TestValSetUpdatesDuplicateEntries(t *testing.T) { testCases := []valSetErrTestCase{ // Duplicate entries in changes { // first entry is duplicated change testValSet(2, 10), []testVal{{"v1", 11}, {"v1", 22}}, }, { // second entry is duplicated change testValSet(2, 10), []testVal{{"v2", 11}, {"v2", 22}}, }, { // change duplicates are separated by a valid change testValSet(2, 10), []testVal{{"v1", 11}, {"v2", 22}, {"v1", 12}}, }, { // change duplicates are separated by a valid change testValSet(3, 10), []testVal{{"v1", 11}, {"v3", 22}, {"v1", 12}}, }, // Duplicate entries in remove { // first entry is duplicated remove testValSet(2, 10), []testVal{{"v1", 0}, {"v1", 0}}, }, { // second entry is duplicated remove testValSet(2, 10), []testVal{{"v2", 0}, {"v2", 0}}, }, { // remove duplicates are separated by a valid remove testValSet(2, 10), []testVal{{"v1", 0}, {"v2", 0}, {"v1", 0}}, }, { // remove duplicates are separated by a valid remove testValSet(3, 10), []testVal{{"v1", 0}, {"v3", 0}, {"v1", 0}}, }, { // remove and update same val testValSet(2, 10), []testVal{{"v1", 0}, {"v2", 20}, {"v1", 30}}, }, { // duplicate entries in removes + changes testValSet(2, 10), []testVal{{"v1", 0}, {"v2", 20}, {"v2", 30}, {"v1", 0}}, }, { // duplicate entries in removes + changes testValSet(3, 10), []testVal{{"v1", 0}, {"v3", 5}, {"v2", 20}, {"v2", 30}, {"v1", 0}}, }, } for i, tt := range testCases { executeValSetErrTestCase(t, i, tt) } } func TestValSetUpdatesOverflows(t *testing.T) { maxVP := MaxTotalVotingPower testCases := []valSetErrTestCase{ { // single update leading to overflow testValSet(2, 10), []testVal{{"v1", math.MaxInt64}}, }, { // single update leading to overflow testValSet(2, 10), []testVal{{"v2", math.MaxInt64}}, }, { // add validator leading to overflow testValSet(1, maxVP), []testVal{{"v2", math.MaxInt64}}, }, { // add validator leading to exceed Max testValSet(1, maxVP-1), []testVal{{"v2", 5}}, }, { // add validator leading to exceed Max testValSet(2, maxVP/3), []testVal{{"v3", maxVP / 2}}, }, { // add validator leading to exceed Max testValSet(1, maxVP), []testVal{{"v2", maxVP}}, }, } for i, tt := range testCases { executeValSetErrTestCase(t, i, tt) } } func TestValSetUpdatesOtherErrors(t *testing.T) { testCases := []valSetErrTestCase{ { // update with negative voting power testValSet(2, 10), []testVal{{"v1", -123}}, }, { // update with negative voting power testValSet(2, 10), []testVal{{"v2", -123}}, }, { // remove non-existing validator testValSet(2, 10), []testVal{{"v3", 0}}, }, { // delete all validators []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}}, []testVal{{"v1", 0}, {"v2", 0}, {"v3", 0}}, }, } for i, tt := range testCases { executeValSetErrTestCase(t, i, tt) } } func TestValSetUpdatesBasicTestsExecute(t *testing.T) { valSetUpdatesBasicTests := []struct { startVals []testVal updateVals []testVal expectedVals []testVal }{ { // no changes testValSet(2, 10), []testVal{}, testValSet(2, 10), }, { // voting power changes testValSet(2, 10), []testVal{{"v2", 22}, {"v1", 11}}, []testVal{{"v2", 22}, {"v1", 11}}, }, { // add new validators []testVal{{"v2", 20}, {"v1", 10}}, []testVal{{"v4", 40}, {"v3", 30}}, []testVal{{"v4", 40}, {"v3", 30}, {"v2", 20}, {"v1", 10}}, }, { // add new validator to middle []testVal{{"v3", 20}, {"v1", 10}}, []testVal{{"v2", 30}}, []testVal{{"v2", 30}, {"v3", 20}, {"v1", 10}}, }, { // add new validator to beginning []testVal{{"v3", 20}, {"v2", 10}}, []testVal{{"v1", 30}}, []testVal{{"v1", 30}, {"v3", 20}, {"v2", 10}}, }, { // delete validators []testVal{{"v3", 30}, {"v2", 20}, {"v1", 10}}, []testVal{{"v2", 0}}, []testVal{{"v3", 30}, {"v1", 10}}, }, } for i, tt := range valSetUpdatesBasicTests { // create a new set and apply updates, keeping copies for the checks valSet := createNewValidatorSet(tt.startVals) valList := createNewValidatorList(tt.updateVals) err := valSet.UpdateWithChangeSet(valList) assert.NoError(t, err, "test %d", i) valListCopy := validatorListCopy(valSet.Validators) // check that the voting power in the set's validators is not changing if the voting power // is changed in the list of validators previously passed as parameter to UpdateWithChangeSet. // this is to make sure copies of the validators are made by UpdateWithChangeSet. if len(valList) > 0 { valList[0].VotingPower++ assert.Equal(t, toTestValList(valListCopy), toTestValList(valSet.Validators), "test %v", i) } // check the final validator list is as expected and the set is properly scaled and centered. assert.Equal(t, tt.expectedVals, toTestValList(valSet.Validators), "test %v", i) verifyValidatorSet(t, valSet) } } // Test that different permutations of an update give the same result. func TestValSetUpdatesOrderIndependenceTestsExecute(t *testing.T) { // startVals - initial validators to create the set with // updateVals - a sequence of updates to be applied to the set. // updateVals is shuffled a number of times during testing to check for same resulting validator set. valSetUpdatesOrderTests := []struct { startVals []testVal updateVals []testVal }{ 0: { // order of changes should not matter, the final validator sets should be the same []testVal{{"v4", 40}, {"v3", 30}, {"v2", 10}, {"v1", 10}}, []testVal{{"v4", 44}, {"v3", 33}, {"v2", 22}, {"v1", 11}}}, 1: { // order of additions should not matter []testVal{{"v2", 20}, {"v1", 10}}, []testVal{{"v3", 30}, {"v4", 40}, {"v5", 50}, {"v6", 60}}}, 2: { // order of removals should not matter []testVal{{"v4", 40}, {"v3", 30}, {"v2", 20}, {"v1", 10}}, []testVal{{"v1", 0}, {"v3", 0}, {"v4", 0}}}, 3: { // order of mixed operations should not matter []testVal{{"v4", 40}, {"v3", 30}, {"v2", 20}, {"v1", 10}}, []testVal{{"v1", 0}, {"v3", 0}, {"v2", 22}, {"v5", 50}, {"v4", 44}}}, } for i, tt := range valSetUpdatesOrderTests { // create a new set and apply updates valSet := createNewValidatorSet(tt.startVals) valSetCopy := valSet.Copy() valList := createNewValidatorList(tt.updateVals) assert.NoError(t, valSetCopy.UpdateWithChangeSet(valList)) // save the result as expected for next updates valSetExp := valSetCopy.Copy() // perform at most 20 permutations on the updates and call UpdateWithChangeSet() n := len(tt.updateVals) maxNumPerms := tmmath.MinInt(20, n*n) for j := 0; j < maxNumPerms; j++ { // create a copy of original set and apply a random permutation of updates valSetCopy := valSet.Copy() valList := createNewValidatorList(permutation(tt.updateVals)) // check there was no error and the set is properly scaled and centered. assert.NoError(t, valSetCopy.UpdateWithChangeSet(valList), "test %v failed for permutation %v", i, valList) verifyValidatorSet(t, valSetCopy) // verify the resulting test is same as the expected assert.Equal(t, valSetCopy, valSetExp, "test %v failed for permutation %v", i, valList) } } } // This tests the private function validator_set.go:applyUpdates() function, used only for additions and changes. // Should perform a proper merge of updatedVals and startVals func TestValSetApplyUpdatesTestsExecute(t *testing.T) { valSetUpdatesBasicTests := []struct { startVals []testVal updateVals []testVal expectedVals []testVal }{ // additions 0: { // prepend []testVal{{"v4", 44}, {"v5", 55}}, []testVal{{"v1", 11}}, []testVal{{"v1", 11}, {"v4", 44}, {"v5", 55}}}, 1: { // append []testVal{{"v4", 44}, {"v5", 55}}, []testVal{{"v6", 66}}, []testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}}}, 2: { // insert []testVal{{"v4", 44}, {"v6", 66}}, []testVal{{"v5", 55}}, []testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}}}, 3: { // insert multi []testVal{{"v4", 44}, {"v6", 66}, {"v9", 99}}, []testVal{{"v5", 55}, {"v7", 77}, {"v8", 88}}, []testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}, {"v7", 77}, {"v8", 88}, {"v9", 99}}}, // changes 4: { // head []testVal{{"v1", 111}, {"v2", 22}}, []testVal{{"v1", 11}}, []testVal{{"v1", 11}, {"v2", 22}}}, 5: { // tail []testVal{{"v1", 11}, {"v2", 222}}, []testVal{{"v2", 22}}, []testVal{{"v1", 11}, {"v2", 22}}}, 6: { // middle []testVal{{"v1", 11}, {"v2", 222}, {"v3", 33}}, []testVal{{"v2", 22}}, []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}}, 7: { // multi []testVal{{"v1", 111}, {"v2", 222}, {"v3", 333}}, []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}, []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}}, // additions and changes 8: { []testVal{{"v1", 111}, {"v2", 22}}, []testVal{{"v1", 11}, {"v3", 33}, {"v4", 44}}, []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}, {"v4", 44}}}, } for i, tt := range valSetUpdatesBasicTests { // create a new validator set with the start values valSet := createNewValidatorSet(tt.startVals) // applyUpdates() with the update values valList := createNewValidatorList(tt.updateVals) valSet.applyUpdates(valList) // check the new list of validators for proper merge assert.Equal(t, toTestValList(valSet.Validators), tt.expectedVals, "test %v", i) } } type testVSetCfg struct { name string startVals []testVal deletedVals []testVal updatedVals []testVal addedVals []testVal expectedVals []testVal expErr error } func randTestVSetCfg(t *testing.T, nBase, nAddMax int) testVSetCfg { if nBase <= 0 || nAddMax < 0 { t.Fatalf("bad parameters %v %v", nBase, nAddMax) } const maxPower = 1000 var nOld, nDel, nChanged, nAdd int nOld = int(uint(rand.Int())%uint(nBase)) + 1 if nBase-nOld > 0 { nDel = int(uint(rand.Int()) % uint(nBase-nOld)) } nChanged = nBase - nOld - nDel if nAddMax > 0 { nAdd = rand.Int()%nAddMax + 1 } cfg := testVSetCfg{} cfg.startVals = make([]testVal, nBase) cfg.deletedVals = make([]testVal, nDel) cfg.addedVals = make([]testVal, nAdd) cfg.updatedVals = make([]testVal, nChanged) cfg.expectedVals = make([]testVal, nBase-nDel+nAdd) for i := 0; i < nBase; i++ { cfg.startVals[i] = testVal{fmt.Sprintf("v%d", i), int64(uint(rand.Int())%maxPower + 1)} if i < nOld { cfg.expectedVals[i] = cfg.startVals[i] } if i >= nOld && i < nOld+nChanged { cfg.updatedVals[i-nOld] = testVal{fmt.Sprintf("v%d", i), int64(uint(rand.Int())%maxPower + 1)} cfg.expectedVals[i] = cfg.updatedVals[i-nOld] } if i >= nOld+nChanged { cfg.deletedVals[i-nOld-nChanged] = testVal{fmt.Sprintf("v%d", i), 0} } } for i := nBase; i < nBase+nAdd; i++ { cfg.addedVals[i-nBase] = testVal{fmt.Sprintf("v%d", i), int64(uint(rand.Int())%maxPower + 1)} cfg.expectedVals[i-nDel] = cfg.addedVals[i-nBase] } sort.Sort(testValsByVotingPower(cfg.startVals)) sort.Sort(testValsByVotingPower(cfg.deletedVals)) sort.Sort(testValsByVotingPower(cfg.updatedVals)) sort.Sort(testValsByVotingPower(cfg.addedVals)) sort.Sort(testValsByVotingPower(cfg.expectedVals)) return cfg } func applyChangesToValSet(t *testing.T, expErr error, valSet *ValidatorSet, valsLists ...[]testVal) { changes := make([]testVal, 0) for _, valsList := range valsLists { changes = append(changes, valsList...) } valList := createNewValidatorList(changes) err := valSet.UpdateWithChangeSet(valList) if expErr != nil { assert.Equal(t, expErr, err) } else { assert.NoError(t, err) } } func TestValSetUpdatePriorityOrderTests(t *testing.T) { const nMaxElections int32 = 5000 testCases := []testVSetCfg{ 0: { // remove high power validator, keep old equal lower power validators startVals: []testVal{{"v3", 1000}, {"v1", 1}, {"v2", 1}}, deletedVals: []testVal{{"v3", 0}}, updatedVals: []testVal{}, addedVals: []testVal{}, expectedVals: []testVal{{"v1", 1}, {"v2", 1}}, }, 1: { // remove high power validator, keep old different power validators startVals: []testVal{{"v3", 1000}, {"v2", 10}, {"v1", 1}}, deletedVals: []testVal{{"v3", 0}}, updatedVals: []testVal{}, addedVals: []testVal{}, expectedVals: []testVal{{"v2", 10}, {"v1", 1}}, }, 2: { // remove high power validator, add new low power validators, keep old lower power startVals: []testVal{{"v3", 1000}, {"v2", 2}, {"v1", 1}}, deletedVals: []testVal{{"v3", 0}}, updatedVals: []testVal{{"v2", 1}}, addedVals: []testVal{{"v5", 50}, {"v4", 40}}, expectedVals: []testVal{{"v5", 50}, {"v4", 40}, {"v1", 1}, {"v2", 1}}, }, // generate a configuration with 100 validators, // randomly select validators for updates and deletes, and // generate 10 new validators to be added 3: randTestVSetCfg(t, 100, 10), 4: randTestVSetCfg(t, 1000, 100), 5: randTestVSetCfg(t, 10, 100), 6: randTestVSetCfg(t, 100, 1000), 7: randTestVSetCfg(t, 1000, 1000), } for _, cfg := range testCases { // create a new validator set valSet := createNewValidatorSet(cfg.startVals) verifyValidatorSet(t, valSet) // run election up to nMaxElections times, apply changes and verify that the priority order is correct verifyValSetUpdatePriorityOrder(t, valSet, cfg, nMaxElections) } } func verifyValSetUpdatePriorityOrder(t *testing.T, valSet *ValidatorSet, cfg testVSetCfg, nMaxElections int32) { // Run election up to nMaxElections times, sort validators by priorities valSet.IncrementProposerPriority(rand.Int31()%nMaxElections + 1) // apply the changes, get the updated validators, sort by priorities applyChangesToValSet(t, nil, valSet, cfg.addedVals, cfg.updatedVals, cfg.deletedVals) // basic checks assert.Equal(t, cfg.expectedVals, toTestValList(valSet.Validators)) verifyValidatorSet(t, valSet) // verify that the added validators have the smallest priority: // - they should be at the beginning of updatedValsPriSorted since it is // sorted by priority if len(cfg.addedVals) > 0 { updatedValsPriSorted := validatorListCopy(valSet.Validators) sort.Sort(validatorsByPriority(updatedValsPriSorted)) addedValsPriSlice := updatedValsPriSorted[:len(cfg.addedVals)] sort.Sort(ValidatorsByVotingPower(addedValsPriSlice)) assert.Equal(t, cfg.addedVals, toTestValList(addedValsPriSlice)) // - and should all have the same priority expectedPri := addedValsPriSlice[0].ProposerPriority for _, val := range addedValsPriSlice[1:] { assert.Equal(t, expectedPri, val.ProposerPriority) } } } func TestNewValidatorSetFromExistingValidators(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() size := 5 vals := make([]*Validator, size) for i := 0; i < size; i++ { pv := NewMockPV() vals[i] = pv.ExtractIntoValidator(ctx, int64(i+1)) } valSet := NewValidatorSet(vals) valSet.IncrementProposerPriority(5) newValSet := NewValidatorSet(valSet.Validators) assert.NotEqual(t, valSet, newValSet) existingValSet, err := ValidatorSetFromExistingValidators(valSet.Validators) assert.NoError(t, err) assert.Equal(t, valSet, existingValSet) assert.Equal(t, valSet.CopyIncrementProposerPriority(3), existingValSet.CopyIncrementProposerPriority(3)) } func TestValSetUpdateOverflowRelated(t *testing.T) { testCases := []testVSetCfg{ { name: "1 no false overflow error messages for updates", startVals: []testVal{{"v2", MaxTotalVotingPower - 1}, {"v1", 1}}, updatedVals: []testVal{{"v1", MaxTotalVotingPower - 1}, {"v2", 1}}, expectedVals: []testVal{{"v1", MaxTotalVotingPower - 1}, {"v2", 1}}, expErr: nil, }, { // this test shows that it is important to apply the updates in the order of the change in power // i.e. apply first updates with decreases in power, v2 change in this case. name: "2 no false overflow error messages for updates", startVals: []testVal{{"v2", MaxTotalVotingPower - 1}, {"v1", 1}}, updatedVals: []testVal{{"v1", MaxTotalVotingPower/2 - 1}, {"v2", MaxTotalVotingPower / 2}}, expectedVals: []testVal{{"v2", MaxTotalVotingPower / 2}, {"v1", MaxTotalVotingPower/2 - 1}}, expErr: nil, }, { name: "3 no false overflow error messages for deletes", startVals: []testVal{{"v1", MaxTotalVotingPower - 2}, {"v2", 1}, {"v3", 1}}, deletedVals: []testVal{{"v1", 0}}, addedVals: []testVal{{"v4", MaxTotalVotingPower - 2}}, expectedVals: []testVal{{"v4", MaxTotalVotingPower - 2}, {"v2", 1}, {"v3", 1}}, expErr: nil, }, { name: "4 no false overflow error messages for adds, updates and deletes", startVals: []testVal{ {"v1", MaxTotalVotingPower / 4}, {"v2", MaxTotalVotingPower / 4}, {"v3", MaxTotalVotingPower / 4}, {"v4", MaxTotalVotingPower / 4}}, deletedVals: []testVal{{"v2", 0}}, updatedVals: []testVal{ {"v1", MaxTotalVotingPower/2 - 2}, {"v3", MaxTotalVotingPower/2 - 3}, {"v4", 2}}, addedVals: []testVal{{"v5", 3}}, expectedVals: []testVal{ {"v1", MaxTotalVotingPower/2 - 2}, {"v3", MaxTotalVotingPower/2 - 3}, {"v5", 3}, {"v4", 2}}, expErr: nil, }, { name: "5 check panic on overflow is prevented: update 8 validators with power int64(math.MaxInt64)/8", startVals: []testVal{ {"v1", 1}, {"v2", 1}, {"v3", 1}, {"v4", 1}, {"v5", 1}, {"v6", 1}, {"v7", 1}, {"v8", 1}, {"v9", 1}}, updatedVals: []testVal{ {"v1", MaxTotalVotingPower}, {"v2", MaxTotalVotingPower}, {"v3", MaxTotalVotingPower}, {"v4", MaxTotalVotingPower}, {"v5", MaxTotalVotingPower}, {"v6", MaxTotalVotingPower}, {"v7", MaxTotalVotingPower}, {"v8", MaxTotalVotingPower}, {"v9", 8}}, expectedVals: []testVal{ {"v1", 1}, {"v2", 1}, {"v3", 1}, {"v4", 1}, {"v5", 1}, {"v6", 1}, {"v7", 1}, {"v8", 1}, {"v9", 1}}, expErr: ErrTotalVotingPowerOverflow, }, } for _, tt := range testCases { tt := tt t.Run(tt.name, func(t *testing.T) { valSet := createNewValidatorSet(tt.startVals) verifyValidatorSet(t, valSet) // execute update and verify returned error is as expected applyChangesToValSet(t, tt.expErr, valSet, tt.addedVals, tt.updatedVals, tt.deletedVals) // verify updated validator set is as expected assert.Equal(t, tt.expectedVals, toTestValList(valSet.Validators)) verifyValidatorSet(t, valSet) }) } } func TestSafeMul(t *testing.T) { testCases := []struct { a int64 b int64 c int64 overflow bool }{ 0: {0, 0, 0, false}, 1: {1, 0, 0, false}, 2: {2, 3, 6, false}, 3: {2, -3, -6, false}, 4: {-2, -3, 6, false}, 5: {-2, 3, -6, false}, 6: {math.MaxInt64, 1, math.MaxInt64, false}, 7: {math.MaxInt64 / 2, 2, math.MaxInt64 - 1, false}, 8: {math.MaxInt64 / 2, 3, 0, true}, 9: {math.MaxInt64, 2, 0, true}, } for i, tc := range testCases { c, overflow := safeMul(tc.a, tc.b) assert.Equal(t, tc.c, c, "#%d", i) assert.Equal(t, tc.overflow, overflow, "#%d", i) } } func TestValidatorSetProtoBuf(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() valset, _ := randValidatorPrivValSet(ctx, t, 10, 100) valset2, _ := randValidatorPrivValSet(ctx, t, 10, 100) valset2.Validators[0] = &Validator{} valset3, _ := randValidatorPrivValSet(ctx, t, 10, 100) valset3.Proposer = nil valset4, _ := randValidatorPrivValSet(ctx, t, 10, 100) valset4.Proposer = &Validator{} testCases := []struct { msg string v1 *ValidatorSet expPass1 bool expPass2 bool }{ {"success", valset, true, true}, {"fail valSet2, pubkey empty", valset2, false, false}, {"fail nil Proposer", valset3, false, false}, {"fail empty Proposer", valset4, false, false}, {"fail empty valSet", &ValidatorSet{}, true, false}, {"false nil", nil, true, false}, } for _, tc := range testCases { protoValSet, err := tc.v1.ToProto() if tc.expPass1 { require.NoError(t, err, tc.msg) } else { require.Error(t, err, tc.msg) } valSet, err := ValidatorSetFromProto(protoValSet) if tc.expPass2 { require.NoError(t, err, tc.msg) require.EqualValues(t, tc.v1, valSet, tc.msg) } else { require.Error(t, err, tc.msg) } } } //--------------------- // Sort validators by priority and address type validatorsByPriority []*Validator func (valz validatorsByPriority) Len() int { return len(valz) } func (valz validatorsByPriority) Less(i, j int) bool { if valz[i].ProposerPriority < valz[j].ProposerPriority { return true } if valz[i].ProposerPriority > valz[j].ProposerPriority { return false } return bytes.Compare(valz[i].Address, valz[j].Address) < 0 } func (valz validatorsByPriority) Swap(i, j int) { valz[i], valz[j] = valz[j], valz[i] } //------------------------------------- type testValsByVotingPower []testVal func (tvals testValsByVotingPower) Len() int { return len(tvals) } func (tvals testValsByVotingPower) Less(i, j int) bool { if tvals[i].power == tvals[j].power { return bytes.Compare([]byte(tvals[i].name), []byte(tvals[j].name)) == -1 } return tvals[i].power > tvals[j].power } func (tvals testValsByVotingPower) Swap(i, j int) { tvals[i], tvals[j] = tvals[j], tvals[i] } //------------------------------------- // Benchmark tests // func BenchmarkUpdates(b *testing.B) { const ( n = 100 m = 2000 ) // Init with n validators vs := make([]*Validator, n) for j := 0; j < n; j++ { vs[j] = newValidator([]byte(fmt.Sprintf("v%d", j)), 100) } valSet := NewValidatorSet(vs) l := len(valSet.Validators) // Make m new validators newValList := make([]*Validator, m) for j := 0; j < m; j++ { newValList[j] = newValidator([]byte(fmt.Sprintf("v%d", j+l)), 1000) } b.ResetTimer() for i := 0; i < b.N; i++ { // Add m validators to valSetCopy valSetCopy := valSet.Copy() assert.NoError(b, valSetCopy.UpdateWithChangeSet(newValList)) } } func BenchmarkValidatorSet_VerifyCommit_Ed25519(b *testing.B) { // nolint ctx, cancel := context.WithCancel(context.Background()) defer cancel() for _, n := range []int{1, 8, 64, 1024} { n := n var ( chainID = "test_chain_id" h = int64(3) blockID = makeBlockIDRandom() ) b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { b.ReportAllocs() // generate n validators voteSet, valSet, vals := randVoteSet(ctx, b, h, 0, tmproto.PrecommitType, n, int64(n*5)) // create a commit with n validators commit, err := makeCommit(ctx, blockID, h, 0, voteSet, vals, time.Now()) require.NoError(b, err) for i := 0; i < b.N/n; i++ { err = valSet.VerifyCommit(chainID, blockID, h, commit) assert.NoError(b, err) } }) } } func BenchmarkValidatorSet_VerifyCommitLight_Ed25519(b *testing.B) { // nolint ctx, cancel := context.WithCancel(context.Background()) defer cancel() for _, n := range []int{1, 8, 64, 1024} { n := n var ( chainID = "test_chain_id" h = int64(3) blockID = makeBlockIDRandom() ) b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { b.ReportAllocs() // generate n validators voteSet, valSet, vals := randVoteSet(ctx, b, h, 0, tmproto.PrecommitType, n, int64(n*5)) // create a commit with n validators commit, err := makeCommit(ctx, blockID, h, 0, voteSet, vals, time.Now()) require.NoError(b, err) for i := 0; i < b.N/n; i++ { err = valSet.VerifyCommitLight(chainID, blockID, h, commit) assert.NoError(b, err) } }) } } func BenchmarkValidatorSet_VerifyCommitLightTrusting_Ed25519(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() for _, n := range []int{1, 8, 64, 1024} { n := n var ( chainID = "test_chain_id" h = int64(3) blockID = makeBlockIDRandom() ) b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { b.ReportAllocs() // generate n validators voteSet, valSet, vals := randVoteSet(ctx, b, h, 0, tmproto.PrecommitType, n, int64(n*5)) // create a commit with n validators commit, err := makeCommit(ctx, blockID, h, 0, voteSet, vals, time.Now()) require.NoError(b, err) for i := 0; i < b.N/n; i++ { err = valSet.VerifyCommitLightTrusting(chainID, commit, tmmath.Fraction{Numerator: 1, Denominator: 3}) assert.NoError(b, err) } }) } } // Testing Utils // deterministicValidatorSet returns a deterministic validator set (size: +numValidators+), // where each validator has a power of 50 // // EXPOSED FOR TESTING. func deterministicValidatorSet(ctx context.Context, t *testing.T) (*ValidatorSet, []PrivValidator) { var ( valz = make([]*Validator, 10) privValidators = make([]PrivValidator, 10) ) t.Helper() for i := 0; i < 10; i++ { // val, privValidator := DeterministicValidator(ed25519.PrivKey([]byte(deterministicKeys[i]))) val, privValidator := deterministicValidator(ctx, t, ed25519.GenPrivKeyFromSecret([]byte(fmt.Sprintf("key: %x", i)))) valz[i] = val privValidators[i] = privValidator } sort.Sort(PrivValidatorsByAddress(privValidators)) return NewValidatorSet(valz), privValidators }