lib.rs 132 KiB
Newer Older
			all_nominators.push(self_vote);
Gavin Wood's avatar
Gavin Wood committed
			all_validators.push(validator);
		}
		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
			// slashing span.
			targets.retain(|stash| {
				<Self as Store>::SlashingSpans::get(&stash).map_or(
					true,
					|spans| submitted_in >= spans.last_nonzero_slash(),
		all_nominators.extend(nominator_votes.map(|(n, ns)| {
			let s = weight_of(&n);
Kian Paimani's avatar
Kian Paimani committed
		if all_validators.len() < Self::minimum_validator_count().max(1) as usize {
			// If we don't have enough candidates, nothing to do.
			log!(
				warn,
				"💸 Chain does not have enough staking candidates to operate. Era {:?}.",
				Self::current_era()
			);
Kian Paimani's avatar
Kian Paimani committed
			None
		} else {
			seq_phragmen::<_, Accuracy>(
				Self::validator_count() as usize,
				all_validators,
				all_nominators,
				Some((iterations, 0)), // exactly run `iterations` rounds.
			)
			.map_err(|err| log!(error, "Call to seq-phragmen failed due to {:?}", err))
	/// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a [`Exposure`]
Kian Paimani's avatar
Kian Paimani committed
	fn collect_exposure(
		supports: SupportMap<T::AccountId>,
	) -> Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)> {
		let total_issuance = T::Currency::total_issuance();
		let to_currency = |e: 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<_, _>)>>()
Gav Wood's avatar
Gav Wood committed
	}
	/// Remove all associated data of a stash account from the staking system.
	///
	/// Assumes storage is upgraded before calling.
	///
Gavin Wood's avatar
Gavin Wood committed
	/// This is called:
	/// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance.
Gavin Wood's avatar
Gavin Wood committed
	/// - 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);
Gavin Wood's avatar
Gavin Wood committed
		<Ledger<T>>::remove(&controller);

		<Payee<T>>::remove(stash);
		<Validators<T>>::remove(stash);
		<Nominators<T>>::remove(stash);
		system::Module::<T>::dec_consumers(stash);
Gavin Wood's avatar
Gavin Wood committed

		Ok(())
	/// 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)
		})
	}

thiolliere's avatar
thiolliere committed
	/// 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.
thiolliere's avatar
thiolliere committed
	///
	/// 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`.
Gavin Wood's avatar
Gavin Wood committed
	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;
Gavin Wood's avatar
Gavin Wood committed
			});
		}
	}

	/// 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),
		}
	fn will_era_be_forced() -> bool {
		match ForceEra::get() {
			Forcing::ForceAlways | Forcing::ForceNew => true,
			Forcing::ForceNone | Forcing::NotForcing => false,
		}
	}

	#[cfg(feature = "runtime-benchmarks")]
	pub fn add_era_stakers(current_era: EraIndex, controller: T::AccountId, exposure: Exposure<T::AccountId, BalanceOf<T>>) {
		<ErasStakers<T>>::insert(&current_era, &controller, &exposure);
	}

	#[cfg(feature = "runtime-benchmarks")]
	pub fn put_election_status(status: ElectionStatus::<T::BlockNumber>) {
		<EraElectionStatus<T>>::put(status);
	}

	#[cfg(feature = "runtime-benchmarks")]
	pub fn set_slash_reward_fraction(fraction: Perbill) {
		SlashRewardFraction::put(fraction);
	}
Gavin Wood's avatar
Gavin Wood committed
/// 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>> {
		frame_support::debug::native::trace!(
			target: LOG_TARGET,
			"[{}] planning new_session({})",
			<frame_system::Module<T>>::block_number(),
			new_index
		);
Gavin Wood's avatar
Gavin Wood committed
		Self::new_session(new_index)
	}
	fn start_session(start_index: SessionIndex) {
		frame_support::debug::native::trace!(
			target: LOG_TARGET,
			"[{}] starting start_session({})",
			<frame_system::Module<T>>::block_number(),
			start_index
		);
Gavin Wood's avatar
Gavin Wood committed
		Self::start_session(start_index)
	}
	fn end_session(end_index: SessionIndex) {
		frame_support::debug::native::trace!(
			target: LOG_TARGET,
			"[{}] ending end_session({})",
			<frame_system::Module<T>>::block_number(),
			end_index
		);
Gavin Wood's avatar
Gavin Wood committed
		Self::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| {
Gavin Wood's avatar
Gavin Wood committed
			let current_era = Self::current_era()
				// Must be some as a new era has been created.
				.unwrap_or(0);

			validators.into_iter().map(|v| {
Gavin Wood's avatar
Gavin Wood committed
				let exposure = Self::eras_stakers(current_era, &v);
				(v, exposure)
			}).collect()
		})
	}
Gavin Wood's avatar
Gavin Wood committed
	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.
Gavin Wood's avatar
Gavin Wood committed
impl<T> pallet_authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T>
	where
		T: Config + pallet_authorship::Config + pallet_session::Config
