From 68a03d69dcfaa64a6edb829b6efed40e72e8e7bd Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:19:21 +0200 Subject: [PATCH] WIP: removed score from validator - tests still red --- dash/quorum/validator_conn_executor_test.go | 2 +- internal/consensus/msgs_test.go | 2 +- internal/consensus/state_data.go | 2 +- internal/consensus/state_proposaler_test.go | 72 ++-- internal/consensus/state_test.go | 13 +- internal/consensus/types/round_state.go | 2 +- internal/evidence/pool_test.go | 1 - .../validatorscoring/height_round_score.go | 133 +------ .../features/validatorscoring/height_score.go | 129 ++++-- .../validatorscoring/height_score_test.go | 58 +-- .../validatorscoring/validator_scoring.go | 6 +- internal/rpc/core/consensus.go | 13 +- internal/state/helpers_test.go | 6 +- internal/state/state_test.go | 375 +----------------- internal/state/store.go | 82 ++-- internal/state/store_test.go | 53 ++- internal/statesync/syncer_test.go | 4 +- light/client_test.go | 1 - light/provider/http/http.go | 16 +- test/e2e/runner/evidence.go | 12 +- test/e2e/tests/validator_test.go | 2 +- types/block_test.go | 5 +- types/light_test.go | 4 +- types/validator.go | 61 +-- types/validator_set.go | 277 ++----------- types/validator_set_test.go | 123 +----- 26 files changed, 385 insertions(+), 1069 deletions(-) diff --git a/dash/quorum/validator_conn_executor_test.go b/dash/quorum/validator_conn_executor_test.go index 8ea0bc2591..4430648700 100644 --- a/dash/quorum/validator_conn_executor_test.go +++ b/dash/quorum/validator_conn_executor_test.go @@ -621,7 +621,7 @@ func makeState(nVals int, height int64) (sm.State, dbm.DB) { } func makeBlock(ctx context.Context, t *testing.T, blockExec *sm.BlockExecutor, state sm.State, _height int64, commit *types.Commit) *types.Block { - block, crs, err := blockExec.CreateProposalBlock(ctx, 1, 0, state, commit, state.Validators.Proposer.ProTxHash, 1) + block, crs, err := blockExec.CreateProposalBlock(ctx, 1, 0, state, commit, state.Validators.Proposer().ProTxHash, 1) require.NoError(t, err) err = crs.UpdateBlock(block) diff --git a/internal/consensus/msgs_test.go b/internal/consensus/msgs_test.go index 419102e828..2dea8c9bed 100644 --- a/internal/consensus/msgs_test.go +++ b/internal/consensus/msgs_test.go @@ -78,7 +78,7 @@ func TestMsgToProto(t *testing.T) { vote, err := factory.MakeVote( ctx, pv, - &types.ValidatorSet{Proposer: val, Validators: []*types.Validator{val}, QuorumHash: quorumHash, ThresholdPublicKey: pk}, + &types.ValidatorSet{Validators: []*types.Validator{val}, QuorumHash: quorumHash, ThresholdPublicKey: pk}, "chainID", 0, 1, diff --git a/internal/consensus/state_data.go b/internal/consensus/state_data.go index 8069d281d0..20b60e4287 100644 --- a/internal/consensus/state_data.go +++ b/internal/consensus/state_data.go @@ -36,7 +36,7 @@ type StateDataStore struct { emitter *eventemitter.EventEmitter replayMode bool version int64 - validatorScoring validatorscoring.ValidatorScoringStrategy + validatorScoring validatorscoring.ProposerProvider } // NewStateDataStore creates and returns a new state-data store diff --git a/internal/consensus/state_proposaler_test.go b/internal/consensus/state_proposaler_test.go index 15b3c0cbe8..86a8724be1 100644 --- a/internal/consensus/state_proposaler_test.go +++ b/internal/consensus/state_proposaler_test.go @@ -24,6 +24,7 @@ type ProposalerTestSuite struct { suite.Suite proposer *Proposaler + proposerSelector validatorscoring.ProposerProvider mockBlockExec *mocks.Executor mockPrivVals []types.PrivValidator mockValSet *types.ValidatorSet @@ -88,19 +89,19 @@ func (suite *ProposalerTestSuite) SetupTest() { blockExec: blockExec, committedState: suite.committedState, } - - vs, err := validatorscoring.NewProposerStrategy( + var err error + suite.proposerSelector, err = validatorscoring.NewProposerStrategy( suite.committedState.ConsensusParams, - suite.committedState.Validators.Copy(), - suite.committedState.LastBlockHeight, - suite.committedState.LastBlockRound, + valSet, + 0, + 0, nil, ) if err != nil { panic(fmt.Errorf("failed to create validator scoring strategy: %w", err)) } - suite.proposerProTxHash = vs.MustGetProposer(100, 0).ProTxHash + suite.proposerProTxHash = suite.proposerSelector.MustGetProposer(100, 0).ProTxHash suite.blockH100R0 = suite.committedState.MakeBlock(100, []types.Tx{}, &suite.commitH99R0, nil, suite.proposerProTxHash, 0) } @@ -158,7 +159,11 @@ func (suite *ProposalerTestSuite) TestSet() { wantErr: ErrInvalidProposalPOLRound.Error(), }, { - rs: cstypes.RoundState{Height: 100, Round: 0, Validators: suite.mockValSet}, + rs: cstypes.RoundState{Height: 100, + Round: 0, + Validators: suite.mockValSet, + ProposerSelector: suite.proposerSelector, + }, proposal: *proposalH100R0, receivedAt: receivedAt, wantProposal: proposalH100R0, @@ -183,6 +188,8 @@ func (suite *ProposalerTestSuite) TestDecide() { state := suite.committedState proposalH100R0 := types.NewProposal(100, state.LastCoreChainLockedBlockHeight, 0, 0, blockID, suite.blockH100R0.Header.Time) suite.signProposal(ctx, proposalH100R0) + vs, err := validatorscoring.NewProposerStrategy(types.ConsensusParams{}, suite.mockValSet, 100, 0, nil) + suite.Require().NoError(err) testCases := []struct { height int64 round int32 @@ -195,12 +202,13 @@ func (suite *ProposalerTestSuite) TestDecide() { height: 100, round: 0, rs: cstypes.RoundState{ - Height: 100, - Round: 0, - Validators: suite.mockValSet, - ValidBlock: nil, - LastCommit: &suite.commitH99R0, - ValidRound: 0, + Height: 100, + Round: 0, + Validators: suite.mockValSet, + ValidBlock: nil, + LastCommit: &suite.commitH99R0, + ValidRound: 0, + ProposerSelector: vs, }, mockFn: func(rs cstypes.RoundState) { suite.mockBlockExec. @@ -229,6 +237,7 @@ func (suite *ProposalerTestSuite) TestDecide() { ValidBlockRecvTime: suite.blockH100R0.Time.Add(100 * time.Millisecond), LastCommit: &suite.commitH99R0, ValidRound: 0, + ProposerSelector: vs, }, wantProposal: proposalH100R0, }, @@ -266,9 +275,11 @@ func (suite *ProposalerTestSuite) TestVerifyProposal() { proposalH100R0wrongSig := *proposalH100R0 proposalH100R0wrongSig.Signature = make([]byte, 96) valSet := *suite.mockValSet - proposer := valSet.Proposer.Copy() + proposer := valSet.Proposer() proposer.PubKey = nil - valSet.Proposer = proposer + idx, _ := valSet.GetByProTxHash(proposer.ProTxHash) + valSet.Validators[int(idx)] = proposer + testCases := []struct { proposal *types.Proposal rs cstypes.RoundState @@ -277,53 +288,60 @@ func (suite *ProposalerTestSuite) TestVerifyProposal() { { proposal: proposalH100R0, rs: cstypes.RoundState{ - Validators: suite.mockValSet, + Validators: suite.mockValSet, + ProposerSelector: suite.proposerSelector, }, }, { proposal: &proposalH100R0wrongSig, rs: cstypes.RoundState{ - Validators: suite.mockValSet, + Validators: suite.mockValSet, + ProposerSelector: suite.proposerSelector, }, wantErr: ErrInvalidProposalSignature.Error(), }, { proposal: proposalH100R0, rs: cstypes.RoundState{ - Commit: nil, - Validators: &valSet, + Commit: nil, + Validators: &valSet, + ProposerSelector: suite.proposerSelector, }, wantErr: ErrUnableToVerifyProposal.Error(), }, { proposal: proposalH100R0, rs: cstypes.RoundState{ - Commit: &types.Commit{Height: 99}, - Validators: &valSet, + Commit: &types.Commit{Height: 99}, + Validators: &valSet, + ProposerSelector: suite.proposerSelector, }, wantErr: ErrUnableToVerifyProposal.Error(), }, { proposal: proposalH100R0, rs: cstypes.RoundState{ - Commit: &types.Commit{Height: 100, Round: 1}, - Validators: &valSet, + Commit: &types.Commit{Height: 100, Round: 1}, + Validators: &valSet, + ProposerSelector: suite.proposerSelector, }, wantErr: ErrUnableToVerifyProposal.Error(), }, { proposal: proposalH100R0, rs: cstypes.RoundState{ - Commit: &types.Commit{Height: 100, Round: 0, BlockID: types.BlockID{Hash: nil}}, - Validators: &valSet, + Commit: &types.Commit{Height: 100, Round: 0, BlockID: types.BlockID{Hash: nil}}, + Validators: &valSet, + ProposerSelector: suite.proposerSelector, }, wantErr: ErrInvalidProposalForCommit.Error(), }, { proposal: proposalH100R0, rs: cstypes.RoundState{ - Commit: &types.Commit{Height: 100, Round: 0, BlockID: proposalH100R0.BlockID}, - Validators: &valSet, + Commit: &types.Commit{Height: 100, Round: 0, BlockID: proposalH100R0.BlockID}, + Validators: &valSet, + ProposerSelector: suite.proposerSelector, }, }, } diff --git a/internal/consensus/state_test.go b/internal/consensus/state_test.go index 45ba31b9de..87b3d62fb4 100644 --- a/internal/consensus/state_test.go +++ b/internal/consensus/state_test.go @@ -135,7 +135,7 @@ func TestStateProposerSelection2(t *testing.T) { // everyone just votes nil. we get a new proposer each round for i := int32(0); int(i) < len(vss); i++ { prop := stateData.ProposerSelector.MustGetProposer(height, round) - proTxHash, err := vss[int(i+round)%len(vss)].GetProTxHash(ctx) + proTxHash, err := vss[int(int32(height-1)+round)%len(vss)].GetProTxHash(ctx) require.NoError(t, err) correctProposer := proTxHash require.True(t, bytes.Equal(prop.ProTxHash, correctProposer), @@ -263,10 +263,11 @@ func TestStateProposerSelectionBetweenRoundsAndHeights(t *testing.T) { } // ensure correct order of voting - for i, p := range proposers { - expectedProTx := expectedValidatorSet.Validators[(i)%len(expectedValidatorSet.Validators)].ProTxHash - assert.Equal(t, expectedProTx, p.proposer.ProTxHash) - t.Logf("height %d, round %d, proposer %x", p.height, p.round, p.proposer.ProTxHash[:6]) + + for _, p := range proposers { + expectedProTx := expectedValidatorSet.Validators[(int(p.height-1)+int(p.round))%expectedValidatorSet.Size()].ProTxHash + assert.Equal(t, expectedProTx, p.proposer.ProTxHash, + "height %d, round %d, proposer %x, vset %v", p.height, p.round, p.proposer.ProTxHash[:6], expectedValidatorSet) } } @@ -3373,7 +3374,7 @@ func TestStateTryAddCommitCallsProcessProposal(t *testing.T) { parts, err := block.MakePartSet(999999999) require.NoError(t, err) - peerID := css0StateData.Validators.Proposer.NodeAddress.NodeID + peerID := css0StateData.Validators.Proposer().NodeAddress.NodeID css1StateData.Proposal = proposal css1StateData.ProposalBlock = block css1StateData.ProposalBlockParts = parts diff --git a/internal/consensus/types/round_state.go b/internal/consensus/types/round_state.go index 9609382974..ecde62be90 100644 --- a/internal/consensus/types/round_state.go +++ b/internal/consensus/types/round_state.go @@ -77,7 +77,7 @@ type RoundState struct { // Subjective time when +2/3 precommits for Block at Round were found CommitTime time.Time `json:"commit_time"` Validators *types.ValidatorSet `json:"validators"` - ProposerSelector validatorscoring.ValidatorScoringStrategy + ProposerSelector validatorscoring.ProposerProvider Proposal *types.Proposal `json:"proposal"` ProposalReceiveTime time.Time `json:"proposal_receive_time"` ProposalBlock *types.Block `json:"proposal_block"` diff --git a/internal/evidence/pool_test.go b/internal/evidence/pool_test.go index 82d4315acb..1142c16859 100644 --- a/internal/evidence/pool_test.go +++ b/internal/evidence/pool_test.go @@ -525,7 +525,6 @@ func initializeValidatorState( // create validator set and state valSet := &types.ValidatorSet{ Validators: []*types.Validator{validator}, - Proposer: validator, ThresholdPublicKey: validator.PubKey, QuorumType: quorumType, QuorumHash: quorumHash, diff --git a/internal/features/validatorscoring/height_round_score.go b/internal/features/validatorscoring/height_round_score.go index d316efbdb4..50e20e7d5e 100644 --- a/internal/features/validatorscoring/height_round_score.go +++ b/internal/features/validatorscoring/height_round_score.go @@ -2,16 +2,14 @@ package validatorscoring import ( "fmt" - "math" - "math/big" "sync" - tmmath "github.com/dashpay/tenderdash/libs/math" "github.com/dashpay/tenderdash/types" ) type BlockCommitStore interface { LoadBlockCommit(height int64) *types.Commit + LoadBlockMeta(height int64) *types.BlockMeta } type heightRoundBasedScoringStrategy struct { @@ -43,7 +41,7 @@ type heightRoundBasedScoringStrategy struct { func NewHeightRoundBasedScoringStrategy(vset *types.ValidatorSet, currentHeight int64, currentRound int32, commitStore BlockCommitStore, -) (ValidatorScoringStrategy, error) { +) (ProposerProvider, error) { if vset.IsNilOrEmpty() { return nil, fmt.Errorf("empty validator set") } @@ -60,18 +58,15 @@ func (s *heightRoundBasedScoringStrategy) UpdateScores(h int64, r int32) error { s.mtx.Lock() defer s.mtx.Unlock() - // ensure all scores are correctly adjusted - s.valSet.Recalculate() - - if err := s.updateHeight(h, r); err != nil { + if err := s.updateHeightRound(h, r); err != nil { return fmt.Errorf("update validator set for height: %w", err) } return nil } -// updateHeight updates the validator scores from the current height to the newHeight, round 0. -func (s *heightRoundBasedScoringStrategy) updateHeight(newHeight int64, newRound int32) error { +// updateHeightRound updates the validator scores from the current height to the newHeight, round 0. +func (s *heightRoundBasedScoringStrategy) updateHeightRound(newHeight int64, newRound int32) error { heightDiff := newHeight - s.height if heightDiff == 0 { return s.updateRound(newRound) @@ -83,17 +78,17 @@ func (s *heightRoundBasedScoringStrategy) updateHeight(newHeight int64, newRound if heightDiff == 1 && newRound == 0 && s.commitStore == nil { // we assume it's just new round, and we have valset for the last round of previous height - s.updatePriorities(1) + s.valSet.IncProposerIndex(1) return nil } if s.commitStore == nil { return fmt.Errorf("block store required to update validator scores from %d:%d to %d:%d", s.height, s.round, newHeight, newRound) } - current_height := s.height + currentHeight := s.height // process all heights from current height to new height, exclusive (as the new height is not processed yet, and // we have target round provided anyway). - for h := current_height; h < newHeight; h++ { + for h := currentHeight; h < newHeight; h++ { // get the last commit for the height commit := s.commitStore.LoadBlockCommit(h) if commit == nil { @@ -105,7 +100,7 @@ func (s *heightRoundBasedScoringStrategy) updateHeight(newHeight int64, newRound return fmt.Errorf("cannot update validator scores to round %d:%d", h, commit.Round) } // go to (h+1, 0) - s.updatePriorities(1) + s.valSet.IncProposerIndex(1) s.height = h + 1 s.round = 0 } @@ -123,21 +118,24 @@ func (s *heightRoundBasedScoringStrategy) updateRound(newRound int32) error { return fmt.Errorf("cannot go back in round: %d -> %d", s.round, newRound) } - s.updatePriorities(int64(roundDiff)) + s.valSet.IncProposerIndex(roundDiff) s.round = newRound return nil } func (s *heightRoundBasedScoringStrategy) GetProposer(height int64, round int32) (*types.Validator, error) { - // no locking here, as we rely on locks inside UpdateScores - if err := s.UpdateScores(height, round); err != nil { - return nil, fmt.Errorf("cannot update scores for height %d round %d: %w", height, round, err) - } - s.mtx.Lock() defer s.mtx.Unlock() - val := s.getValWithMostPriority() + + if s.valSet.IsNilOrEmpty() { + return nil, fmt.Errorf("empty validator set") + } + + if err := s.updateHeightRound(height, round); err != nil { + return nil, fmt.Errorf("update validator set for height: %w", err) + } + val := s.valSet.Proposer() if val == nil { return nil, fmt.Errorf("no validator found at height %d, round %d", height, round) } @@ -157,7 +155,7 @@ func (s *heightRoundBasedScoringStrategy) ValidatorSet() *types.ValidatorSet { return s.valSet } -func (s *heightRoundBasedScoringStrategy) Copy() ValidatorScoringStrategy { +func (s *heightRoundBasedScoringStrategy) Copy() ProposerProvider { s.mtx.Lock() defer s.mtx.Unlock() @@ -168,94 +166,3 @@ func (s *heightRoundBasedScoringStrategy) Copy() ValidatorScoringStrategy { commitStore: s.commitStore, } } - -// updatePriorities increments ProposerPriority of each validator and -// updates the proposer. Panics if validator set is empty. -// `times` must be positive. -func (s *heightRoundBasedScoringStrategy) updatePriorities(times int64) { - if s.valSet.IsNilOrEmpty() { - panic("empty validator set") - } - if times < 0 { - panic("Cannot call updatePriorities with negative times") - } - - // 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 := types.PriorityWindowSizeFactor * s.valSet.TotalVotingPower() - s.valSet.RescalePriorities(diffMax) - - var proposer *types.Validator - // Call IncrementProposerPriority(1) times times. - for i := int64(0); i < times; i++ { - proposer = s.incrementProposerPriority() - } - - s.valSet.Proposer = proposer -} - -func (s *heightRoundBasedScoringStrategy) incrementProposerPriority() *types.Validator { - vals := s.valSet - for _, val := range vals.Validators { - // Check for overflow for sum. - newPrio := tmmath.SafeAddClipInt64(val.ProposerPriority, val.VotingPower) - val.ProposerPriority = newPrio - } - // Decrement the validator with most ProposerPriority. - mostest := s.getValWithMostPriority() - // Mind the underflow. - mostest.ProposerPriority = tmmath.SafeSubClipInt64(mostest.ProposerPriority, vals.TotalVotingPower()) - - return mostest -} - -// safe addition/subtraction/multiplication - -// Should not be called on an empty validator set. -func (s *heightRoundBasedScoringStrategy) computeAvgProposerPriority() int64 { - vals := s.valSet - n := int64(len(vals.Validators)) - sum := big.NewInt(0) - for _, val := range vals.Validators { - sum.Add(sum, big.NewInt(val.ProposerPriority)) - } - avg := sum.Div(sum, big.NewInt(n)) - if avg.IsInt64() { - return avg.Int64() - } - - // This should never happen: each val.ProposerPriority is in bounds of int64. - panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg)) -} - -// Compute the difference between the max and min ProposerPriority of that set. -func computeMaxMinPriorityDiff(vals *types.ValidatorSet) int64 { - if vals.IsNilOrEmpty() { - panic("empty validator set") - } - 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 - } - return diff -} - -func (s *heightRoundBasedScoringStrategy) getValWithMostPriority() *types.Validator { - vals := s.valSet - var res *types.Validator - for _, val := range vals.Validators { - res = res.CompareProposerPriority(val) - } - return res -} diff --git a/internal/features/validatorscoring/height_score.go b/internal/features/validatorscoring/height_score.go index af224ff78a..88d3ce3cbd 100644 --- a/internal/features/validatorscoring/height_score.go +++ b/internal/features/validatorscoring/height_score.go @@ -2,12 +2,15 @@ package validatorscoring import ( "fmt" + "sync" "github.com/dashpay/tenderdash/types" ) type heightBasedScoringStrategy struct { - inner *heightRoundBasedScoringStrategy + valSet *types.ValidatorSet + height int64 + mtx sync.Mutex } // NewHeightBasedScoringStrategy creates a new height-based scoring strategy @@ -24,67 +27,111 @@ type heightBasedScoringStrategy struct { // // * `vset` - the validator set; it must not be empty and can be modified in place // * `currentHeight` - the current height for which vset has correct scores -func NewHeightBasedScoringStrategy(vset *types.ValidatorSet, currentHeight int64, bs BlockCommitStore) (ValidatorScoringStrategy, error) { +func NewHeightBasedScoringStrategy(vset *types.ValidatorSet, currentHeight int64, bs BlockCommitStore) (ProposerProvider, error) { if vset.IsNilOrEmpty() { return nil, fmt.Errorf("empty validator set") } - heightRoundStrategy, err := NewHeightRoundBasedScoringStrategy(vset, currentHeight, 0, bs) - if err != nil { - return nil, fmt.Errorf("error creating inner scoring strategy: %v", err) + s := &heightBasedScoringStrategy{ + valSet: vset, + height: currentHeight, } - s, ok := heightRoundStrategy.(*heightRoundBasedScoringStrategy) - if !ok { - return nil, fmt.Errorf("inner scoring strategy is not of type heightRoundBasedScoringStrategy") + + // if we have a block store, we can determine the proposer for the current height; + // otherwise we just trust the state of `vset` + if bs != nil { + if err := s.initProposer(currentHeight, bs); err != nil { + return nil, err + } } - return &heightBasedScoringStrategy{ - inner: s, - }, nil + return s, nil } -func (s *heightBasedScoringStrategy) UpdateScores(newHeight int64, _round int32) error { - heightDiff := newHeight - s.inner.height +// initProposer determines the proposer for the given height using block store and consensus params. +func (s *heightBasedScoringStrategy) initProposer(height int64, bs BlockCommitStore) error { + if bs == nil { + return fmt.Errorf("block store is nil") + } + + // special case for genesis + if height == 0 || height == 1 { + // we take first proposer from the validator set + if err := s.valSet.SetProposer(s.valSet.Validators[0].ProTxHash); err != nil { + return fmt.Errorf("could not set initial proposer: %w", err) + } + + return nil + } + + meta := bs.LoadBlockMeta(height) + addToIdx := int32(0) + if meta == nil { + // we use previous height, and will just add 1 to proposer index + meta = bs.LoadBlockMeta(height - 1) + if meta == nil { + return fmt.Errorf("could not find block meta for previous height %d", height-1) + } + addToIdx = 1 + } + + if err := s.valSet.SetProposer(meta.Header.ProposerProTxHash); err != nil { + return fmt.Errorf("could not set proposer: %w", err) + } + + if (addToIdx + meta.Round) > 0 { + // we want to return proposer at round 0, so we move back + s.valSet.IncProposerIndex(addToIdx - meta.Round) + } + + return nil +} + +// UpdateScores updates the scores of the validators to the given height. +// Here, we ignore the round, as we don't want to persist round info. +func (s *heightBasedScoringStrategy) UpdateScores(newHeight int64, round int32) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + return s.updateScores(newHeight, round) +} + +func (s *heightBasedScoringStrategy) updateScores(newHeight int64, _round int32) error { + heightDiff := newHeight - s.height if heightDiff == 0 { // NOOP return nil } if heightDiff < 0 { // TODO: handle going back in height - return fmt.Errorf("cannot go back in height: %d -> %d", s.inner.height, newHeight) + return fmt.Errorf("cannot go back in height: %d -> %d", s.height, newHeight) } - for h := s.inner.height; h < newHeight; h++ { - s.inner.incrementProposerPriority() + if heightDiff > 1 { + return fmt.Errorf("cannot jump more than one height: %d -> %d", s.height, newHeight) } - s.inner.valSet.Recalculate() - s.inner.height = newHeight + + s.valSet.IncProposerIndex(int32(heightDiff)) //nolint:gosec + + s.height = newHeight return nil } func (s *heightBasedScoringStrategy) GetProposer(height int64, round int32) (*types.Validator, error) { - if err := s.UpdateScores(height, 0); err != nil { + s.mtx.Lock() + defer s.mtx.Unlock() + + if err := s.updateScores(height, 0); err != nil { return nil, err } if round == 0 { - return s.inner.GetProposer(height, round) + return s.valSet.Proposer(), nil } // advance a copy of the validator set to the correct round, but don't persist the changes - inner := s.inner.Copy().(*heightRoundBasedScoringStrategy) - proposer, err := inner.GetProposer(height, round) - if err != nil { - return nil, fmt.Errorf("error getting proposer for height %d and round %d: %v", height, round, err) - } - - // now, we take proposer from original set, in case someone wants to modify it (eg. for testing) - protx := proposer.ProTxHash - _, proposer = s.inner.ValidatorSet().GetByProTxHash(protx) - if proposer == nil { - return nil, fmt.Errorf("proposer %x not found in the validator set", protx) - } - - return proposer, nil + vs := s.valSet.Copy() + vs.IncProposerIndex(round) + return vs.Proposer(), nil } func (s *heightBasedScoringStrategy) MustGetProposer(height int64, round int32) *types.Validator { @@ -96,12 +143,18 @@ func (s *heightBasedScoringStrategy) MustGetProposer(height int64, round int32) } func (s *heightBasedScoringStrategy) ValidatorSet() *types.ValidatorSet { - return s.inner.ValidatorSet() + s.mtx.Lock() + defer s.mtx.Unlock() + + return s.valSet } -func (s *heightBasedScoringStrategy) Copy() ValidatorScoringStrategy { - innerCopy := s.inner.Copy().(*heightRoundBasedScoringStrategy) +func (s *heightBasedScoringStrategy) Copy() ProposerProvider { + s.mtx.Lock() + defer s.mtx.Unlock() + return &heightBasedScoringStrategy{ - inner: innerCopy, + valSet: s.valSet.Copy(), + height: s.height, } } diff --git a/internal/features/validatorscoring/height_score_test.go b/internal/features/validatorscoring/height_score_test.go index 73ea1e2c15..3a4b151d08 100644 --- a/internal/features/validatorscoring/height_score_test.go +++ b/internal/features/validatorscoring/height_score_test.go @@ -130,55 +130,7 @@ func TestProposerSelection3(t *testing.T) { } } -func TestAveragingInIncrementProposerPriority(t *testing.T) { - // Test that the averaging works as expected inside of IncrementProposerPriority. - // Each validator comes with zero voting power which simplifies reasoning about - // the expected ProposerPriority. - tcs := []struct { - vs types.ValidatorSet - times int32 - avg int64 - }{ - 0: {types.ValidatorSet{ - Validators: []*types.Validator{ - {ProTxHash: []byte("a"), ProposerPriority: 1}, - {ProTxHash: []byte("b"), ProposerPriority: 2}, - {ProTxHash: []byte("c"), ProposerPriority: 3}}}, - 1, - 2, - }, - 1: {types.ValidatorSet{ - Validators: []*types.Validator{ - {ProTxHash: []byte("a"), ProposerPriority: 10}, - {ProTxHash: []byte("b"), ProposerPriority: -10}, - {ProTxHash: []byte("c"), ProposerPriority: 1}}}, - // this should average twice but the average should be 0 after the first iteration - // (voting power is 0 -> no changes) - // 1/3 -> 0 - 11, 0}, - 2: {types.ValidatorSet{ - Validators: []*types.Validator{ - {ProTxHash: []byte("a"), ProposerPriority: 100}, - {ProTxHash: []byte("b"), ProposerPriority: -10}, - {ProTxHash: []byte("c"), ProposerPriority: 1}}}, - 1, - 91 / 3, - }, - } - for i, tc := range tcs { - // work on copy to have the old ProposerPriorities: - vs, err := validatorscoring.NewProposerStrategy(types.ConsensusParams{}, tc.vs.Copy(), 0, 0, nil) - require.NoError(t, err) - require.NoError(t, vs.UpdateScores(int64(tc.times), 0)) - newVset := vs.ValidatorSet() - for _, val := range tc.vs.Validators { - _, updatedVal := newVset.GetByProTxHash(val.ProTxHash) - assert.Equal(t, updatedVal.ProposerPriority, val.ProposerPriority-tc.avg, "test case: %v", i) - } - } -} - -func setupTestHeightScore(t *testing.T, genesisHeight int64) ([]crypto.ProTxHash, validatorscoring.ValidatorScoringStrategy) { +func setupTestHeightScore(t *testing.T, genesisHeight int64) ([]crypto.ProTxHash, validatorscoring.ProposerProvider) { var proTxHashes []crypto.ProTxHash for i := byte(1); i <= 5; i++ { protx := make([]byte, 32) @@ -215,12 +167,12 @@ func TestHeightScoreHR(t *testing.T) { proTxHashes, vs := setupTestHeightScore(t, genesisHeight) // now test with rounds - for h := int64(1); h < 100; h++ { - for r := int32(0); r < 100; r++ { + for h := int64(1); h < 10; h++ { + for r := int32(0); r < 10; r++ { proposer := vs.MustGetProposer(h, r) pos := (h - genesisHeight + int64(r)) % int64(len(proTxHashes)) - assert.Equal(t, proTxHashes[pos], proposer.ProTxHash, "height %d", h) - require.NoError(t, vs.UpdateScores(h, r), "height %d", h) + require.Equal(t, proTxHashes[pos], proposer.ProTxHash, "height %d, round %d", h, r) + require.NoError(t, vs.UpdateScores(h, r), "height %d, round %d", h, r) } } } diff --git a/internal/features/validatorscoring/validator_scoring.go b/internal/features/validatorscoring/validator_scoring.go index 27eb295c91..8c704177c8 100644 --- a/internal/features/validatorscoring/validator_scoring.go +++ b/internal/features/validatorscoring/validator_scoring.go @@ -7,7 +7,7 @@ import ( "github.com/dashpay/tenderdash/types" ) -type ValidatorScoringStrategy interface { +type ProposerProvider interface { // GetProposer returns the proposer for the given height and round. It calls Update if necessary. GetProposer(height int64, round int32) (*types.Validator, error) @@ -24,7 +24,7 @@ type ValidatorScoringStrategy interface { // Returns pointer to underlying validator set; not thread-safe, and should be modified with caution ValidatorSet() *types.ValidatorSet // Create deep copy of the strategy and its underlying validator set - Copy() ValidatorScoringStrategy + Copy() ProposerProvider } // NewProposerStrategy creates a ValidatorScoringStrategy from the ValidatorSet. @@ -41,7 +41,7 @@ type ValidatorScoringStrategy interface { // - `valsetRound` - current round of the validator set // - `bs` - block store used to retreve info about historical commits; optional, can be nil func NewProposerStrategy(cp types.ConsensusParams, valSet *types.ValidatorSet, valsetHeight int64, valsetRound int32, - bs BlockCommitStore) (ValidatorScoringStrategy, error) { + bs BlockCommitStore) (ProposerProvider, error) { switch cp.Version.ConsensusVersion { case int32(tmtypes.VersionParams_CONSENSUS_VERSION_0): return NewHeightBasedScoringStrategy(valSet, valsetHeight, bs) diff --git a/internal/rpc/core/consensus.go b/internal/rpc/core/consensus.go index 19d1121d13..983e041c44 100644 --- a/internal/rpc/core/consensus.go +++ b/internal/rpc/core/consensus.go @@ -38,11 +38,16 @@ func (env *Environment) Validators(_ctx context.Context, req *coretypes.RequestV v := validators.Validators[skipCount : skipCount+tmmath.MinInt(perPage, totalCount-skipCount)] + quorumHash := validators.QuorumHash.Copy() + result := &coretypes.ResultValidators{ - BlockHeight: height, - Validators: v, - Count: len(v), - Total: totalCount, + BlockHeight: height, + Validators: v, + Count: len(v), + Total: totalCount, + ThresholdPublicKey: &validators.ThresholdPublicKey, + QuorumType: validators.QuorumType, + QuorumHash: &quorumHash, } if libs.BoolValue(req.RequestQuorumInfo) { result.QuorumHash = &validators.QuorumHash diff --git a/internal/state/helpers_test.go b/internal/state/helpers_test.go index c90dcf8819..b7c824bb1a 100644 --- a/internal/state/helpers_test.go +++ b/internal/state/helpers_test.go @@ -194,8 +194,10 @@ func makeRandomStateFromValidatorSet( if err != nil { panic(err) } - if err := expectedVS.UpdateScores(height, 0); err != nil { - panic(err) + for h := lastHeightValidatorsChanged; h <= height; h++ { + if err := expectedVS.UpdateScores(h, 0); err != nil { + panic(err) + } } return sm.State{ diff --git a/internal/state/state_test.go b/internal/state/state_test.go index a84e92503f..c687f1a682 100644 --- a/internal/state/state_test.go +++ b/internal/state/state_test.go @@ -3,7 +3,6 @@ package state_test import ( "context" "fmt" - "math/big" "os" "testing" @@ -247,18 +246,28 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { statestore := sm.NewStore(stateDB) blockStore := mocks.NewBlockStore(t) - blockStore.On("LoadBlockCommit", mock.Anything).Return(&types.Commit{}) // Can't load anything for height 0. _, err := statestore.LoadValidators(0, blockStore) assert.IsType(t, sm.ErrNoValSetForHeight{}, err, "expected err at height 0") // Should be able to load for height 1. + blockStore.On("LoadBlockMeta", int64(1)).Return(&types.BlockMeta{ + Header: types.Header{ + Height: 1, + ProposerProTxHash: state.Validators.GetByIndex((int32(state.LastBlockHeight) % int32(state.Validators.Size()))).ProTxHash, + }}) + v, err := statestore.LoadValidators(1, blockStore) require.NoError(t, err, "expected no err at height 1") assert.Equal(t, v.Hash(), state.Validators.Hash(), "expected validator hashes to match") // Should NOT be able to load for height 2. + blockStore.On("LoadBlockMeta", int64(2)).Return(&types.BlockMeta{ + Header: types.Header{ + Height: 2, + ProposerProTxHash: state.Validators.GetByIndex((int32(state.LastBlockHeight) % int32(state.Validators.Size()))).ProTxHash, + }}) _, err = statestore.LoadValidators(2, blockStore) require.Error(t, err, "expected no err at height 2") @@ -501,145 +510,6 @@ func TestEmptyValidatorUpdates(t *testing.T) { // } //} -// 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) - - ld := llmq.MustGenerate(crypto.RandProTxHashes(2)) - - val1VotingPower := types.DefaultDashVotingPower - val1ProTxHash := ld.ProTxHashes[0] - val1PubKey := ld.PubKeyShares[0] - val1 := &types.Validator{ProTxHash: val1ProTxHash, PubKey: val1PubKey, VotingPower: val1VotingPower} - - quorumHash := crypto.RandQuorumHash() - state.Validators = types.NewValidatorSet([]*types.Validator{val1}, val1PubKey, btcjson.LLMQType_5_60, quorumHash, true) - - // NewValidatorSet calls IncrementProposerPriority but uses on a copy of val1 - assert.EqualValues(t, 0, val1.ProposerPriority) - - block, err := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit), 0) - require.NoError(t, err) - blockID := block.BlockID(nil) - require.NoError(t, err) - - // Any node pro tx hash should do - firstNode := state.Validators.GetByIndex(0) - ctx := dash.ContextWithProTxHash(context.Background(), firstNode.ProTxHash) - changes, err := state.NewStateChangeset(ctx, sm.RoundParams{}) - assert.NoError(t, err) - updatedState, err := state.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - curTotal := val1VotingPower - // one increment step and one validator: 0 + power - total_power == 0 - assert.Equal(t, 0+val1VotingPower-curTotal, updatedState.Validators.Validators[0].ProposerPriority) - - // add a validator - val2ProTxHash := ld.ProTxHashes[1] - val2PubKey := ld.PubKeyShares[1] - val2VotingPower := types.DefaultDashVotingPower - fvp, err := cryptoenc.PubKeyToProto(val2PubKey) - require.NoError(t, err) - - updateAddVal := abci.ValidatorUpdate{ProTxHash: val2ProTxHash, PubKey: &fvp, Power: val2VotingPower} - validatorSetUpdate := &abci.ValidatorSetUpdate{ - ValidatorUpdates: []abci.ValidatorUpdate{updateAddVal}, - ThresholdPublicKey: fvp, - QuorumHash: quorumHash, - } - - changes, err = updatedState.NewStateChangeset(ctx, sm.RoundParams{ValidatorSetUpdate: validatorSetUpdate}) - assert.NoError(t, err) - - updatedState2, err := updatedState.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - - valsetScoresNewHeight(t, &updatedState2) - - require.Equal(t, len(updatedState2.Validators.Validators), 2) - _, updatedVal1 := updatedState2.Validators.GetByProTxHash(val1ProTxHash) - _, addedVal2 := updatedState2.Validators.GetByProTxHash(val2ProTxHash) - - // 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 validators does not reset the ProposerPriority to zero if we keep the same quorum: - // If we change quorums it will! - // 1. Add - Val2 VotingPower change to 1 => - abciValidatorUpdates := types.ValidatorUpdatesRegenerateOnProTxHashes(ld.ProTxHashes) - - // this will cause the diff of priorities (77) - // to be larger than threshold == 2*totalVotingPower (22): - abciValidatorUpdates.QuorumHash = quorumHash - changes, err = updatedState2.NewStateChangeset(ctx, sm.RoundParams{ValidatorSetUpdate: validatorSetUpdate}) - require.NoError(t, err) - - updatedState3, err := updatedState2.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - - valsetScoresNewHeight(t, &updatedState3) - - require.Equal(t, len(updatedState3.Validators.Validators), 2) - _, prevVal1 := updatedState2.Validators.GetByProTxHash(val1ProTxHash) - _, prevVal2 := updatedState2.Validators.GetByProTxHash(val2ProTxHash) - _, updatedVal1 = updatedState3.Validators.GetByProTxHash(val1ProTxHash) - _, updatedVal2 := updatedState3.Validators.GetByProTxHash(val2ProTxHash) - - assert.NotZero(t, updatedVal1) - assert.NotZero(t, updatedVal2) - // 2. Scale - // old prios: v1(100):13, v2(100):-12 - wantVal1Prio = prevVal1.ProposerPriority - wantVal2Prio = prevVal2.ProposerPriority - // scale to diffMax = 400 = 2 * tvp, diff=13-(-12)=25 - // new totalPower - totalPower := updatedVal1.VotingPower + updatedVal2.VotingPower - dist := wantVal2Prio - wantVal1Prio - if dist < 0 { // get the absolute distance - dist *= -1 - } - // ratio := (dist + 2*totalPower - 1) / 2*totalPower = 224/200 = 1 - ratio := int64(float64(dist+2*totalPower-1) / float64(2*totalPower)) - // v1(100):13/1, v2(100):-12/1 - if ratio != 0 { - wantVal1Prio /= ratio // 13 - wantVal2Prio /= ratio // -12 - } - - // 3. Center - noop - // 4. IncrementProposerPriority() -> - // v1(100):13+100, v2(100):-12+100 -> v2 proposer so subtract tvp(11) - // v1(100):-87, v2(1):88 - wantVal2Prio += updatedVal2.VotingPower // 88 -> prop - wantVal1Prio += updatedVal1.VotingPower // 113 - wantVal1Prio -= totalPower // -87 - - assert.Equal(t, wantVal2Prio, updatedVal2.ProposerPriority) - assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) -} - func valsetScoresNewHeight(t *testing.T, state *sm.State) *types.Validator { ps, err := validatorscoring.NewProposerStrategy( state.ConsensusParams, @@ -655,187 +525,6 @@ func valsetScoresNewHeight(t *testing.T, state *sm.State) *types.Validator { return ps.MustGetProposer(state.LastBlockHeight+1, 0) } -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) - - ld := llmq.MustGenerate(crypto.RandProTxHashes(2)) - thresholdPublicKey := cryptoenc.MustPubKeyToProto(ld.ThresholdPubKey) - - val1VotingPower := types.DefaultDashVotingPower - val1ProTxHash := ld.ProTxHashes[0] - val1PubKey := ld.PubKeyShares[0] - val1 := &types.Validator{ProTxHash: val1ProTxHash, PubKey: val1PubKey, VotingPower: val1VotingPower} - - // reset state validators to above validator, the threshold key is just the validator key since there is only 1 validator - quorumHash := crypto.RandQuorumHash() - state.Validators = types.NewValidatorSet([]*types.Validator{val1}, val1PubKey, btcjson.LLMQType_5_60, quorumHash, true) - // valsetScoresNewHeight(t, &state) - // we only have one validator: - assert.Equal(t, val1ProTxHash, state.Validators.Proposer.ProTxHash) - - firstNode := state.Validators.GetByIndex(0) - ctx := dash.ContextWithProTxHash(context.Background(), firstNode.ProTxHash) - - block, err := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit), 0) - require.NoError(t, err) - blockID := block.BlockID(nil) - require.NoError(t, err) - - // no updates: - changes, err := state.NewStateChangeset(ctx, sm.RoundParams{}) - assert.NoError(t, err) - - updatedState, err := state.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - // valsetScoresNewHeight(t, &updatedState) // this usually happens in new round - - // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 - totalPower := val1VotingPower - wantVal1Prio := 0 + val1VotingPower - totalPower - assert.Equal(t, wantVal1Prio, updatedState.Validators.Validators[0].ProposerPriority) - assert.Equal(t, val1ProTxHash, updatedState.Validators.Proposer.ProTxHash) - - // add a validator with the same voting power as the first - val2ProTxHash := ld.ProTxHashes[1] - val2PubKey := ld.PubKeyShares[1] - fvp, err := cryptoenc.PubKeyToProto(val2PubKey) - require.NoError(t, err) - updateAddVal := abci.ValidatorUpdate{ProTxHash: val2ProTxHash, PubKey: &fvp, Power: val1VotingPower} - valsetUpdate := &abci.ValidatorSetUpdate{ - ValidatorUpdates: []abci.ValidatorUpdate{updateAddVal}, - ThresholdPublicKey: thresholdPublicKey, - QuorumHash: quorumHash, - } - changes, err = updatedState.NewStateChangeset(ctx, sm.RoundParams{ValidatorSetUpdate: valsetUpdate}) - assert.NoError(t, err) - - updatedState2, err := updatedState.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - // valsetScoresNewHeight(t, &updatedState2) // this usually happens in new round - - require.Equal(t, len(updatedState2.Validators.Validators), 2) - updatedState2.Validators.Recalculate() - // val1 will still be proposer as val2 just got added: - assert.Equal(t, val1ProTxHash, updatedState.Validators.Proposer.ProTxHash) - assert.Equal(t, val1ProTxHash, updatedState2.Validators.Proposer.ProTxHash) - - _, updatedVal1 := updatedState2.Validators.GetByProTxHash(val1ProTxHash) - _, oldVal1 := updatedState.Validators.GetByProTxHash(val1ProTxHash) - _, updatedVal2 := updatedState2.Validators.GetByProTxHash(val2ProTxHash) - - // 1. Add - val2VotingPower := val1VotingPower - totalPower = val1VotingPower + val2VotingPower // 200 - v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) // -225 - // 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, - ) - - changes, err = updatedState2.NewStateChangeset(ctx, sm.RoundParams{ValidatorSetUpdate: valsetUpdate}) - assert.NoError(t, err) - updatedState3, err := updatedState2.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - //valsetScoresNewHeight(t, &updatedState3) // this usually happens in new round - - // assert.Equal(t, updatedState3.Validators, updatedState2.Validators) - _, updatedVal1 = updatedState3.Validators.GetByProTxHash(val1ProTxHash) - _, updatedVal2 = updatedState3.Validators.GetByProTxHash(val2ProTxHash) - - // val1 will still be proposer: - assert.Equal(t, val1ProTxHash, updatedState3.Validators.Proposer.ProTxHash) - - // 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 - changes, err = oldState.NewStateChangeset(ctx, sm.RoundParams{}) - assert.NoError(t, err) - oldState, err = oldState.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - expectedVal1Prio2 = 13 - expectedVal2Prio2 = -12 - expectedVal1Prio = -87 - expectedVal2Prio = 88 - - for i := 0; i < 1000; i++ { - // no validator updates: - changes, err = oldState.NewStateChangeset(ctx, sm.RoundParams{}) - require.NoError(t, err) - - updatedState, err := oldState.Update(blockID, &block.Header, &changes) - assert.NoError(t, err) - // valsetScoresNewHeight(t, &updatedState) // this usually happens in new round - - // alternate (and cyclic priorities): - assert.NotEqual( - t, - updatedState.Validators.Proposer.ProTxHash, - oldState.Validators.Proposer.ProTxHash, - "iter: %v", - i, - ) - assert.Equal(t, oldState.LastValidators.Proposer.ProTxHash, updatedState.Validators.Proposer.ProTxHash, "iter: %v", i) - - _, updatedVal1 = updatedState.Validators.GetByProTxHash(val1ProTxHash) - assert.NotNil(t, updatedVal1) - _, updatedVal2 = updatedState.Validators.GetByProTxHash(val2ProTxHash) - assert.NotNil(t, updatedVal2) - - if i%2 == 0 { - assert.Equal(t, updatedState.Validators.Proposer.ProTxHash, val1ProTxHash) - assert.Equal(t, expectedVal1Prio, updatedVal1.ProposerPriority) // -19 - assert.Equal(t, expectedVal2Prio, updatedVal2.ProposerPriority) // 0 - } else { - // assert.Equal(t, updatedState.Validators.Proposer.ProTxHash, val2ProTxHash) - assert.Equal(t, expectedVal1Prio2, updatedVal1.ProposerPriority) // -9 - assert.Equal(t, expectedVal2Prio2, updatedVal2.ProposerPriority) // -10 - } - // update for next iteration: - oldState = updatedState - } -} - func TestFourAddFourMinusOneGenesisValidators(t *testing.T) { tearDown, _, state := setupTestCase(t) defer tearDown(t) @@ -945,47 +634,14 @@ func TestFourAddFourMinusOneGenesisValidators(t *testing.T) { updatedState = execute(state, updatedState, changes) if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks): if proposers[i%numVals] == nil { - proposers[i%numVals] = updatedState.Validators.Proposer + proposers[i%numVals] = updatedState.Validators.Proposer() } else { - assert.Equal(t, proposers[i%numVals], updatedState.Validators.Proposer) + assert.Equal(t, proposers[i%numVals], updatedState.Validators.Proposer()) } } } } -func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { - const valSetSize = 2 - tearDown, stateDB, state := setupTestCase(t) - t.Cleanup(func() { tearDown(t) }) - stateStore := sm.NewStore(stateDB) - state.Validators, _ = types.RandValidatorSet(valSetSize) - err := stateStore.Save(state) - require.NoError(t, err) - - blockStore := mocks.NewBlockStore(t) - blockStore.On("LoadBlockCommit", mock.Anything).Return(&types.Commit{}).Maybe() - - state2 := state.Copy() - state2.LastBlockHeight++ - state2.Validators = state.Validators.Copy() - state2.LastHeightValidatorsChanged = state2.LastBlockHeight + 1 - valsetScoresNewHeight(t, &state2) // this is normally done in updateState - err = stateStore.Save(state2) - require.NoError(t, err) - - nextHeight := state.LastBlockHeight + 1 - - v0, err := stateStore.LoadValidators(nextHeight, blockStore) - assert.NoError(t, err) - acc0 := v0.Validators[0].ProposerPriority - - v1, err := stateStore.LoadValidators(nextHeight+1, blockStore) - assert.NoError(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) { @@ -1000,6 +656,11 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { require.Equal(t, int64(0), state.LastBlockHeight) state.Validators, _ = types.RandValidatorSet(valSetSize) err := stateStore.Save(state) + blockStore.On("LoadBlockMeta", state.LastBlockHeight).Return(&types.BlockMeta{ + Header: types.Header{ + Height: state.LastBlockHeight, + ProposerProTxHash: state.Validators.GetByIndex(int32(state.LastBlockHeight-state.InitialHeight) % valSetSize).ProTxHash, + }}).Maybe() require.NoError(t, err) // ====== HEIGHT 2 ====== // diff --git a/internal/state/store.go b/internal/state/store.go index 4466c4eabf..92bb46ae98 100644 --- a/internal/state/store.go +++ b/internal/state/store.go @@ -11,6 +11,7 @@ import ( abci "github.com/dashpay/tenderdash/abci/types" "github.com/dashpay/tenderdash/internal/features/validatorscoring" + "github.com/dashpay/tenderdash/libs/log" tmmath "github.com/dashpay/tenderdash/libs/math" tmstate "github.com/dashpay/tenderdash/proto/tendermint/state" tmproto "github.com/dashpay/tenderdash/proto/tendermint/types" @@ -114,14 +115,15 @@ type Store interface { // dbStore wraps a db (github.com/tendermint/tm-db) type dbStore struct { - db dbm.DB + db dbm.DB + logger log.Logger } var _ Store = (*dbStore)(nil) // NewStore creates the dbStore of the state pkg. func NewStore(db dbm.DB) Store { - return dbStore{db} + return dbStore{db, log.NewNopLogger()} } // LoadState loads the State from the database. @@ -501,15 +503,14 @@ func (store dbStore) SaveValidatorSets(lowerHeight, upperHeight int64, vals *typ // LoadValidators loads the ValidatorSet for a given height and round 0. // // Returns ErrNoValSetForHeight if the validator set can't be found for this height. -func (store dbStore) LoadValidators(height int64, commitStore validatorscoring.BlockCommitStore) (*types.ValidatorSet, error) { +func (store dbStore) LoadValidators(height int64, bs validatorscoring.BlockCommitStore) (*types.ValidatorSet, error) { valInfo, err := loadValidatorsInfo(store.db, height) if err != nil { return nil, ErrNoValSetForHeight{Height: height, Err: err} } - var lastStoredHeight int64 if valInfo.ValidatorSet == nil { - lastStoredHeight = lastStoredHeightFor(height, valInfo.LastHeightChanged) + lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged) valInfo, err = loadValidatorsInfo(store.db, lastStoredHeight) if err != nil || valInfo.ValidatorSet == nil { return nil, @@ -519,8 +520,6 @@ func (store dbStore) LoadValidators(height int64, commitStore validatorscoring.B err, ) } - } else { - lastStoredHeight = valInfo.LastHeightChanged } valSet, err := types.ValidatorSetFromProto(valInfo.ValidatorSet) @@ -528,37 +527,62 @@ func (store dbStore) LoadValidators(height int64, commitStore validatorscoring.B return nil, err } - cp := *types.DefaultConsensusParams() + // FIND PROPOSER - // using proposer strategy defined in the consensus params at height H, process all rounds at that height. - var round int32 - for h := lastStoredHeight + 1; h <= height; h++ { - // if cp cannot be loaded, we use previous ones - or default - if retrievedConsensusParams, err := store.LoadConsensusParams(h); err == nil { - cp = retrievedConsensusParams + // As per definition, proposer at height 1 is the first validator in the validator set. + if height == 1 { + proposer := valSet.GetByIndex(0) + if err := valSet.SetProposer(proposer.ProTxHash); err != nil { + return nil, fmt.Errorf("could not set proposer: %w", err) } + return valSet, nil + } - // valSet is from height `h-1`, round `round` - // TODO: ensure we save validator set at round 0, so that initial round == 0 here. - strategy, err := validatorscoring.NewProposerStrategy(cp, valSet, h-1, round, commitStore) - if err != nil { - return nil, fmt.Errorf("failed to create validator scoring strategy: %w", err) + // load consensus params to determine algorithm to use for proposer selection + cp, err := store.LoadConsensusParams(height) + if err != nil { + store.logger.Warn("failed to load consensus params, falling back to defaults", "height", height, "err", err) + cp = *types.DefaultConsensusParams() + } + + // if we have that block in block store, we just rolllback to round 0 + if meta := bs.LoadBlockMeta(height); meta != nil { + proposer := meta.Header.ProposerProTxHash + if err := valSet.SetProposer(proposer); err != nil { + return nil, fmt.Errorf("could not set proposer: %w", err) } - // load new round; we default to round 0 for not committed height - round = 0 - if commit := commitStore.LoadBlockCommit(h); commit != nil { - round = commit.Round + strategy, err := validatorscoring.NewProposerStrategy(cp, valSet, meta.Header.Height, meta.Round, bs) + if err != nil { + return nil, fmt.Errorf("failed to create validator scoring strategy: %w", err) } - if err = strategy.UpdateScores(h, round); err != nil { - return nil, fmt.Errorf("failed to update validator scores at height %d, round %d: %w", - h, round, err) + if err := strategy.UpdateScores(meta.Header.Height, 0); err != nil { + return nil, fmt.Errorf("failed to update validator scores at height %d, round 0: %w", meta.Header.Height, err) } + return strategy.ValidatorSet(), nil + } + + // If we have that height in the block store, we just take proposer from previous block and advance it. + // We don't use current height block because we want to return proposer at round 0. + prevMeta := bs.LoadBlockMeta(height - 1) + if prevMeta == nil { + return nil, fmt.Errorf("could not find block meta for height %d", height-1) + } + // Configure proposer strategy; first set proposer from previous block + if err := valSet.SetProposer(prevMeta.Header.ProposerProTxHash); err != nil { + return nil, fmt.Errorf("could not set proposer: %w", err) + } + strategy, err := validatorscoring.NewProposerStrategy(cp, valSet, prevMeta.Header.Height, prevMeta.Round, bs) + if err != nil { + return nil, fmt.Errorf("failed to create validator scoring strategy: %w", err) + } - // not needed as original valSet still points to the same valSet, but doing it for consistency - valSet = strategy.ValidatorSet() + // now, advance to (height,0) + if err := strategy.UpdateScores(height, 0); err != nil { + return nil, fmt.Errorf("failed to update validator scores at height %d, round 0: %w", height, err) } - return valSet, nil + + return strategy.ValidatorSet(), nil } func lastStoredHeightFor(height, lastHeightChanged int64) int64 { diff --git a/internal/state/store_test.go b/internal/state/store_test.go index b1148c8ad9..b1b5486c77 100644 --- a/internal/state/store_test.go +++ b/internal/state/store_test.go @@ -33,7 +33,15 @@ func TestStoreBootstrap(t *testing.T) { vals, _ := types.RandValidatorSet(3) blockStore := mocks.NewBlockStore(t) - blockStore.On("LoadBlockCommit", mock.Anything).Return(&types.Commit{}).Maybe() + for h := int64(99); h <= 100; h++ { + blockStore.On("LoadBlockMeta", h). + Return(&types.BlockMeta{ + Header: types.Header{ + Height: h, + ProposerProTxHash: vals.GetByIndex(int32(int(h+1) % vals.Size())).ProTxHash, + }, + }) + } bootstrapState := makeRandomStateFromValidatorSet(vals, 100, 100, blockStore) require.NoError(t, stateStore.Bootstrap(bootstrapState)) @@ -60,7 +68,7 @@ func assertProposer(t *testing.T, valSet *types.ValidatorSet, h int64) { const initialHeight = 1 // check if currently selected proposer is correct - idx, _ := valSet.GetByProTxHash(valSet.Proposer.ProTxHash) + idx, _ := valSet.GetByProTxHash(valSet.Proposer().ProTxHash) exp := (h - initialHeight) % int64(valSet.Size()) assert.EqualValues(t, exp, idx, "pre-set proposer at height %d", h) @@ -76,17 +84,26 @@ func assertProposer(t *testing.T, valSet *types.ValidatorSet, h int64) { func TestStoreLoadValidators(t *testing.T) { stateDB := dbm.NewMemDB() stateStore := sm.NewStore(stateDB) - blockStore := mocks.NewBlockStore(t) - blockStore.On("LoadBlockCommit", mock.Anything).Return(&types.Commit{}) - vals, _ := types.RandValidatorSet(3) - expectedVS, err := validatorscoring.NewProposerStrategy(types.ConsensusParams{}, vals.Copy(), 1, 0, blockStore) + expectedVS, err := validatorscoring.NewProposerStrategy(types.ConsensusParams{}, vals.Copy(), 1, 0, nil) require.NoError(t, err) + // initialize block store - create mock validators for each height + blockStoreVS := expectedVS.Copy() + blockStore := mocks.NewBlockStore(t) + for h := int64(1); h <= valSetCheckpointInterval; h++ { + blockStore.On("LoadBlockMeta", h).Return(&types.BlockMeta{ + Header: types.Header{ + Height: h, + ProposerProTxHash: blockStoreVS.MustGetProposer(h, 0).ProTxHash, + }}).Maybe() + } + // 1) LoadValidators loads validators using a height where they were last changed // Note that only the current validators at height h are saved require.NoError(t, stateStore.Save(makeRandomStateFromValidatorSet(vals, 1, 1, blockStore))) + require.NoError(t, stateStore.Save(makeRandomStateFromValidatorSet(vals, 2, 1, blockStore))) loadedValsH1, err := stateStore.LoadValidators(1, blockStore) @@ -109,7 +126,7 @@ func TestStoreLoadValidators(t *testing.T) { // 2) LoadValidators loads validators using a checkpoint height // add a validator set after the checkpoint - state := makeRandomStateFromValidatorSet(vals, valSetCheckpointInterval+1, 1, blockStore) + state := makeRandomStateFromValidatorSet(vals, valSetCheckpointInterval+1, 1, nil) err = stateStore.Save(state) require.NoError(t, err) @@ -129,7 +146,9 @@ func TestStoreLoadValidators(t *testing.T) { // ensure we have correct validator set loaded; at height h, we expcect `(h+1) % 3` // (adding 1 as we start from initial height 1). - require.NoError(t, expectedVS.UpdateScores(valSetCheckpointInterval-1, 0)) + for h := int64(2); h <= valSetCheckpointInterval-1; h++ { + require.NoError(t, expectedVS.UpdateScores(h, 0)) + } expected := expectedVS.ValidatorSet() assertProposer(t, expected, valSetCheckpointInterval-1) require.NotEqual(t, expected, valsAtCheckpoint) @@ -212,9 +231,6 @@ func TestStoreLoadConsensusParams(t *testing.T) { } func TestPruneStates(t *testing.T) { - blockStore := mocks.NewBlockStore(t) - blockStore.On("LoadBlockCommit", mock.Anything).Return(&types.Commit{}) - testcases := map[string]struct { startHeight int64 endHeight int64 @@ -248,7 +264,6 @@ func TestPruneStates(t *testing.T) { validator := &types.Validator{VotingPower: types.DefaultDashVotingPower, PubKey: pk, ProTxHash: proTxHash} validatorSet := &types.ValidatorSet{ Validators: []*types.Validator{validator}, - Proposer: validator, ThresholdPublicKey: validator.PubKey, QuorumHash: crypto.RandQuorumHash(), } @@ -301,6 +316,20 @@ func TestPruneStates(t *testing.T) { } require.NoError(t, err) + blockStore := mocks.NewBlockStore(t) + // We initialize block store from remainingValSetHeight just to pass this test; in practive, it can be + // pruned. But here we want to check state store logic, not block store logic. + for h := int64(1); h < tc.remainingValSetHeight; h++ { + blockStore.On("LoadBlockMeta", h).Return(nil).Maybe() + } + for h := tc.remainingValSetHeight; h <= tc.endHeight; h++ { + blockStore.On("LoadBlockMeta", h).Return(&types.BlockMeta{ + Header: types.Header{ + Height: h, + ProposerProTxHash: proTxHash, + }, + }).Maybe() + } for h := tc.pruneHeight; h <= tc.endHeight; h++ { vals, err := stateStore.LoadValidators(h, blockStore) require.NoError(t, err, h) diff --git a/internal/statesync/syncer_test.go b/internal/statesync/syncer_test.go index e5934608a1..00da83c9ed 100644 --- a/internal/statesync/syncer_test.go +++ b/internal/statesync/syncer_test.go @@ -91,10 +91,10 @@ func (suite *SyncerTestSuite) TestSyncAny() { LastAppHash: []byte("app_hash"), LastValidators: &types.ValidatorSet{ - Proposer: &types.Validator{ProTxHash: crypto.Checksum([]byte("val1"))}, + Validators: []*types.Validator{{ProTxHash: crypto.Checksum([]byte("val1"))}}, }, Validators: &types.ValidatorSet{ - Proposer: &types.Validator{ProTxHash: crypto.Checksum([]byte("val2"))}, + Validators: []*types.Validator{{ProTxHash: crypto.Checksum([]byte("val2"))}}, }, ConsensusParams: *types.DefaultConsensusParams(), diff --git a/light/client_test.go b/light/client_test.go index 34daf19e03..f0a74e3919 100644 --- a/light/client_test.go +++ b/light/client_test.go @@ -576,7 +576,6 @@ func TestClient(t *testing.T) { t.Run("EnsureValidHeadersAndValSets", func(t *testing.T) { emptyValSet := &types.ValidatorSet{ Validators: nil, - Proposer: nil, } testCases := []struct { diff --git a/light/provider/http/http.go b/light/provider/http/http.go index 546d2334d4..a9fc216bf2 100644 --- a/light/provider/http/http.go +++ b/light/provider/http/http.go @@ -138,7 +138,7 @@ func (p *http) LightBlock(ctx context.Context, height int64) (*types.LightBlock, Reason: fmt.Errorf("height %d responded doesn't match height %d requested", sh.Height, height), } } - vs, err := p.validatorSet(ctx, &sh.Height) + vs, err := p.validatorSet(ctx, &sh.Height, sh.Header.ProposerProTxHash) if err != nil { return nil, err } @@ -162,7 +162,7 @@ func (p *http) ReportEvidence(ctx context.Context, ev types.Evidence) error { return err } -func (p *http) validatorSet(ctx context.Context, height *int64) (*types.ValidatorSet, error) { +func (p *http) validatorSet(ctx context.Context, height *int64, proposer types.ProTxHash) (*types.ValidatorSet, error) { // Since the malicious node could report a massive number of pages, making us // spend a considerable time iterating, we restrict the number of pages here. // => 10000 validators max @@ -238,11 +238,17 @@ func (p *http) validatorSet(ctx context.Context, height *int64) (*types.Validato break } } + valSet := types.NewValidatorSet(vals, thresholdPubKey, quorumType, quorumHash, false) - valSet, err := types.ValidatorSetFromExistingValidators(vals, thresholdPubKey, quorumType, quorumHash) - if err != nil { - return nil, provider.ErrBadLightBlock{Reason: err} + if valSet == nil || valSet.IsNilOrEmpty() { + return nil, provider.ErrBadLightBlock{Reason: fmt.Errorf("retrieved nil or empty validator set")} + } + if err := valSet.ValidateBasic(); err != nil { + return nil, provider.ErrBadLightBlock{Reason: fmt.Errorf("invalid validator set retrieved: %w", err)} } + + valSet.SetProposer(proposer) + return valSet, nil } diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index 84192cf2a1..0901990194 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -68,9 +68,15 @@ func InjectEvidence(ctx context.Context, logger log.Logger, r *rand.Rand, testne return errors.New("quorum hash must be returned when requested") } - valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators, *valRes.ThresholdPublicKey, valRes.QuorumType, *valRes.QuorumHash) - if err != nil { - return err + valSet := types.NewValidatorSet(valRes.Validators, *valRes.ThresholdPublicKey, + valRes.QuorumType, + *valRes.QuorumHash, + false) + if valSet == nil { + return fmt.Errorf("could not create validator set from response") + } + if err = valSet.SetProposer(blockRes.Block.ProposerProTxHash); err != nil { + return fmt.Errorf("could not set proposer: %w", err) } // get the private keys of all the validators in the network diff --git a/test/e2e/tests/validator_test.go b/test/e2e/tests/validator_test.go index 6be1201434..1fceb237c6 100644 --- a/test/e2e/tests/validator_test.go +++ b/test/e2e/tests/validator_test.go @@ -129,7 +129,7 @@ func TestValidator_Propose(t *testing.T) { // validatorSchedule is a validator set iterator, which takes into account // validator set updates. type validatorSchedule struct { - Set validatorscoring.ValidatorScoringStrategy + Set validatorscoring.ProposerProvider height int64 updates map[int64]e2e.ValidatorsMap thresholdPublicKeyUpdates map[int64]crypto.PubKey diff --git a/types/block_test.go b/types/block_test.go index 241f179611..7d77bcce86 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -89,8 +89,7 @@ func TestBlockValidateBasic(t *testing.T) { }{ {"Make Block", func(blk *Block) {}, false}, {"Make Block w/ proposer pro_tx_hash", func(blk *Block) { - // use of obsolete findProposer method, only for test - blk.ProposerProTxHash = valSet.findProposer().ProTxHash + blk.ProposerProTxHash = valSet.Proposer().ProTxHash }, false}, {"Negative Height", func(blk *Block) { blk.Height = -1 }, true}, {"Modify the last Commit", func(blk *Block) { @@ -131,7 +130,7 @@ func TestBlockValidateBasic(t *testing.T) { j := i t.Run(tcRun.testName, func(t *testing.T) { block := MakeBlock(h, txs, commit, evList) - block.ProposerProTxHash = valSet.findProposer().ProTxHash + block.ProposerProTxHash = valSet.Proposer().ProTxHash tcRun.malleateBlock(block) err = block.ValidateBasic() assert.Equal(t, tcRun.expErr, err != nil, "#%d: %v", j, err) diff --git a/types/light_test.go b/types/light_test.go index bcb1bad17f..bba922c2bd 100644 --- a/types/light_test.go +++ b/types/light_test.go @@ -26,7 +26,7 @@ func TestLightBlockValidateBasic(t *testing.T) { header.Version.Block = version.BlockProtocol vals2, _ := RandValidatorSet(3) vals3 := vals.Copy() - vals3.Proposer = &Validator{} + vals3.QuorumHash = []byte("invalid") commit.BlockID.Hash = header.Hash() sh := &SignedHeader{ @@ -94,8 +94,6 @@ func TestLightBlockProtobuf(t *testing.T) { header.LastBlockID = commit.BlockID header.Version.Block = version.BlockProtocol header.ValidatorsHash = vals.Hash() - vals3 := vals.Copy() - vals3.Proposer = &Validator{} commit.BlockID.Hash = header.Hash() commit.QuorumHash = vals.QuorumHash diff --git a/types/validator.go b/types/validator.go index 9316f96b53..72b229abd6 100644 --- a/types/validator.go +++ b/types/validator.go @@ -1,7 +1,6 @@ package types import ( - "bytes" "encoding/json" "errors" "fmt" @@ -25,8 +24,6 @@ type Validator struct { PubKey crypto.PubKey VotingPower int64 NodeAddress ValidatorAddress - - ProposerPriority int64 } type validatorJSON struct { @@ -39,9 +36,8 @@ type validatorJSON struct { func (v Validator) MarshalJSON() ([]byte, error) { val := validatorJSON{ - ProTxHash: v.ProTxHash, - VotingPower: v.VotingPower, - ProposerPriority: v.ProposerPriority, + ProTxHash: v.ProTxHash, + VotingPower: v.VotingPower, } if v.PubKey != nil { pk, err := jsontypes.Marshal(v.PubKey) @@ -63,23 +59,20 @@ func (v *Validator) UnmarshalJSON(data []byte) error { } v.ProTxHash = val.ProTxHash v.VotingPower = val.VotingPower - v.ProposerPriority = val.ProposerPriority return nil } func NewTestValidatorGeneratedFromProTxHash(proTxHash crypto.ProTxHash) *Validator { return &Validator{ - VotingPower: DefaultDashVotingPower, - ProposerPriority: 0, - ProTxHash: proTxHash, + VotingPower: DefaultDashVotingPower, + ProTxHash: proTxHash, } } func NewTestRemoveValidatorGeneratedFromProTxHash(proTxHash crypto.ProTxHash) *Validator { return &Validator{ - VotingPower: 0, - ProposerPriority: 0, - ProTxHash: proTxHash, + VotingPower: 0, + ProTxHash: proTxHash, } } @@ -100,11 +93,10 @@ func NewValidator(pubKey crypto.PubKey, votingPower int64, proTxHash ProTxHash, } } return &Validator{ - PubKey: pubKey, - VotingPower: votingPower, - ProposerPriority: 0, - ProTxHash: proTxHash, - NodeAddress: addr, + PubKey: pubKey, + VotingPower: votingPower, + ProTxHash: proTxHash, + NodeAddress: addr, } } @@ -154,29 +146,6 @@ func (v *Validator) Copy() *Validator { return &vCopy } -// CompareProposerPriority Returns the one with higher ProposerPriority. -func (v *Validator) CompareProposerPriority(other *Validator) *Validator { - if v == nil { - return other - } - switch { - case v.ProposerPriority > other.ProposerPriority: - return v - case v.ProposerPriority < other.ProposerPriority: - return other - default: - result := bytes.Compare(v.ProTxHash, other.ProTxHash) - switch { - case result < 0: - return v - case result > 0: - return other - default: - panic("Cannot compare identical validators") - } - } -} - // String returns a string representation of String. // // 1. address @@ -188,11 +157,10 @@ func (v *Validator) String() string { if v == nil { return "nil-Validator" } - return fmt.Sprintf("Validator{%v %v VP:%v A:%v N:%s}", + return fmt.Sprintf("Validator{%v %v VP:%v N:%s}", v.ProTxHash, v.PubKey, v.VotingPower, - v.ProposerPriority, v.NodeAddress.String()) } @@ -209,7 +177,6 @@ func (v *Validator) ShortStringBasic() string { func (v *Validator) MarshalZerologObject(e *zerolog.Event) { e.Str("protxhash", v.ProTxHash.ShortString()) e.Int64("voting_power", v.VotingPower) - e.Int64("proposer_priority", v.ProposerPriority) e.Str("address", v.NodeAddress.String()) if v.PubKey != nil { @@ -262,9 +229,8 @@ func (v *Validator) ToProto() (*tmproto.Validator, error) { } vp := tmproto.Validator{ - VotingPower: v.VotingPower, - ProposerPriority: v.ProposerPriority, - ProTxHash: v.ProTxHash, + VotingPower: v.VotingPower, + ProTxHash: v.ProTxHash, } if v.PubKey != nil && len(v.PubKey.Bytes()) > 0 { @@ -287,7 +253,6 @@ func ValidatorFromProto(vp *tmproto.Validator) (*Validator, error) { } v := new(Validator) v.VotingPower = vp.GetVotingPower() - v.ProposerPriority = vp.GetProposerPriority() v.ProTxHash = vp.ProTxHash var err error diff --git a/types/validator_set.go b/types/validator_set.go index 14ccdf421c..272c3c3ab9 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "math" - "math/big" "sort" "strings" @@ -19,7 +18,6 @@ import ( "github.com/dashpay/tenderdash/crypto/merkle" "github.com/dashpay/tenderdash/dash/llmq" tmbytes "github.com/dashpay/tenderdash/libs/bytes" - tmmath "github.com/dashpay/tenderdash/libs/math" tmproto "github.com/dashpay/tenderdash/proto/tendermint/types" ) @@ -51,27 +49,20 @@ var ( // ValidatorSet represent a set of *Validator at a given height. // // The validators can be fetched by address or index. -// The index is in order of .VotingPower, so the indices are fixed for all +// The index is in order of .ProTxHash, so the indices are fixed for all // rounds of a given blockchain height - ie. the validators are sorted by their -// voting power (descending). Secondary index - .ProTxHash (ascending). -// -// On the other hand, the .ProposerPriority of each validator and the -// designated .GetProposer() of a set changes every round, upon calling -// .IncrementProposerPriority(). +// .ProTxHash (ascending). // // NOTE: Not goroutine-safe. // NOTE: All get/set to validators should copy the value for safety. type ValidatorSet struct { // NOTE: persisted via reflect, must be exported. - Validators []*Validator `json:"validators"` - Proposer *Validator `json:"proposer"` + Validators []*Validator `json:"validators"` + proposerIndex int32 ThresholdPublicKey crypto.PubKey `json:"threshold_public_key"` QuorumHash crypto.QuorumHash `json:"quorum_hash"` QuorumType btcjson.LLMQType `json:"quorum_type"` HasPublicKeys bool `json:"has_public_keys"` - - // cached (unexported) - totalVotingPower int64 } // NewValidatorSet initializes a ValidatorSet by copying over the values from @@ -96,8 +87,6 @@ func NewValidatorSet(valz []*Validator, newThresholdPublicKey crypto.PubKey, quo panic(fmt.Sprintf("Cannot create validator set: %v", err)) } - vals.Recalculate() - return vals } @@ -128,8 +117,8 @@ func (vals *ValidatorSet) ValidateBasic() error { return ErrValidatorSetNilOrEmpty } - if vals.Proposer == nil { - return errors.New("validator set proposer is not set") + if vals.proposerIndex >= int32(vals.Size()) { + return fmt.Errorf("validator set proposer index %d out of range, expected < %d", vals.proposerIndex, vals.Size()) } for idx, val := range vals.Validators { @@ -152,7 +141,7 @@ func (vals *ValidatorSet) ValidateBasic() error { return fmt.Errorf("quorumHash error: %w", err) } - if err := vals.Proposer.ValidateBasic(); err != nil { + if err := vals.Proposer().ValidateBasic(); err != nil { return fmt.Errorf("proposer failed validate basic, error: %w", err) } @@ -238,91 +227,38 @@ func (vals *ValidatorSet) QuorumHashValid() error { return nil } -// RescalePriorities rescales the priorities such that the distance between the -// maximum and minimum is smaller than `diffMax`. Panics if validator set is -// empty. -func (vals *ValidatorSet) RescalePriorities(diffMax int64) { +// Proposer returns the proposer of the validator set. +// +// Panics on empty validator set. +func (vals *ValidatorSet) Proposer() *Validator { if vals.IsNilOrEmpty() { panic("empty validator set") } - // 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 - } - - // Calculating 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 diff > diffMax && ratio != 0 { - for _, val := range vals.Validators { - val.ProposerPriority /= ratio - } - } - - vals.shiftByAvgProposerPriority() + return vals.GetByIndex(vals.proposerIndex) } -// Recaulculate recalculates all dynamic/cached parameters for the validator set, like scores, total voting power, etc. -func (vals *ValidatorSet) Recalculate() { - if vals.IsNilOrEmpty() { - return +// SetProposer sets the proposer of the validator set. +func (vals *ValidatorSet) SetProposer(newProposer ProTxHash) error { + idx, _ := vals.GetByProTxHash(newProposer) + if idx < 0 { + return fmt.Errorf("proposer %X not found in validator set", newProposer) } - vals.updateTotalVotingPower() - vals.Proposer = vals.findProposer() - vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower()) + vals.proposerIndex = idx + return nil } -// Should not be called on an empty validator set. -func (vals *ValidatorSet) computeAvgProposerPriority() int64 { - n := int64(len(vals.Validators)) - sum := big.NewInt(0) - for _, val := range vals.Validators { - sum.Add(sum, big.NewInt(val.ProposerPriority)) - } - avg := sum.Div(sum, big.NewInt(n)) - if avg.IsInt64() { - return avg.Int64() - } - - // This should never happen: each val.ProposerPriority is in bounds of int64. - panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg)) -} +// increaseProposerIndex increments the proposer index by `times`, wrapping around if necessary. +// It also supports negative `times` to decrement the index. +func (vals *ValidatorSet) IncProposerIndex(times int32) { + newIndex := (vals.proposerIndex + times) + nVals := int32(vals.Size()) -// Compute the difference between the max and min ProposerPriority of that set. -func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 { - if vals.IsNilOrEmpty() { - panic("empty validator set") - } - 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 - } + for newIndex < 0 { + newIndex += nVals } - diff := max - min - if diff < 0 { - return -1 * diff - } - return diff -} -func (vals *ValidatorSet) shiftByAvgProposerPriority() { - if vals.IsNilOrEmpty() { - panic("empty validator set") - } - avgProposerPriority := vals.computeAvgProposerPriority() - for _, val := range vals.Validators { - val.ProposerPriority = tmmath.SafeSubClipInt64(val.ProposerPriority, avgProposerPriority) - } + vals.proposerIndex = newIndex % nVals } // Makes a copy of the validator list. @@ -344,13 +280,12 @@ func (vals *ValidatorSet) Copy() *ValidatorSet { } valset := &ValidatorSet{ Validators: validatorListCopy(vals.Validators), - Proposer: vals.Proposer, ThresholdPublicKey: vals.ThresholdPublicKey, QuorumHash: vals.QuorumHash, QuorumType: vals.QuorumType, HasPublicKeys: vals.HasPublicKeys, + proposerIndex: vals.proposerIndex, } - valset.Recalculate() return valset } @@ -436,28 +371,7 @@ func (vals *ValidatorSet) Size() int { // TotalVotingPower returns the sum of the voting powers of all validators. // It recomputes the total voting power if required. func (vals *ValidatorSet) TotalVotingPower() int64 { - if vals.totalVotingPower == 0 { - vals.updateTotalVotingPower() - } - return vals.totalVotingPower -} - -// Forces recalculation of the set's total voting power. -// Panics if total voting power is bigger than MaxTotalVotingPower. -func (vals *ValidatorSet) updateTotalVotingPower() { - sum := int64(0) - for _, val := range vals.Validators { - // mind overflow - sum = tmmath.SafeAddClipInt64(sum, val.VotingPower) - if sum > MaxTotalVotingPower { - panic(fmt.Sprintf( - "Total voting power should be guarded to not exceed %v; got: %v", - MaxTotalVotingPower, - sum)) - } - } - - vals.totalVotingPower = sum + return int64(vals.Size()) * DefaultDashVotingPower } // QuorumVotingPower returns the voting power of the quorum if all the members existed. @@ -489,16 +403,6 @@ func (vals *ValidatorSet) QuorumTypeThresholdCount() int { return threshold } -func (vals *ValidatorSet) findProposer() *Validator { - var proposer *Validator - for _, val := range vals.Validators { - if proposer == nil || !bytes.Equal(val.ProTxHash, proposer.ProTxHash) { - proposer = proposer.CompareProposerPriority(val) - } - } - return proposer -} - // Hash returns the Quorum Hash. func (vals *ValidatorSet) Hash() tmbytes.HexBytes { if vals == nil || vals.QuorumHash == nil || vals.ThresholdPublicKey == nil { @@ -627,39 +531,6 @@ func numNewValidators(updates []*Validator, vals *ValidatorSet) int { return numNewValidators } -// computeNewPriorities computes the proposer priority for the validators not present in the set based on -// 'updatedTotalVotingPower'. -// Leaves unchanged the priorities of validators that are changed. -// -// 'updates' parameter must be a list of unique validators to be added or updated. -// -// 'updatedTotalVotingPower' is the total voting power of a set where all updates would be applied but -// -// not the removals. It must be < 2*MaxTotalVotingPower and may be close to this limit if close to -// MaxTotalVotingPower will be removed. This is still safe from overflow since MaxTotalVotingPower is maxInt64/8. -// -// No changes are made to the validator set 'vals'. -func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotalVotingPower int64) { - for _, valUpdate := range updates { - proTxHash := valUpdate.ProTxHash - _, val := vals.GetByProTxHash(proTxHash) - if val == nil { - // add val - // Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't - // un-bond and then re-bond to reset their (potentially previously negative) ProposerPriority to zero. - // - // Contract: updatedVotingPower < 2 * MaxTotalVotingPower to ensure ProposerPriority does - // not exceed the bounds of int64. - // - // Compute ProposerPriority = -1.125*totalVotingPower == -(updatedVotingPower + (updatedVotingPower >> 3)). - valUpdate.ProposerPriority = -(updatedTotalVotingPower + (updatedTotalVotingPower >> 3)) - } else { - valUpdate.ProposerPriority = val.ProposerPriority - } - } - -} - // Merges the vals' validator list with the updates list. // When two elements with same address are seen, the one from updates is selected. // Expects updates to be a list of updates sorted by proTxHash with no duplicates or errors, @@ -790,25 +661,15 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes // Verify that applying the 'updates' against 'vals' will not result in error. // Get the updated total voting power before removal. Note that this is < 2 * MaxTotalVotingPower - tvpAfterUpdatesBeforeRemovals, err := verifyUpdates(updates, vals, removedVotingPower) + _, err = verifyUpdates(updates, vals, removedVotingPower) if err != nil { return err } - - // Compute the priorities for updates. - computeNewPriorities(updates, vals, tvpAfterUpdatesBeforeRemovals) - vals.updateTotalVotingPower() - // Apply updates and removals. vals.applyUpdates(updates) vals.applyRemovals(deletes) - vals.updateTotalVotingPower() // will panic if total voting power > MaxTotalVotingPower - - // Scale and center. - vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower()) - - sort.Sort(ValidatorsByVotingPower(vals.Validators)) + sort.Sort(ValidatorsByProTxHashes(vals.Validators)) vals.ThresholdPublicKey = newThresholdPublicKey vals.QuorumHash = newQuorumHash @@ -874,24 +735,6 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, return nil } -// findPreviousProposer reverses the compare proposer priority function to find the validator -// with the lowest proposer priority which would have been the previous proposer. -// -// Is used when recreating a validator set from an existing array of validators. -func (vals *ValidatorSet) findPreviousProposer() *Validator { - var previousProposer *Validator - for _, val := range vals.Validators { - if previousProposer == nil { - previousProposer = val - continue - } - if previousProposer == previousProposer.CompareProposerPriority(val) { - previousProposer = val - } - } - return previousProposer -} - //----------------- // IsErrNotEnoughVotingPowerSigned returns true if err is @@ -1003,23 +846,6 @@ func (vals *ValidatorSet) MarshalZerologObject(e *zerolog.Event) { //------------------------------------- -// ValidatorsByVotingPower implements sort.Interface for []*Validator based on -// the VotingPower and Address fields. -type ValidatorsByVotingPower []*Validator - -func (valz ValidatorsByVotingPower) Len() int { return len(valz) } - -func (valz ValidatorsByVotingPower) Less(i, j int) bool { - if valz[i].VotingPower == valz[j].VotingPower { - return bytes.Compare(valz[i].ProTxHash, valz[j].ProTxHash) == -1 - } - return valz[i].VotingPower > valz[j].VotingPower -} - -func (valz ValidatorsByVotingPower) Swap(i, j int) { - valz[i], valz[j] = valz[j], valz[i] -} - // ValidatorsByAddress implements sort.Interface for []*Validator based on // the Address field. type ValidatorsByProTxHashes []*Validator @@ -1051,7 +877,7 @@ func (vals *ValidatorSet) ToProto() (*tmproto.ValidatorSet, error) { } vp.Validators = valsProto - valProposer, err := vals.Proposer.ToProto() + valProposer, err := vals.Proposer().ToProto() if err != nil { return nil, fmt.Errorf("toProto: validatorSet proposer error: %w", err) } @@ -1106,8 +932,7 @@ func ValidatorSetFromProto(vp *tmproto.ValidatorSet) (*ValidatorSet, error) { var err error proposer := vp.GetProposer() if proposer != nil { - vals.Proposer, err = ValidatorFromProto(vp.GetProposer()) - if err != nil { + if err := vals.SetProposer(proposer.GetProTxHash()); err != nil { return nil, fmt.Errorf("fromProto: validatorSet proposer error: %w", err) } } @@ -1135,42 +960,6 @@ func ValidatorSetFromProto(vp *tmproto.ValidatorSet) (*ValidatorSet, error) { return vals, vals.ValidateBasic() } -// ValidatorSetFromExistingValidators takes an existing array of validators and rebuilds -// the exact same validator set that corresponds to it without changing the proposer priority or power -// if any of the validators fail validate basic then an empty set is returned. -func ValidatorSetFromExistingValidators( - valz []*Validator, - thresholdPublicKey crypto.PubKey, - quorumType btcjson.LLMQType, - quorumHash crypto.QuorumHash, -) (*ValidatorSet, error) { - if len(valz) == 0 { - return nil, errors.New("validator set is empty") - } - hasPublicKeys := true - for _, val := range valz { - err := val.ValidateBasic() - if val.PubKey == nil { - hasPublicKeys = false - } - if err != nil { - return nil, fmt.Errorf("can't create validator set: %w", err) - } - } - - vals := &ValidatorSet{ - Validators: valz, - ThresholdPublicKey: thresholdPublicKey, - QuorumType: quorumType, - QuorumHash: quorumHash, - HasPublicKeys: hasPublicKeys, - } - vals.Proposer = vals.findPreviousProposer() - vals.updateTotalVotingPower() - sort.Sort(ValidatorsByVotingPower(vals.Validators)) - return vals, nil -} - //---------------------------------------- func ValidatorUpdatesRegenerateOnProTxHashes(proTxHashes []crypto.ProTxHash) abci.ValidatorSetUpdate { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index ad6d91463b..1a7106a35e 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "math" "math/rand" "sort" "strconv" @@ -42,7 +41,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.Equal(t, int64(0), vset.TotalVotingPower()) assert.Equal(t, tmbytes.HexBytes(nil), vset.Hash()) // add - val = randModuloValidator(vset.TotalVotingPower()) + val = randValidator() assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val}, val.PubKey, crypto.RandQuorumHash())) assert.True(t, vset.HasProTxHash(val.ProTxHash)) @@ -55,16 +54,10 @@ func TestValidatorSetBasic(t *testing.T) { assert.NotNil(t, vset.Hash()) // update - val = randModuloValidator(vset.TotalVotingPower()) + val = randValidator() assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val}, val.PubKey, crypto.RandQuorumHash())) _, val = vset.GetByProTxHash(val.ProTxHash) val.PubKey = bls12381.GenPrivKey().PubKey() - proposerPriority := val.ProposerPriority - - val.ProposerPriority = 0 - assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val}, val.PubKey, crypto.RandQuorumHash())) - _, val = vset.GetByProTxHash(val.ProTxHash) - assert.Equal(t, proposerPriority, val.ProposerPriority) } @@ -128,8 +121,7 @@ func TestValidatorSetValidateBasic(t *testing.T) { QuorumHash: crypto.RandQuorumHash(), HasPublicKeys: true, }, - err: true, - msg: "validator set proposer is not set", + err: false, }, { testName: "Validator in set has wrong public key for threshold", @@ -138,7 +130,6 @@ func TestValidatorSetValidateBasic(t *testing.T) { ThresholdPublicKey: bls12381.GenPrivKey().PubKey(), QuorumHash: crypto.RandQuorumHash(), HasPublicKeys: true, - Proposer: val, }, err: true, msg: "thresholdPublicKey error: incorrect threshold public key", @@ -150,7 +141,6 @@ func TestValidatorSetValidateBasic(t *testing.T) { ThresholdPublicKey: bls12381.GenPrivKey().PubKey(), QuorumHash: crypto.RandQuorumHash(), HasPublicKeys: true, - Proposer: badValNoPublicKey, }, err: true, msg: "invalid validator pub key #0: validator does not have a public key", @@ -162,7 +152,6 @@ func TestValidatorSetValidateBasic(t *testing.T) { ThresholdPublicKey: bls12381.GenPrivKey().PubKey(), QuorumHash: crypto.RandQuorumHash(), HasPublicKeys: true, - Proposer: badValNoProTxHash, }, err: true, msg: "invalid validator #0: validator does not have a provider transaction hash", @@ -171,7 +160,6 @@ func TestValidatorSetValidateBasic(t *testing.T) { testName: "Validator set needs quorum hash", vals: ValidatorSet{ Validators: []*Validator{val}, - Proposer: val, ThresholdPublicKey: val.PubKey, HasPublicKeys: true, }, @@ -182,7 +170,6 @@ func TestValidatorSetValidateBasic(t *testing.T) { testName: "Validator set single val good", vals: ValidatorSet{ Validators: []*Validator{val}, - Proposer: val, ThresholdPublicKey: val.PubKey, QuorumHash: crypto.RandQuorumHash(), HasPublicKeys: true, @@ -194,7 +181,6 @@ func TestValidatorSetValidateBasic(t *testing.T) { testName: "Validator set needs threshold public key", vals: ValidatorSet{ Validators: []*Validator{val}, - Proposer: val, QuorumHash: crypto.RandQuorumHash(), HasPublicKeys: true, }, @@ -271,12 +257,9 @@ func randPubKey() crypto.PubKey { return bls12381.PubKey(tmrand.Bytes(32)) } -func randModuloValidator(totalVotingPower int64) *Validator { - // this modulo limits the ProposerPriority/VotingPower to stay in the - // bounds of MaxTotalVotingPower minus the already existing voting power: +func randValidator() *Validator { address := RandValidatorAddress().String() val := NewValidator(randPubKey(), DefaultDashVotingPower, crypto.RandProTxHash(), address) - val.ProposerPriority = rand.Int63() % (MaxTotalVotingPower - totalVotingPower) return val } @@ -314,47 +297,10 @@ func (vals *ValidatorSet) fromBytes(t *testing.T, b []byte) *ValidatorSet { return vs } -func TestAvgProposerPriority(t *testing.T) { - // Create Validator set without calling IncrementProposerPriority: - tcs := []struct { - vs ValidatorSet - want int64 - }{ - 0: {ValidatorSet{Validators: []*Validator{{ProposerPriority: 0}, {ProposerPriority: 0}, {ProposerPriority: 0}}}, 0}, - 1: { - ValidatorSet{ - Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: 0}, {ProposerPriority: 0}}, - }, math.MaxInt64 / 3, - }, - 2: { - ValidatorSet{ - Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: 0}}, - }, math.MaxInt64 / 2, - }, - 3: { - ValidatorSet{ - Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: math.MaxInt64}}, - }, math.MaxInt64, - }, - 4: { - ValidatorSet{ - Validators: []*Validator{{ProposerPriority: math.MinInt64}, {ProposerPriority: math.MinInt64}}, - }, math.MinInt64, - }, - } - for i, tc := range tcs { - got := tc.vs.computeAvgProposerPriority() - assert.Equal(t, tc.want, got, "test case: %v", i) - } -} - func TestEmptySet(t *testing.T) { var valList []*Validator valSet := NewValidatorSet(valList, bls12381.PubKey{}, btcjson.LLMQType_5_60, crypto.QuorumHash{}, true) - assert.Panics(t, func() { valSet.RescalePriorities(100) }) - assert.Panics(t, func() { valSet.shiftByAvgProposerPriority() }) - assert.Panics(t, func() { assert.Zero(t, computeMaxMinPriorityDiff(valSet)) }) // Add to empty set proTxHashes := []crypto.ProTxHash{crypto.Checksum([]byte("v1")), crypto.Checksum([]byte("v2"))} @@ -397,9 +343,8 @@ func TestUpdatesForNewValidatorSet(t *testing.T) { // Verify set including validator with negative voting power cannot be created v1 = NewTestValidatorGeneratedFromProTxHash(crypto.Checksum([]byte("v1"))) v2 = &Validator{ - VotingPower: -20, - ProposerPriority: 0, - ProTxHash: crypto.Checksum([]byte("v2")), + VotingPower: -20, + ProTxHash: crypto.Checksum([]byte("v2")), } v3 = NewTestValidatorGeneratedFromProTxHash(crypto.Checksum([]byte("v3"))) valList = []*Validator{v1, v2, v3} @@ -493,36 +438,16 @@ func addValidatorsToValidatorSet(vals *ValidatorSet, testValList []testVal) ([]* } -func valSetTotalProposerPriority(valSet *ValidatorSet) int64 { - sum := int64(0) - for _, val := range valSet.Validators { - // mind overflow - sum = tmmath.SafeAddClipInt64(sum, val.ProposerPriority) - } - return sum -} - func verifyValidatorSet(t *testing.T, valSet *ValidatorSet) { // verify that the capacity and length of validators is the same assert.Equal(t, len(valSet.Validators), cap(valSet.Validators)) // verify that the set's total voting power has been updated - tvp := valSet.totalVotingPower - valSet.updateTotalVotingPower() - expectedTvp := valSet.TotalVotingPower() - assert.Equal(t, expectedTvp, tvp, - "expected TVP %d. Got %d, valSet=%s", expectedTvp, tvp, valSet) - - // verify that validator priorities are centered - valsCount := int64(len(valSet.Validators)) - tpp := valSetTotalProposerPriority(valSet) - assert.True(t, tpp < valsCount && tpp > -valsCount, - "expected total priority in (-%d, %d). Got %d", valsCount, valsCount, tpp) - - // verify that priorities are scaled - dist := computeMaxMinPriorityDiff(valSet) - assert.True(t, dist <= PriorityWindowSizeFactor*tvp, - "expected priority distance < %d. Got %d", PriorityWindowSizeFactor*tvp, dist) + tvp := int64(0) + for _, v := range valSet.Validators { + tvp += v.VotingPower + } + assert.Equal(t, tvp, valSet.TotalVotingPower()) recoveredPublicKey, err := bls12381.RecoverThresholdPublicKeyFromPublicKeys(valSet.GetPublicKeys(), valSet.GetProTxHashesAsByteArrays()) assert.NoError(t, err) @@ -949,10 +874,10 @@ func TestValidatorSetProtoBuf(t *testing.T) { valset2.Validators[0] = &Validator{} valset3, _ := RandValidatorSet(10) - valset3.Proposer = nil + valset3.Validators[0] = nil valset4, _ := RandValidatorSet(10) - valset4.Proposer = &Validator{} + valset4.Validators[0] = &Validator{} testCases := []struct { msg string @@ -985,28 +910,6 @@ func TestValidatorSetProtoBuf(t *testing.T) { } } -// --------------------- -// Sort validators by priority and address -type validatorsByPriority []*Validator - -func (valz validatorsByPriority) Len() int { - return len(valz) -} - -func (valz validatorsByPriority) Less(i, j int) bool { - if valz[i].ProposerPriority < valz[j].ProposerPriority { - return true - } - if valz[i].ProposerPriority > valz[j].ProposerPriority { - return false - } - return bytes.Compare(valz[i].ProTxHash, valz[j].ProTxHash) < 0 -} - -func (valz validatorsByPriority) Swap(i, j int) { - valz[i], valz[j] = valz[j], valz[i] -} - //------------------------------------- type testValsByVotingPower []testVal