Newer
Older
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Implementations for the Staking FRAME Pallet.
use frame_election_provider_support::{
bounds::{CountBound, SizeBound},
data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
ScoreProvider, SortedListProvider, VoteWeight, VoterOf,
};
use frame_support::{
dispatch::WithPostDispatchInfo,
pallet_prelude::*,
traits::{
Currency, Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, Len,
OnUnbalanced, TryCollect, UnixTime,
weights::Weight,
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use pallet_session::historical;
use sp_runtime::{
traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero},
Perbill, Percent,
};
use sp_staking::{
currency_to_vote::CurrencyToVote,
offence::{DisableStrategy, OffenceDetails, OnOffenceHandler},
EraIndex, OnStakingUpdate, Page, SessionIndex, Stake,
StakingAccount::{self, Controller, Stash},
StakingInterface,
use sp_std::prelude::*;
use crate::{
election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo,
BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure,
MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf,
RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs,
#[cfg(feature = "try-runtime")]
use frame_support::ensure;
#[cfg(any(test, feature = "try-runtime"))]
use sp_runtime::TryRuntimeError;
Kian Paimani
committed
/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in
/// `get_npos_voters`.
///
/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is
/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n
/// times and then give up.
const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
impl<T: Config> Pallet<T> {
/// Fetches the ledger associated with a controller or stash account, if any.
pub fn ledger(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
StakingLedger::<T>::get(account)
}
pub fn payee(account: StakingAccount<T::AccountId>) -> Option<RewardDestination<T::AccountId>> {
StakingLedger::<T>::reward_destination(account)
}
/// Fetches the controller bonded to a stash account, if any.
pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
StakingLedger::<T>::paired_account(Stash(stash.clone()))
}
/// The total balance that can be slashed from a stash account as of right now.
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
// Weight note: consider making the stake accessible through stash.
Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
}
/// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`].
pub fn slashable_balance_of_vote_weight(
stash: &T::AccountId,
issuance: BalanceOf<T>,
) -> VoteWeight {
T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
}
/// Returns a closure around `slashable_balance_of_vote_weight` that can be passed around.
///
/// This prevents call sites from repeatedly requesting `total_issuance` from backend. But it is
/// important to be only used while the total issuance is not changing.
pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
// NOTE: changing this to unboxed `impl Fn(..)` return type and the pallet will still
// compile, while some types in mock fail to resolve.
let issuance = T::Currency::total_issuance();
Box::new(move |who: &T::AccountId| -> VoteWeight {
Self::slashable_balance_of_vote_weight(who, issuance)
})
}
/// Same as `weight_of_fn`, but made for one time use.
pub fn weight_of(who: &T::AccountId) -> VoteWeight {
let issuance = T::Currency::total_issuance();
Self::slashable_balance_of_vote_weight(who, issuance)
}
pub(super) fn do_withdraw_unbonded(
controller: &T::AccountId,
num_slashing_spans: u32,
) -> Result<Weight, DispatchError> {
let mut ledger = Self::ledger(Controller(controller.clone()))?;
let (stash, old_total) = (ledger.stash.clone(), ledger.total);
if let Some(current_era) = Self::current_era() {
ledger = ledger.consolidate_unlocked(current_era)
}
let used_weight =
if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() {
// This account must have called `unbond()` with some value that caused the active
// portion to fall below existential deposit + will have no more unlocking chunks
// left. We can now safely remove all staking-related information.
Self::kill_stash(&ledger.stash, num_slashing_spans)?;
T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans)
} else {
// This was the consequence of a partial unbond. just update the ledger and move on.
// This is only an update, so we use less overall weight.
T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
};
// `old_total` should never be less than the new total because
// `consolidate_unlocked` strictly subtracts balance.
// Already checked that this won't overflow by entry condition.
let value = old_total.defensive_saturating_sub(new_total);
Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
// notify listeners.
T::EventListeners::on_withdraw(controller, value);
}
Ok(used_weight)
}
pub(super) fn do_payout_stakers(
validator_stash: T::AccountId,
era: EraIndex,
) -> DispatchResultWithPostInfo {
let controller = Self::bonded(&validator_stash).ok_or_else(|| {
Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
let ledger = <Ledger<T>>::get(&controller).ok_or(Error::<T>::NotController)?;
let page = EraInfo::<T>::get_next_claimable_page(era, &validator_stash, &ledger)
.ok_or_else(|| {
Error::<T>::AlreadyClaimed
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
Self::do_payout_stakers_by_page(validator_stash, era, page)
}
pub(super) fn do_payout_stakers_by_page(
validator_stash: T::AccountId,
era: EraIndex,
page: Page,
) -> DispatchResultWithPostInfo {
// Validate input data
let current_era = CurrentEra::<T>::get().ok_or_else(|| {
Error::<T>::InvalidEraToReward
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
let history_depth = T::HistoryDepth::get();
ensure!(
era <= current_era && era >= current_era.saturating_sub(history_depth),
Error::<T>::InvalidEraToReward
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
);
ensure!(
page < EraInfo::<T>::get_page_count(era, &validator_stash),
Error::<T>::InvalidPage.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
);
// Note: if era has no reward to be claimed, era may be future. better not to update
// `ledger.legacy_claimed_rewards` in this case.
let era_payout = <ErasValidatorReward<T>>::get(&era).ok_or_else(|| {
Error::<T>::InvalidEraToReward
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
let account = StakingAccount::Stash(validator_stash.clone());
let mut ledger = Self::ledger(account.clone()).or_else(|_| {
if StakingLedger::<T>::is_bonded(account) {
Err(Error::<T>::NotController.into())
} else {
Err(Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
}
// clean up older claimed rewards
.legacy_claimed_rewards
.retain(|&x| x >= current_era.saturating_sub(history_depth));
ledger.clone().update()?;
let stash = ledger.stash.clone();
if EraInfo::<T>::is_rewards_claimed_with_legacy_fallback(era, &ledger, &stash, page) {
return Err(Error::<T>::AlreadyClaimed
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
} else {
EraInfo::<T>::set_rewards_as_claimed(era, &stash, page);
let exposure = EraInfo::<T>::get_paged_exposure(era, &stash, page).ok_or_else(|| {
Error::<T>::InvalidEraToReward
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
// Input data seems good, no errors allowed after this point
// Get Era reward points. It has TOTAL and INDIVIDUAL
// Find the fraction of the era reward that belongs to the validator
// Take that fraction of the eras rewards to split to nominator and validator
//
// Then look at the validator, figure out the proportion of their reward
// which goes to them and each of their nominators.
let era_reward_points = <ErasRewardPoints<T>>::get(&era);
let total_reward_points = era_reward_points.total;
let validator_reward_points =
era_reward_points.individual.get(&stash).copied().unwrap_or_else(Zero::zero);
// Nothing to do if they have no reward points.
if validator_reward_points.is_zero() {
return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into())
}
// This is the fraction of the total reward that the validator and the
// nominators will get.
let validator_total_reward_part =
Perbill::from_rational(validator_reward_points, total_reward_points);
// This is how much validator + nominators are entitled to.
let validator_total_payout = validator_total_reward_part * era_payout;
let validator_commission = EraInfo::<T>::get_validator_commission(era, &ledger.stash);
// total commission validator takes across all nominator pages
let validator_total_commission_payout = validator_commission * validator_total_payout;
let validator_leftover_payout =
validator_total_payout.defensive_saturating_sub(validator_total_commission_payout);
// Now let's calculate how this is split to the validator.
let validator_exposure_part = Perbill::from_rational(exposure.own(), exposure.total());
let validator_staking_payout = validator_exposure_part * validator_leftover_payout;
let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total());
// validator commission is paid out in fraction across pages proportional to the page stake.
let validator_commission_payout = page_stake_part * validator_total_commission_payout;
Self::deposit_event(Event::<T>::PayoutStarted {
era_index: era,
Gavin Wood
committed
let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
// We can now make total validator payout:
if let Some((imbalance, dest)) =
Self::make_payout(&stash, validator_staking_payout + validator_commission_payout)
Self::deposit_event(Event::<T>::Rewarded { stash, dest, amount: imbalance.peek() });
// Track the number of payout ops to nominators. Note:
// `WeightInfo::payout_stakers_alive_staked` always assumes at least a validator is paid
// out, so we do not need to count their payout op.
let mut nominator_payout_count: u32 = 0;
// Lets now calculate how this is split to the nominators.
// Reward only the clipped exposures. Note this is not necessarily sorted.
for nominator in exposure.others().iter() {
let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total());
let nominator_reward: BalanceOf<T> =
nominator_exposure_part * validator_leftover_payout;
// We can now make nominator payout:
if let Some((imbalance, dest)) = Self::make_payout(&nominator.who, nominator_reward) {
// Note: this logic does not count payouts for `RewardDestination::None`.
nominator_payout_count += 1;
let e = Event::<T>::Rewarded {
stash: nominator.who.clone(),
dest,
amount: imbalance.peek(),
};
Gavin Wood
committed
Self::deposit_event(e);
}
}
T::Reward::on_unbalanced(total_imbalance);
debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
}
/// Chill a stash account.
pub(crate) fn chill_stash(stash: &T::AccountId) {
let chilled_as_validator = Self::do_remove_validator(stash);
let chilled_as_nominator = Self::do_remove_nominator(stash);
if chilled_as_validator || chilled_as_nominator {
Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
}
}
/// Actually make a payment to a staker. This uses the currency's reward function
/// to pay the right payee for the given staker account.
fn make_payout(
stash: &T::AccountId,
amount: BalanceOf<T>,
) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
// noop if amount is zero
if amount.is_zero() {
return None
}
let dest = Self::payee(StakingAccount::Stash(stash.clone()))?;
RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(),
RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
.and_then(|mut ledger| {
ledger.active += amount;
ledger.total += amount;
let r = T::Currency::deposit_into_existing(stash, amount).ok();
let _ = ledger
.update()
.defensive_proof("ledger fetched from storage, so it exists; qed.");
Ok(r)
})
.unwrap_or_default(),
RewardDestination::Account(ref dest_account) =>
Some(T::Currency::deposit_creating(&dest_account, amount)),
RewardDestination::None => None,
#[allow(deprecated)]
RewardDestination::Controller => Self::bonded(stash)
.map(|controller| {
defensive!("Paying out controller as reward destination which is deprecated and should be migrated.");
// This should never happen once payees with a `Controller` variant have been migrated.
// But if it does, just pay the controller account.
T::Currency::deposit_creating(&controller, amount)
}),
maybe_imbalance.map(|imbalance| (imbalance, dest))
}
/// Plan a new session potentially trigger a new era.
fn new_session(
session_index: SessionIndex,
is_genesis: bool,
) -> Option<BoundedVec<T::AccountId, MaxWinnersOf<T>>> {
if let Some(current_era) = Self::current_era() {
// Initial era has been set.
let current_era_start_session_index = Self::eras_start_session_index(current_era)
.unwrap_or_else(|| {
frame_support::print("Error: start_session_index must be set for current_era");
0
});
let era_length = session_index.saturating_sub(current_era_start_session_index); // Must never happen.
match ForceEra::<T>::get() {
// Will be set to `NotForcing` again if a new era has been triggered.
Forcing::ForceNew => (),
// Short circuit to `try_trigger_new_era`.
Forcing::ForceAlways => (),
// Only go to `try_trigger_new_era` if deadline reached.
Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
_ => {
// Either `Forcing::ForceNone`,
// or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`.
return None
},
}
// New era.
let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis);
if maybe_new_era_validators.is_some() &&
matches!(ForceEra::<T>::get(), Forcing::ForceNew)
{
Self::set_force_era(Forcing::NotForcing);
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
}
maybe_new_era_validators
} else {
// Set initial era.
log!(debug, "Starting the first era.");
Self::try_trigger_new_era(session_index, is_genesis)
}
}
/// Start a session potentially starting an era.
fn start_session(start_session: SessionIndex) {
let next_active_era = Self::active_era().map(|e| e.index + 1).unwrap_or(0);
// This is only `Some` when current era has already progressed to the next era, while the
// active era is one behind (i.e. in the *last session of the active era*, or *first session
// of the new current era*, depending on how you look at it).
if let Some(next_active_era_start_session_index) =
Self::eras_start_session_index(next_active_era)
{
if next_active_era_start_session_index == start_session {
Self::start_era(start_session);
} else if next_active_era_start_session_index < start_session {
// This arm should never happen, but better handle it than to stall the staking
// pallet.
frame_support::print("Warning: A session appears to have been skipped.");
Self::start_era(start_session);
}
}
André Silva
committed
// disable all offending validators that have been disabled for the whole era
for (index, disabled) in <OffendingValidators<T>>::get() {
if disabled {
T::SessionInterface::disable_validator(index);
}
}
}
/// End a session potentially ending an era.
fn end_session(session_index: SessionIndex) {
if let Some(active_era) = Self::active_era() {
if let Some(next_active_era_start_session_index) =
Self::eras_start_session_index(active_era.index + 1)
{
if next_active_era_start_session_index == session_index + 1 {
Self::end_era(active_era, session_index);
}
}
}
}
/// Start a new era. It does:
/// * Increment `active_era.index`,
/// * reset `active_era.start`,
/// * update `BondedEras` and apply slashes.
fn start_era(start_session: SessionIndex) {
let active_era = ActiveEra::<T>::mutate(|active_era| {
let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0);
*active_era = Some(ActiveEraInfo {
index: new_index,
// Set new active era start in next `on_finalize`. To guarantee usage of `Time`
start: None,
});
new_index
});
let bonding_duration = T::BondingDuration::get();
BondedEras::<T>::mutate(|bonded| {
bonded.push((active_era, start_session));
if active_era > bonding_duration {
let first_kept = active_era.defensive_saturating_sub(bonding_duration);
// Prune out everything that's from before the first-kept index.
let n_to_prune =
bonded.iter().take_while(|&&(era_idx, _)| era_idx < first_kept).count();
// Kill slashing metadata.
for (pruned_era, _) in bonded.drain(..n_to_prune) {
slashing::clear_era_metadata::<T>(pruned_era);
}
if let Some(&(_, first_session)) = bonded.first() {
T::SessionInterface::prune_historical_up_to(first_session);
}
}
});
Self::apply_unapplied_slashes(active_era);
}
/// Compute payout for era.
fn end_era(active_era: ActiveEraInfo, _session_index: SessionIndex) {
// Note: active_era_start can be None if end era is called during genesis config.
if let Some(active_era_start) = active_era.start {
let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::<u64>();
let era_duration = (now_as_millis_u64.defensive_saturating_sub(active_era_start))
.saturated_into::<u64>();
let staked = Self::eras_total_stake(&active_era.index);
let issuance = T::Currency::total_issuance();
let (validator_payout, remainder) =
T::EraPayout::era_payout(staked, issuance, era_duration);
let total_payout = validator_payout.saturating_add(remainder);
let max_staked_rewards =
MaxStakedRewards::<T>::get().unwrap_or(Percent::from_percent(100));
// apply cap to validators payout and add difference to remainder.
let validator_payout = validator_payout.min(max_staked_rewards * total_payout);
let remainder = total_payout.saturating_sub(validator_payout);
Self::deposit_event(Event::<T>::EraPaid {
era_index: active_era.index,
validator_payout,
remainder,
});
// Set ending era reward.
<ErasValidatorReward<T>>::insert(&active_era.index, validator_payout);
T::RewardRemainder::on_unbalanced(T::Currency::issue(remainder));
André Silva
committed
// Clear offending validators.
<OffendingValidators<T>>::kill();
}
}
/// Plan a new era.
///
/// * Bump the current era storage (which holds the latest planned era).
/// * Store start session index for the new planned era.
/// * Clean old era information.
/// * Store staking information for the new planned era
///
/// Returns the new validator set.
pub fn trigger_new_era(
start_session_index: SessionIndex,
exposures: BoundedVec<
(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>),
MaxWinnersOf<T>,
>,
) -> BoundedVec<T::AccountId, MaxWinnersOf<T>> {
// Increment or set current era.
let new_planned_era = CurrentEra::<T>::mutate(|s| {
*s = Some(s.map(|s| s + 1).unwrap_or(0));
s.unwrap()
});
ErasStartSessionIndex::<T>::insert(&new_planned_era, &start_session_index);
// Clean old era information.
if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) {
Self::clear_era_information(old_era);
}
// Set staking information for the new era.
Self::store_stakers_info(exposures, new_planned_era)
}
/// Potentially plan a new era.
///
/// Get election result from `T::ElectionProvider`.
/// In case election result has more than [`MinimumValidatorCount`] validator trigger a new era.
///
/// In case a new era is planned, the new validator set is returned.
pub(crate) fn try_trigger_new_era(
start_session_index: SessionIndex,
is_genesis: bool,
) -> Option<BoundedVec<T::AccountId, MaxWinnersOf<T>>> {
let election_result: BoundedVec<_, MaxWinnersOf<T>> = if is_genesis {
let result = <T::GenesisElectionProvider>::elect().map_err(|e| {
log!(warn, "genesis election provider failed due to {:?}", e);
Self::deposit_event(Event::StakingElectionFailed);
});
result
.ok()?
.into_inner()
.try_into()
// both bounds checked in integrity test to be equal
.defensive_unwrap_or_default()
let result = <T::ElectionProvider>::elect().map_err(|e| {
log!(warn, "election provider failed due to {:?}", e);
Self::deposit_event(Event::StakingElectionFailed);
});
result.ok()?
};
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
let exposures = Self::collect_exposures(election_result);
if (exposures.len() as u32) < Self::minimum_validator_count().max(1) {
// Session will panic if we ever return an empty validator set, thus max(1) ^^.
match CurrentEra::<T>::get() {
Some(current_era) if current_era > 0 => log!(
warn,
"chain does not have enough staking candidates to operate for era {:?} ({} \
elected, minimum is {})",
CurrentEra::<T>::get().unwrap_or(0),
exposures.len(),
Self::minimum_validator_count(),
),
None => {
// The initial era is allowed to have no exposures.
// In this case the SessionManager is expected to choose a sensible validator
// set.
// TODO: this should be simplified #8911
CurrentEra::<T>::put(0);
ErasStartSessionIndex::<T>::insert(&0, &start_session_index);
},
_ => (),
}
Self::deposit_event(Event::StakingElectionFailed);
return None
}
Gavin Wood
committed
Self::deposit_event(Event::StakersElected);
Some(Self::trigger_new_era(start_session_index, exposures))
}
/// Process the output of the election.
///
/// Store staking information for the new planned era
pub fn store_stakers_info(
exposures: BoundedVec<
(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>),
MaxWinnersOf<T>,
>,
new_planned_era: EraIndex,
) -> BoundedVec<T::AccountId, MaxWinnersOf<T>> {
// Populate elected stash, stakers, exposures, and the snapshot of validator prefs.
let mut total_stake: BalanceOf<T> = Zero::zero();
let mut elected_stashes = Vec::with_capacity(exposures.len());
exposures.into_iter().for_each(|(stash, exposure)| {
// build elected stash
elected_stashes.push(stash.clone());
// accumulate total stake
total_stake = total_stake.saturating_add(exposure.total);
// store staker exposure for this era
EraInfo::<T>::set_exposure(new_planned_era, &stash, exposure);
let elected_stashes: BoundedVec<_, MaxWinnersOf<T>> = elected_stashes
.try_into()
.expect("elected_stashes.len() always equal to exposures.len(); qed");
EraInfo::<T>::set_total_stake(new_planned_era, total_stake);
// Collect the pref of all winners.
for stash in &elected_stashes {
let pref = Self::validators(stash);
<ErasValidatorPrefs<T>>::insert(&new_planned_era, stash, pref);
}
if new_planned_era > 0 {
log!(
info,
"new validator set of size {:?} has been processed for era {:?}",
elected_stashes.len(),
new_planned_era,
);
}
elected_stashes
}
/// Consume a set of [`BoundedSupports`] from [`sp_npos_elections`] and collect them into a
/// [`Exposure`].
fn collect_exposures(
supports: BoundedSupportsOf<T::ElectionProvider>,
) -> BoundedVec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>), MaxWinnersOf<T>> {
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
let total_issuance = T::Currency::total_issuance();
let to_currency = |e: frame_election_provider_support::ExtendedBalance| {
T::CurrencyToVote::to_currency(e, total_issuance)
};
supports
.into_iter()
.map(|(validator, support)| {
// Build `struct exposure` from `support`.
let mut others = Vec::with_capacity(support.voters.len());
let mut own: BalanceOf<T> = Zero::zero();
let mut total: BalanceOf<T> = Zero::zero();
support
.voters
.into_iter()
.map(|(nominator, weight)| (nominator, to_currency(weight)))
.for_each(|(nominator, stake)| {
if nominator == validator {
own = own.saturating_add(stake);
} else {
others.push(IndividualExposure { who: nominator, value: stake });
}
total = total.saturating_add(stake);
});
let exposure = Exposure { own, others, total };
(validator, exposure)
})
.try_collect()
.expect("we only map through support vector which cannot change the size; qed")
}
/// Remove all associated data of a stash account from the staking system.
///
/// Assumes storage is upgraded before calling.
///
/// This is called:
/// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance.
/// - through `reap_stash()` if the balance has fallen to zero (through slashing).
pub(crate) fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult {
slashing::clear_stash_metadata::<T>(&stash, num_slashing_spans)?;
// removes controller from `Bonded` and staking ledger from `Ledger`, as well as reward
// setting of the stash in `Payee`.
StakingLedger::<T>::kill(&stash)?;
Self::do_remove_validator(&stash);
Self::do_remove_nominator(&stash);
frame_system::Pallet::<T>::dec_consumers(&stash);
Ok(())
}
/// Clear all era information for given era.
pub(crate) fn clear_era_information(era_index: EraIndex) {
// FIXME: We can possibly set a reasonable limit since we do this only once per era and
// clean up state across multiple blocks.
let mut cursor = <ErasStakers<T>>::clear_prefix(era_index, u32::MAX, None);
debug_assert!(cursor.maybe_cursor.is_none());
cursor = <ErasStakersClipped<T>>::clear_prefix(era_index, u32::MAX, None);
debug_assert!(cursor.maybe_cursor.is_none());
cursor = <ErasValidatorPrefs<T>>::clear_prefix(era_index, u32::MAX, None);
debug_assert!(cursor.maybe_cursor.is_none());
cursor = <ClaimedRewards<T>>::clear_prefix(era_index, u32::MAX, None);
debug_assert!(cursor.maybe_cursor.is_none());
cursor = <ErasStakersPaged<T>>::clear_prefix((era_index,), u32::MAX, None);
debug_assert!(cursor.maybe_cursor.is_none());
cursor = <ErasStakersOverview<T>>::clear_prefix(era_index, u32::MAX, None);
debug_assert!(cursor.maybe_cursor.is_none());
<ErasValidatorReward<T>>::remove(era_index);
<ErasRewardPoints<T>>::remove(era_index);
<ErasTotalStake<T>>::remove(era_index);
ErasStartSessionIndex::<T>::remove(era_index);
}
/// Apply previously-unapplied slashes on the beginning of a new era, after a delay.
fn apply_unapplied_slashes(active_era: EraIndex) {
let era_slashes = UnappliedSlashes::<T>::take(&active_era);
log!(
debug,
"found {} slashes scheduled to be executed in era {:?}",
era_slashes.len(),
active_era,
);
for slash in era_slashes {
let slash_era = active_era.saturating_sub(T::SlashDeferDuration::get());
slashing::apply_slash::<T>(slash, slash_era);
}
}
/// Add reward points to validators using their stash account ID.
///
/// Validators are keyed by stash account ID and must be in the current elected set.
///
/// For each element in the iterator the given number of points in u32 is added to the
/// validator, thus duplicates are handled.
///
/// At the end of the era each the total payout will be distributed among validator
/// relatively to their points.
///
/// COMPLEXITY: Complexity is `number_of_validator_to_reward x current_elected_len`.
pub fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
if let Some(active_era) = Self::active_era() {
<ErasRewardPoints<T>>::mutate(active_era.index, |era_rewards| {
for (validator, points) in validators_points.into_iter() {
*era_rewards.individual.entry(validator).or_default() += points;
era_rewards.total += points;
}
});
}
}
/// Helper to set a new `ForceEra` mode.
pub(crate) fn set_force_era(mode: Forcing) {
log!(info, "Setting force era mode {:?}.", mode);
ForceEra::<T>::put(mode);
Self::deposit_event(Event::<T>::ForceEra { mode });
}
/// Ensures that at the end of the current session there will be a new era.
pub(crate) fn ensure_new_era() {
match ForceEra::<T>::get() {
Forcing::ForceAlways | Forcing::ForceNew => (),
_ => Self::set_force_era(Forcing::ForceNew),
}
}
#[cfg(feature = "runtime-benchmarks")]
pub fn add_era_stakers(
current_era: EraIndex,
exposure: Exposure<T::AccountId, BalanceOf<T>>,
) {
EraInfo::<T>::set_exposure(current_era, &stash, exposure);
}
#[cfg(feature = "runtime-benchmarks")]
pub fn set_slash_reward_fraction(fraction: Perbill) {
SlashRewardFraction::<T>::put(fraction);
}
/// Get all of the voters that are eligible for the npos election.
///
Kian Paimani
committed
/// `maybe_max_len` can imposes a cap on the number of voters returned;
/// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of
/// nominators.
///
/// This function is self-weighing as [`DispatchClass::Mandatory`].
pub fn get_npos_voters(bounds: DataProviderBounds) -> Vec<VoterOf<Self>> {
let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
let final_predicted_len = {
let all_voter_count = T::VoterList::count();
bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
};
let mut all_voters = Vec::<_>::with_capacity(final_predicted_len as usize);
Kian Paimani
committed
// cache a few things.
let weight_of = Self::weight_of_fn();
Kian Paimani
committed
let mut voters_seen = 0u32;
let mut validators_taken = 0u32;
let mut nominators_taken = 0u32;
let mut min_active_stake = u64::MAX;
Kian Paimani
committed
let mut sorted_voters = T::VoterList::iter();
while all_voters.len() < final_predicted_len as usize &&
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
Kian Paimani
committed
{
let voter = match sorted_voters.next() {
Some(voter) => {
voters_seen.saturating_inc();
voter
},
None => break,
};
Gonçalo Pestana
committed
let voter_weight = weight_of(&voter);
// if voter weight is zero, do not consider this voter for the snapshot.
if voter_weight.is_zero() {
log!(debug, "voter's active balance is 0. skip this voter.");
continue
}
if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
if !targets.is_empty() {
// Note on lazy nomination quota: we do not check the nomination quota of the
// voter at this point and accept all the current nominations. The nomination
// quota is only enforced at `nominate` time.
let voter = (voter, voter_weight, targets);
if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
// no more space left for the election result, stop iterating.
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
size: voters_size_tracker.size as u32,
});
break
}
all_voters.push(voter);
nominators_taken.saturating_inc();
// technically should never happen, but not much we can do about it.
}
min_active_stake =
if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
Kian Paimani
committed
} else if Validators::<T>::contains_key(&voter) {
// if this voter is a validator:
let self_vote = (
voter.clone(),
Gonçalo Pestana
committed
voter_weight,
Kian Paimani
committed
vec![voter.clone()]
.try_into()
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
);
if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
// no more space left for the election snapshot, stop iterating.
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
size: voters_size_tracker.size as u32,
});
break
}
Kian Paimani
committed
all_voters.push(self_vote);
validators_taken.saturating_inc();
} else {
Kian Paimani
committed
// this can only happen if: 1. there a bug in the bags-list (or whatever is the
// sorted list) logic and the state of the two pallets is no longer compatible, or
// because the nominators is not decodable since they have more nomination than
// `T::NominationsQuota::get_quota`. The latter can rarely happen, and is not
// really an emergency or bug if it does.
defensive!(
"DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
voter,
);
}
}
// all_voters should have not re-allocated.
debug_assert!(all_voters.capacity() == final_predicted_len as usize);
Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken));
let min_active_stake: T::CurrencyBalance =
Gonçalo Pestana
committed
if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
MinimumActiveStake::<T>::put(min_active_stake);
log!(
info,
"generated {} npos voters, {} from validators and {} nominators",
all_voters.len(),
validators_taken,
nominators_taken
);
Kian Paimani
committed
all_voters
}
/// Get the targets for an upcoming npos election.
///
/// This function is self-weighing as [`DispatchClass::Mandatory`].
pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
let final_predicted_len = {
let all_target_count = T::TargetList::count();
bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
};
let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
let mut targets_seen = 0;
let mut targets_iter = T::TargetList::iter();
while all_targets.len() < final_predicted_len as usize &&
targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
{
let target = match targets_iter.next() {
Some(target) => {
targets_seen.saturating_inc();
target
},
None => break,
};
if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
// no more space left for the election snapshot, stop iterating.
Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
size: targets_size_tracker.size as u32,
});
break
}
if Validators::<T>::contains_key(&target) {
all_targets.push(target);
}
}
Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32));
log!(info, "generated {} npos targets", all_targets.len());
all_targets
}
/// This function will add a nominator to the `Nominators` storage map,
Kian Paimani
committed
/// and `VoterList`.
///
/// If the nominator already exists, their nominations will be updated.
///
/// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access
/// to `Nominators` or `VoterList` outside of this function is almost certainly
/// wrong.
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
if !Nominators::<T>::contains_key(who) {
Kian Paimani
committed
// maybe update sorted list.
let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))