Newer
Older
/// The total balance that can be slashed from a validator controller account as of
/// right now.
pub fn slashable_balance(who: &T::AccountId) -> BalanceOf<T> {
// MUTABLES (DANGEROUS)
/// 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,
T::BlockNumber::max_value(),
WithdrawReasons::except(WithdrawReason::TransactionPayment),
<Ledger<T>>::insert(controller, ledger);
}
/// Slash a given validator by a specific amount with given (historical) exposure.
///
/// Removes the slash from the validator's balance by preference,
/// and reduces the nominators' balance if needed.
///
/// Returns the resulting `NegativeImbalance` to allow distributing the slashed amount and
/// pushes an entry onto the slash journal.
fn slash_validator(
stash: &T::AccountId,
slash: BalanceOf<T>,
exposure: &Exposure<T::AccountId, BalanceOf<T>>,
journal: &mut Vec<SlashJournalEntry<T::AccountId, BalanceOf<T>>>,
) -> NegativeImbalanceOf<T> {
// The amount we are actually going to slash (can't be bigger than the validator's total
// exposure)
let slash = slash.min(exposure.total);
// limit what we'll slash of the stash's own to only what's in
// the exposure.
//
// note: this is fine only because we limit reports of the current era.
// otherwise, these funds may have already been slashed due to something
// reported from a prior era.
let already_slashed_own = journal.iter()
.filter(|entry| &entry.who == stash)
.map(|entry| entry.own_slash)
.fold(<BalanceOf<T>>::zero(), |a, c| a.saturating_add(c));
let own_remaining = exposure.own.saturating_sub(already_slashed_own);
// The amount we'll slash from the validator's stash directly.
let own_slash = own_remaining.min(slash);
let (mut imbalance, missing) = T::Currency::slash(stash, own_slash);
let own_slash = own_slash - missing;
// The amount remaining that we can't slash from the validator,
// that must be taken from the nominators.
let rest_slash = slash - own_slash;
if !rest_slash.is_zero() {
// The total to be slashed from the nominators.
let total = exposure.total - exposure.own;
for i in exposure.others.iter() {
let per_u64 = Perbill::from_rational_approximation(i.value, total);
// best effort - not much that can be done on fail.
imbalance.subsume(T::Currency::slash(&i.who, per_u64 * rest_slash).0)
journal.push(SlashJournalEntry {
who: stash.clone(),
own_slash: own_slash.clone(),
amount: slash,
});
// trigger the event
Self::deposit_event(
RawEvent::Slash(stash.clone(), slash)
);
imbalance
/// 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);
/// Reward a given validator by a specific amount. Add the reward to the validator's, and its
/// nominators' balance, pro-rata based on their exposure, after having removed the validator's
/// pre-payout cut.
fn reward_validator(stash: &T::AccountId, reward: BalanceOf<T>) -> PositiveImbalanceOf<T> {
let off_the_table = reward.min(Self::validators(stash).validator_payment);
let reward = reward - off_the_table;
let mut imbalance = <PositiveImbalanceOf<T>>::zero();
let validator_cut = if reward.is_zero() {
Zero::zero()
} else {
let exposure = Self::stakers(stash);
let total = exposure.total.max(One::one());
let per_u64 = Perbill::from_rational_approximation(i.value, total);
imbalance.maybe_subsume(Self::make_payout(&i.who, per_u64 * reward));
let per_u64 = Perbill::from_rational_approximation(exposure.own, total);
per_u64 * reward
imbalance.maybe_subsume(Self::make_payout(stash, validator_cut + off_the_table));
/// Session has just ended. Provide the validator set for the next session if it's an era-end, along
/// with the exposure of the prior validator set.
fn new_session(session_index: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
let era_length = session_index.checked_sub(Self::current_era_start_session_index()).unwrap_or(0);
match ForceEra::get() {
Forcing::ForceNew => ForceEra::kill(),
Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
let validators = T::SessionInterface::validators();
let prior = validators.into_iter()
.map(|v| { let e = Self::stakers(&v); (v, e) })
.collect();
Self::new_era(session_index).map(move |new| (new, prior))
}
/// The era has changed - enact new staking set.
///
/// NOTE: This always happens immediately before a session change to ensure that new validators
/// get a chance to set their session keys.
fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
let rewards = CurrentEraRewards::take();
let now = T::Time::now();
let previous_era_start = <CurrentEraStart<T>>::mutate(|v| {
});
let era_duration = now - previous_era_start;
if !era_duration.is_zero() {
let validators = Self::current_elected();
let validator_len: BalanceOf<T> = (validators.len() as u32).into();
let total_rewarded_stake = Self::slot_stake() * validator_len;
let total_payout = inflation::compute_total_payout(
total_rewarded_stake.clone(),
T::Currency::total_issuance(),
// Era of duration more than u32::MAX is rewarded as u32::MAX.
<BalanceOf<T>>::from(era_duration.saturated_into::<u32>()),
);
let mut total_imbalance = <PositiveImbalanceOf<T>>::zero();
let total_points = rewards.total;
for (v, points) in validators.iter().zip(rewards.rewards.into_iter()) {
if points != 0 {
let reward = multiply_by_rational(total_payout, points, total_points);
total_imbalance.subsume(Self::reward_validator(v, reward));
}
let total_reward = total_imbalance.peek();
Self::deposit_event(RawEvent::Reward(total_reward));
T::Reward::on_unbalanced(total_imbalance);
T::OnRewardMinted::on_dilution(total_reward, total_rewarded_stake);
let current_era = CurrentEra::mutate(|s| { *s += 1; *s });
// prune journal for last era.
<EraSlashJournal<T>>::remove(current_era - 1);
CurrentEraStartSessionIndex::mutate(|v| {
*v = start_session_index;
});
let bonding_duration = T::BondingDuration::get();
if current_era > bonding_duration {
let first_kept = current_era - bonding_duration;
BondedEras::mutate(|bonded| {
bonded.push((current_era, start_session_index));
// 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();
bonded.drain(..n_to_prune);
if let Some(&(_, first_session)) = bonded.first() {
T::SessionInterface::prune_historical_up_to(first_session);
}
})
}
let (_slot_stake, maybe_new_validators) = Self::select_validators();
fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
/// Select a new validator set from the assembled stakers and their role preferences.
///
/// Returns the new `SlotStake` value and a set of newly selected _stash_ IDs.
fn select_validators() -> (BalanceOf<T>, Option<Vec<T::AccountId>>) {
let maybe_phragmen_result = elect::<_, _, _, T::CurrencyToVote>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
<Validators<T>>::enumerate().map(|(who, _)| who).collect::<Vec<T::AccountId>>(),
<Nominators<T>>::enumerate().collect(),
Self::slashable_balance_of,
if let Some(phragmen_result) = maybe_phragmen_result {
let elected_stashes = phragmen_result.winners;
let mut assignments = phragmen_result.assignments;
let to_balance = |b: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let to_votes = |b: BalanceOf<T>|
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
// The return value of this is safe to be converted to u64.
// The original balance, `b` is within the scope of u64. It is just extended to u128
// to be properly multiplied by a ratio, which will lead to another value
// less than u64 for sure. The result can then be safely passed to `to_balance`.
// For now the backward convert is used. A simple `TryFrom<u64>` is also safe.
let ratio_of = |b, r: ExtendedBalance| r.saturating_mul(to_votes(b)) / ACCURACY;
// Initialize the support of each candidate.
let mut supports = <SupportMap<T::AccountId>>::new();
.map(|e| (e, to_votes(Self::slashable_balance_of(e))))
let item = Support { own: s, total: s, ..Default::default() };
supports.insert(e.clone(), item);
// convert the ratio in-place (and replace) to the balance but still in the extended
// balance type.
for (n, assignment) in assignments.iter_mut() {
for (c, r) in assignment.iter_mut() {
let nominator_stake = Self::slashable_balance_of(n);
let other_stake = ratio_of(nominator_stake, *r);
if let Some(support) = supports.get_mut(c) {
// This for an astronomically rich validator with more astronomically rich
// set of nominators, this might saturate.
support.total = support.total.saturating_add(other_stake);
support.others.push((n.clone(), other_stake));
// convert the ratio to extended balance
*r = other_stake;
if cfg!(feature = "equalize") {
let tolerance = 0_u128;
let iterations = 2_usize;
equalize::<_, _, T::CurrencyToVote, _>(
assignments,
&mut supports,
tolerance,
iterations,
Self::slashable_balance_of,
);
for v in Self::current_elected().iter() {
<Stakers<T>>::remove(v);
// Populate Stakers and figure out the minimum stake behind a slot.
let mut slot_stake = BalanceOf::<T>::max_value();
for (c, s) in supports.into_iter() {
// build `struct exposure` from `support`
let exposure = Exposure {
own: to_balance(s.own),
// This might reasonably saturate and we cannot do much about it. The sum of
// someone's stake might exceed the balance type if they have the maximum amount
// of balance and receive some support. This is super unlikely to happen, yet
// we simulate it in some tests.
total: to_balance(s.total),
others: s.others
.into_iter()
.map(|(who, value)| IndividualExposure { who, value: to_balance(value) })
.collect::<Vec<IndividualExposure<_, _>>>(),
};
if exposure.total < slot_stake {
slot_stake = exposure.total;
<Stakers<T>>::insert(c.clone(), exposure.clone());
<SlotStake<T>>::put(&slot_stake);
// Set the new validator set in sessions.
<CurrentElected<T>>::put(&elected_stashes);
// In order to keep the property required by `n_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.
(slot_stake, Some(elected_stashes))
} 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.
/// Remove all associated data of a stash account from the staking system.
///
/// This is called :
/// - Immediately when an account's balance falls below existential deposit.
/// - after a `withdraw_unbond()` call that frees all of a stash's bonded balance.
fn kill_stash(stash: &T::AccountId) {
if let Some(controller) = <Bonded<T>>::take(stash) {
<Ledger<T>>::remove(&controller);
}
<Payee<T>>::remove(stash);
<Validators<T>>::remove(stash);
<Nominators<T>>::remove(stash);
}
/// 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.
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
///
/// 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)>) {
CurrentEraRewards::mutate(|rewards| {
let current_elected = <Module<T>>::current_elected();
for (validator, points) in validators_points.into_iter() {
if let Some(index) = current_elected.iter()
.position(|elected| *elected == validator)
{
rewards.add_points_to_index(index as u32, points);
}
}
});
}
/// Add reward points to validators using their validator index.
///
/// For each element in the iterator the given number of points in u32 is added to the
/// validator, thus duplicates are handled.
pub fn reward_by_indices(validators_points: impl IntoIterator<Item = (u32, u32)>) {
// TODO: This can be optimised once #3302 is implemented.
let current_elected_len = <Module<T>>::current_elected().len() as u32;
CurrentEraRewards::mutate(|rewards| {
for (validator_index, points) in validators_points.into_iter() {
if validator_index < current_elected_len {
rewards.add_points_to_index(validator_index, points);
}
}
});
impl<T: Trait> session::OnSessionEnding<T::AccountId> for Module<T> {
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option<Vec<T::AccountId>> {
Self::new_session(start_session - 1).map(|(new, _old)| new)
}
}
impl<T: Trait> OnSessionEnding<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>> for Module<T> {
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
Self::new_session(start_session - 1)
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
fn on_free_balance_zero(stash: &T::AccountId) {
Self::kill_stash(stash);
/// 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: Trait + authorship::Trait> authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T> {
fn note_author(author: T::AccountId) {
}
fn note_uncle(author: T::AccountId, _age: T::BlockNumber) {
Self::reward_by_ids(vec![
(<authorship::Module<T>>::author(), 2),
(author, 1)
])
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
}
}
// This is guarantee not to overflow on whatever values.
// `num` must be inferior to `den` otherwise it will be reduce to `den`.
fn multiply_by_rational<N>(value: N, num: u32, den: u32) -> N
where N: SimpleArithmetic + Clone
{
let num = num.min(den);
let result_divisor_part = value.clone() / den.into() * num.into();
let result_remainder_part = {
let rem = value % den.into();
// Fits into u32 because den is u32 and remainder < den
let rem_u32 = rem.saturated_into::<u32>();
// Multiplication fits into u64 as both term are u32
let rem_part = rem_u32 as u64 * num as u64 / den as u64;
// Result fits into u32 as num < total_points
(rem_part as u32).into()
};
result_divisor_part + result_remainder_part
}
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
/// A `Convert` implementation that finds the stash of the given controller account,
/// if any.
pub struct StashOf<T>(rstd::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 current exposure of nominators
/// on that account.
pub struct ExposureOf<T>(rstd::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>>> {
Some(<Module<T>>::stakers(&validator))
}
}
impl<T: Trait> SelectInitialValidators<T::AccountId> for Module<T> {
fn select_initial_validators() -> Option<Vec<T::AccountId>> {
<Module<T>>::select_validators().1
}
}
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
/// This is intended to be used with `FilterHistoricalOffences`.
impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::IdentificationTuple<T>> for Module<T> where
T: session::Trait<ValidatorId = <T as system::Trait>::AccountId>,
T: session::historical::Trait<
FullIdentification = Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>,
FullIdentificationOf = ExposureOf<T>,
>,
T::SessionHandler: session::SessionHandler<<T as system::Trait>::AccountId>,
T::OnSessionEnding: session::OnSessionEnding<<T as system::Trait>::AccountId>,
T::SelectInitialValidators: session::SelectInitialValidators<<T as system::Trait>::AccountId>,
T::ValidatorIdOf: Convert<<T as system::Trait>::AccountId, Option<<T as system::Trait>::AccountId>>
{
fn on_offence(
offenders: &[OffenceDetails<T::AccountId, session::historical::IdentificationTuple<T>>],
slash_fraction: &[Perbill],
) {
let mut remaining_imbalance = <NegativeImbalanceOf<T>>::zero();
let slash_reward_fraction = SlashRewardFraction::get();
let era_now = Self::current_era();
let mut journal = Self::era_slash_journal(era_now);
for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
let stash = &details.offender.0;
let exposure = &details.offender.1;
// Skip if the validator is invulnerable.
if Self::invulnerables().contains(stash) {
continue
}
// calculate the amount to slash
let slash_exposure = exposure.total;
let amount = *slash_fraction * slash_exposure;
// in some cases `slash_fraction` can be just `0`,
// which means we are not slashing this time.
if amount.is_zero() {
continue;
}
// make sure to disable validator in next sessions
let _ = T::SessionInterface::disable_validator(stash);
// force a new era, to select a new validator set
ForceEra::put(Forcing::ForceNew);
// actually slash the validator
let slashed_amount = Self::slash_validator(stash, amount, exposure, &mut journal);
// distribute the rewards according to the slash
let slash_reward = slash_reward_fraction * slashed_amount.peek();
if !slash_reward.is_zero() && !details.reporters.is_empty() {
let (mut reward, rest) = slashed_amount.split(slash_reward);
// split the reward between reporters equally. Division cannot fail because
// we guarded against it in the enclosing if.
let per_reporter = reward.peek() / (details.reporters.len() as u32).into();
for reporter in &details.reporters {
let (reporter_reward, rest) = reward.split(per_reporter);
reward = rest;
T::Currency::resolve_creating(reporter, reporter_reward);
}
// The rest goes to the treasury.
remaining_imbalance.subsume(reward);
remaining_imbalance.subsume(rest);
} else {
remaining_imbalance.subsume(slashed_amount);
}
}
<EraSlashJournal<T>>::insert(era_now, journal);
// Handle the rest of imbalances
T::Slash::on_unbalanced(remaining_imbalance);
}
}
/// Filter historical offences out and only allow those from the current era.
pub struct FilterHistoricalOffences<T, R> {
_inner: rstd::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) {
// disallow any slashing from before the current era.
let offence_session = offence.session_index();
if offence_session >= <Module<T>>::current_era_start_session_index() {
R::report_offence(reporters, offence)
} else {
<Module<T>>::deposit_event(
RawEvent::OldSlashingReportDiscarded(offence_session)
)
}
}
}
/// Returns the currently elected validator set represented by their stash accounts.
pub struct CurrentElectedStashAccounts<T>(rstd::marker::PhantomData<T>);
impl<T: Trait> CurrentElectedSet<T::AccountId> for CurrentElectedStashAccounts<T> {
fn current_elected_set() -> Vec<T::AccountId> {
<Module<T>>::current_elected()
}
}