Newer
Older
era_reward_points.total,
);
let reward = validator_point_part.saturating_mul(
commission.saturating_add(
Perbill::one().saturating_sub(commission).saturating_mul(exposure_part)
)
);
if let Some(imbalance) = Self::make_payout(&ledger.stash, reward * era_payout) {
Self::deposit_event(RawEvent::Reward(who, 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|
T::Currency::deposit_into_existing(&controller, amount).ok()
),
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() => (),
_ => {
// not forcing, not a new era either. If final, set the flag.
if era_length + 1 >= T::SessionsPerEra::get() {
IsCurrentSessionFinal::put(true);
}
return None
},
// new era.
IsCurrentSessionFinal::put(false);
Self::new_era(session_index)
} else {
// Set initial era
Self::new_era(session_index)
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
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
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
/// Basic and cheap checks that we perform in validate unsigned, and in the execution.
pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> Result<(), Error<T>> {
// discard solutions that are not in-time
// check window open
ensure!(
Self::era_election_status().is_open(),
Error::<T>::PhragmenEarlySubmission,
);
// check current era.
if let Some(current_era) = Self::active_era().map(|e| e.index) {
ensure!(
current_era == era,
Error::<T>::PhragmenEarlySubmission,
)
}
// 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!(
is_score_better(queued_score, score),
Error::<T>::PhragmenWeakSubmission,
)
}
Ok(())
}
/// 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: PhragmenScore,
era: EraIndex,
) -> Result<(), Error<T>> {
// Do the basic checks. era, claimed score and window open.
Self::pre_dispatch_checks(claimed_score, era)?;
// Check that the number of presented winners is sane. Most often we have more candidates
// that we need. Then it should be Self::validator_count(). Else it should be all the
// candidates.
let snapshot_length = <SnapshotValidators<T>>::decode_len()
.map_err(|_| Error::<T>::SnapshotUnavailable)?;
let desired_winners = Self::validator_count().min(snapshot_length as u32);
ensure!(winners.len() as u32 == desired_winners, Error::<T>::PhragmenBogusWinnerCount);
// 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 = <Module<T>>::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.
debug::native::warn!(
target: "staking",
"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.
debug::native::error!(
target: "staking",
"detected an error in the staking locking and snapshot."
);
// abort.
return Err(Error::<T>::PhragmenBogusNominator);
}
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);
}
if <Self as Store>::SlashingSpans::get(&t).map_or(
false,
|spans| nomination.submitted_in < spans.last_nonzero_slash(),
) {
return Err(Error::<T>::PhragmenSlashedNomination);
}
}
} 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_phragmen::assignment_ratio_to_staked(
assignments,
Self::slashable_balance_of_extended,
);
// 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);
debug::native::info!(
target: "staking",
"A better solution (with compute {:?}) has been validated and stored on chain.",
compute,
);
// write new results.
<QueuedElected<T>>::put(ElectionResult {
elected_stashes: winners,
compute,
exposures,
});
QueuedScore::put(submitted_score);
Ok(())
}
/// 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;
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
let (total_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>(),
);
// Set ending era reward.
<ErasValidatorReward<T>>::insert(&active_era.index, total_payout);
}
}
/// 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);
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
/// 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_phragmen() {
// We have chosen the new validator set. Submission is no longer allowed.
<EraElectionStatus<T>>::put(ElectionStatus::Closed);
// kill the snapshots.
Self::kill_stakers_snapshot();
// 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);
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
// emit event
Self::deposit_event(RawEvent::StakingElection(compute));
debug::native::info!(
target: "staking",
"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 phragmen.
///
/// If [`QueuedElected`] and [`QueuedScore`] exists, they are both removed. No further storage
/// is updated.
fn try_do_phragmen() -> Option<ElectionResult<T::AccountId, BalanceOf<T>>> {
// a phragmen 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 phragmen and return the new results. The edge weights are processed into support
/// values.
///
/// This is basically a wrapper around [`do_phragmen`] which translates `PhragmenResult` into
/// `ElectionResult`.
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
/// 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_phragmen::assignment_ratio_to_staked(
assignments,
Self::slashable_balance_of_extended,
);
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 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.
/// No storage item is updated.
fn do_phragmen<Accuracy: PerThing>() -> Option<PhragmenResult<T::AccountId, Accuracy>> {
let mut all_nominators: Vec<(T::AccountId, BalanceOf<T>, Vec<T::AccountId>)> = Vec::new();
for (validator, _) in <Validators<T>>::iter() {
// append self vote
let self_vote = (validator.clone(), Self::slashable_balance_of(&validator), vec![validator.clone()]);
let nominator_votes = <Nominators<T>>::iter().map(|(nominator, nominations)| {
let Nominations { submitted_in, mut targets, suppressed: _ } = nominations;
// Filter out nomination targets which were nominated before the most recent
targets.retain(|stash| {
<Self as Store>::SlashingSpans::get(&stash).map_or(
true,
|spans| submitted_in >= spans.last_nonzero_slash(),
)
});
(nominator, targets)
});
all_nominators.extend(nominator_votes.map(|(n, ns)| {
let s = Self::slashable_balance_of(&n);
(n, s, ns)
}));
elect::<_, _, T::CurrencyToVote, Accuracy>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
all_validators,
all_nominators,
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
/// Consume a set of [`Supports`] from [`sp_phragmen`] and collect them into a [`Exposure`]
fn collect_exposure(supports: SupportMap<T::AccountId>) -> Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)> {
let to_balance = |e: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(e);
supports.into_iter().map(|(validator, support)| {
// build `struct exposure` from `support`
let mut others = Vec::new();
let mut own: BalanceOf<T> = Zero::zero();
let mut total: BalanceOf<T> = Zero::zero();
support.voters
.into_iter()
.map(|(nominator, weight)| (nominator, to_balance(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_unbond()` 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) -> DispatchResult {
let controller = Bonded::<T>::take(stash).ok_or(Error::<T>::NotStash)?;
<Ledger<T>>::remove(&controller);
<Payee<T>>::remove(stash);
<Validators<T>>::remove(stash);
<Nominators<T>>::remove(stash);
slashing::clear_stash_metadata::<T>(stash);
system::Module::<T>::dec_ref(stash);
Ok(())
}
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
/// 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),
}
/// 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: Trait> pallet_session::SessionManager<T::AccountId> for Module<T> {
fn new_session(new_index: SessionIndex) -> Option<Vec<T::AccountId>> {
Self::new_session(new_index)
}
fn start_session(start_index: SessionIndex) {
Self::start_session(start_index)
}
fn end_session(end_index: SessionIndex) {
Self::end_session(end_index)
impl<T: Trait> 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: Trait + pallet_authorship::Trait + pallet_session::Trait
{
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: Trait> 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: Trait> 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: Trait> OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>> for Module<T> where
T: pallet_session::Trait<ValidatorId = <T as frame_system::Trait>::AccountId>,
T: pallet_session::historical::Trait<
FullIdentification = Exposure<<T as frame_system::Trait>::AccountId, BalanceOf<T>>,
FullIdentificationOf = ExposureOf<T>,
>,
T::SessionHandler: pallet_session::SessionHandler<<T as frame_system::Trait>::AccountId>,
T::SessionManager: pallet_session::SessionManager<<T as frame_system::Trait>::AccountId>,
T::ValidatorIdOf: Convert<<T as frame_system::Trait>::AccountId, Option<<T as frame_system::Trait>::AccountId>>
offenders: &[OffenceDetails<T::AccountId, pallet_session::historical::IdentificationTuple<T>>],
slash_session: SessionIndex,
) -> Result<(), ()> {
if !Self::can_report() {
return Err(())
}
let reward_proportion = SlashRewardFraction::get();
let active_era = {
let active_era = Self::active_era();
if active_era.is_none() {
// this offence need not be re-submitted.
return Ok(())
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() {
None => return Ok(()), // before bonding period. defensive - should be filtered out.
Some(&(ref slash_era, _)) => *slash_era,
}
};
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| {
if earliest.is_none() {
}
});
let slash_defer_duration = T::SlashDeferDuration::get();
for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
let (stash, exposure) = &details.offender;
// Skip if the validator is invulnerable.
if Self::invulnerables().contains(stash) {
continue
}
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 {
unapplied.reporters = details.reporters.clone();
if slash_defer_duration == 0 {
// apply right away.
slashing::apply_slash::<T>(unapplied);
} else {
// defer to end of some `slash_defer_duration` from now.
<Self as Store>::UnappliedSlashes::mutate(
move |for_later| for_later.push(unapplied),
);
Ok(())
}
fn can_report() -> bool {
Self::era_election_status().is_closed()
/// 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
T: Trait,
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)
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
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
2992
2993
2994
2995
2996
2997
2998
2999
3000
/// Disallows any transactions that change the election result to be submitted after the election
/// window is open.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct LockStakingStatus<T>(sp_std::marker::PhantomData<T>);
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for LockStakingStatus<T> {
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "LockStakingStatus")
}
}
impl<T> LockStakingStatus<T> {
/// Create new `LockStakingStatus`.
pub fn new() -> Self {
Self(sp_std::marker::PhantomData)
}
}
impl<T> Default for LockStakingStatus<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Trait + Send + Sync> SignedExtension for LockStakingStatus<T> {
const IDENTIFIER: &'static str = "LockStakingStatus";
type AccountId = T::AccountId;
type Call = <T as Trait>::Call;
type AdditionalSigned = ();
type DispatchInfo = frame_support::weights::DispatchInfo;
type Pre = ();
fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) }
fn validate(
&self,
_who: &Self::AccountId,
call: &Self::Call,
_info: Self::DispatchInfo,
_len: usize,
) -> TransactionValidity {
if let Some(inner_call) = call.is_sub_type() {
if let ElectionStatus::Open(_) = <Module<T>>::era_election_status() {
match inner_call {
Call::<T>::set_payee(..) |
Call::<T>::set_controller(..) |
Call::<T>::set_validator_count(..) |
Call::<T>::force_no_eras(..) |
Call::<T>::force_new_era(..) |
Call::<T>::set_invulnerables(..) |
Call::<T>::force_unstake(..) |
Call::<T>::force_new_era_always(..) |
Call::<T>::cancel_deferred_slash(..) |
Call::<T>::set_history_depth(..) |
Call::<T>::reap_stash(..) |
Call::<T>::submit_election_solution(..) |
Call::<T>::submit_election_solution_unsigned(..) => {
// These calls are allowed. Nothing.
}
_ => {
return Err(InvalidTransaction::Stale.into());
}
}
}
}
Ok(Default::default())
}
}
impl<T: Trait> From<Error<T>> for InvalidTransaction {
fn from(e: Error<T>) -> Self {
match e {
<Error<T>>::PhragmenEarlySubmission => InvalidTransaction::Future,
_ => InvalidTransaction::Custom(e.as_u8()),
}
}
}
#[allow(deprecated)]
impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::submit_election_solution_unsigned(
_,
_,
score,
era,
) = call {
use offchain_election::DEFAULT_LONGEVITY;
// discard solution not coming from the local OCW.
match source {
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }
_ => {
debug::native::debug!(
target: "staking",
"rejecting unsigned transaction because it is not local/in-block."
);
return InvalidTransaction::Call.into();
}
}