diff --git a/types/validator_set_test.go b/types/validator_set_test.go index f0e41a1df..26793cc1e 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -45,7 +45,8 @@ func TestValidatorSetBasic(t *testing.T) { assert.Nil(t, vset.Hash()) // add - val = randValidator_() + + val = randValidator_(vset.TotalVotingPower()) assert.True(t, vset.Add(val)) assert.True(t, vset.HasAddress(val.Address)) idx, val2 := vset.GetByAddress(val.Address) @@ -61,7 +62,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.NotPanics(t, func() { vset.IncrementProposerPriority(1) }) // update - assert.False(t, vset.Update(randValidator_())) + assert.False(t, vset.Update(randValidator_(vset.TotalVotingPower()))) _, val = vset.GetByAddress(val.Address) val.VotingPower += 100 proposerPriority := val.ProposerPriority @@ -73,7 +74,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.Equal(t, proposerPriority, val.ProposerPriority) // remove - val2, removed := vset.Remove(randValidator_().Address) + val2, removed := vset.Remove(randValidator_(vset.TotalVotingPower()).Address) assert.Nil(t, val2) assert.False(t, removed) val2, removed = vset.Remove(val.Address) @@ -280,16 +281,20 @@ func randPubKey() crypto.PubKey { return ed25519.PubKeyEd25519(pubKey) } -func randValidator_() *Validator { - val := NewValidator(randPubKey(), cmn.RandInt64()) - val.ProposerPriority = cmn.RandInt64() % MaxTotalVotingPower +func randValidator_(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(), cmn.RandInt64()%(MaxTotalVotingPower-totalVotingPower)) + val.ProposerPriority = cmn.RandInt64() % (MaxTotalVotingPower - totalVotingPower) return val } func randValidatorSet(numValidators int) *ValidatorSet { validators := make([]*Validator, numValidators) + totalVotingPower := int64(0) for i := 0; i < numValidators; i++ { - validators[i] = randValidator_() + validators[i] = randValidator_(totalVotingPower) + totalVotingPower += validators[i].VotingPower } return NewValidatorSet(validators) } @@ -342,7 +347,174 @@ func TestAvgProposerPriority(t *testing.T) { 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 int + 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, 1 / 3}, + 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. + vals := ValidatorSet{Validators: []*Validator{ + {Address: []byte{0}, ProposerPriority: 0, VotingPower: 10}, + {Address: []byte{1}, ProposerPriority: 0, VotingPower: 1}, + {Address: []byte{2}, ProposerPriority: 0, VotingPower: 1}}} + tcs := []struct { + vals *ValidatorSet + wantProposerPrioritys []int64 + times int + wantProposer *Validator + }{ + + 0: { + vals.Copy(), + []int64{ + // Acumm+VotingPower-Avg: + 0 + 10 - 12 - 4, // mostest will be subtracted by total voting power (12) + 0 + 1 - 4, + 0 + 1 - 4}, + 1, + vals.Validators[0]}, + 1: { + vals.Copy(), + []int64{ + (0 + 10 - 12 - 4) + 10 - 12 + 4, // this will be mostest on 2nd iter, too + (0 + 1 - 4) + 1 + 4, + (0 + 1 - 4) + 1 + 4}, + 2, + vals.Validators[0]}, // increment twice -> expect average to be subtracted twice + 2: { + vals.Copy(), + []int64{ + ((0 + 10 - 12 - 4) + 10 - 12) + 10 - 12 + 4, // still mostest + ((0 + 1 - 4) + 1) + 1 + 4, + ((0 + 1 - 4) + 1) + 1 + 4}, + 3, + vals.Validators[0]}, + 3: { + vals.Copy(), + []int64{ + 0 + 4*(10-12) + 4 - 4, // still mostest + 0 + 4*1 + 4 - 4, + 0 + 4*1 + 4 - 4}, + 4, + vals.Validators[0]}, + 4: { + vals.Copy(), + []int64{ + 0 + 4*(10-12) + 10 + 4 - 4, // 4 iters was mostest + 0 + 5*1 - 12 + 4 - 4, // now this val is mostest for the 1st time (hence -12==totalVotingPower) + 0 + 5*1 + 4 - 4}, + 5, + vals.Validators[1]}, + 5: { + vals.Copy(), + []int64{ + 0 + 6*10 - 5*12 + 4 - 4, // mostest again + 0 + 6*1 - 12 + 4 - 4, // mostest once up to here + 0 + 6*1 + 4 - 4}, + 6, + vals.Validators[0]}, + 6: { + vals.Copy(), + []int64{ + 0 + 7*10 - 6*12 + 4 - 4, // in 7 iters this val is mostest 6 times + 0 + 7*1 - 12 + 4 - 4, // in 7 iters this val is mostest 1 time + 0 + 7*1 + 4 - 4}, + 7, + vals.Validators[0]}, + 7: { + vals.Copy(), + []int64{ + 0 + 8*10 - 7*12 + 4 - 4, // mostest + 0 + 8*1 - 12 + 4 - 4, + 0 + 8*1 + 4 - 4}, + 8, + vals.Validators[0]}, + 8: { + vals.Copy(), + []int64{ + 0 + 9*10 - 7*12 + 4 - 4, + 0 + 9*1 - 12 + 4 - 4, + 0 + 9*1 - 12 + 4 - 4}, // mostest + 9, + vals.Validators[2]}, + 9: { + vals.Copy(), + []int64{ + 0 + 10*10 - 8*12 + 4 - 4, // after 10 iters this is mostest again + 0 + 10*1 - 12 + 4 - 4, // after 6 iters this val is "mostest" once and not in between + 0 + 10*1 - 12 + 4 - 4}, // in between 10 iters this val is "mostest" once + 10, + vals.Validators[0]}, + 10: { + vals.Copy(), + []int64{ + // shift twice inside incrementProposerPriority (shift every 10th iter); + // don't shift at the end of IncremenctProposerPriority + // last avg should be zero because + // ProposerPriority of validator 0: (0 + 11*10 - 8*12 - 4) == 10 + // ProposerPriority of validator 1 and 2: (0 + 11*1 - 12 - 4) == -5 + // and (10 + 5 - 5) / 3 == 0 + 0 + 11*10 - 8*12 - 4 - 12 - 0, + 0 + 11*1 - 12 - 4 - 0, // after 6 iters this val is "mostest" once and not in between + 0 + 11*1 - 12 - 4 - 0}, // 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) {