code.vegaprotocol.io/vega@v0.79.0/core/validators/validator_set.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package validators
    17  
    18  import (
    19  	"context"
    20  	"encoding/base64"
    21  	"errors"
    22  	"math/rand"
    23  	"sort"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	"code.vegaprotocol.io/vega/logging"
    29  	proto "code.vegaprotocol.io/vega/protos/vega"
    30  
    31  	tmtypes "github.com/cometbft/cometbft/abci/types"
    32  	"github.com/cometbft/cometbft/crypto/encoding"
    33  )
    34  
    35  var (
    36  	ErrUnknownValidator            = errors.New("unknown validator ID")
    37  	ErrUnexpectedSignedBlockHeight = errors.New("unexpected signed block height")
    38  
    39  	PerformanceIncrement        = num.DecimalFromFloat(0.1)
    40  	DecimalOne                  = num.DecimalFromFloat(1)
    41  	VotingPowerScalingFactor, _ = num.DecimalFromString("10000")
    42  )
    43  
    44  type ValidatorStatus int32
    45  
    46  const (
    47  	ValidatorStatusPending = iota
    48  	ValidatorStatusErsatz
    49  	ValidatorStatusTendermint
    50  )
    51  
    52  var ValidatorStatusToName = map[ValidatorStatus]string{
    53  	ValidatorStatusPending:    "pending",
    54  	ValidatorStatusErsatz:     "ersatz",
    55  	ValidatorStatusTendermint: "tendermint",
    56  }
    57  
    58  type valState struct {
    59  	data                            ValidatorData
    60  	blockAdded                      int64                      // the block it was added to vega
    61  	status                          ValidatorStatus            // the status of the node (tendermint, ersatz, waiting list)
    62  	statusChangeBlock               int64                      // the block id in which it got to its current status
    63  	lastBlockWithPositiveRanking    int64                      // the last epoch with non zero ranking for the validator
    64  	numberOfEthereumEventsForwarded uint64                     // number of events forwarded by the validator
    65  	heartbeatTracker                *validatorHeartbeatTracker // track hearbeat transactions
    66  	validatorPower                  int64                      // the voting power of the validator
    67  	rankingScore                    *proto.RankingScore        // the last ranking score of the validator
    68  }
    69  
    70  // UpdateNumberEthMultisigSigners updates the required number of multisig signers.
    71  func (t *Topology) UpdateNumberEthMultisigSigners(_ context.Context, numberEthMultisigSigners *num.Uint) error {
    72  	t.numberEthMultisigSigners = int(numberEthMultisigSigners.Uint64())
    73  	return nil
    74  }
    75  
    76  // UpdateNumberOfTendermintValidators updates with the quota for tendermint validators. It updates accordingly the number of slots for ersatzvalidators.
    77  func (t *Topology) UpdateNumberOfTendermintValidators(_ context.Context, noValidators *num.Uint) error {
    78  	t.numberOfTendermintValidators = int(noValidators.Uint64())
    79  	t.numberOfErsatzValidators = int(t.ersatzValidatorsFactor.Mul(noValidators.ToDecimal()).IntPart())
    80  	return nil
    81  }
    82  
    83  // UpdateErsatzValidatorsFactor updates the ratio between the tendermint validators list and the ersatz validators list.
    84  func (t *Topology) UpdateErsatzValidatorsFactor(_ context.Context, ersatzFactor num.Decimal) error {
    85  	t.ersatzValidatorsFactor = ersatzFactor
    86  	t.numberOfErsatzValidators = int(t.ersatzValidatorsFactor.Mul(num.DecimalFromInt64(int64(t.numberOfTendermintValidators))).IntPart())
    87  	return nil
    88  }
    89  
    90  // UpdateValidatorIncumbentBonusFactor updates with the net param for incumbent bonus, saved as incumbentBonusFactor + 1.
    91  func (t *Topology) UpdateValidatorIncumbentBonusFactor(_ context.Context, incumbentBonusFactor num.Decimal) error {
    92  	t.validatorIncumbentBonusFactor = DecimalOne.Add(incumbentBonusFactor)
    93  	return nil
    94  }
    95  
    96  // UpdateMinimumEthereumEventsForNewValidator updates the minimum number of events forwarded by / voted for by the joining validator.
    97  func (t *Topology) UpdateMinimumEthereumEventsForNewValidator(_ context.Context, minimumEthereumEventsForNewValidator *num.Uint) error {
    98  	t.minimumEthereumEventsForNewValidator = minimumEthereumEventsForNewValidator.Uint64()
    99  	return nil
   100  }
   101  
   102  // UpdateMinimumRequireSelfStake updates the minimum requires stake for a validator.
   103  func (t *Topology) UpdateMinimumRequireSelfStake(_ context.Context, minStake num.Decimal) error {
   104  	t.minimumStake, _ = num.UintFromDecimal(minStake)
   105  	return nil
   106  }
   107  
   108  // AddForwarder records the times that a validator fowards an eth event.
   109  func (t *Topology) AddForwarder(pubKey string) {
   110  	t.mu.RLock()
   111  	defer t.mu.RUnlock()
   112  	for _, vs := range t.validators {
   113  		if vs.data.VegaPubKey == pubKey {
   114  			vs.numberOfEthereumEventsForwarded++
   115  		}
   116  	}
   117  }
   118  
   119  // RecalcValidatorSet is called at the before a new epoch is started to update the validator sets.
   120  // the delegation state corresponds to the epoch about to begin.
   121  func (t *Topology) RecalcValidatorSet(ctx context.Context, epochSeq string, delegationState []*types.ValidatorData, stakeScoreParams types.StakeScoreParams) []*types.PartyContributionScore {
   122  	// This can actually change the validators structure so no reads should be allowed in parallel to this.
   123  	t.mu.Lock()
   124  	defer t.mu.Unlock()
   125  
   126  	consensusAndErsatzValidatorsRankingScores := []*types.PartyContributionScore{}
   127  
   128  	// first we record the current status of validators before the promotion/demotion so we can capture in an event.
   129  	currentState := make(map[string]StatusAddress, len(t.validators))
   130  	for k, vs := range t.validators {
   131  		currentState[k] = StatusAddress{
   132  			Status:           vs.status,
   133  			EthAddress:       vs.data.EthereumAddress,
   134  			SubmitterAddress: vs.data.SubmitterAddress,
   135  		}
   136  	}
   137  
   138  	keys := make([]string, 0, len(currentState))
   139  	for k := range currentState {
   140  		keys = append(keys, k)
   141  	}
   142  	sort.Strings(keys)
   143  
   144  	// get the ranking of the validators for the purpose of promotion
   145  	stakeScore, perfScore, rankingScore := t.getRankingScore(delegationState)
   146  
   147  	// apply promotion logic - returns the tendermint updates with voting power changes (including removals and additions)
   148  	vpu, nextVotingPower := t.applyPromotion(perfScore, rankingScore, delegationState, stakeScoreParams)
   149  	t.validatorPowerUpdates = vpu
   150  	for _, vu := range t.validatorPowerUpdates {
   151  		cPubKey, _ := encoding.PubKeyFromProto(vu.PubKey)
   152  		t.log.Info("setting voting power to", logging.String(("address"), cPubKey.Address().String()), logging.Uint64("power", uint64(vu.Power)))
   153  	}
   154  
   155  	newState := make(map[string]StatusAddress, len(t.validators))
   156  	for k, vs := range t.validators {
   157  		newState[k] = StatusAddress{
   158  			Status:           vs.status,
   159  			EthAddress:       vs.data.EthereumAddress,
   160  			SubmitterAddress: vs.data.SubmitterAddress,
   161  		}
   162  	}
   163  
   164  	t.signatures.PreparePromotionsSignatures(ctx, t.timeService.GetTimeNow(), t.epochSeq, currentState, newState)
   165  
   166  	// prepare and send the events
   167  	evts := make([]events.Event, 0, len(currentState))
   168  	for _, nodeID := range keys {
   169  		status := "removed"
   170  		if vd, ok := t.validators[nodeID]; ok {
   171  			status = ValidatorStatusToName[vd.status]
   172  		}
   173  
   174  		vp, ok := nextVotingPower[nodeID]
   175  		if !ok {
   176  			vp = 0
   177  		}
   178  
   179  		if vd, ok := t.validators[nodeID]; ok {
   180  			vd.rankingScore = &proto.RankingScore{
   181  				StakeScore:       stakeScore[nodeID].String(),
   182  				PerformanceScore: perfScore[nodeID].String(),
   183  				RankingScore:     rankingScore[nodeID].String(),
   184  				PreviousStatus:   statusToProtoStatus(ValidatorStatusToName[currentState[nodeID].Status]),
   185  				Status:           statusToProtoStatus(status),
   186  				VotingPower:      uint32(vp),
   187  			}
   188  			if vd.rankingScore.Status == proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_TENDERMINT || vd.rankingScore.Status == proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_ERSATZ {
   189  				consensusAndErsatzValidatorsRankingScores = append(consensusAndErsatzValidatorsRankingScores, &types.PartyContributionScore{Party: vd.data.VegaPubKey, Score: rankingScore[nodeID]})
   190  			}
   191  		}
   192  
   193  		evts = append(evts, events.NewValidatorRanking(ctx, epochSeq, nodeID, stakeScore[nodeID].String(), perfScore[nodeID].String(), rankingScore[nodeID].String(), ValidatorStatusToName[currentState[nodeID].Status], status, int(vp)))
   194  	}
   195  	t.broker.SendBatch(evts)
   196  
   197  	nodeIDs := make([]string, 0, len(rankingScore))
   198  	for k := range rankingScore {
   199  		nodeIDs = append(nodeIDs, k)
   200  	}
   201  	sort.Strings(nodeIDs)
   202  
   203  	// update the lastBlockWithNonZeroRanking
   204  	for _, k := range nodeIDs {
   205  		d := rankingScore[k]
   206  		if d.IsPositive() {
   207  			t.validators[k].lastBlockWithPositiveRanking = int64(t.currentBlockHeight)
   208  			continue
   209  		}
   210  
   211  		if t.validators[k].status == ValidatorStatusTendermint {
   212  			continue // can't kick out tendermint validator
   213  		}
   214  
   215  		if t.validators[k].status == ValidatorStatusPending && (t.validators[k].data.FromEpoch+10) > t.epochSeq {
   216  			continue // pending validators have 10 epochs from when they started their heartbeats to get a positive perf score
   217  		}
   218  
   219  		if !stakeScore[k].IsZero() {
   220  			continue // it has stake, we can't kick it out it'll get lost
   221  		}
   222  
   223  		// if the node hasn't had a positive score for more than 10 epochs it is dropped
   224  		if int64(t.currentBlockHeight)-t.validators[k].lastBlockWithPositiveRanking > t.blocksToKeepMalperforming {
   225  			t.log.Info("removing validator with 0 positive ranking for too long", logging.String("node-id", k))
   226  			t.validators[k].data.FromEpoch = t.epochSeq
   227  			t.sendValidatorUpdateEvent(ctx, t.validators[k].data, false)
   228  			delete(t.validators, k)
   229  		}
   230  	}
   231  	return consensusAndErsatzValidatorsRankingScores
   232  }
   233  
   234  func protoStatusToString(status proto.ValidatorNodeStatus) string {
   235  	if status == proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_TENDERMINT {
   236  		return "tendermint"
   237  	}
   238  	if status == proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_ERSATZ {
   239  		return "ersatz"
   240  	}
   241  	return "pending"
   242  }
   243  
   244  func statusToProtoStatus(status string) proto.ValidatorNodeStatus {
   245  	if status == "tendermint" {
   246  		return proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_TENDERMINT
   247  	}
   248  	if status == "ersatz" {
   249  		return proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_ERSATZ
   250  	}
   251  	return proto.ValidatorNodeStatus_VALIDATOR_NODE_STATUS_PENDING
   252  }
   253  
   254  func sortValidatorDescRankingScoreAscBlockcompare(validators []*valState, rankingScore map[string]num.Decimal, blockComparator func(*valState, *valState) bool, rng *rand.Rand) {
   255  	// because we may need the bit of randomness in the sorting below - we need to start from all of the validators in a consistent order
   256  	sort.SliceStable(validators, func(i, j int) bool { return validators[i].data.ID < validators[j].data.ID })
   257  
   258  	// sort the tendermint validators in descending order of their ranking score with the earlier block added as a tier breaker
   259  	sort.SliceStable(validators, func(i, j int) bool {
   260  		// tiebreaker: the one which was promoted to tm validator first gets higher
   261  		if rankingScore[validators[i].data.ID].Equal(rankingScore[validators[j].data.ID]) {
   262  			if validators[i].statusChangeBlock == validators[j].statusChangeBlock {
   263  				return rng.Int31n(2) > 0
   264  			}
   265  			return blockComparator(validators[i], validators[j])
   266  		}
   267  		return rankingScore[validators[i].data.ID].GreaterThan(rankingScore[validators[j].data.ID])
   268  	})
   269  }
   270  
   271  // applyPromotion calculates the new validator set for tendermint and ersatz and returns the set of updates to apply to tendermint voting powers.
   272  func (t *Topology) applyPromotion(performanceScore, rankingScore map[string]num.Decimal, delegationState []*types.ValidatorData, stakeScoreParams types.StakeScoreParams) ([]tmtypes.ValidatorUpdate, map[string]int64) {
   273  	tendermintValidators := []*valState{}
   274  	remainingValidators := []*valState{}
   275  
   276  	// split the validator set into current tendermint validators and the others
   277  	for _, vd := range t.validators {
   278  		if vd.status == ValidatorStatusTendermint {
   279  			tendermintValidators = append(tendermintValidators, vd)
   280  		} else {
   281  			remainingValidators = append(remainingValidators, vd)
   282  		}
   283  	}
   284  
   285  	// sort the tendermint validators in descending order of their ranking score with the earlier block added as a tier breaker
   286  	byStatusChangeBlock := func(val1, val2 *valState) bool { return val1.statusChangeBlock < val2.statusChangeBlock }
   287  	byBlockAdded := func(val1, val2 *valState) bool { return val1.blockAdded < val2.blockAdded }
   288  	sortValidatorDescRankingScoreAscBlockcompare(tendermintValidators, rankingScore, byStatusChangeBlock, t.rng)
   289  	sortValidatorDescRankingScoreAscBlockcompare(remainingValidators, rankingScore, byBlockAdded, t.rng)
   290  
   291  	signers := map[string]struct{}{}
   292  	for _, sig := range t.primaryMultisig.GetSigners() {
   293  		signers[sig] = struct{}{}
   294  	}
   295  
   296  	threshold := num.MaxV(t.primaryMultisig.GetThreshold(), t.secondaryMultisig.GetThreshold())
   297  
   298  	// if there are not enough slots, demote from tm to remaining
   299  	tendermintValidators, remainingValidators, removedFromTM := handleSlotChanges(tendermintValidators, remainingValidators, ValidatorStatusTendermint, ValidatorStatusErsatz, t.numberOfTendermintValidators, int64(t.currentBlockHeight+1), rankingScore, signers, threshold)
   300  	t.log.Info("removedFromTM", logging.Strings("IDs", removedFromTM))
   301  
   302  	// now we're sorting the remaining validators - some of which may be eratz, some may have been tendermint (as demoted above) and some just in the waiting list
   303  	// we also sort the tendermint set again as there may have been a promotion due to a slot change
   304  	sortValidatorDescRankingScoreAscBlockcompare(tendermintValidators, rankingScore, byStatusChangeBlock, t.rng)
   305  	sortValidatorDescRankingScoreAscBlockcompare(remainingValidators, rankingScore, byBlockAdded, t.rng)
   306  
   307  	// apply promotions and demotions from tendermint to ersatz and vice versa
   308  	remainingValidators, demotedFromTM := promote(tendermintValidators, remainingValidators, ValidatorStatusTendermint, ValidatorStatusErsatz, t.numberOfTendermintValidators, rankingScore, int64(t.currentBlockHeight+1))
   309  	removedFromTM = append(removedFromTM, demotedFromTM...)
   310  
   311  	// by this point we're done with promotions to tendermint. check if any validator from the waiting list can join the ersatz list
   312  	ersatzValidators := []*valState{}
   313  	waitingListValidators := []*valState{}
   314  	for _, vd := range remainingValidators {
   315  		if vd.status == ValidatorStatusErsatz {
   316  			ersatzValidators = append(ersatzValidators, vd)
   317  		} else if rankingScore[vd.data.ID].IsPositive() {
   318  			waitingListValidators = append(waitingListValidators, vd)
   319  		}
   320  	}
   321  
   322  	// demoted tendermint validators are also ersatz so we need to add them
   323  	for _, id := range demotedFromTM {
   324  		ersatzValidators = append(ersatzValidators, t.validators[id])
   325  	}
   326  
   327  	// demote from ersatz to pending due to more ersatz than slots allowed
   328  	ersatzValidators, waitingListValidators, _ = handleSlotChanges(ersatzValidators, waitingListValidators, ValidatorStatusErsatz, ValidatorStatusPending, t.numberOfErsatzValidators, int64(t.currentBlockHeight+1), rankingScore, map[string]struct{}{}, 0)
   329  	sortValidatorDescRankingScoreAscBlockcompare(ersatzValidators, rankingScore, byBlockAdded, t.rng)
   330  	sortValidatorDescRankingScoreAscBlockcompare(waitingListValidators, rankingScore, byBlockAdded, t.rng)
   331  	// apply promotions and demotions from ersatz to pending and vice versa
   332  	promote(ersatzValidators, waitingListValidators, ValidatorStatusErsatz, ValidatorStatusPending, t.numberOfErsatzValidators, rankingScore, int64(t.currentBlockHeight+1))
   333  
   334  	nextValidators := make([]string, 0, t.numberOfTendermintValidators+len(removedFromTM))
   335  	for _, vd := range t.validators {
   336  		if vd.status == ValidatorStatusTendermint {
   337  			nextValidators = append(nextValidators, vd.data.ID)
   338  		}
   339  	}
   340  	nextValidatorSet := make(map[string]struct{}, len(nextValidators))
   341  	for _, v := range nextValidators {
   342  		nextValidatorSet[v] = struct{}{}
   343  	}
   344  
   345  	// extract the delegation and the total delegation of the new set of validators for tendermint
   346  	tmDelegation, tmTotalDelegation := CalcDelegation(nextValidatorSet, delegationState)
   347  	// optimal stake is calculated with respect to tendermint validators, i.e. total stake by tendermint validators
   348  	optimalStake := GetOptimalStake(tmTotalDelegation, len(tmDelegation), stakeScoreParams)
   349  
   350  	// calculate the anti-whaling stake score of the validators with respect to stake represented by the tm validators
   351  	nextValidatorsStakeScore := CalcAntiWhalingScore(tmDelegation, tmTotalDelegation, optimalStake, stakeScoreParams)
   352  
   353  	// recored the performance score of the tm validators
   354  	nextValidatorsPerformanceScore := make(map[string]num.Decimal, len(nextValidatorSet))
   355  	for k := range nextValidatorSet {
   356  		nextValidatorsPerformanceScore[k] = performanceScore[k]
   357  	}
   358  	// calculate the score as stake_score x perf_score (no need to normalise, this will be done inside calculateVotingPower
   359  	nextValidatorsScore := getValScore(nextValidatorsStakeScore, nextValidatorsPerformanceScore)
   360  
   361  	// calculate the voting power of the next tendermint validators
   362  	nextValidatorsVotingPower := t.calculateVotingPower(nextValidators, nextValidatorsScore)
   363  
   364  	// add the removed validators with 0 voting power
   365  	for _, removed := range removedFromTM {
   366  		nextValidators = append(nextValidators, removed)
   367  		nextValidatorsVotingPower[removed] = 0
   368  	}
   369  
   370  	sort.Strings(nextValidators)
   371  
   372  	// generate the tendermint updates from the voting power
   373  	vUpdates := make([]tmtypes.ValidatorUpdate, 0, len(nextValidators))
   374  
   375  	// make sure we update the validator power to all nodes, so first reset all to 0
   376  	for _, vd := range t.validators {
   377  		vd.validatorPower = 0
   378  	}
   379  
   380  	// now update the validator power for the ones that go to tendermint
   381  	for _, v := range nextValidators {
   382  		vd := t.validators[v]
   383  		pubkey, err := base64.StdEncoding.DecodeString(vd.data.TmPubKey)
   384  		if err != nil {
   385  			continue
   386  		}
   387  		vd.validatorPower = nextValidatorsVotingPower[v]
   388  		update := tmtypes.UpdateValidator(pubkey, nextValidatorsVotingPower[v], "")
   389  		vUpdates = append(vUpdates, update)
   390  	}
   391  
   392  	for k, d := range rankingScore {
   393  		t.log.Info("ranking score for promotion", logging.String(k, d.String()))
   394  	}
   395  
   396  	for _, vu := range vUpdates {
   397  		pkey := vu.PubKey.GetEd25519()
   398  		if pkey == nil || len(pkey) <= 0 {
   399  			pkey = vu.PubKey.GetSecp256K1()
   400  		}
   401  		// tendermint pubkey are marshalled in base64,
   402  		// so let's do this as well here for logging
   403  		spkey := base64.StdEncoding.EncodeToString(pkey)
   404  
   405  		t.log.Info("voting power update", logging.String("pubKey", spkey), logging.Int64("power", vu.Power))
   406  	}
   407  
   408  	return vUpdates, nextValidatorsVotingPower
   409  }
   410  
   411  // handleSlotChanges the number of slots may have increased or decreased and so we slide the nodes into the different sets based on the change.
   412  func handleSlotChanges(seriesA []*valState, seriesB []*valState, statusA ValidatorStatus, statusB ValidatorStatus, maxForSeriesA int, nextBlockHeight int64, rankingScore map[string]num.Decimal, signers map[string]struct{}, multisigThreshold uint32) ([]*valState, []*valState, []string) {
   413  	removedFromSeriesA := []string{}
   414  
   415  	if len(seriesA) == maxForSeriesA {
   416  		// no change we're done
   417  		return seriesA, seriesB, removedFromSeriesA
   418  	}
   419  
   420  	removed := 0
   421  	removedSigners := 0
   422  
   423  	// count how many signers we have in the validtor set - we need to do that as the contract may not have been updated with the signers yet but the
   424  	// list of validators has been updated.
   425  	numSigners := 0
   426  	for _, vs := range seriesA {
   427  		if _, ok := signers[vs.data.EthereumAddress]; ok {
   428  			numSigners++
   429  		}
   430  	}
   431  
   432  	// the number of slots for series A has decrease, move some into series B
   433  	// when demoting from tendermint - we only allow removal of one signer per round as long as there are sufficient validators remaining
   434  	// to satisfy the threshold.
   435  	if len(seriesA) > maxForSeriesA {
   436  		nDescreased := len(seriesA) - maxForSeriesA
   437  		for i := 0; i < nDescreased; i++ {
   438  			toDemote := seriesA[len(seriesA)-1-i]
   439  			if _, ok := signers[toDemote.data.EthereumAddress]; ok {
   440  				if len(signers) > 0 && uint32(1000*(numSigners-1)/len(signers)) <= multisigThreshold {
   441  					break
   442  				}
   443  				removed++
   444  				removedSigners++
   445  			} else {
   446  				removed++
   447  			}
   448  
   449  			toDemote.status = statusB
   450  			toDemote.statusChangeBlock = nextBlockHeight
   451  
   452  			// add to the remaining validators so it can compete with the ersatzvalidators
   453  			seriesB = append(seriesB, toDemote)
   454  			removedFromSeriesA = append(removedFromSeriesA, toDemote.data.ID)
   455  			if removedSigners > 0 {
   456  				break
   457  			}
   458  		}
   459  
   460  		// they've been added to seriesB slice, remove them from seriesA
   461  		seriesA = seriesA[:len(seriesA)-removed]
   462  		return seriesA, seriesB, removedFromSeriesA
   463  	}
   464  
   465  	// the number of slots for series A has increased, move some in from series B
   466  	if len(seriesA) < maxForSeriesA && len(seriesB) > 0 {
   467  		nIncreased := maxForSeriesA - len(seriesA)
   468  
   469  		if nIncreased > len(seriesB) {
   470  			nIncreased = len(seriesB)
   471  		}
   472  
   473  		for i := 0; i < nIncreased; i++ {
   474  			toPromote := seriesB[0]
   475  
   476  			score := rankingScore[toPromote.data.ID]
   477  			if score.IsZero() {
   478  				break // the nodes are ordered by ranking score and we do not want to promote one with 0 score so we stop here
   479  			}
   480  
   481  			toPromote.status = statusA
   482  			toPromote.statusChangeBlock = nextBlockHeight
   483  			// add to the remaining validators so it can compete with the ersatzvalidators
   484  			seriesA = append(seriesA, toPromote)
   485  			seriesB = seriesB[1:]
   486  		}
   487  		return seriesA, seriesB, removedFromSeriesA
   488  	}
   489  
   490  	return seriesA, seriesB, removedFromSeriesA
   491  }
   492  
   493  // promote returns seriesA and seriesB updated with promotions moved from B to A and a slice of removed from series A in case of swap-promotion.
   494  func promote(seriesA []*valState, seriesB []*valState, statusA ValidatorStatus, statusB ValidatorStatus, maxForSeriesA int, rankingScore map[string]num.Decimal, nextBlockHeight int64) ([]*valState, []string) {
   495  	removedFromSeriesA := []string{}
   496  
   497  	if maxForSeriesA > 0 && maxForSeriesA == len(seriesA) && len(seriesB) > 0 {
   498  		// the best of the remaining is better than the worst tendermint validator
   499  		if rankingScore[seriesA[len(seriesA)-1].data.ID].LessThan(rankingScore[seriesB[0].data.ID]) {
   500  			vd := seriesA[len(seriesA)-1]
   501  			vd.status = statusB
   502  			vd.statusChangeBlock = nextBlockHeight
   503  			removedFromSeriesA = append(removedFromSeriesA, vd.data.ID)
   504  
   505  			vd = seriesB[0]
   506  			vd.status = statusA
   507  			vd.statusChangeBlock = nextBlockHeight
   508  			if len(seriesB) > 1 {
   509  				seriesB = seriesB[1:]
   510  			} else {
   511  				seriesB = []*valState{}
   512  			}
   513  		}
   514  	}
   515  	return seriesB, removedFromSeriesA
   516  }
   517  
   518  // calculateVotingPower returns the voting powers as the normalised ranking scores scaled by VotingPowerScalingFactor with a minimum of 1.
   519  func (t *Topology) calculateVotingPower(IDs []string, rankingScores map[string]num.Decimal) map[string]int64 {
   520  	votingPower := make(map[string]int64, len(IDs))
   521  	sumOfScores := num.DecimalZero()
   522  	for _, ID := range IDs {
   523  		sumOfScores = sumOfScores.Add(rankingScores[ID])
   524  	}
   525  
   526  	for _, ID := range IDs {
   527  		if sumOfScores.IsPositive() {
   528  			votingPower[ID] = num.MaxD(DecimalOne, rankingScores[ID].Div(sumOfScores).Mul(VotingPowerScalingFactor)).IntPart()
   529  		} else {
   530  			votingPower[ID] = 10
   531  		}
   532  	}
   533  	return votingPower
   534  }
   535  
   536  // GetValidatorPowerUpdates returns the voting power changes if this is the first block of an epoch.
   537  func (t *Topology) GetValidatorPowerUpdates() []tmtypes.ValidatorUpdate {
   538  	if t.newEpochStarted {
   539  		t.newEpochStarted = false
   540  		// it's safer to reset the validator performance counter here which is the exact time we're updating tendermint on the voting power.
   541  		t.validatorPerformance.Reset()
   542  		return t.validatorPowerUpdates
   543  	}
   544  	return []tmtypes.ValidatorUpdate{}
   545  }