Browse Source

Normalize priorities to not exceed total voting power (#3049)

* more proposer priority tests

 - test that we don't reset to zero when updating / adding
 - test that same power validators alternate

* add another test to track / simulate similar behaviour as in #2960

* address some of Chris' review comments

* address some more of Chris' review comments

* temporarily pushing branch with the following changes:
The total power might change if:
   - a validator is added
   - a validator is removed
   - a validator is updated

Decrement the accums (of all validators) directly after any of these events
(by the inverse of the change)

* Fix 2960 by re-normalizing / scaling priorities to be in bounds of total
power, additionally:

 - remove heap where it doesn't make sense
 - avg. only at the end of IncrementProposerPriority instead of each
   iteration
 - update (and slightly improve)
   TestAveragingInIncrementProposerPriorityWithVotingPower to reflect
   above changes

* Fix 2960 by re-normalizing / scaling priorities to be in bounds of total
power, additionally:

 - remove heap where it doesn't make sense
 - avg. only at the end of IncrementProposerPriority instead of each
   iteration
 - update (and slightly improve)
   TestAveragingInIncrementProposerPriorityWithVotingPower to reflect
   above changes

* fix tests

* add comment

* update changelog pending & some minor changes

* comment about division will floor the result & fix typo

* Update TestLargeGenesisValidator:
 - remove TODO and increase large genesis validator's voting power
accordingly

* move changelog entry to P2P Protocol

* Ceil instead of flooring when dividing & update test

* quickly fix failing TestProposerPriorityDoesNotGetResetToZero:

 - divide by Ceil((maxPriority - minPriority) / 2*totalVotingPower)

* fix typo: rename getValWitMostPriority -> getValWithMostPriority

* test proposer frequencies

* return absolute value for diff. keep testing

* use for loop for div

* cleanup, more tests

* spellcheck

* get rid of using floats: manually ceil where necessary

* Remove float, simplify, fix tests to match chris's proof (#3157)
pull/3168/head
Ismail Khoffi 6 years ago
committed by Ethan Buchman
parent
commit
40c887baf7
5 changed files with 318 additions and 124 deletions
  1. +2
    -0
      CHANGELOG_PENDING.md
  2. +1
    -1
      p2p/metrics.go
  3. +206
    -40
      state/state_test.go
  4. +67
    -41
      types/validator_set.go
  5. +42
    -42
      types/validator_set_test.go

+ 2
- 0
CHANGELOG_PENDING.md View File

@ -18,6 +18,8 @@ Special thanks to external contributors on this release:
* [merkle] \#2713 Merkle trees now match the RFC 6962 specification
* P2P Protocol
- [consensus] \#2960 normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection
heavily preferring earlier joined validators in the case of an early bonded large validator unbonding
### FEATURES:


+ 1
- 1
p2p/metrics.go View File

@ -72,7 +72,7 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics {
// NopMetrics returns no-op Metrics.
func NopMetrics() *Metrics {
return &Metrics{
Peers: discard.NewGauge(),
Peers: discard.NewGauge(),
PeerReceiveBytesTotal: discard.NewCounter(),
PeerSendBytesTotal: discard.NewCounter(),
PeerPendingSendBytes: discard.NewGauge(),


+ 206
- 40
state/state_test.go View File

@ -3,6 +3,7 @@ package state
import (
"bytes"
"fmt"
"math"
"math/big"
"testing"
@ -264,14 +265,133 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
}
}
func TestProposerFrequency(t *testing.T) {
// some explicit test cases
testCases := []struct {
powers []int64
}{
// 2 vals
{[]int64{1, 1}},
{[]int64{1, 2}},
{[]int64{1, 100}},
{[]int64{5, 5}},
{[]int64{5, 100}},
{[]int64{50, 50}},
{[]int64{50, 100}},
{[]int64{1, 1000}},
// 3 vals
{[]int64{1, 1, 1}},
{[]int64{1, 2, 3}},
{[]int64{1, 2, 3}},
{[]int64{1, 1, 10}},
{[]int64{1, 1, 100}},
{[]int64{1, 10, 100}},
{[]int64{1, 1, 1000}},
{[]int64{1, 10, 1000}},
{[]int64{1, 100, 1000}},
// 4 vals
{[]int64{1, 1, 1, 1}},
{[]int64{1, 2, 3, 4}},
{[]int64{1, 1, 1, 10}},
{[]int64{1, 1, 1, 100}},
{[]int64{1, 1, 1, 1000}},
{[]int64{1, 1, 10, 100}},
{[]int64{1, 1, 10, 1000}},
{[]int64{1, 1, 100, 1000}},
{[]int64{1, 10, 100, 1000}},
}
for caseNum, testCase := range testCases {
// run each case 5 times to sample different
// initial priorities
for i := 0; i < 5; i++ {
valSet := genValSetWithPowers(testCase.powers)
testProposerFreq(t, caseNum, valSet)
}
}
// some random test cases with up to 300 validators
maxVals := 100
maxPower := 1000
nTestCases := 5
for i := 0; i < nTestCases; i++ {
N := cmn.RandInt() % maxVals
vals := make([]*types.Validator, N)
totalVotePower := int64(0)
for j := 0; j < N; j++ {
votePower := int64(cmn.RandInt() % maxPower)
totalVotePower += votePower
privVal := types.NewMockPV()
pubKey := privVal.GetPubKey()
val := types.NewValidator(pubKey, votePower)
val.ProposerPriority = cmn.RandInt64()
vals[j] = val
}
valSet := types.NewValidatorSet(vals)
valSet.RescalePriorities(totalVotePower)
testProposerFreq(t, i, valSet)
}
}
// new val set with given powers and random initial priorities
func genValSetWithPowers(powers []int64) *types.ValidatorSet {
size := len(powers)
vals := make([]*types.Validator, size)
totalVotePower := int64(0)
for i := 0; i < size; i++ {
totalVotePower += powers[i]
val := types.NewValidator(ed25519.GenPrivKey().PubKey(), powers[i])
val.ProposerPriority = cmn.RandInt64()
vals[i] = val
}
valSet := types.NewValidatorSet(vals)
valSet.RescalePriorities(totalVotePower)
return valSet
}
// test a proposer appears as frequently as expected
func testProposerFreq(t *testing.T, caseNum int, valSet *types.ValidatorSet) {
N := valSet.Size()
totalPower := valSet.TotalVotingPower()
// run the proposer selection and track frequencies
runMult := 1
runs := int(totalPower) * runMult
freqs := make([]int, N)
for i := 0; i < runs; i++ {
prop := valSet.GetProposer()
idx, _ := valSet.GetByAddress(prop.Address)
freqs[idx] += 1
valSet.IncrementProposerPriority(1)
}
// assert frequencies match expected (max off by 1)
for i, freq := range freqs {
_, val := valSet.GetByIndex(i)
expectFreq := int(val.VotingPower) * runMult
gotFreq := freq
abs := int(math.Abs(float64(expectFreq - gotFreq)))
// max bound on expected vs seen freq was proven
// to be 1 for the 2 validator case in
// https://github.com/cwgoes/tm-proposer-idris
// and inferred to generalize to N-1
bound := N - 1
require.True(t, abs <= bound, fmt.Sprintf("Case %d val %d (%d): got %d, expected %d", caseNum, i, N, gotFreq, expectFreq))
}
}
// TestProposerPriorityDoesNotGetResetToZero assert that we preserve accum when calling updateState
// see https://github.com/tendermint/tendermint/issues/2718
func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
// assert that we preserve accum when calling updateState:
// https://github.com/tendermint/tendermint/issues/2718
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
origVotingPower := int64(10)
val1VotingPower := int64(10)
val1PubKey := ed25519.GenPrivKey().PubKey()
val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotingPower}
val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: val1VotingPower}
state.Validators = types.NewValidatorSet([]*types.Validator{val1})
state.NextValidators = state.Validators
@ -288,8 +408,9 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
require.NoError(t, err)
updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
assert.NoError(t, err)
assert.Equal(t, -origVotingPower, updatedState.NextValidators.Validators[0].ProposerPriority)
curTotal := val1VotingPower
// one increment step and one validator: 0 + power - total_power == 0
assert.Equal(t, 0+val1VotingPower-curTotal, updatedState.NextValidators.Validators[0].ProposerPriority)
// add a validator
val2PubKey := ed25519.GenPrivKey().PubKey()
@ -301,22 +422,33 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
assert.NoError(t, err)
require.Equal(t, len(updatedState2.NextValidators.Validators), 2)
_, updatedVal1 := updatedState2.NextValidators.GetByAddress(val1PubKey.Address())
_, addedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address())
// adding a validator should not lead to a ProposerPriority equal to zero (unless the combination of averaging and
// incrementing would cause so; which is not the case here)
totalPowerBefore2 := origVotingPower // 10
wantVal2ProposerPrio := -(totalPowerBefore2 + (totalPowerBefore2 >> 3)) + val2VotingPower // 89
avg := (0 + wantVal2ProposerPrio) / 2 // 44
wantVal2ProposerPrio -= avg // 45
totalPowerAfter := origVotingPower + val2VotingPower // 110
wantVal2ProposerPrio -= totalPowerAfter // -65
assert.Equal(t, wantVal2ProposerPrio, addedVal2.ProposerPriority) // not zero == -65
totalPowerBefore2 := curTotal
// while adding we compute prio == -1.125 * total:
wantVal2ProposerPrio := -(totalPowerBefore2 + (totalPowerBefore2 >> 3))
wantVal2ProposerPrio = wantVal2ProposerPrio + val2VotingPower
// then increment:
totalPowerAfter := val1VotingPower + val2VotingPower
// mostest:
wantVal2ProposerPrio = wantVal2ProposerPrio - totalPowerAfter
avg := big.NewInt(0).Add(big.NewInt(val1VotingPower), big.NewInt(wantVal2ProposerPrio))
avg.Div(avg, big.NewInt(2))
wantVal2ProposerPrio = wantVal2ProposerPrio - avg.Int64()
wantVal1Prio := 0 + val1VotingPower - avg.Int64()
assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority)
assert.Equal(t, wantVal2ProposerPrio, addedVal2.ProposerPriority)
// Updating a validator does not reset the ProposerPriority to zero:
updatedVotingPowVal2 := int64(1)
updateVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: updatedVotingPowVal2}
validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateVal})
assert.NoError(t, err)
// this will cause the diff of priorities (31)
// to be larger than threshold == 2*totalVotingPower (22):
updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates)
assert.NoError(t, err)
@ -324,11 +456,18 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
_, prevVal1 := updatedState3.Validators.GetByAddress(val1PubKey.Address())
_, updatedVal2 := updatedState3.NextValidators.GetByAddress(val2PubKey.Address())
expectedVal1PrioBeforeAvg := prevVal1.ProposerPriority + prevVal1.VotingPower // -44 + 10 == -34
wantVal2ProposerPrio = wantVal2ProposerPrio + updatedVotingPowVal2 // -64
avg = (wantVal2ProposerPrio + expectedVal1PrioBeforeAvg) / 2 // (-64-34)/2 == -49
wantVal2ProposerPrio = wantVal2ProposerPrio - avg // -15
assert.Equal(t, wantVal2ProposerPrio, updatedVal2.ProposerPriority) // -15
// divide previous priorities by 2 == CEIL(31/22) as diff > threshold:
expectedVal1PrioBeforeAvg := prevVal1.ProposerPriority/2 + prevVal1.VotingPower
wantVal2ProposerPrio = wantVal2ProposerPrio/2 + updatedVotingPowVal2
// val1 will be proposer:
total := val1VotingPower + updatedVotingPowVal2
expectedVal1PrioBeforeAvg = expectedVal1PrioBeforeAvg - total
avgI64 := (wantVal2ProposerPrio + expectedVal1PrioBeforeAvg) / 2
wantVal2ProposerPrio = wantVal2ProposerPrio - avgI64
wantVal1Prio = expectedVal1PrioBeforeAvg - avgI64
assert.Equal(t, wantVal2ProposerPrio, updatedVal2.ProposerPriority)
_, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address())
assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority)
}
func TestProposerPriorityProposerAlternates(t *testing.T) {
@ -338,9 +477,9 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
// have the same voting power (and the 2nd was added later).
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
origVotinPower := int64(10)
val1VotingPower := int64(10)
val1PubKey := ed25519.GenPrivKey().PubKey()
val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotinPower}
val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: val1VotingPower}
// reset state validators to above validator
state.Validators = types.NewValidatorSet([]*types.Validator{val1})
@ -361,12 +500,14 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
assert.NoError(t, err)
// 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10
assert.Equal(t, -origVotinPower, updatedState.NextValidators.Validators[0].ProposerPriority)
totalPower := val1VotingPower
wantVal1Prio := 0 + val1VotingPower - totalPower
assert.Equal(t, wantVal1Prio, updatedState.NextValidators.Validators[0].ProposerPriority)
assert.Equal(t, val1PubKey.Address(), updatedState.NextValidators.Proposer.Address)
// add a validator with the same voting power as the first
val2PubKey := ed25519.GenPrivKey().PubKey()
updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: origVotinPower}
updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val1VotingPower}
validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal})
assert.NoError(t, err)
@ -386,16 +527,18 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
_, oldVal1 := updatedState2.Validators.GetByAddress(val1PubKey.Address())
_, updatedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address())
totalPower := origVotinPower
totalPower = val1VotingPower // no update
v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3))
v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + origVotinPower // -11 + 10 == -1
v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + origVotinPower // -10 + 10 == 0
v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + val1VotingPower // -11 + 10 == -1
v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + val1VotingPower // -10 + 10 == 0
totalPower = 2 * val1VotingPower // now we have to validators with that power
v1PrioWhenAddedVal2 = v1PrioWhenAddedVal2 - totalPower // mostest
// have to express the AVG in big.Ints as -1/2 == -1 in big.Int while -1/2 == 0 in int64
avgSum := big.NewInt(0).Add(big.NewInt(v2PrioWhenAddedVal2), big.NewInt(v1PrioWhenAddedVal2))
avg := avgSum.Div(avgSum, big.NewInt(2))
expectedVal2Prio := v2PrioWhenAddedVal2 - avg.Int64()
totalPower = 2 * origVotinPower // 10 + 10
expectedVal1Prio := oldVal1.ProposerPriority + origVotinPower - avg.Int64() - totalPower
totalPower = 2 * val1VotingPower // 10 + 10
expectedVal1Prio := oldVal1.ProposerPriority + val1VotingPower - avg.Int64() - totalPower
// val1's ProposerPriority story: -10 (see above) + 10 (voting pow) - (-1) (avg) - 20 (total) == -19
assert.EqualValues(t, expectedVal1Prio, updatedVal1.ProposerPriority)
// val2 prio when added: -(totalVotingPower + (totalVotingPower >> 3)) == -11
@ -421,10 +564,12 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
assert.Equal(t, val2PubKey.Address(), updatedState3.NextValidators.Proposer.Address)
// check if expected proposer prio is matched:
avgSum = big.NewInt(oldVal1.ProposerPriority + origVotinPower + oldVal2.ProposerPriority + origVotinPower)
expectedVal1Prio2 := oldVal1.ProposerPriority + val1VotingPower
expectedVal2Prio2 := oldVal2.ProposerPriority + val1VotingPower - totalPower
avgSum = big.NewInt(expectedVal1Prio + expectedVal2Prio)
avg = avgSum.Div(avgSum, big.NewInt(2))
expectedVal1Prio2 := oldVal1.ProposerPriority + origVotinPower - avg.Int64()
expectedVal2Prio2 := oldVal2.ProposerPriority + origVotinPower - avg.Int64() - totalPower
expectedVal1Prio -= avg.Int64()
expectedVal2Prio -= avg.Int64()
// -19 + 10 - 0 (avg) == -9
assert.EqualValues(t, expectedVal1Prio2, updatedVal1.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2)
@ -468,9 +613,8 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
func TestLargeGenesisValidator(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// TODO: increase genesis voting power to sth. more close to MaxTotalVotingPower with changes that
// fix with tendermint/issues/2960; currently, the last iteration would take forever though
genesisVotingPower := int64(types.MaxTotalVotingPower / 100000000000000)
genesisVotingPower := int64(types.MaxTotalVotingPower / 1000)
genesisPubKey := ed25519.GenPrivKey().PubKey()
// fmt.Println("genesis addr: ", genesisPubKey.Address())
genesisVal := &types.Validator{Address: genesisPubKey.Address(), PubKey: genesisPubKey, VotingPower: genesisVotingPower}
@ -494,11 +638,11 @@ func TestLargeGenesisValidator(t *testing.T) {
blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()}
updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates)
// no changes in voting power (ProposerPrio += VotingPower == 0 in 1st round; than shiftByAvg == no-op,
// no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0,
// than -Total == -Voting)
// -> no change in ProposerPrio (stays -Total == -VotingPower):
// -> no change in ProposerPrio (stays zero):
assert.EqualValues(t, oldState.NextValidators, updatedState.NextValidators)
assert.EqualValues(t, -genesisVotingPower, updatedState.NextValidators.Proposer.ProposerPriority)
assert.EqualValues(t, 0, updatedState.NextValidators.Proposer.ProposerPriority)
oldState = updatedState
}
@ -508,7 +652,6 @@ func TestLargeGenesisValidator(t *testing.T) {
// see how long it takes until the effect wears off and both begin to alternate
// see: https://github.com/tendermint/tendermint/issues/2960
firstAddedValPubKey := ed25519.GenPrivKey().PubKey()
// fmt.Println("first added addr: ", firstAddedValPubKey.Address())
firstAddedValVotingPower := int64(10)
firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower}
validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal})
@ -598,10 +741,33 @@ func TestLargeGenesisValidator(t *testing.T) {
}
count++
}
// first proposer change happens after this many iters; we probably want to lower this number:
// TODO: change with https://github.com/tendermint/tendermint/issues/2960
firstProposerChangeExpectedAfter := 438
updatedState = curState
// the proposer changes after this number of blocks
firstProposerChangeExpectedAfter := 1
assert.Equal(t, firstProposerChangeExpectedAfter, count)
// store proposers here to see if we see them again in the same order:
numVals := len(updatedState.Validators.Validators)
proposers := make([]*types.Validator, numVals)
for i := 0; i < 100; i++ {
// no updates:
abciResponses := &ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
}
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates)
require.NoError(t, err)
block := makeBlock(updatedState, updatedState.LastBlockHeight+1)
blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()}
updatedState, err = updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates)
if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks):
if proposers[i%numVals] == nil {
proposers[i%numVals] = updatedState.NextValidators.Proposer
} else {
assert.Equal(t, proposers[i%numVals], updatedState.NextValidators.Proposer)
}
}
}
}
func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) {


+ 67
- 41
types/validator_set.go View File

@ -13,13 +13,13 @@ import (
)
// The maximum allowed total voting power.
// We set the ProposerPriority of freshly added validators to -1.125*totalVotingPower.
// To compute 1.125*totalVotingPower efficiently, we do:
// totalVotingPower + (totalVotingPower >> 3) because
// x + (x >> 3) = x + x/8 = x * (1 + 0.125).
// MaxTotalVotingPower is the largest int64 `x` with the property that `x + (x >> 3)` is
// still in the bounds of int64.
const MaxTotalVotingPower = int64(8198552921648689607)
// It needs to be sufficiently small to, in all cases::
// 1. prevent clipping in incrementProposerPriority()
// 2. let (diff+diffMax-1) not overflow in IncrementPropposerPriotity()
// (Proof of 1 is tricky, left to the reader).
// It could be higher, but this is sufficiently large for our purposes,
// and leaves room for defensive purposes.
const MaxTotalVotingPower = int64(math.MaxInt64) / 8
// ValidatorSet represent a set of *Validator at a given height.
// The validators can be fetched by address or index.
@ -78,44 +78,57 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) {
panic("Cannot call IncrementProposerPriority with non-positive times")
}
const shiftEveryNthIter = 10
// Cap the difference between priorities to be proportional to 2*totalPower by
// re-normalizing priorities, i.e., rescale all priorities by multiplying with:
// 2*totalVotingPower/(maxPriority - minPriority)
diffMax := 2 * vals.TotalVotingPower()
vals.RescalePriorities(diffMax)
var proposer *Validator
// call IncrementProposerPriority(1) times times:
for i := 0; i < times; i++ {
shiftByAvgProposerPriority := i%shiftEveryNthIter == 0
proposer = vals.incrementProposerPriority(shiftByAvgProposerPriority)
}
isShiftedAvgOnLastIter := (times-1)%shiftEveryNthIter == 0
if !isShiftedAvgOnLastIter {
validatorsHeap := cmn.NewHeap()
vals.shiftByAvgProposerPriority(validatorsHeap)
proposer = vals.incrementProposerPriority()
}
vals.shiftByAvgProposerPriority()
vals.Proposer = proposer
}
func (vals *ValidatorSet) incrementProposerPriority(subAvg bool) *Validator {
for _, val := range vals.Validators {
// Check for overflow for sum.
val.ProposerPriority = safeAddClip(val.ProposerPriority, val.VotingPower)
func (vals *ValidatorSet) RescalePriorities(diffMax int64) {
// NOTE: This check is merely a sanity check which could be
// removed if all tests would init. voting power appropriately;
// i.e. diffMax should always be > 0
if diffMax == 0 {
return
}
validatorsHeap := cmn.NewHeap()
if subAvg { // shift by avg ProposerPriority
vals.shiftByAvgProposerPriority(validatorsHeap)
} else { // just update the heap
// Caculating ceil(diff/diffMax):
// Re-normalization is performed by dividing by an integer for simplicity.
// NOTE: This may make debugging priority issues easier as well.
diff := computeMaxMinPriorityDiff(vals)
ratio := (diff + diffMax - 1) / diffMax
if ratio > 1 {
for _, val := range vals.Validators {
validatorsHeap.PushComparable(val, proposerPriorityComparable{val})
val.ProposerPriority /= ratio
}
}
}
func (vals *ValidatorSet) incrementProposerPriority() *Validator {
for _, val := range vals.Validators {
// Check for overflow for sum.
newPrio := safeAddClip(val.ProposerPriority, val.VotingPower)
val.ProposerPriority = newPrio
}
// Decrement the validator with most ProposerPriority:
mostest := validatorsHeap.Peek().(*Validator)
mostest := vals.getValWithMostPriority()
// mind underflow
mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower())
return mostest
}
// should not be called on an empty validator set
func (vals *ValidatorSet) computeAvgProposerPriority() int64 {
n := int64(len(vals.Validators))
sum := big.NewInt(0)
@ -131,11 +144,38 @@ func (vals *ValidatorSet) computeAvgProposerPriority() int64 {
panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg))
}
func (vals *ValidatorSet) shiftByAvgProposerPriority(validatorsHeap *cmn.Heap) {
// compute the difference between the max and min ProposerPriority of that set
func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 {
max := int64(math.MinInt64)
min := int64(math.MaxInt64)
for _, v := range vals.Validators {
if v.ProposerPriority < min {
min = v.ProposerPriority
}
if v.ProposerPriority > max {
max = v.ProposerPriority
}
}
diff := max - min
if diff < 0 {
return -1 * diff
} else {
return diff
}
}
func (vals *ValidatorSet) getValWithMostPriority() *Validator {
var res *Validator
for _, val := range vals.Validators {
res = res.CompareProposerPriority(val)
}
return res
}
func (vals *ValidatorSet) shiftByAvgProposerPriority() {
avgProposerPriority := vals.computeAvgProposerPriority()
for _, val := range vals.Validators {
val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority)
validatorsHeap.PushComparable(val, proposerPriorityComparable{val})
}
}
@ -508,20 +548,6 @@ func (valz ValidatorsByAddress) Swap(i, j int) {
valz[j] = it
}
//-------------------------------------
// Use with Heap for sorting validators by ProposerPriority
type proposerPriorityComparable struct {
*Validator
}
// We want to find the validator with the greatest ProposerPriority.
func (ac proposerPriorityComparable) Less(o interface{}) bool {
other := o.(proposerPriorityComparable).Validator
larger := ac.CompareProposerPriority(other)
return bytes.Equal(larger.Address, ac.Address)
}
//----------------------------------------
// For testing


+ 42
- 42
types/validator_set_test.go View File

@ -392,10 +392,16 @@ func TestAveragingInIncrementProposerPriority(t *testing.T) {
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: 10},
{Address: []byte{1}, ProposerPriority: 0, VotingPower: 1},
{Address: []byte{2}, ProposerPriority: 0, VotingPower: 1}}}
{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
@ -407,95 +413,89 @@ func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) {
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},
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 + 10 - 12 - 4) + 10 - 12 + 4, // this will be mostest on 2nd iter, too
(0 + 1 - 4) + 1 + 4,
(0 + 1 - 4) + 1 + 4},
(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 + 10 - 12 - 4) + 10 - 12) + 10 - 12 + 4, // still mostest
((0 + 1 - 4) + 1) + 1 + 4,
((0 + 1 - 4) + 1) + 1 + 4},
0 + 3*(vp0-total) - avg, // still mostest
0 + 3*vp1,
0 + 3*vp2},
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},
0 + 4*(vp0-total), // still mostest
0 + 4*vp1,
0 + 4*vp2},
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},
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*10 - 5*12 + 4 - 4, // mostest again
0 + 6*1 - 12 + 4 - 4, // mostest once up to here
0 + 6*1 + 4 - 4},
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*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},
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*10 - 7*12 + 4 - 4, // mostest
0 + 8*1 - 12 + 4 - 4,
0 + 8*1 + 4 - 4},
0 + 8*vp0 - 7*total, // mostest again
0 + 8*vp1 - total,
0 + 8*vp2},
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
0 + 9*vp0 - 7*total,
0 + 9*vp1 - total,
0 + 9*vp2 - total}, // 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
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{
// 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
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]},
}


Loading…
Cancel
Save