From 1ccc0918f5f57f3e74ae471385258e504df01a27 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sun, 13 Jan 2019 23:40:50 +0100 Subject: [PATCH] More ProposerPriority tests (#2966) * 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 --- state/state_test.go | 341 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) diff --git a/state/state_test.go b/state/state_test.go index 2ca5f8b21..0448008ee 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "math/big" "testing" "github.com/stretchr/testify/assert" @@ -263,6 +264,346 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } } +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) + val1PubKey := ed25519.GenPrivKey().PubKey() + val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotingPower} + + state.Validators = types.NewValidatorSet([]*types.Validator{val1}) + state.NextValidators = state.Validators + + // NewValidatorSet calls IncrementProposerPriority but uses on a copy of val1 + assert.EqualValues(t, 0, val1.ProposerPriority) + + block := makeBlock(state, state.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + 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) + + // add a validator + val2PubKey := ed25519.GenPrivKey().PubKey() + val2VotingPower := int64(100) + updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val2VotingPower} + validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) + assert.NoError(t, err) + updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + require.Equal(t, len(updatedState2.NextValidators.Validators), 2) + _, 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 + + // 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) + updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + require.Equal(t, len(updatedState3.NextValidators.Validators), 2) + _, 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 +} + +func TestProposerPriorityProposerAlternates(t *testing.T) { + // Regression test that would fail if the inner workings of + // IncrementProposerPriority change. + // Additionally, make sure that same power validators alternate if both + // have the same voting power (and the 2nd was added later). + tearDown, _, state := setupTestCase(t) + defer tearDown(t) + origVotinPower := int64(10) + val1PubKey := ed25519.GenPrivKey().PubKey() + val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotinPower} + + // reset state validators to above validator + state.Validators = types.NewValidatorSet([]*types.Validator{val1}) + state.NextValidators = state.Validators + // we only have one validator: + assert.Equal(t, val1PubKey.Address(), state.Validators.Proposer.Address) + + block := makeBlock(state, state.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 + assert.Equal(t, -origVotinPower, 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} + validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) + assert.NoError(t, err) + + updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + require.Equal(t, len(updatedState2.NextValidators.Validators), 2) + assert.Equal(t, updatedState2.Validators, updatedState.NextValidators) + + // val1 will still be proposer as val2 just got added: + assert.Equal(t, val1PubKey.Address(), updatedState.NextValidators.Proposer.Address) + assert.Equal(t, updatedState2.Validators.Proposer.Address, updatedState2.NextValidators.Proposer.Address) + assert.Equal(t, updatedState2.Validators.Proposer.Address, val1PubKey.Address()) + assert.Equal(t, updatedState2.NextValidators.Proposer.Address, val1PubKey.Address()) + + _, updatedVal1 := updatedState2.NextValidators.GetByAddress(val1PubKey.Address()) + _, oldVal1 := updatedState2.Validators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) + + totalPower := origVotinPower + v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) + v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + origVotinPower // -11 + 10 == -1 + v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + origVotinPower // -10 + 10 == 0 + // 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 + // 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 + // -> -11 + 10 (voting power) - (-1) (avg) == 0 + assert.EqualValues(t, expectedVal2Prio, updatedVal2.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) + + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + // proposer changes from now on (every iteration) as long as there are no changes in the validator set: + assert.NotEqual(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) + + assert.Equal(t, updatedState3.Validators, updatedState2.NextValidators) + _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) + _, oldVal1 = updatedState3.Validators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 = updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) + _, oldVal2 := updatedState3.Validators.GetByAddress(val2PubKey.Address()) + + // val2 will be proposer: + 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) + avg = avgSum.Div(avgSum, big.NewInt(2)) + expectedVal1Prio2 := oldVal1.ProposerPriority + origVotinPower - avg.Int64() + expectedVal2Prio2 := oldVal2.ProposerPriority + origVotinPower - avg.Int64() - totalPower + + // -19 + 10 - 0 (avg) == -9 + assert.EqualValues(t, expectedVal1Prio2, updatedVal1.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) + // 0 + 10 - 0 (avg) - 20 (total) == -10 + assert.EqualValues(t, expectedVal2Prio2, updatedVal2.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) + + // no changes in voting power and both validators have same voting power + // -> proposers should alternate: + oldState := updatedState3 + for i := 0; i < 1000; i++ { + // no validator updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + // alternate (and cyclic priorities): + assert.NotEqual(t, updatedState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) + assert.Equal(t, oldState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) + + _, updatedVal1 = updatedState.NextValidators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 = updatedState.NextValidators.GetByAddress(val2PubKey.Address()) + + if i%2 == 0 { + assert.Equal(t, updatedState.Validators.Proposer.Address, val2PubKey.Address()) + assert.Equal(t, expectedVal1Prio, updatedVal1.ProposerPriority) // -19 + assert.Equal(t, expectedVal2Prio, updatedVal2.ProposerPriority) // 0 + } else { + assert.Equal(t, updatedState.Validators.Proposer.Address, val1PubKey.Address()) + assert.Equal(t, expectedVal1Prio2, updatedVal1.ProposerPriority) // -9 + assert.Equal(t, expectedVal2Prio2, updatedVal2.ProposerPriority) // -10 + } + // update for next iteration: + oldState = updatedState + } +} + +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) + genesisPubKey := ed25519.GenPrivKey().PubKey() + // fmt.Println("genesis addr: ", genesisPubKey.Address()) + genesisVal := &types.Validator{Address: genesisPubKey.Address(), PubKey: genesisPubKey, VotingPower: genesisVotingPower} + // reset state validators to above validator + state.Validators = types.NewValidatorSet([]*types.Validator{genesisVal}) + state.NextValidators = state.Validators + require.True(t, len(state.Validators.Validators) == 1) + + // update state a few times with no validator updates + // asserts that the single validator's ProposerPrio stays the same + oldState := state + for i := 0; i < 10; i++ { + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + block := makeBlock(oldState, oldState.LastBlockHeight+1) + 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, + // than -Total == -Voting) + // -> no change in ProposerPrio (stays -Total == -VotingPower): + assert.EqualValues(t, oldState.NextValidators, updatedState.NextValidators) + assert.EqualValues(t, -genesisVotingPower, updatedState.NextValidators.Proposer.ProposerPriority) + + oldState = updatedState + } + // add another validator, do a few iterations (create blocks), + // add more validators with same voting power as the 2nd + // let the genesis validator "unbond", + // 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}) + assert.NoError(t, err) + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, + } + block := makeBlock(oldState, oldState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + + lastState := updatedState + for i := 0; i < 200; i++ { + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + block := makeBlock(lastState, lastState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + + updatedStateInner, err := updateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) + lastState = updatedStateInner + } + // set state to last state of above iteration + state = lastState + + // set oldState to state before above iteration + oldState = updatedState + _, oldGenesisVal := oldState.NextValidators.GetByAddress(genesisVal.Address) + _, newGenesisVal := state.NextValidators.GetByAddress(genesisVal.Address) + _, addedOldVal := oldState.NextValidators.GetByAddress(firstAddedValPubKey.Address()) + _, addedNewVal := state.NextValidators.GetByAddress(firstAddedValPubKey.Address()) + // expect large negative proposer priority for both (genesis validator decreased, 2nd validator increased): + assert.True(t, oldGenesisVal.ProposerPriority > newGenesisVal.ProposerPriority) + assert.True(t, addedOldVal.ProposerPriority < addedNewVal.ProposerPriority) + + // add 10 validators with the same voting power as the one added directly after genesis: + for i := 0; i < 10; i++ { + addedPubKey := ed25519.GenPrivKey().PubKey() + + addedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(addedPubKey), Power: firstAddedValVotingPower} + validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) + assert.NoError(t, err) + + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, + } + block := makeBlock(oldState, oldState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + } + require.Equal(t, 10+2, len(state.NextValidators.Validators)) + + // remove genesis validator: + removeGenesisVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(genesisPubKey), Power: 0} + abciResponses = &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, + } + block = makeBlock(oldState, oldState.LastBlockHeight+1) + blockID = types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + updatedState, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + require.NoError(t, err) + // only the first added val (not the genesis val) should be left + assert.Equal(t, 11, len(updatedState.NextValidators.Validators)) + + // call update state until the effect for the 3rd added validator + // being proposer for a long time after the genesis validator left wears off: + curState := updatedState + count := 0 + isProposerUnchanged := true + for isProposerUnchanged { + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + block = makeBlock(curState, curState.LastBlockHeight+1) + blockID = types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + curState, err = updateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) + if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { + isProposerUnchanged = false + } + 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 + assert.Equal(t, firstProposerChangeExpectedAfter, count) +} + func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { const valSetSize = 2 tearDown, stateDB, state := setupTestCase(t)