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 }