Gavin Wood's avatar
Gavin Wood committed
{
	fn note_author(author: T::AccountId) {
Gavin Wood's avatar
Gavin Wood committed
		Self::reward_by_ids(vec![(author, 20)])
	}
	fn note_uncle(author: T::AccountId, _age: T::BlockNumber) {
thiolliere's avatar
thiolliere committed
		Self::reward_by_ids(vec![
			(<pallet_authorship::Module<T>>::author(), 2),
thiolliere's avatar
thiolliere committed
			(author, 1)
		])
/// 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)
	}
}

Gavin Wood's avatar
Gavin Wood committed
/// A typed conversion from stash account ID to the active exposure of nominators
Gavin Wood's avatar
Gavin Wood committed
///
/// 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>>> {
Gavin Wood's avatar
Gavin Wood committed
		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`.
	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::ValidatorIdOf: Convert<
		<T as frame_system::Config>::AccountId,
		Option<<T as frame_system::Config>::AccountId>,
		offenders: &[OffenceDetails<T::AccountId, pallet_session::historical::IdentificationTuple<T>>],
		slash_fraction: &[Perbill],
		slash_session: SessionIndex,
	) -> Result<Weight, ()> {
		if !Self::can_report() {
			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);
		};
Gavin Wood's avatar
Gavin Wood committed
		let active_era = {
			let active_era = Self::active_era();
			add_db_reads_writes(1, 0);
Gavin Wood's avatar
Gavin Wood committed
			if active_era.is_none() {
				// this offence need not be re-submitted.
				return Ok(consumed_weight)
Gavin Wood's avatar
Gavin Wood committed
			}
			active_era.expect("value checked not to be `None`; qed").index
Gavin Wood's avatar
Gavin Wood committed
		};
		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
			});
		add_db_reads_writes(1, 0);
Gavin Wood's avatar
Gavin Wood committed

		let window_start = active_era.saturating_sub(T::BondingDuration::get());
Gavin Wood's avatar
Gavin Wood committed
		// 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();
			add_db_reads_writes(1, 0);

			// 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() {
Gavin Wood's avatar
Gavin Wood committed
				*earliest = Some(active_era)
		add_db_reads_writes(1, 1);

		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,
Gavin Wood's avatar
Gavin Wood committed
				now: active_era,
				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(
Gavin Wood's avatar
Gavin Wood committed
						active_era,
						move |for_later| for_later.push(unapplied),
					);
					add_db_reads_writes(1, 1);
			} else {
				add_db_reads_writes(4 /* fetch_spans */, 5 /* kick_out_if_recent */)
		Ok(consumed_weight)
	}

	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
	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)
	}
#[allow(deprecated)]
impl<T: Config> 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 */ }
				_ => {
					log!(debug, "rejecting unsigned transaction because it is not local/in-block.");
					return InvalidTransaction::Call.into();
				}
			}

			if let Err(error_with_post_info) = Self::pre_dispatch_checks(*score, *era) {
					"💸 validate unsigned pre dispatch checks failed due to error #{:?}.",
				return invalid.into();
			log!(debug, "💸 validateUnsigned succeeded for a solution at era {}.", era);
			ValidTransaction::with_tag_prefix("StakingOffchain")
				// The higher the score[0], the better a solution is.
				.priority(T::UnsignedPriority::get().saturating_add(score[0].saturated_into()))
				// Defensive only. A single solution can exist in the pool per era. Each validator
				// will run OCW at most once per era, hence there should never exist more than one
				// transaction anyhow.
				.and_provides(era)
				// Note: this can be more accurate in the future. We do something like
				// `era_end_block - current_block` but that is not needed now as we eagerly run
				// offchain workers now and the above should be same as `T::ElectionLookahead`
				// without the need to query more storage in the validation phase. If we randomize
				// offchain worker, then we might re-consider this.
				.longevity(TryInto::<u64>::try_into(
						T::ElectionLookahead::get()).unwrap_or(DEFAULT_LONGEVITY)
				)
				// We don't propagate this. This can never the validated at a remote node.
				.propagate(false)
				.build()
		} else {
			InvalidTransaction::Call.into()
		}
	}

	fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
		if let Call::submit_election_solution_unsigned(
			_,
			_,
			score,
			era,
			_,
		) = call {
			// IMPORTANT NOTE: These checks are performed in the dispatch call itself, yet we need
			// to duplicate them here to prevent a block producer from putting a previously
			// validated, yet no longer valid solution on chain.
			// OPTIMISATION NOTE: we could skip this in the `submit_election_solution_unsigned`
			// since we already do it here. The signed version needs it though. Yer for now we keep
			// this duplicate check here so both signed and unsigned can use a singular
			// `check_and_replace_solution`.
			Self::pre_dispatch_checks(*score, *era)
				.map(|_| ())
				.map_err(to_invalid)
				.map_err(Into::into)
		} else {
			Err(InvalidTransaction::Call.into())
		}
/// 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])
}

/// convert a DispatchErrorWithPostInfo to a custom InvalidTransaction with the inner code being the
/// error number.
fn to_invalid(error_with_post_info: DispatchErrorWithPostInfo) -> InvalidTransaction {
	let error = error_with_post_info.error;
	let error_number = match error {
		DispatchError::Module { error, ..} => error,
		_ => 0,
	};
	InvalidTransaction::Custom(error_number)
}