From 22173430a8f6476b744debe6eb970b41c7285215 Mon Sep 17 00:00:00 2001 From: beer-1 Date: Mon, 11 May 2026 14:54:48 +0900 Subject: [PATCH] fix test to cover sequencing reactor --- state/execution_test.go | 6 +- state/helpers_test.go | 46 ++- state/rollback_test.go | 5 +- state/state_test.go | 731 +++--------------------------------- state/store_test.go | 5 +- state/validation_test.go | 35 +- types/validation.go | 12 +- types/validator_set.go | 3 + types/validator_set_test.go | 377 ++----------------- 9 files changed, 145 insertions(+), 1075 deletions(-) diff --git a/state/execution_test.go b/state/execution_test.go index 8eb0f02bf9..c7b10c2079 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -286,7 +286,7 @@ func TestFinalizeBlockMisbehavior(t *testing.T) { // we don't need to worry about validating the evidence as long as they pass validate basic dve, err := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID) require.NoError(t, err) - dve.ValidatorPower = 1000 + dve.ValidatorPower = state.Validators.Validators[0].VotingPower lcae := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ SignedHeader: &types.SignedHeader{ @@ -317,7 +317,7 @@ func TestFinalizeBlockMisbehavior(t *testing.T) { Height: 3, Time: defaultEvidenceTime, Validator: types.TM2PB.Validator(state.Validators.Validators[0]), - TotalVotingPower: 10, + TotalVotingPower: dve.TotalVotingPower, }, { Type: abci.MisbehaviorType_LIGHT_CLIENT_ATTACK, @@ -415,7 +415,7 @@ func TestProcessProposal(t *testing.T) { BlockIdFlag: cmtproto.BlockIDFlagCommit, Validator: abci.Validator{ Address: addr, - Power: 1000, + Power: testValidatorPower(0), }, }) lastCommitSig = append(lastCommitSig, vote.CommitSig()) diff --git a/state/helpers_test.go b/state/helpers_test.go index 89bdf932c3..a90532accb 100644 --- a/state/helpers_test.go +++ b/state/helpers_test.go @@ -132,7 +132,7 @@ func makeState(nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValida vals[i] = types.GenesisValidator{ Address: valAddr, PubKey: pk.PubKey(), - Power: 1000, + Power: testValidatorPower(i), Name: fmt.Sprintf("test%d", i), } privVals[valAddr.String()] = types.NewMockPVWithParams(pk, false, false) @@ -165,11 +165,18 @@ func makeState(nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValida func genValSet(size int) *types.ValidatorSet { vals := make([]*types.Validator, size) for i := 0; i < size; i++ { - vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), 10) + vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), testValidatorPower(i)) } return types.NewValidatorSet(vals) } +func testValidatorPower(index int) int64 { + if index == 0 { + return types.SequencerVotingPower + } + return types.AttestorVotingPower +} + func makeHeaderPartsResponsesValPubKeyChange( state sm.State, pubkey crypto.PubKey, @@ -180,32 +187,11 @@ func makeHeaderPartsResponsesValPubKeyChange( } abciResponses := &abci.ResponseFinalizeBlock{} // If the pubkey is new, remove the old and add the new. - _, val := state.NextValidators.GetByIndex(0) + val := getSequencer(state.NextValidators) if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { abciResponses.ValidatorUpdates = []abci.ValidatorUpdate{ types.TM2PB.NewValidatorUpdate(val.PubKey, 0), - types.TM2PB.NewValidatorUpdate(pubkey, 10), - } - } - - return block.Header, types.BlockID{Hash: block.Hash(), PartSetHeader: types.PartSetHeader{}}, abciResponses -} - -func makeHeaderPartsResponsesValPowerChange( - state sm.State, - power int64, -) (types.Header, types.BlockID, *abci.ResponseFinalizeBlock) { - block, err := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - if err != nil { - return types.Header{}, types.BlockID{}, nil - } - abciResponses := &abci.ResponseFinalizeBlock{} - - // If the pubkey is new, remove the old and add the new. - _, val := state.NextValidators.GetByIndex(0) - if val.VotingPower != power { - abciResponses.ValidatorUpdates = []abci.ValidatorUpdate{ - types.TM2PB.NewValidatorUpdate(val.PubKey, power), + types.TM2PB.NewValidatorUpdate(pubkey, val.VotingPower), } } @@ -226,6 +212,16 @@ func makeHeaderPartsResponsesParams( return block.Header, types.BlockID{Hash: block.Hash(), PartSetHeader: types.PartSetHeader{}}, abciResponses } +func getSequencer(vals *types.ValidatorSet) *types.Validator { + for i := 0; i < vals.Size(); i++ { + _, val := vals.GetByIndex(int32(i)) + if val.VotingPower == types.SequencerVotingPower { + return val + } + } + return nil +} + func randomGenesisDoc() *types.GenesisDoc { pubkey := ed25519.GenPrivKey().PubKey() return &types.GenesisDoc{ diff --git a/state/rollback_test.go b/state/rollback_test.go index 67c90c404c..a15b570f52 100644 --- a/state/rollback_test.go +++ b/state/rollback_test.go @@ -91,7 +91,7 @@ func TestRollbackHard(t *testing.T) { blockStore := store.NewBlockStore(dbm.NewMemDB()) stateStore := state.NewStore(dbm.NewMemDB(), state.StoreOptions{DiscardABCIResponses: false}) - valSet, _ := types.RandValidatorSet(5, 10) + valSet := genValSet(5) params := types.DefaultConsensusParams() params.Version.App = 10 @@ -240,7 +240,7 @@ func TestRollbackDifferentStateHeight(t *testing.T) { func setupStateStore(t *testing.T, height int64) state.Store { stateStore := state.NewStore(dbm.NewMemDB(), state.StoreOptions{DiscardABCIResponses: false}) - valSet, _ := types.RandValidatorSet(5, 10) + valSet := genValSet(5) params := types.DefaultConsensusParams() params.Version.App = 10 @@ -347,6 +347,7 @@ func TestRollback_To(t *testing.T) { } // rollback the state + blockStore.On("DeleteBlocksFromHeight", nextHeight).Return(nil) rollbackHeight, rollbackHash, err := state.RollbackTo(blockStore, stateStore, height, false) require.NoError(t, err) require.EqualValues(t, height, rollbackHeight) diff --git a/state/state_test.go b/state/state_test.go index 4df6c1589e..02798ecad1 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -3,8 +3,6 @@ package state_test import ( "bytes" "fmt" - "math" - "math/big" "os" "testing" @@ -15,9 +13,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/crypto/ed25519" - cryptoenc "github.com/cometbft/cometbft/crypto/encoding" "github.com/cometbft/cometbft/internal/test" - cmtrand "github.com/cometbft/cometbft/libs/rand" sm "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/types" ) @@ -265,13 +261,19 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { assert.Equal(vp1.Hash(), state.NextValidators.Hash(), "expected next validator hashes to match") } -// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes. -func TestOneValidatorChangesSaveLoad(t *testing.T) { +// TestSequencerValidatorChangesSaveLoad tests saving and loading validator set +// history when the single sequencer validator is replaced. +func TestSequencerValidatorChangesSaveLoad(t *testing.T) { tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardABCIResponses: false, }) + state.Validators = genValSet(1) + state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) + err := stateStore.Save(state) + require.NoError(t, err) + initialSequencerAddr := getSequencer(state.Validators).Address // Change vals at these heights. changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20} @@ -281,17 +283,16 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { // with the right validator set for each height. highestHeight := changeHeights[N-1] + 5 changeIndex := 0 - _, val := state.Validators.GetByIndex(0) - power := val.VotingPower - var err error + replacementAddrs := make(map[int64][]byte, N) var validatorUpdates []*types.Validator for i := int64(1); i < highestHeight; i++ { - // When we get to a change height, use the next pubkey. + pubkey := getSequencer(state.NextValidators).PubKey if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] { + pubkey = ed25519.GenPrivKey().PubKey() + replacementAddrs[i] = pubkey.Address() changeIndex++ - power++ } - header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, power) + header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, pubkey) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.ValidatorUpdates) require.NoError(t, err) state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) @@ -300,638 +301,60 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { require.NoError(t, err) } - // On each height change, increment the power by one. - testCases := make([]int64, highestHeight) + testCases := make([][]byte, highestHeight) changeIndex = 0 - power = val.VotingPower + expectedAddr := initialSequencerAddr for i := int64(1); i < highestHeight+1; i++ { - // We get to the height after a change height use the next pubkey (note + // We get to the height after a change height use the next validator (note // our counter starts at 0 this time). if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 { + expectedAddr = replacementAddrs[changeHeights[changeIndex]] changeIndex++ - power++ } - testCases[i-1] = power + testCases[i-1] = expectedAddr } - for i, power := range testCases { + for i, expectedAddr := range testCases { v, err := stateStore.LoadValidators(int64(i + 1 + 1)) // +1 because vset changes delayed by 1 block. assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i)) - assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size()) - _, val := v.GetByIndex(0) - - assert.Equal(t, val.VotingPower, power, fmt.Sprintf(`unexpected powerat - height %d`, i)) + assert.Equal(t, v.Size(), 1, "validator set size is not 1: %d", v.Size()) + _, val := v.GetByAddress(expectedAddr) + require.NotNil(t, val) + assert.Equal(t, int64(types.SequencerVotingPower), val.VotingPower, "unexpected power at height %d", i) } } func TestProposerFrequency(t *testing.T) { - // some explicit test cases testCases := []struct { + name string 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 100 validators - maxVals := 100 - maxPower := 1000 - nTestCases := 5 - for i := 0; i < nTestCases; i++ { - N := cmtrand.Int()%maxVals + 1 - vals := make([]*types.Validator, N) - totalVotePower := int64(0) - for j := 0; j < N; j++ { - // make sure votePower > 0 - votePower := int64(cmtrand.Int()%maxPower) + 1 - totalVotePower += votePower - privVal := types.NewMockPV() - pubKey, err := privVal.GetPubKey() - require.NoError(t, err) - val := types.NewValidator(pubKey, votePower) - val.ProposerPriority = cmtrand.Int64() - 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 = cmtrand.Int64() - 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]++ - valSet.IncrementProposerPriority(1) - } - - // assert frequencies match expected (max off by 1) - for i, freq := range freqs { - _, val := valSet.GetByIndex(int32(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) { - tearDown, _, state := setupTestCase(t) - defer tearDown(t) - val1VotingPower := int64(10) - val1PubKey := ed25519.GenPrivKey().PubKey() - val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: val1VotingPower} - - 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, err := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - abciResponses := &abci.ResponseFinalizeBlock{} - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) - assert.NoError(t, err) - 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() - val2VotingPower := int64(100) - fvp, err := cryptoenc.PubKeyToProto(val2PubKey) - require.NoError(t, err) - - updateAddVal := abci.ValidatorUpdate{PubKey: fvp, Power: val2VotingPower} - validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) - assert.NoError(t, err) - updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) - 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) - // Steps from adding new validator: - // 0 - val1 prio is 0, TVP after add: - wantVal1Prio := int64(0) - totalPowerAfter := val1VotingPower + val2VotingPower - // 1. Add - Val2 should be initially added with (-123) => - wantVal2Prio := -(totalPowerAfter + (totalPowerAfter >> 3)) - // 2. Scale - noop - // 3. Center - with avg, resulting val2:-61, val1:62 - avg := big.NewInt(0).Add(big.NewInt(wantVal1Prio), big.NewInt(wantVal2Prio)) - avg.Div(avg, big.NewInt(2)) - wantVal2Prio -= avg.Int64() // -61 - wantVal1Prio -= avg.Int64() // 62 - - // 4. Steps from IncrementProposerPriority - wantVal1Prio += val1VotingPower // 72 - wantVal2Prio += val2VotingPower // 39 - wantVal1Prio -= totalPowerAfter // -38 as val1 is proposer - - assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) - assert.Equal(t, wantVal2Prio, addedVal2.ProposerPriority) - - // Updating a validator does not reset the ProposerPriority to zero: - // 1. Add - Val2 VotingPower change to 1 => - updatedVotingPowVal2 := int64(1) - updateVal := abci.ValidatorUpdate{PubKey: fvp, Power: updatedVotingPowVal2} - validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateVal}) - assert.NoError(t, err) - - // this will cause the diff of priorities (77) - // to be larger than threshold == 2*totalVotingPower (22): - updatedState3, err := sm.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()) - _, prevVal2 := updatedState3.Validators.GetByAddress(val2PubKey.Address()) - _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) - _, updatedVal2 := updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) - - // 2. Scale - // old prios: v1(10):-38, v2(1):39 - wantVal1Prio = prevVal1.ProposerPriority - wantVal2Prio = prevVal2.ProposerPriority - // scale to diffMax = 22 = 2 * tvp, diff=39-(-38)=77 - // new totalPower - totalPower := updatedVal1.VotingPower + updatedVal2.VotingPower - dist := wantVal2Prio - wantVal1Prio - // ratio := (dist + 2*totalPower - 1) / 2*totalPower = 98/22 = 4 - ratio := (dist + 2*totalPower - 1) / (2 * totalPower) - // v1(10):-38/4, v2(1):39/4 - wantVal1Prio /= ratio // -9 - wantVal2Prio /= ratio // 9 - - // 3. Center - noop - // 4. IncrementProposerPriority() -> - // v1(10):-9+10, v2(1):9+1 -> v2 proposer so subsract tvp(11) - // v1(10):1, v2(1):-1 - wantVal2Prio += updatedVal2.VotingPower // 10 -> prop - wantVal1Prio += updatedVal1.VotingPower // 1 - wantVal2Prio -= totalPower // -1 - - assert.Equal(t, wantVal2Prio, updatedVal2.ProposerPriority) - assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) -} - -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) - val1VotingPower := int64(10) - val1PubKey := ed25519.GenPrivKey().PubKey() - val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: val1VotingPower} - - // 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, err := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - assert.NoError(t, err) - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - // no updates: - abciResponses := &abci.ResponseFinalizeBlock{} - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) - assert.NoError(t, err) - - // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 - 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() - fvp, err := cryptoenc.PubKeyToProto(val2PubKey) - require.NoError(t, err) - updateAddVal := abci.ValidatorUpdate{PubKey: fvp, Power: val1VotingPower} - validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) - assert.NoError(t, err) - - updatedState2, err := sm.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()) - - // 1. Add - val2VotingPower := val1VotingPower - totalPower = val1VotingPower + val2VotingPower // 20 - v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) // -22 - // 2. Scale - noop - // 3. Center - avgSum := big.NewInt(0).Add(big.NewInt(v2PrioWhenAddedVal2), big.NewInt(oldVal1.ProposerPriority)) - avg := avgSum.Div(avgSum, big.NewInt(2)) // -11 - expectedVal2Prio := v2PrioWhenAddedVal2 - avg.Int64() // -11 - expectedVal1Prio := oldVal1.ProposerPriority - avg.Int64() // 11 - // 4. Increment - expectedVal2Prio += val2VotingPower // -11 + 10 = -1 - expectedVal1Prio += val1VotingPower // 11 + 10 == 21 - expectedVal1Prio -= totalPower // 1, val1 proposer - - assert.EqualValues(t, expectedVal1Prio, updatedVal1.ProposerPriority) - assert.EqualValues( - t, - expectedVal2Prio, - updatedVal2.ProposerPriority, - "unexpected proposer priority for validator: %v", - updatedVal2, - ) - - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) - assert.NoError(t, err) - - assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) - - assert.Equal(t, updatedState3.Validators, updatedState2.NextValidators) - _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) - _, updatedVal2 = updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) - - // val1 will still be proposer: - assert.Equal(t, val1PubKey.Address(), updatedState3.NextValidators.Proposer.Address) - - // check if expected proposer prio is matched: - // Increment - expectedVal2Prio2 := expectedVal2Prio + val2VotingPower // -1 + 10 = 9 - expectedVal1Prio2 := expectedVal1Prio + val1VotingPower // 1 + 10 == 11 - expectedVal1Prio2 -= totalPower // -9, val1 proposer - - assert.EqualValues( - t, - expectedVal1Prio2, - updatedVal1.ProposerPriority, - "unexpected proposer priority for validator: %v", - updatedVal2, - ) - 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 - abciResponses = &abci.ResponseFinalizeBlock{} - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - oldState, err = sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) - assert.NoError(t, err) - expectedVal1Prio2 = 1 - expectedVal2Prio2 = -1 - expectedVal1Prio = -9 - expectedVal2Prio = 9 - - for i := 0; i < 1000; i++ { - // no validator updates: - abciResponses := &abci.ResponseFinalizeBlock{} - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - updatedState, err := sm.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) - - genesisVotingPower := types.MaxTotalVotingPower / 1000 - 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 := &abci.ResponseFinalizeBlock{} - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - block, err := makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - - updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) - require.NoError(t, err) - // no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0, - // than -Total == -Voting) - // -> no change in ProposerPrio (stays zero): - assert.EqualValues(t, oldState.NextValidators, updatedState.NextValidators) - assert.EqualValues(t, 0, 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() - firstAddedValVotingPower := int64(10) - fvp, err := cryptoenc.PubKeyToProto(firstAddedValPubKey) - require.NoError(t, err) - firstAddedVal := abci.ValidatorUpdate{PubKey: fvp, Power: firstAddedValVotingPower} - validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) - assert.NoError(t, err) - abciResponses := &abci.ResponseFinalizeBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}, - } - block, err := makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) - require.NoError(t, err) - - lastState := updatedState - for i := 0; i < 200; i++ { - // no updates: - abciResponses := &abci.ResponseFinalizeBlock{} - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - block, err := makeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - - bps, err = block.MakePartSet(testPartSize) - require.NoError(t, err) - - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - - updatedStateInner, err := sm.UpdateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) - require.NoError(t, err) - 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() - ap, err := cryptoenc.PubKeyToProto(addedPubKey) - require.NoError(t, err) - addedVal := abci.ValidatorUpdate{PubKey: ap, Power: firstAddedValVotingPower} - validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) - assert.NoError(t, err) - - abciResponses := &abci.ResponseFinalizeBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{addedVal}, - } - block, err := makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - state, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) - require.NoError(t, err) - } - require.Equal(t, 10+2, len(state.NextValidators.Validators)) - - // remove genesis validator: - gp, err := cryptoenc.PubKeyToProto(genesisPubKey) - require.NoError(t, err) - removeGenesisVal := abci.ValidatorUpdate{PubKey: gp, Power: 0} - abciResponses = &abci.ResponseFinalizeBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}, - } - - block, err = makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - - bps, err = block.MakePartSet(testPartSize) - require.NoError(t, err) - - blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - updatedState, err = sm.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 := &abci.ResponseFinalizeBlock{} - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - block, err = makeBlock(curState, curState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - - blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - curState, err = sm.UpdateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) - require.NoError(t, err) - if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { - isProposerUnchanged = false - } - count++ - } - 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 := &abci.ResponseFinalizeBlock{} - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.ValidatorUpdates) - require.NoError(t, err) - - block, err := makeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) - - bps, err := block.MakePartSet(testPartSize) - require.NoError(t, err) - - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - - updatedState, err = sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) - require.NoError(t, err) - 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) + {"single sequencer", []int64{types.SequencerVotingPower}}, + {"sequencer and attestor", []int64{types.SequencerVotingPower, types.AttestorVotingPower}}, + {"sequencer and multiple attestors", []int64{types.SequencerVotingPower, types.AttestorVotingPower, 100, 1000}}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + vals := make([]*types.Validator, len(tc.powers)) + var sequencerAddr []byte + for i, power := range tc.powers { + vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), power) + if power == types.SequencerVotingPower { + sequencerAddr = vals[i].Address + } } - } + valSet := types.NewValidatorSet(vals) + for i := 0; i < 10; i++ { + assert.True(t, bytes.Equal(sequencerAddr, valSet.GetProposer().Address)) + valSet.IncrementProposerPriority(1) + } + }) } } -func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { - const valSetSize = 2 +func TestStoreLoadValidatorsPreservesSingleSequencer(t *testing.T) { + const valSetSize = 1 tearDown, stateDB, state := setupTestCase(t) t.Cleanup(func() { tearDown(t) }) stateStore := sm.NewStore(stateDB, sm.StoreOptions{ @@ -946,66 +369,14 @@ func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { v0, err := stateStore.LoadValidators(nextHeight) assert.Nil(t, err) - acc0 := v0.Validators[0].ProposerPriority - - v1, err := stateStore.LoadValidators(nextHeight + 1) - assert.Nil(t, err) - acc1 := v1.Validators[0].ProposerPriority - - assert.NotEqual(t, acc1, acc0, "expected ProposerPriority value to change between heights") -} - -// TestValidatorChangesSaveLoad tests saving and loading a validator set with -// changes. -func TestManyValidatorChangesSaveLoad(t *testing.T) { - const valSetSize = 7 - tearDown, stateDB, state := setupTestCase(t) - defer tearDown(t) - stateStore := sm.NewStore(stateDB, sm.StoreOptions{ - DiscardABCIResponses: false, - }) - require.Equal(t, int64(0), state.LastBlockHeight) - state.Validators = genValSet(valSetSize) - state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - err := stateStore.Save(state) - require.NoError(t, err) - - _, valOld := state.Validators.GetByIndex(0) - pubkeyOld := valOld.PubKey - pubkey := ed25519.GenPrivKey().PubKey() - - // Swap the first validator with a new one (validator set size stays the same). - header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, pubkey) - - // Save state etc. - var validatorUpdates []*types.Validator - validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.ValidatorUpdates) - require.NoError(t, err) - state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) - require.Nil(t, err) - nextHeight := state.LastBlockHeight + 1 - err = stateStore.Save(state) - require.NoError(t, err) - - // Load nextheight, it should be the oldpubkey. - v0, err := stateStore.LoadValidators(nextHeight) - assert.Nil(t, err) - assert.Equal(t, valSetSize, v0.Size()) - index, val := v0.GetByAddress(pubkeyOld.Address()) - assert.NotNil(t, val) - if index < 0 { - t.Fatal("expected to find old validator") - } + require.Equal(t, 1, v0.Size()) + assert.Equal(t, int64(types.SequencerVotingPower), v0.GetProposer().VotingPower) - // Load nextheight+1, it should be the new pubkey. v1, err := stateStore.LoadValidators(nextHeight + 1) assert.Nil(t, err) - assert.Equal(t, valSetSize, v1.Size()) - index, val = v1.GetByAddress(pubkey.Address()) - assert.NotNil(t, val) - if index < 0 { - t.Fatal("expected to find newly added validator") - } + require.Equal(t, 1, v1.Size()) + assert.Equal(t, v0.GetProposer().Address, v1.GetProposer().Address) + assert.Equal(t, int64(types.SequencerVotingPower), v1.GetProposer().VotingPower) } func TestStateMakeBlock(t *testing.T) { diff --git a/state/store_test.go b/state/store_test.go index 51de014d3c..d458c04825 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -23,7 +23,8 @@ func TestStoreLoadValidators(t *testing.T) { stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardABCIResponses: false, }) - val, _ := types.RandValidator(true, 10) + pubKey := ed25519.GenPrivKey().PubKey() + val := types.NewValidator(pubKey, types.SequencerVotingPower) vals := types.NewValidatorSet([]*types.Validator{val}) // 1) LoadValidators loads validators using a height where they were last changed @@ -125,7 +126,7 @@ func TestPruneStates(t *testing.T) { // Generate a bunch of state data. Validators change for heights ending with 3, and // parameters when ending with 5. - validator := &types.Validator{Address: pk.Address(), VotingPower: 100, PubKey: pk} + validator := &types.Validator{Address: pk.Address(), VotingPower: types.SequencerVotingPower, PubKey: pk} validatorSet := &types.ValidatorSet{ Validators: []*types.Validator{validator}, Proposer: validator, diff --git a/state/validation_test.go b/state/validation_test.go index 47e08e4795..ba8db50782 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -195,9 +195,10 @@ func TestValidateBlockCommit(t *testing.T) { /* #2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size() */ - _, err = makeBlock(state, height, wrongSigsCommit) + block, err = makeBlock(state, height, wrongSigsCommit) + require.NoError(t, err) + err = blockExec.ValidateBlock(state, block) require.Error(t, err) - require.ErrorContains(t, err, "error making block") } /* @@ -422,18 +423,35 @@ func TestValidateBlockTime(t *testing.T) { require.ErrorContains(t, err, "not greater than last block time") }) - t.Run("block time after last block time, different than median time", func(t *testing.T) { + t.Run("block time after last block time does not require median time", func(t *testing.T) { height := int64(3) block, err := makeBlock(state, height, lastCommit) require.NoError(t, err) - // Set time to after the median time block.Time = block.Time.Add(time.Second) err = blockExec.ValidateBlock(state, block) + require.NoError(t, err) + }) + + t.Run("block time exceeds wall clock tolerance", func(t *testing.T) { + blockExecWithTol := sm.NewBlockExecutor( + stateStore, + log.TestingLogger(), + proxyApp.Consensus(), + mp, + sm.EmptyEvidencePool{}, + blockStore, + sm.BlockExecutorWithBlockTimeTolerance(30*time.Second), + ) + height := int64(3) + block, err := makeBlock(state, height, lastCommit) + require.NoError(t, err) + block.Time = time.Now().Add(1000 * time.Hour) + err = blockExecWithTol.ValidateBlock(state, block) require.Error(t, err) - require.Contains(t, err.Error(), "invalid block time") + assert.Contains(t, err.Error(), "too far in the future") }) - t.Run("block time after last block time, same as median time", func(t *testing.T) { + t.Run("block time after last block time passes without tolerance", func(t *testing.T) { height := int64(3) block, err := makeBlock(state, height, lastCommit) require.NoError(t, err) @@ -506,8 +524,9 @@ func TestValidateBlockInvalidCommit(t *testing.T) { }, } - _, err := makeBlock(state, height, invalidCommit) + block, err := makeBlock(state, height, invalidCommit) + require.NoError(t, err) + err = blockExec.ValidateBlock(state, block) require.Error(t, err) - require.Contains(t, err.Error(), "commit validator not found in validator set") }) } diff --git a/types/validation.go b/types/validation.go index 537dc46c84..8c802df73f 100644 --- a/types/validation.go +++ b/types/validation.go @@ -13,8 +13,16 @@ import ( const batchVerifyThreshold = 2 func shouldBatchVerify(vals *ValidatorSet, commit *Commit) bool { - return len(commit.Signatures) >= batchVerifyThreshold && - batch.SupportsBatchVerifier(vals.GetProposer().PubKey) && + if len(commit.Signatures) < batchVerifyThreshold { + return false + } + + proposer := vals.Proposer + if proposer == nil { + proposer = vals.findProposer() + } + return proposer != nil && + batch.SupportsBatchVerifier(proposer.PubKey) && vals.AllKeysHaveSameType() } diff --git a/types/validator_set.go b/types/validator_set.go index f01d47846a..0a73f68dbb 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -355,6 +355,9 @@ func (vals *ValidatorSet) GetProposer() (proposer *Validator) { if vals.Proposer == nil { vals.Proposer = vals.findProposer() } + if vals.Proposer == nil { + panic("validator set has no sequencer proposer") + } return vals.Proposer.Copy() } diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 42b529400d..119be71f08 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -5,7 +5,6 @@ import ( "fmt" "math" "sort" - "strings" "testing" "testing/quick" @@ -53,7 +52,7 @@ func TestValidatorSetBasic(t *testing.T) { 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, }, vset.Hash()) // add - val = randValidator(vset.TotalVotingPower()) + val = testValidator(SequencerVotingPower) assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) assert.True(t, vset.HasAddress(val.Address)) @@ -68,7 +67,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.Equal(t, val.Address, vset.GetProposer().Address) // update - val = randValidator(vset.TotalVotingPower()) + val = testValidator(AttestorVotingPower) assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) _, val = vset.GetByAddress(val.Address) val.VotingPower += 100 @@ -147,7 +146,7 @@ func TestValidatorSet_ValidateBasic(t *testing.T) { } func TestCopy(t *testing.T) { - vset := randValidatorSet(10) + vset := sequencerValidatorSet(10) vsetHash := vset.Hash() if len(vsetHash) == 0 { t.Fatalf("ValidatorSet had unexpected zero hash") @@ -165,7 +164,7 @@ func TestValidatorSet_ProposerPriorityHash(t *testing.T) { vset := NewValidatorSet(nil) assert.Equal(t, []byte(nil), vset.ProposerPriorityHash()) - vset = randValidatorSet(3) + vset = sequencerValidatorSet(3) assert.NotNil(t, vset.ProposerPriorityHash()) // Marshaling and unmarshalling do not affect ProposerPriorityHash @@ -217,187 +216,6 @@ func BenchmarkValidatorSetCopy(b *testing.B) { } } -//------------------------------------------------------------------- - -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) { - vals := []*Validator{ - newValidator([]byte("avalidator_address12"), 1), - newValidator([]byte("bvalidator_address12"), 1), - newValidator([]byte("cvalidator_address12"), 1), - newValidator([]byte("dvalidator_address12"), 1), - } - - for i := 0; i < 4; i++ { - pk := ed25519.GenPrivKey().PubKey() - vals[i].PubKey = pk - vals[i].Address = pk.Address() - } - sort.Sort(ValidatorsByAddress(vals)) - vset := NewValidatorSet(vals) - proposerOrder := make([]*Validator, 4) - for i := 0; i < 4; i++ { - 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("vset.Proposer (%X) does not match expected proposer (%X) for (%d, %d)", got, expected, i, j) - } - - // serialize, deserialize, check proposer - b := vset.toBytes() - vset = vset.fromBytes(b) - - computed := vset.GetProposer() // findGetProposer() - if i != 0 { - if !bytes.Equal(got, computed.Address) { - t.Fatalf( - "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 := (cmtrand.Int() % 5) + 1 - if cmtrand.Int()%mod > 0 { - // sometimes its up to 5 - times = (cmtrand.Int31() % 4) + 1 - } - vset.IncrementProposerPriority(times) - - j += times - } -} - func newValidator(address []byte, power int64) *Validator { return &Validator{Address: address, VotingPower: power} } @@ -408,20 +226,20 @@ func randPubKey() crypto.PubKey { return ed25519.PubKey(cmtrand.Bytes(32)) } -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(), int64(cmtrand.Uint64()%uint64(MaxTotalVotingPower-totalVotingPower))) - val.ProposerPriority = cmtrand.Int64() % (MaxTotalVotingPower - totalVotingPower) +func testValidator(power int64) *Validator { + val := NewValidator(randPubKey(), power) + val.ProposerPriority = cmtrand.Int64() % MaxTotalVotingPower return val } -func randValidatorSet(numValidators int) *ValidatorSet { +func sequencerValidatorSet(numValidators int) *ValidatorSet { validators := make([]*Validator, numValidators) - totalVotingPower := int64(0) for i := 0; i < numValidators; i++ { - validators[i] = randValidator(totalVotingPower) - totalVotingPower += validators[i].VotingPower + power := int64(AttestorVotingPower) + if i == 0 { + power = SequencerVotingPower + } + validators[i] = testValidator(power) } return NewValidatorSet(validators) } @@ -578,156 +396,6 @@ 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: 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) @@ -1520,14 +1188,14 @@ func TestSafeMul(t *testing.T) { } func TestValidatorSetProtoBuf(t *testing.T) { - valset, _ := RandValidatorSet(10, 100) - valset2, _ := RandValidatorSet(10, 100) + valset := sequencerValidatorSet(10) + valset2 := sequencerValidatorSet(10) valset2.Validators[0] = &Validator{} - valset3, _ := RandValidatorSet(10, 100) + valset3 := sequencerValidatorSet(10) valset3.Proposer = nil - valset4, _ := RandValidatorSet(10, 100) + valset4 := sequencerValidatorSet(10) valset4.Proposer = &Validator{} testCases := []struct { @@ -1633,7 +1301,10 @@ func BenchmarkUpdates(b *testing.B) { func TestVerifyCommitWithInvalidProposerKey(t *testing.T) { vs := &ValidatorSet{ - Validators: []*Validator{{}, {}}, + Validators: []*Validator{ + {VotingPower: SequencerVotingPower}, + {VotingPower: AttestorVotingPower}, + }, } commit := &Commit{ Height: 100, @@ -1676,15 +1347,15 @@ func TestValidatorSet_AllKeysHaveSameType(t *testing.T) { sameType: true, }, { - vals: randValidatorSet(1), + vals: sequencerValidatorSet(1), sameType: true, }, { - vals: randValidatorSet(2), + vals: sequencerValidatorSet(2), sameType: true, }, { - vals: NewValidatorSet([]*Validator{randValidator(100), NewValidator(sr25519.GenPrivKey().PubKey(), 200)}), + vals: NewValidatorSet([]*Validator{testValidator(SequencerVotingPower), NewValidator(sr25519.GenPrivKey().PubKey(), 200)}), sameType: false, }, }