Newer
Older
// Validate input data
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
let current_era = CurrentEra::get().ok_or(Error::<T>::InvalidEraToReward)?;
ensure!(era <= current_era, Error::<T>::InvalidEraToReward);
let history_depth = Self::history_depth();
ensure!(era >= current_era.saturating_sub(history_depth), Error::<T>::InvalidEraToReward);
// Note: if era has no reward to be claimed, era may be future. better not to update
// `ledger.claimed_rewards` in this case.
let era_payout = <ErasValidatorReward<T>>::get(&era)
.ok_or_else(|| Error::<T>::InvalidEraToReward)?;
let controller = Self::bonded(&validator_stash).ok_or(Error::<T>::NotStash)?;
let mut ledger = <Ledger<T>>::get(&controller).ok_or_else(|| Error::<T>::NotController)?;
ledger.claimed_rewards.retain(|&x| x >= current_era.saturating_sub(history_depth));
match ledger.claimed_rewards.binary_search(&era) {
Ok(_) => Err(Error::<T>::AlreadyClaimed)?,
Err(pos) => ledger.claimed_rewards.insert(pos, era),
}
let exposure = <ErasStakersClipped<T>>::get(&era, &ledger.stash);
/* Input data seems good, no errors allowed after this point */
<Ledger<T>>::insert(&controller, &ledger);
// 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(&ledger.stash)
.map(|points| *points)
.unwrap_or_else(|| Zero::zero());
// Nothing to do if they have no reward points.
if validator_reward_points.is_zero() { return Ok(())}
// 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_prefs = Self::eras_validator_prefs(&era, &validator_stash);
// Validator first gets a cut off the top.
let validator_commission = validator_prefs.commission;
let validator_commission_payout = validator_commission * validator_total_payout;
let validator_leftover_payout = validator_total_payout - validator_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;
// We can now make total validator payout:
if let Some(imbalance) = Self::make_payout(
&ledger.stash,
validator_staking_payout + validator_commission_payout
) {
Self::deposit_event(RawEvent::Reward(ledger.stash, imbalance.peek()));
}
// 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) = Self::make_payout(&nominator.who, nominator_reward) {
Self::deposit_event(RawEvent::Reward(nominator.who.clone(), imbalance.peek()));
}
}
Ok(())
}
/// Update the ledger for a controller.
///
/// This will also update the stash lock.
fn update_ledger(
controller: &T::AccountId,
ledger: &StakingLedger<T::AccountId, BalanceOf<T>>
) {
T::Currency::set_lock(
STAKING_ID,
&ledger.stash,
ledger.total,
<Ledger<T>>::insert(controller, ledger);
}
/// Chill a stash account.
fn chill_stash(stash: &T::AccountId) {
<Validators<T>>::remove(stash);
<Nominators<T>>::remove(stash);
}
/// 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>> {
let dest = Self::payee(stash);
match dest {
RewardDestination::Controller => Self::bonded(stash)
.and_then(|controller|
Some(T::Currency::deposit_creating(&controller, amount))
),
RewardDestination::Stash =>
T::Currency::deposit_into_existing(stash, amount).ok(),
RewardDestination::Staked => Self::bonded(stash)
.and_then(|c| Self::ledger(&c).map(|l| (c, l)))
.and_then(|(controller, mut l)| {
l.active += amount;
l.total += amount;
let r = T::Currency::deposit_into_existing(stash, amount).ok();
Self::update_ledger(&controller, &l);
RewardDestination::Account(dest_account) => {
Some(T::Currency::deposit_creating(&dest_account, amount))
Wei Tang
committed
},
RewardDestination::None => None,
/// Plan a new session potentially trigger a new era.
fn new_session(session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
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.checked_sub(current_era_start_session_index)
.unwrap_or(0); // Must never happen.
match ForceEra::get() {
// Will set to default again, which is `NotForcing`.
// Short circuit to `new_era`.
// Only go to `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()`.
Self::new_era(session_index)
} else {
// Set initial era
log!(debug, "Starting the first era.");
/// 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);
}
}
/// 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) =
{
if next_active_era_start_session_index == session_index + 1 {
Self::end_era(active_era, session_index);
}
/// * Increment `active_era.index`,
/// * reset `active_era.start`,
/// * update `BondedEras` and apply slashes.
fn start_era(start_session: SessionIndex) {
let active_era = ActiveEra::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::mutate(|bonded| {
if active_era > bonding_duration {
let first_kept = active_era - 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 - active_era_start).saturated_into::<u64>();
let staked = Self::eras_total_stake(&active_era.index);
let issuance = T::Currency::total_issuance();
let (validator_payout, rest) = T::EraPayout::era_payout(staked, issuance, era_duration);
Self::deposit_event(RawEvent::EraPayout(active_era.index, validator_payout, rest));
<ErasValidatorReward<T>>::insert(&active_era.index, validator_payout);
T::RewardRemainder::on_unbalanced(T::Currency::issue(rest));
}
}
/// Plan a new era. Return the potential new staking set.
fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
// Increment or set current era.
let current_era = CurrentEra::mutate(|s| {
*s = Some(s.map(|s| s + 1).unwrap_or(0));
s.unwrap()
});
ErasStartSessionIndex::insert(¤t_era, &start_session_index);
// Clean old era information.
if let Some(old_era) = current_era.checked_sub(Self::history_depth() + 1) {
Self::clear_era_information(old_era);
}
// Set staking information for new era.
let maybe_new_validators = Self::enact_election(current_era);
/// Enact and process the election using the `ElectionProvider` type.
/// This will also process the election, as noted in [`process_election`].
fn enact_election(current_era: EraIndex) -> Option<Vec<T::AccountId>> {
T::ElectionProvider::elect()
.map_err(|e| {
log!(warn, "election provider failed due to {:?}", e)
})
.and_then(|(res, weight)| {
<frame_system::Pallet<T>>::register_extra_weight_unchecked(
weight,
frame_support::weights::DispatchClass::Mandatory,
Self::process_election(res, current_era)
/// Process the output of the election.
///
/// This ensures enough validators have been elected, converts all supports to exposures and
/// writes them to the associated storage.
///
/// Returns `Err(())` if less than [`MinimumValidatorCount`] validators have been elected, `Ok`
/// otherwise.
pub fn process_election(
flat_supports: sp_npos_elections::Supports<T::AccountId>,
current_era: EraIndex,
) -> Result<Vec<T::AccountId>, ()> {
let exposures = Self::collect_exposures(flat_supports);
let elected_stashes = exposures.iter().cloned().map(|(x, _)| x).collect::<Vec<_>>();
if (elected_stashes.len() as u32) < Self::minimum_validator_count().max(1) {
// Session will panic if we ever return an empty validator set, thus max(1) ^^.
if current_era > 0 {
log!(
warn,
"chain does not have enough staking candidates to operate for era {:?} ({} elected, minimum is {})",
elected_stashes.len(),
Self::minimum_validator_count(),
return Err(());
}
// Populate stakers, exposures, and the snapshot of validator prefs.
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
let mut total_stake: BalanceOf<T> = Zero::zero();
exposures.into_iter().for_each(|(stash, exposure)| {
total_stake = total_stake.saturating_add(exposure.total);
<ErasStakers<T>>::insert(current_era, &stash, &exposure);
let mut exposure_clipped = exposure;
let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize;
if exposure_clipped.others.len() > clipped_max_len {
exposure_clipped.others.sort_by(|a, b| a.value.cmp(&b.value).reverse());
exposure_clipped.others.truncate(clipped_max_len);
}
<ErasStakersClipped<T>>::insert(¤t_era, &stash, exposure_clipped);
});
// Insert current era staking information
<ErasTotalStake<T>>::insert(¤t_era, total_stake);
// collect the pref of all winners
for stash in &elected_stashes {
let pref = Self::validators(stash);
<ErasValidatorPrefs<T>>::insert(¤t_era, stash, pref);
}
// emit event
Self::deposit_event(RawEvent::StakingElection);
if current_era > 0 {
log!(
info,
"new validator set of size {:?} has been processed for era {:?}",
elected_stashes.len(),
current_era,
);
}
Ok(elected_stashes)
}
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
/// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a
/// [`Exposure`].
fn collect_exposures(
supports: Supports<T::AccountId>,
) -> Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)> {
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)
})
.collect::<Vec<(T::AccountId, Exposure<_, _>)>>()
/// Remove all associated data of a stash account from the staking system.
///
/// Assumes storage is upgraded before calling.
///
/// - 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).
fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult {
let controller = <Bonded<T>>::get(stash).ok_or(Error::<T>::NotStash)?;
slashing::clear_stash_metadata::<T>(stash, num_slashing_spans)?;
<Bonded<T>>::remove(stash);
<Payee<T>>::remove(stash);
<Validators<T>>::remove(stash);
<Nominators<T>>::remove(stash);
system::Pallet::<T>::dec_consumers(stash);
}
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
/// Clear all era information for given era.
fn clear_era_information(era_index: EraIndex) {
<ErasStakers<T>>::remove_prefix(era_index);
<ErasStakersClipped<T>>::remove_prefix(era_index);
<ErasValidatorPrefs<T>>::remove_prefix(era_index);
<ErasValidatorReward<T>>::remove(era_index);
<ErasRewardPoints<T>>::remove(era_index);
<ErasTotalStake<T>>::remove(era_index);
ErasStartSessionIndex::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 slash_defer_duration = T::SlashDeferDuration::get();
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| if let Some(ref mut earliest) = earliest {
let keep_from = active_era.saturating_sub(slash_defer_duration);
for era in (*earliest)..keep_from {
let era_slashes = <Self as Store>::UnappliedSlashes::take(&era);
for slash in era_slashes {
slashing::apply_slash::<T>(slash);
}
}
*earliest = (*earliest).max(keep_from)
})
}
/// 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`.
/// If you need to reward lots of validator consider using `reward_by_indices`.
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;
});
}
}
/// Ensures that at the end of the current session there will be a new era.
fn ensure_new_era() {
match ForceEra::get() {
Forcing::ForceAlways | Forcing::ForceNew => (),
_ => ForceEra::put(Forcing::ForceNew),
}
#[cfg(feature = "runtime-benchmarks")]
pub fn add_era_stakers(
current_era: EraIndex,
controller: T::AccountId,
exposure: Exposure<T::AccountId, BalanceOf<T>>,
) {
<ErasStakers<T>>::insert(¤t_era, &controller, &exposure);
}
#[cfg(feature = "runtime-benchmarks")]
pub fn set_slash_reward_fraction(fraction: Perbill) {
SlashRewardFraction::put(fraction);
}
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
/// Get all of the voters that are eligible for the npos election.
///
/// This will use all on-chain nominators, and all the validators will inject a self vote.
///
/// ### Slashing
///
/// All nominations that have been submitted before the last non-zero slash of the validator are
/// auto-chilled.
///
/// Note that this is VERY expensive. Use with care.
pub fn get_npos_voters() -> Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)> {
let weight_of = Self::slashable_balance_of_fn();
let mut all_voters = Vec::new();
for (validator, _) in <Validators<T>>::iter() {
// append self vote
let self_vote = (validator.clone(), weight_of(&validator), vec![validator.clone()]);
all_voters.push(self_vote);
}
// collect all slashing spans into a BTreeMap for further queries.
let slashing_spans = <SlashingSpans<T>>::iter().collect::<BTreeMap<_, _>>();
for (nominator, nominations) in <Nominators<T>>::iter() {
let Nominations { submitted_in, mut targets, suppressed: _ } = nominations;
// Filter out nomination targets which were nominated before the most recent
// slashing span.
targets.retain(|stash| {
slashing_spans
.get(stash)
.map_or(true, |spans| submitted_in >= spans.last_nonzero_slash())
});
let vote_weight = weight_of(&nominator);
all_voters.push((nominator, vote_weight, targets))
}
all_voters
}
pub fn get_npos_targets() -> Vec<T::AccountId> {
<Validators<T>>::iter().map(|(v, _)| v).collect::<Vec<_>>()
}
}
impl<T: Config> frame_election_provider_support::ElectionDataProvider<T::AccountId, T::BlockNumber>
for Module<T>
{
fn desired_targets() -> data_provider::Result<(u32, Weight)> {
Ok((Self::validator_count(), <T as frame_system::Config>::DbWeight::get().reads(1)))
}
fn voters(
maybe_max_len: Option<usize>,
) -> data_provider::Result<(Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>, Weight)> {
// NOTE: reading these counts already needs to iterate a lot of storage keys, but they get
// cached. This is okay for the case of `Ok(_)`, but bad for `Err(_)`, as the trait does not
// report weight in failures.
let nominator_count = <Nominators<T>>::iter().count();
let validator_count = <Validators<T>>::iter().count();
let voter_count = nominator_count.saturating_add(validator_count);
if maybe_max_len.map_or(false, |max_len| voter_count > max_len) {
return Err("Voter snapshot too big");
}
let slashing_span_count = <SlashingSpans<T>>::iter().count();
let weight = T::WeightInfo::get_npos_voters(
nominator_count as u32,
validator_count as u32,
slashing_span_count as u32,
);
Ok((Self::get_npos_voters(), weight))
fn targets(maybe_max_len: Option<usize>) -> data_provider::Result<(Vec<T::AccountId>, Weight)> {
let target_count = <Validators<T>>::iter().count();
if maybe_max_len.map_or(false, |max_len| target_count > max_len) {
return Err("Target snapshot too big");
}
let weight = <T as frame_system::Config>::DbWeight::get().reads(target_count as u64);
Ok((Self::get_npos_targets(), weight))
}
fn next_election_prediction(now: T::BlockNumber) -> T::BlockNumber {
let current_era = Self::current_era().unwrap_or(0);
let current_session = Self::current_planned_session();
let current_era_start_session_index =
Self::eras_start_session_index(current_era).unwrap_or(0);
let era_length = current_session
.saturating_sub(current_era_start_session_index)
.min(T::SessionsPerEra::get());
let session_length = T::NextNewSession::average_session_length();
let until_this_session_end = T::NextNewSession::estimate_next_new_session(now)
André Silva
committed
.0
.unwrap_or_default()
.saturating_sub(now);
let sessions_left: T::BlockNumber = T::SessionsPerEra::get()
.saturating_sub(era_length)
// one session is computed in this_session_end.
.saturating_sub(1)
.into();
now.saturating_add(
until_this_session_end.saturating_add(sessions_left.saturating_mul(session_length)),
)
}
#[cfg(any(feature = "runtime-benchmarks", test))]
fn put_snapshot(
voters: Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
targets: Vec<T::AccountId>,
target_stake: Option<VoteWeight>,
use sp_std::convert::TryFrom;
targets.into_iter().for_each(|v| {
let stake: BalanceOf<T> = target_stake
.and_then(|w| <BalanceOf<T>>::try_from(w).ok())
.unwrap_or(T::Currency::minimum_balance() * 100u32.into());
<Bonded<T>>::insert(v.clone(), v.clone());
<Ledger<T>>::insert(
v.clone(),
StakingLedger {
stash: v.clone(),
active: stake,
total: stake,
unlocking: vec![],
claimed_rewards: vec![],
},
);
<Validators<T>>::insert(
v,
ValidatorPrefs { commission: Perbill::zero(), blocked: false },
);
});
voters.into_iter().for_each(|(v, s, t)| {
let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
});
<Bonded<T>>::insert(v.clone(), v.clone());
<Ledger<T>>::insert(
v.clone(),
StakingLedger {
stash: v.clone(),
active: stake,
total: stake,
unlocking: vec![],
claimed_rewards: vec![],
},
);
<Nominators<T>>::insert(
v,
Nominations { targets: t, submitted_in: 0, suppressed: false },
);
});
}
/// In this implementation `new_session(session)` must be called before `end_session(session-1)`
/// i.e. the new session must be planned before the ending of the previous session.
///
/// Once the first new_session is planned, all session must start and then end in order, though
/// some session can lag in between the newest session planned and the latest session started.
impl<T: Config> pallet_session::SessionManager<T::AccountId> for Module<T> {
fn new_session(new_index: SessionIndex) -> Option<Vec<T::AccountId>> {
log!(trace, "planning new_session({})", new_index);
CurrentPlannedSession::put(new_index);
Self::new_session(new_index)
}
fn start_session(start_index: SessionIndex) {
log!(trace, "starting start_session({})", start_index);
Self::start_session(start_index)
}
fn end_session(end_index: SessionIndex) {
log!(trace, "ending end_session({})", end_index);
impl<T: Config> historical::SessionManager<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>>
for Module<T>
{
fn new_session(
new_index: SessionIndex,
) -> Option<Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>> {
<Self as pallet_session::SessionManager<_>>::new_session(new_index).map(|validators| {
let current_era = Self::current_era()
// Must be some as a new era has been created.
.unwrap_or(0);
(v, exposure)
}).collect()
})
}
fn start_session(start_index: SessionIndex) {
<Self as pallet_session::SessionManager<_>>::start_session(start_index)
}
fn end_session(end_index: SessionIndex) {
<Self as pallet_session::SessionManager<_>>::end_session(end_index)
/// Add reward points to block authors:
/// * 20 points to the block producer for producing a (non-uncle) block in the relay chain,
/// * 2 points to the block producer for each reference to a previously unreferenced uncle, and
/// * 1 point to the producer of each referenced uncle block.
impl<T> pallet_authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T>
where
T: Config + pallet_authorship::Config + pallet_session::Config,
fn note_author(author: T::AccountId) {
}
fn note_uncle(author: T::AccountId, _age: T::BlockNumber) {
(<pallet_authorship::Module<T>>::author(), 2),
/// A `Convert` implementation that finds the stash of the given controller account,
/// if any.
pub struct StashOf<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Convert<T::AccountId, Option<T::AccountId>> for StashOf<T> {
fn convert(controller: T::AccountId) -> Option<T::AccountId> {
<Module<T>>::ledger(&controller).map(|l| l.stash)
}
}
/// A typed conversion from stash account ID to the active exposure of nominators
/// on that account.
///
/// Active exposure is the exposure of the validator set currently validating, i.e. in
/// `active_era`. It can differ from the latest planned exposure in `current_era`.
pub struct ExposureOf<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>>>>
for ExposureOf<T>
{
fn convert(validator: T::AccountId) -> Option<Exposure<T::AccountId, BalanceOf<T>>> {
if let Some(active_era) = <Module<T>>::active_era() {
Some(<Module<T>>::eras_stakers(active_era.index, &validator))
} else {
None
}
/// This is intended to be used with `FilterHistoricalOffences`.
impl<T: Config>
OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight>
for Module<T>
where
T: pallet_session::Config<ValidatorId = <T as frame_system::Config>::AccountId>,
T: pallet_session::historical::Config<
FullIdentification = Exposure<<T as frame_system::Config>::AccountId, BalanceOf<T>>,
FullIdentificationOf = ExposureOf<T>,
>,
T::SessionHandler: pallet_session::SessionHandler<<T as frame_system::Config>::AccountId>,
T::SessionManager: pallet_session::SessionManager<<T as frame_system::Config>::AccountId>,
<T as frame_system::Config>::AccountId,
Option<<T as frame_system::Config>::AccountId>,
offenders: &[OffenceDetails<
T::AccountId,
pallet_session::historical::IdentificationTuple<T>,
>],
slash_session: SessionIndex,
return Err(());
let reward_proportion = SlashRewardFraction::get();
let mut consumed_weight: Weight = 0;
let mut add_db_reads_writes = |reads, writes| {
consumed_weight += T::DbWeight::get().reads_writes(reads, writes);
};
let active_era = {
let active_era = Self::active_era();
// this offence need not be re-submitted.
active_era.expect("value checked not to be `None`; qed").index
};
let active_era_start_session_index = Self::eras_start_session_index(active_era)
.unwrap_or_else(|| {
frame_support::print("Error: start_session_index must be set for current_era");
0
});
let window_start = active_era.saturating_sub(T::BondingDuration::get());
// fast path for active-era report - most likely.
// `slash_session` cannot be in a future active era. It must be in `active_era` or before.
let slash_era = if slash_session >= active_era_start_session_index {
active_era
} else {
let eras = BondedEras::get();
// reverse because it's more likely to find reports from recent eras.
match eras.iter().rev().filter(|&&(_, ref sesh)| sesh <= &slash_session).next() {
Some(&(ref slash_era, _)) => *slash_era,
// before bonding period. defensive - should be filtered out.
None => return Ok(consumed_weight),
}
};
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| {
if earliest.is_none() {
let slash_defer_duration = T::SlashDeferDuration::get();
let invulnerables = Self::invulnerables();
add_db_reads_writes(1, 0);
for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
let (stash, exposure) = &details.offender;
// Skip if the validator is invulnerable.
if invulnerables.contains(stash) {
let unapplied = slashing::compute_slash::<T>(slashing::SlashParams {
stash,
slash: *slash_fraction,
exposure,
slash_era,
window_start,
reward_proportion,
});
if let Some(mut unapplied) = unapplied {
let nominators_len = unapplied.others.len() as u64;
let reporters_len = details.reporters.len() as u64;
{
let upper_bound = 1 /* Validator/NominatorSlashInEra */ + 2 /* fetch_spans */;
let rw = upper_bound + nominators_len * upper_bound;
add_db_reads_writes(rw, rw);
}
unapplied.reporters = details.reporters.clone();
if slash_defer_duration == 0 {
// apply right away.
slashing::apply_slash::<T>(unapplied);
{
let slash_cost = (6, 5);
let reward_cost = (2, 2);
add_db_reads_writes(
(1 + nominators_len) * slash_cost.0 + reward_cost.0 * reporters_len,
(1 + nominators_len) * slash_cost.1 + reward_cost.1 * reporters_len
);
}
} else {
// defer to end of some `slash_defer_duration` from now.
<Self as Store>::UnappliedSlashes::mutate(
move |for_later| for_later.push(unapplied),
);
} else {
add_db_reads_writes(4 /* fetch_spans */, 5 /* kick_out_if_recent */)
}
fn can_report() -> bool {
// TODO: https://github.com/paritytech/substrate/issues/8343
true
/// Filter historical offences out and only allow those from the bonding period.
pub struct FilterHistoricalOffences<T, R> {
_inner: sp_std::marker::PhantomData<(T, R)>,
}
impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
for FilterHistoricalOffences<Module<T>, R>
where
R: ReportOffence<Reporter, Offender, O>,
O: Offence<Offender>,
{
fn report_offence(reporters: Vec<Reporter>, offence: O) -> Result<(), OffenceError> {
// disallow any slashing from before the current bonding period.
let offence_session = offence.session_index();
let bonded_eras = BondedEras::get();
if bonded_eras.first().filter(|(_, start)| offence_session >= *start).is_some() {
R::report_offence(reporters, offence)
} else {
<Module<T>>::deposit_event(
RawEvent::OldSlashingReportDiscarded(offence_session)
fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool {
R::is_known_offence(offenders, time_slot)
}
/// Check that list is sorted and has no duplicates.
fn is_sorted_and_unique(list: &[u32]) -> bool {
list.windows(2).all(|w| w[0] < w[1])
}