Newer
Older
/// Complexity: O(U + S)
/// with U unapplied slashes weighted with U=1000
/// and S is the number of slash indices to be canceled.
/// - Base: 5870 + 34.61 * S µs
/// - Read: Unapplied Slashes
/// - Write: Unapplied Slashes
#[weight = T::DbWeight::get().reads_writes(1, 1)
.saturating_add(5_870 * WEIGHT_PER_MICROS)
.saturating_add((35 * WEIGHT_PER_MICROS).saturating_mul(slash_indices.len() as Weight))
]
fn cancel_deferred_slash(origin, era: EraIndex, slash_indices: Vec<u32>) {
T::SlashCancelOrigin::ensure_origin(origin)?;
ensure!(!slash_indices.is_empty(), Error::<T>::EmptyTargets);
ensure!(is_sorted_and_unique(&slash_indices), Error::<T>::NotSortedAndUnique);
let mut unapplied = <Self as Store>::UnappliedSlashes::get(&era);
let last_item = slash_indices[slash_indices.len() - 1];
ensure!((last_item as usize) < unapplied.len(), Error::<T>::InvalidSlashIndex);
for (removed, index) in slash_indices.into_iter().enumerate() {
let index = (index as usize) - removed;
unapplied.remove(index);
}
<Self as Store>::UnappliedSlashes::insert(&era, &unapplied);
}
/// Pay out all the stakers behind a single validator for a single era.
///
/// - `validator_stash` is the stash account of the validator. Their nominators, up to
/// `T::MaxNominatorRewardedPerValidator`, will also receive their rewards.
/// - `era` may be any era between `[current_era - history_depth; current_era]`.
///
/// The origin of this call must be _Signed_. Any account can call this function, even if
/// it is not one of the stakers.
///
/// This can only be called when [`EraElectionStatus`] is `Closed`.
///
/// # <weight>
/// - Time complexity: at most O(MaxNominatorRewardedPerValidator).
/// - Contains a limited number of reads and writes.
/// -----------
/// N is the Number of payouts for the validator (including the validator)
/// Base Weight:
/// - Reward Destination Staked: 110 + 54.2 * N µs (Median Slopes)
/// - Reward Destination Controller (Creating): 120 + 41.95 * N µs (Median Slopes)
/// - Read: EraElectionStatus, CurrentEra, HistoryDepth, ErasValidatorReward,
/// ErasStakersClipped, ErasRewardPoints, ErasValidatorPrefs (8 items)
/// - Read Each: Bonded, Ledger, Payee, Locks, System Account (5 items)
/// - Write Each: System Account, Locks, Ledger (3 items)
+ 54 * WEIGHT_PER_MICROS * Weight::from(T::MaxNominatorRewardedPerValidator::get())
+ T::DbWeight::get().reads(7)
+ T::DbWeight::get().reads(5) * Weight::from(T::MaxNominatorRewardedPerValidator::get() + 1)
+ T::DbWeight::get().writes(3) * Weight::from(T::MaxNominatorRewardedPerValidator::get() + 1)
]
fn payout_stakers(origin, validator_stash: T::AccountId, era: EraIndex) -> DispatchResult {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
ensure_signed(origin)?;
Self::do_payout_stakers(validator_stash, era)
}
/// Rebond a portion of the stash scheduled to be unlocked.
///
/// The dispatch origin must be signed by the controller, and it can be only called when
/// [`EraElectionStatus`] is `Closed`.
///
/// - Time complexity: O(L), where L is unlocking chunks
/// - Bounded by `MAX_UNLOCKING_CHUNKS`.
/// - Storage changes: Can't increase storage, only decrease it.
/// ---------------
/// - Base Weight: 34.51 µs * .048 L µs
/// - DB Weight:
/// - Reads: EraElectionStatus, Ledger, Locks, [Origin Account]
/// - Writes: [Origin Account], Locks, Ledger
#[weight =
35 * WEIGHT_PER_MICROS
+ 50 * WEIGHT_PER_NANOS * (MAX_UNLOCKING_CHUNKS as Weight)
+ T::DbWeight::get().reads_writes(3, 2)
]
fn rebond(origin, #[compact] value: BalanceOf<T>) -> DispatchResultWithPostInfo {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockChunk);
let ledger = ledger.rebond(value);
Self::update_ledger(&controller, &ledger);
Ok(Some(
35 * WEIGHT_PER_MICROS
+ 50 * WEIGHT_PER_NANOS * (ledger.unlocking.len() as Weight)
+ T::DbWeight::get().reads_writes(3, 2)
).into())
/// Set `HistoryDepth` value. This function will delete any history information
/// when `HistoryDepth` is reduced.
///
/// Parameters:
/// - `new_history_depth`: The new history depth you would like to set.
/// - `era_items_deleted`: The number of items that will be deleted by this dispatch.
/// This should report all the storage items that will be deleted by clearing old
/// era history. Needed to report an accurate weight for the dispatch. Trusted by
/// `Root` to report an accurate number.
///
/// # <weight>
/// - E: Number of history depths removed, i.e. 10 -> 7 = 3
/// - Base Weight: 29.13 * E µs
/// - DB Weight:
/// - Reads: Current Era, History Depth
/// - Writes: History Depth
/// - Clear Prefix Each: Era Stakers, EraStakersClipped, ErasValidatorPrefs
/// - Writes Each: ErasValidatorReward, ErasRewardPoints, ErasTotalStake, ErasStartSessionIndex
/// # </weight>
#[weight = {
let items = Weight::from(*_era_items_deleted);
T::DbWeight::get().reads_writes(2, 1)
.saturating_add(T::DbWeight::get().reads_writes(items, items))
}]
fn set_history_depth(origin,
#[compact] new_history_depth: EraIndex,
#[compact] _era_items_deleted: u32,
) {
ensure_root(origin)?;
if let Some(current_era) = Self::current_era() {
HistoryDepth::mutate(|history_depth| {
let last_kept = current_era.checked_sub(*history_depth).unwrap_or(0);
let new_last_kept = current_era.checked_sub(new_history_depth).unwrap_or(0);
for era_index in last_kept..new_last_kept {
Self::clear_era_information(era_index);
}
*history_depth = new_history_depth
})
}
}
/// Remove all data structure concerning a staker/stash once its balance is zero.
/// This is essentially equivalent to `withdraw_unbonded` except it can be called by anyone
/// and the target `stash` must have no funds left.
///
/// This can be called from any origin.
///
/// - `stash`: The stash account to reap. Its balance must be zero.
///
/// # <weight>
/// Complexity: O(S) where S is the number of slashing spans on the account.
/// Base Weight: 75.94 + 2.396 * S µs
/// DB Weight:
/// - Reads: Stash Account, Bonded, Slashing Spans, Locks
/// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, Stash Account, Locks
/// - Writes Each: SpanSlash * S
/// # </weight>
#[weight = T::DbWeight::get().reads_writes(4, 7)
.saturating_add(76 * WEIGHT_PER_MICROS)
.saturating_add(
WEIGHT_PER_MICROS.saturating_mul(2).saturating_mul(Weight::from(*num_slashing_spans))
)
.saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans)))
// if slashing spans is non-zero, add 1 more write
.saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans).min(1)))
]
fn reap_stash(_origin, stash: T::AccountId, num_slashing_spans: u32) {
ensure!(T::Currency::total_balance(&stash).is_zero(), Error::<T>::FundedTarget);
Self::kill_stash(&stash, num_slashing_spans)?;
/// Submit an election result to the chain. If the solution:
///
/// 1. is valid.
/// 2. has a better score than a potentially existing solution on chain.
///
/// then, it will be _put_ on chain.
///
/// A solution consists of two pieces of data:
///
/// 1. `winners`: a flat vector of all the winners of the round.
/// 2. `assignments`: the compact version of an assignment vector that encodes the edge
/// weights.
///
/// Both of which may be computed using _phragmen_, or any other algorithm.
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
///
/// Additionally, the submitter must provide:
///
/// - The `score` that they claim their solution has.
///
/// Both validators and nominators will be represented by indices in the solution. The
/// indices should respect the corresponding types ([`ValidatorIndex`] and
/// [`NominatorIndex`]). Moreover, they should be valid when used to index into
/// [`SnapshotValidators`] and [`SnapshotNominators`]. Any invalid index will cause the
/// solution to be rejected. These two storage items are set during the election window and
/// may be used to determine the indices.
///
/// A solution is valid if:
///
/// 0. It is submitted when [`EraElectionStatus`] is `Open`.
/// 1. Its claimed score is equal to the score computed on-chain.
/// 2. Presents the correct number of winners.
/// 3. All indexes must be value according to the snapshot vectors. All edge values must
/// also be correct and should not overflow the granularity of the ratio type (i.e. 256
/// or billion).
/// 4. For each edge, all targets are actually nominated by the voter.
/// 5. Has correct self-votes.
///
/// A solutions score is consisted of 3 parameters:
///
/// 1. `min { support.total }` for each support of a winner. This value should be maximized.
/// 2. `sum { support.total }` for each support of a winner. This value should be minimized.
/// 3. `sum { support.total^2 }` for each support of a winner. This value should be
/// minimized (to ensure less variance)
///
/// # <weight>
/// See `crate::weight` module.
#[weight = weight::weight_for_submit_solution::<T>(winners, compact, size)]
pub fn submit_election_solution(
origin,
winners: Vec<ValidatorIndex>,
compact: CompactAssignments,
size: ElectionSize,
) -> DispatchResultWithPostInfo {
let _who = ensure_signed(origin)?;
Self::check_and_replace_solution(
winners,
ElectionCompute::Signed,
score,
era,
}
/// Unsigned version of `submit_election_solution`.
///
/// Note that this must pass the [`ValidateUnsigned`] check which only allows transactions
/// from the local node to be included. In other words, only the block author can include a
/// transaction in the block.
///
/// # <weight>
/// See `crate::weight` module.
/// # </weight>
#[weight = weight::weight_for_submit_solution::<T>(winners, compact, size)]
pub fn submit_election_solution_unsigned(
origin,
winners: Vec<ValidatorIndex>,
compact: CompactAssignments,
size: ElectionSize,
) -> DispatchResultWithPostInfo {
let adjustments = Self::check_and_replace_solution(
ElectionCompute::Unsigned,
score,
era,
).expect(
"An unsigned solution can only be submitted by validators; A validator should \
always produce correct solutions, else this block should not be imported, thus \
effectively depriving the validators from their authoring reward. Hence, this panic
is expected."
);
Ok(adjustments)
/// 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::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
/// internal impl of [`slashable_balance_of`] that returns [`VoteWeight`].
fn slashable_balance_of_vote_weight(stash: &T::AccountId) -> VoteWeight {
<T::CurrencyToVote as Convert<BalanceOf<T>, VoteWeight>>::convert(
}
/// Dump the list of validators and nominators into vectors and keep them on-chain.
///
/// This data is used to efficiently evaluate election results. returns `true` if the operation
/// is successful.
pub fn create_stakers_snapshot() -> (bool, Weight) {
let mut consumed_weight = 0;
let mut add_db_reads_writes = |reads, writes| {
consumed_weight += T::DbWeight::get().reads_writes(reads, writes);
};
let validators = <Validators<T>>::iter().map(|(v, _)| v).collect::<Vec<_>>();
let mut nominators = <Nominators<T>>::iter().map(|(n, _)| n).collect::<Vec<_>>();
let num_validators = validators.len();
let num_nominators = nominators.len();
add_db_reads_writes((num_validators + num_nominators) as Weight, 0);
if
num_validators > MAX_VALIDATORS ||
num_nominators.saturating_add(num_validators) > MAX_NOMINATORS
{
log!(
warn,
"💸 Snapshot size too big [{} <> {}][{} <> {}].",
num_validators,
MAX_VALIDATORS,
num_nominators,
MAX_NOMINATORS,
);
} else {
// all validators nominate themselves;
nominators.extend(validators.clone());
<SnapshotValidators<T>>::put(validators);
<SnapshotNominators<T>>::put(nominators);
add_db_reads_writes(0, 2);
(true, consumed_weight)
}
}
/// Clears both snapshots of stakers.
fn kill_stakers_snapshot() {
<SnapshotValidators<T>>::kill();
<SnapshotNominators<T>>::kill();
}
fn do_payout_stakers(
validator_stash: T::AccountId,
era: EraIndex,
) -> DispatchResult {
// Validate input data
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
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
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
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_approximation(
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_approximation(
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_approximation(
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. The lock will
/// will lock the entire funds except paying for further transactions.
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);
/// 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() {
Forcing::ForceNew => ForceEra::kill(),
Forcing::ForceAlways => (),
Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
// Either `ForceNone`, or `NotForcing && era_length < T::SessionsPerEra::get()`.
if era_length + 1 == T::SessionsPerEra::get() {
} else if era_length >= T::SessionsPerEra::get() {
// Should only happen when we are ready to trigger an era but we have ForceNone,
// otherwise previous arm would short circuit.
Self::close_election_window();
Self::new_era(session_index)
} else {
// Set initial era
Self::new_era(session_index)
/// Basic and cheap checks that we perform in validate unsigned, and in the execution.
///
/// State reads: ElectionState, CurrentEr, QueuedScore.
///
/// This function does weight refund in case of errors, which is based upon the fact that it is
/// called at the very beginning of the call site's function.
pub fn pre_dispatch_checks(score: ElectionScore, era: EraIndex) -> DispatchResultWithPostInfo {
// discard solutions that are not in-time
// check window open
ensure!(
Self::era_election_status().is_open(),
Error::<T>::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(1)),
if let Some(current_era) = Self::current_era() {
Error::<T>::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(2)),
)
}
// assume the given score is valid. Is it better than what we have on-chain, if we have any?
if let Some(queued_score) = Self::queued_score() {
ensure!(
Kian Paimani
committed
is_score_better(score, queued_score, T::MinSolutionScoreBump::get()),
Error::<T>::PhragmenWeakSubmission.with_weight(T::DbWeight::get().reads(3)),
}
/// Checks a given solution and if correct and improved, writes it on chain as the queued result
/// of the next round. This may be called by both a signed and an unsigned transaction.
pub fn check_and_replace_solution(
winners: Vec<ValidatorIndex>,
compact_assignments: CompactAssignments,
compute: ElectionCompute,
claimed_score: ElectionScore,
election_size: ElectionSize,
) -> DispatchResultWithPostInfo {
// Do the basic checks. era, claimed score and window open.
Self::pre_dispatch_checks(claimed_score, era)?;
// the weight that we will refund in case of a correct submission. We compute this now
// because the data needed for it will be consumed further down.
let adjusted_weight = weight::weight_for_correct_submit_solution::<T>(
&winners,
&compact_assignments,
&election_size,
);
// Check that the number of presented winners is sane. Most often we have more candidates
// than we need. Then it should be `Self::validator_count()`. Else it should be all the
let snapshot_validators_length = <SnapshotValidators<T>>::decode_len()
.map(|l| l as u32)
.ok_or_else(|| Error::<T>::SnapshotUnavailable)?;
// size of the solution must be correct.
ensure!(
snapshot_validators_length == u32::from(election_size.validators),
Error::<T>::PhragmenBogusElectionSize,
);
// check the winner length only here and when we know the length of the snapshot validators
// length.
let desired_winners = Self::validator_count().min(snapshot_validators_length);
ensure!(winners.len() as u32 == desired_winners, Error::<T>::PhragmenBogusWinnerCount);
let snapshot_nominators_len = <SnapshotNominators<T>>::decode_len()
.map(|l| l as u32)
.ok_or_else(|| Error::<T>::SnapshotUnavailable)?;
// rest of the size of the solution must be correct.
ensure!(
snapshot_nominators_len == election_size.nominators,
Error::<T>::PhragmenBogusElectionSize,
);
// decode snapshot validators.
let snapshot_validators = Self::snapshot_validators()
.ok_or(Error::<T>::SnapshotUnavailable)?;
// check if all winners were legit; this is rather cheap. Replace with accountId.
let winners = winners.into_iter().map(|widx| {
// NOTE: at the moment, since staking is explicitly blocking any offence until election
// is closed, we don't check here if the account id at `snapshot_validators[widx]` is
// actually a validator. If this ever changes, this loop needs to also check this.
snapshot_validators.get(widx as usize).cloned().ok_or(Error::<T>::PhragmenBogusWinner)
}).collect::<Result<Vec<T::AccountId>, Error<T>>>()?;
// decode the rest of the snapshot.
let snapshot_nominators = Self::snapshot_nominators()
.ok_or(Error::<T>::SnapshotUnavailable)?;
// helpers
let nominator_at = |i: NominatorIndex| -> Option<T::AccountId> {
snapshot_nominators.get(i as usize).cloned()
};
let validator_at = |i: ValidatorIndex| -> Option<T::AccountId> {
snapshot_validators.get(i as usize).cloned()
};
// un-compact.
let assignments = compact_assignments.into_assignment(
nominator_at,
validator_at,
).map_err(|e| {
// log the error since it is not propagated into the runtime error.
log!(warn, "💸 un-compacting solution failed due to {:?}", e);
Error::<T>::PhragmenBogusCompact
})?;
// check all nominators actually including the claimed vote. Also check correct self votes.
// Note that we assume all validators and nominators in `assignments` are properly bonded,
// because they are coming from the snapshot via a given index.
for Assignment { who, distribution } in assignments.iter() {
let is_validator = <Validators<T>>::contains_key(&who);
let maybe_nomination = Self::nominators(&who);
if !(maybe_nomination.is_some() ^ is_validator) {
// all of the indices must map to either a validator or a nominator. If this is ever
// not the case, then the locking system of staking is most likely faulty, or we
// have bigger problems.
log!(error, "💸 detected an error in the staking locking and snapshot.");
return Err(Error::<T>::PhragmenBogusNominator.into());
}
if !is_validator {
// a normal vote
let nomination = maybe_nomination.expect(
"exactly one of `maybe_validator` and `maybe_nomination.is_some` is true. \
is_validator is false; maybe_nomination is some; qed"
);
// NOTE: we don't really have to check here if the sum of all edges are the
// nominator correct. Un-compacting assures this by definition.
for (t, _) in distribution {
// each target in the provided distribution must be actually nominated by the
// nominator after the last non-zero slash.
if nomination.targets.iter().find(|&tt| tt == t).is_none() {
return Err(Error::<T>::PhragmenBogusNomination.into());
}
if <Self as Store>::SlashingSpans::get(&t).map_or(
false,
|spans| nomination.submitted_in < spans.last_nonzero_slash(),
) {
return Err(Error::<T>::PhragmenSlashedNomination.into());
}
}
} else {
// a self vote
ensure!(distribution.len() == 1, Error::<T>::PhragmenBogusSelfVote);
ensure!(distribution[0].0 == *who, Error::<T>::PhragmenBogusSelfVote);
// defensive only. A compact assignment of length one does NOT encode the weight and
// it is always created to be 100%.
ensure!(
distribution[0].1 == OffchainAccuracy::one(),
Error::<T>::PhragmenBogusSelfVote,
);
}
}
// convert into staked assignments.
let staked_assignments = sp_npos_elections::assignment_ratio_to_staked(
Self::slashable_balance_of_vote_weight,
);
// build the support map thereof in order to evaluate.
// OPTIMIZATION: loop to create the staked assignments but it would bloat the code. Okay for
// now as it does not add to the complexity order.
let (supports, num_error) = build_support_map::<T::AccountId>(
&winners,
&staked_assignments,
);
// This technically checks that all targets in all nominators were among the winners.
ensure!(num_error == 0, Error::<T>::PhragmenBogusEdge);
// Check if the score is the same as the claimed one.
let submitted_score = evaluate_support(&supports);
ensure!(submitted_score == claimed_score, Error::<T>::PhragmenBogusScore);
// At last, alles Ok. Exposures and store the result.
let exposures = Self::collect_exposure(supports);
log!(
info,
"💸 A better solution (with compute {:?} and score {:?}) has been validated and stored on chain.",
);
// write new results.
<QueuedElected<T>>::put(ElectionResult {
elected_stashes: winners,
compute,
exposures,
});
QueuedScore::put(submitted_score);
// emit event.
Self::deposit_event(RawEvent::SolutionStored(compute));
Ok(Some(adjusted_weight).into())
/// 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);
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;
let (validator_payout, max_payout) = inflation::compute_total_payout(
&T::RewardCurve::get(),
Self::eras_total_stake(&active_era.index),
T::Currency::total_issuance(),
// Duration of era; more than u64::MAX is rewarded as u64::MAX.
era_duration.saturated_into::<u64>(),
);
let rest = max_payout.saturating_sub(validator_payout);
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::select_and_update_validators(current_era);
/// Remove all the storage items associated with the election.
fn close_election_window() {
// Close window.
<EraElectionStatus<T>>::put(ElectionStatus::Closed);
// Kill snapshots.
Self::kill_stakers_snapshot();
// Don't track final session.
IsCurrentSessionFinal::put(false);
}
/// Select the new validator set at the end of the era.
///
/// Runs [`try_do_phragmen`] and updates the following storage items:
/// - [`EraElectionStatus`]: with `None`.
/// - [`ErasStakers`]: with the new staker set.
/// - [`ErasStakersClipped`].
/// - [`ErasValidatorPrefs`].
/// - [`ErasTotalStake`]: with the new total stake.
/// - [`SnapshotValidators`] and [`SnapshotNominators`] are both removed.
///
/// Internally, [`QueuedElected`], snapshots and [`QueuedScore`] are also consumed.
///
/// If the election has been successful, It passes the new set upwards.
///
/// This should only be called at the end of an era.
fn select_and_update_validators(current_era: EraIndex) -> Option<Vec<T::AccountId>> {
if let Some(ElectionResult::<T::AccountId, BalanceOf<T>> {
elected_stashes,
exposures,
compute,
}) = Self::try_do_election() {
// Totally close the election round and data.
Self::close_election_window();
// Populate Stakers and write slot stake.
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_unstable_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(compute));
log!(
info,
"💸 new validator set of size {:?} has been elected via {:?} for era {:?}",
elected_stashes.len(),
compute,
current_era,
);
Some(elected_stashes)
} else {
None
}
}
/// Select a new validator set from the assembled stakers and their role preferences. It tries
/// first to peek into [`QueuedElected`]. Otherwise, it runs a new on-chain phragmen election.
///
/// If [`QueuedElected`] and [`QueuedScore`] exists, they are both removed. No further storage
/// is updated.
fn try_do_election() -> Option<ElectionResult<T::AccountId, BalanceOf<T>>> {
// an election result from either a stored submission or locally executed one.
let next_result = <QueuedElected<T>>::take().or_else(||
Self::do_phragmen_with_post_processing::<ChainAccuracy>(ElectionCompute::OnChain)
);
// either way, kill this. We remove it here to make sure it always has the exact same
// lifetime as `QueuedElected`.
QueuedScore::kill();
next_result
/// Execute election and return the new results. The edge weights are processed into support
/// This is basically a wrapper around [`do_phragmen`] which translates
/// `PrimitiveElectionResult` into `ElectionResult`.
/// No storage item is updated.
fn do_phragmen_with_post_processing<Accuracy: PerThing>(compute: ElectionCompute)
-> Option<ElectionResult<T::AccountId, BalanceOf<T>>>
where
Accuracy: sp_std::ops::Mul<ExtendedBalance, Output=ExtendedBalance>,
ExtendedBalance: From<<Accuracy as PerThing>::Inner>,
{
if let Some(phragmen_result) = Self::do_phragmen::<Accuracy>() {
let elected_stashes = phragmen_result.winners.iter()
.map(|(s, _)| s.clone())
.collect::<Vec<T::AccountId>>();
let assignments = phragmen_result.assignments;
let staked_assignments = sp_npos_elections::assignment_ratio_to_staked(
Self::slashable_balance_of_vote_weight,
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
);
let (supports, _) = build_support_map::<T::AccountId>(
&elected_stashes,
&staked_assignments,
);
// collect exposures
let exposures = Self::collect_exposure(supports);
// In order to keep the property required by `on_session_ending` that we must return the
// new validator set even if it's the same as the old, as long as any underlying
// economic conditions have changed, we don't attempt to do any optimization where we
// compare against the prior set.
Some(ElectionResult::<T::AccountId, BalanceOf<T>> {
elected_stashes,
exposures,
compute,
})
} else {
// There were not enough candidates for even our minimal level of functionality. This is
// bad. We should probably disable all functionality except for block production and let
// the chain keep producing blocks until we can decide on a sufficiently substantial
// set. TODO: #2494
None
}
}
/// Execute phragmen election and return the new results. No post-processing is applied and the
/// raw edge weights are returned.
/// Self votes are added and nominations before the most recent slashing span are reaped.
fn do_phragmen<Accuracy: PerThing>() -> Option<PrimitiveElectionResult<T::AccountId, Accuracy>> {
let mut all_nominators: Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)> = Vec::new();