lib.rs 120 KiB
Newer Older
		slash_session: SessionIndex,
	) -> Result<(), ()> {
		if !Self::can_report() {
			return Err(())
		}

		let reward_proportion = SlashRewardFraction::get();
Gavin Wood's avatar
Gavin Wood committed
		let active_era = {
			let active_era = Self::active_era();
			if active_era.is_none() {
				// this offence need not be re-submitted.
				return Ok(())
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
			});

		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();

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

		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,
Gavin Wood's avatar
Gavin Wood committed
				now: active_era,
				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(
Gavin Wood's avatar
Gavin Wood committed
						active_era,
						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)
/// 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 Pre = ();

	fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) }

	fn validate(
		&self,
		_who: &Self::AccountId,
		call: &Self::Call,
		_info: &DispatchInfoOf<Self::Call>,
		_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();
				}
			}

			if let Err(e) = Self::pre_dispatch_checks(*score, *era) {
				debug::native::debug!(
					target: "staking",
					"validate unsigned failed due to {:?}.",
					e,
				);
				let invalid: InvalidTransaction = e.into();
				return invalid.into();
			}

			debug::native::debug!(
				target: "staking",
				"Validated an unsigned transaction from the local node for 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(_: &Self::Call) -> Result<(), TransactionValidityError> {
		// IMPORTANT NOTE: By default, a sane `pre-dispatch` should always do the same checks as
		// `validate_unsigned` and overriding this should be done with care. this module has only
		// one unsigned entry point, in which we call into `<Module<T>>::pre_dispatch_checks()`
		// which is all the important checks that we do in `validate_unsigned`. Hence, we can safely
		// override this to save some time.
		Ok(())
	}
}

/// 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])
}