runtime/disputes: slashing (#5535)

* disputes: runtime part of slashing

* disputes: reward winners

* disputes/slashing: validate_unsigned impl

* fmt

* disputes/slashing: report_dispute_lost_unsigned

* disputes/slashing: separate winners from losers and report winners

* disputes/slashing: refactoring

* impl HandleReports

* enable on Wenstend

* fmt

* add slashing pallet to the mock and test runtimes

* fix a bug in report_dispute_lost_unsigned

* fmt

* disputes: remove new_participants from summary

* disputes: remove punish_inconclusive

* impl SlashingHandler for Pallet for type-safety

* do not impl slashing::Config on mainnets yet

* teach spellcheck deduplication

* simplify interfaces and resolve some TODOs

* resolve some more TODOs

* minor typos

* move slashing into a folder

* remove unnecessary clone

* fix validator_set_count calculation

* introduce ValidatorSetCount

* store ValidatorSetCount

* fmt

* add the benchmark

* fmt

* unflatten slashing

* post-rebase fixes

* remove winners eagerly

* use real slashing weights for westend

* remove bench test suite

* zombinet: modify disputes test to check for an offence report

* zombinet: add a timeout

* add slashing pallet to Rococo

* zombienet: revert back to rococo-local

* fmt

* remove TODOs

* revert some accidental changes

* slashing is submodule of disputes

* Change the log target

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* wrap comments with rustfmt, more docs, constants

* use Defensive trait

* cargo update -p sp-io

* merge offence types, remove rewards for now

* cargo update -p sp-io

* benchmark fixes

* fmt

* unused var

* fix block_author impl

* ressurect RewardValidators trait

* remove outdated comment

* more module docs

* introduce BenchmarkingConfig

* typo fix

* teach spellcheck unapplied

* use Weight::new()

* fix mocking rewards

* use RefTimeWeight

* ".git/.scripts/bench-bot.sh" runtime westend-dev runtime_parachains::disputes::slashing

* refactor maybe_identify_validators

* no more ticket in disguise

* remove outdated comments

* lower against valid to 0.1%

* bump zombienet version for debug

* use from_perthousand

* post-merge fixes

* another day, another Weight changes

* Revert "bump zombienet version for debug"

This reverts commit 0d9978711f8ec9a746a5e1c45e8ffbe7c75e7b5c.

* do not reward block authors

* fix outdated comment

* use Pays from frame_support::dispatch::Pays

* add timeout to is up

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: command-bot <>
Co-authored-by: Javier Viola <javier@parity.io>
Co-authored-by: Javier Viola <pepoviola@gmail.com>
This commit is contained in:
Andronik
2022-09-20 12:56:55 +02:00
committed by GitHub
parent b6ed41a464
commit 18c077a818
18 changed files with 1105 additions and 73 deletions
+1 -1
View File
@@ -1173,7 +1173,7 @@ impl parachains_initializer::Config for Runtime {
impl parachains_disputes::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints<Runtime>;
type PunishValidators = ();
type SlashingHandler = ();
type WeightInfo = weights::runtime_parachains_disputes::WeightInfo<Runtime>;
}
+1
View File
@@ -95,6 +95,7 @@ runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-staking/runtime-benchmarks",
"primitives/runtime-benchmarks",
"static_assertions",
"sp-application-crypto",
+49 -29
View File
@@ -39,6 +39,7 @@ use sp_std::{cmp::Ordering, prelude::*};
#[allow(unused_imports)]
pub(crate) use self::tests::run_to_block;
pub mod slashing;
#[cfg(test)]
mod tests;
@@ -73,35 +74,55 @@ impl RewardValidators for () {
}
/// Punishment hooks for disputes.
pub trait PunishValidators {
/// Punish a series of validators who were for an invalid parablock. This is expected to be a major
/// punishment.
pub trait SlashingHandler<BlockNumber> {
/// Punish a series of validators who were for an invalid parablock. This is
/// expected to be a major punishment.
fn punish_for_invalid(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
);
/// Punish a series of validators who were against a valid parablock. This is expected to be a minor
/// punishment.
/// Punish a series of validators who were against a valid parablock. This
/// is expected to be a minor punishment.
fn punish_against_valid(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
);
/// Punish a series of validators who were part of a dispute which never concluded. This is expected
/// to be a minor punishment.
fn punish_inconclusive(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
);
/// Called by the initializer to initialize the slashing pallet.
fn initializer_initialize(now: BlockNumber) -> Weight;
/// Called by the initializer to finalize the slashing pallet.
fn initializer_finalize();
/// Called by the initializer to note that a new session has started.
fn initializer_on_new_session(session_index: SessionIndex);
}
impl PunishValidators for () {
fn punish_for_invalid(_: SessionIndex, _: impl IntoIterator<Item = ValidatorIndex>) {}
impl<BlockNumber> SlashingHandler<BlockNumber> for () {
fn punish_for_invalid(
_: SessionIndex,
_: CandidateHash,
_: impl IntoIterator<Item = ValidatorIndex>,
) {
}
fn punish_against_valid(_: SessionIndex, _: impl IntoIterator<Item = ValidatorIndex>) {}
fn punish_against_valid(
_: SessionIndex,
_: CandidateHash,
_: impl IntoIterator<Item = ValidatorIndex>,
) {
}
fn punish_inconclusive(_: SessionIndex, _: impl IntoIterator<Item = ValidatorIndex>) {}
fn initializer_initialize(_now: BlockNumber) -> Weight {
Weight::zero()
}
fn initializer_finalize() {}
fn initializer_on_new_session(_: SessionIndex) {}
}
/// Binary discriminator to determine if the expensive signature
@@ -412,7 +433,7 @@ pub mod pallet {
pub trait Config: frame_system::Config + configuration::Config + session_info::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RewardValidators: RewardValidators;
type PunishValidators: PunishValidators;
type SlashingHandler: SlashingHandler<Self::BlockNumber>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
@@ -831,14 +852,7 @@ impl<T: Config> Pallet<T> {
// it would be unexpected for any change here to occur when the dispute has not concluded
// in time, as a dispute guaranteed to have at least one honest participant should
// conclude quickly.
let participating = decrement_spam(spam_slots, &dispute);
// Slight punishment as these validators have failed to make data available to
// others in a timely manner.
T::PunishValidators::punish_inconclusive(
session_index,
participating.iter_ones().map(|i| ValidatorIndex(i as _)),
);
let _participating = decrement_spam(spam_slots, &dispute);
});
weight += T::DbWeight::get().reads_writes(2, 2);
@@ -1187,7 +1201,9 @@ impl<T: Config> Pallet<T> {
Error::<T>::SingleSidedDispute,
);
let DisputeStatementSet { session, candidate_hash, .. } = set.clone();
let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
let session = *session;
let candidate_hash = *candidate_hash;
// we can omit spam slot checks, `fn filter_disputes_data` is
// always called before calling this `fn`.
@@ -1227,10 +1243,14 @@ impl<T: Config> Pallet<T> {
// Slash participants on a losing side.
{
// a valid candidate, according to 2/3. Punish those on the 'against' side.
T::PunishValidators::punish_against_valid(session, summary.slash_against);
T::SlashingHandler::punish_against_valid(
session,
candidate_hash,
summary.slash_against,
);
// an invalid candidate, according to 2/3. Punish those on the 'for' side.
T::PunishValidators::punish_for_invalid(session, summary.slash_for);
T::SlashingHandler::punish_for_invalid(session, candidate_hash, summary.slash_for);
}
<Disputes<T>>::insert(&session, &candidate_hash, &summary.state);
@@ -0,0 +1,739 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Dispute slashing pallet.
//!
//! Once a dispute is concluded, we want to slash validators
//! who were on the wrong side of the dispute. The slashing amount
//! depends on whether the candidate was valid (small) or invalid (big).
//! In addition to that, we might want to kick out the validators from the
//! active set.
//!
//! The `offences` pallet from Substrate provides us with a way to do both.
//! Currently, the interface expects us to provide staking information
//! including nominator exposure in order to submit an offence.
//!
//! Normally, we'd able to fetch this information from the runtime as soon as
//! the dispute is concluded. This is also what `im-online` pallet does.
//! However, since a dispute can conclude several sessions after the candidate
//! was backed (see `dispute_period` in `HostConfiguration`), we can't rely on
//! this information be available in the context of the current block. The
//! `babe` and `grandpa` equivocation handlers also have to deal
//! with this problem.
//!
//! Our implementation looks like a hybrid of `im-online` and `grandpa`
//! equivocation handlers. Meaning, we submit an `offence` for the concluded
//! disputes about the current session candidate directly from the runtime.
//! If, however, the dispute is about a past session, we record unapplied
//! slashes on chain, without `FullIdentification` of the offenders.
//! Later on, a block producer can submit an unsigned transaction with
//! `KeyOwnershipProof` of an offender and submit it to the runtime
//! to produce an offence.
use crate::{disputes, initializer::ValidatorSetCount, session_info::IdentificationTuple};
use frame_support::{
dispatch::Pays,
traits::{Defensive, Get, KeyOwnerProofSystem, ValidatorSet, ValidatorSetWithIdentification},
weights::Weight,
};
use parity_scale_codec::{Decode, Encode};
use primitives::v2::{CandidateHash, SessionIndex, ValidatorId, ValidatorIndex};
use scale_info::TypeInfo;
use sp_runtime::{
traits::Convert,
transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
TransactionValidityError, ValidTransaction,
},
DispatchResult, KeyTypeId, Perbill, RuntimeDebug,
};
use sp_session::{GetSessionNumber, GetValidatorCount};
use sp_staking::offence::{DisableStrategy, Kind, Offence, OffenceError, ReportOffence};
use sp_std::{
collections::btree_map::{BTreeMap, Entry},
prelude::*,
};
const LOG_TARGET: &str = "runtime::parachains::slashing";
// These are constants, but we want to make them configurable
// via `HostConfiguration` in the future.
const SLASH_FOR_INVALID: Perbill = Perbill::from_percent(100);
const SLASH_AGAINST_VALID: Perbill = Perbill::from_perthousand(1);
const DEFENSIVE_PROOF: &'static str = "disputes module should bail on old session";
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
/// The benchmarking configuration.
pub trait BenchmarkingConfiguration {
const MAX_VALIDATORS: u32;
}
pub struct BenchConfig<const M: u32>;
impl<const M: u32> BenchmarkingConfiguration for BenchConfig<M> {
const MAX_VALIDATORS: u32 = M;
}
/// Timeslots should uniquely identify offences and are used for the offence
/// deduplication.
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
pub struct DisputesTimeSlot {
// The order of these matters for `derive(Ord)`.
session_index: SessionIndex,
candidate_hash: CandidateHash,
}
impl DisputesTimeSlot {
pub fn new(session_index: SessionIndex, candidate_hash: CandidateHash) -> Self {
Self { session_index, candidate_hash }
}
}
/// An offence that is filed when a series of validators lost a dispute.
#[derive(RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))]
pub struct SlashingOffence<KeyOwnerIdentification> {
/// The size of the validator set in that session.
pub validator_set_count: ValidatorSetCount,
/// Should be unique per dispute.
pub time_slot: DisputesTimeSlot,
/// Staking information about the validators that lost the dispute
/// needed for slashing.
pub offenders: Vec<KeyOwnerIdentification>,
/// What fraction of the total exposure that should be slashed for
/// this offence.
pub slash_fraction: Perbill,
/// Whether the candidate was valid or invalid.
pub kind: SlashingOffenceKind,
}
impl<Offender> Offence<Offender> for SlashingOffence<Offender>
where
Offender: Clone,
{
const ID: Kind = *b"disputes:slashin";
type TimeSlot = DisputesTimeSlot;
fn offenders(&self) -> Vec<Offender> {
self.offenders.clone()
}
fn session_index(&self) -> SessionIndex {
self.time_slot.session_index
}
fn validator_set_count(&self) -> ValidatorSetCount {
self.validator_set_count
}
fn time_slot(&self) -> Self::TimeSlot {
self.time_slot.clone()
}
fn disable_strategy(&self) -> DisableStrategy {
match self.kind {
SlashingOffenceKind::ForInvalid => DisableStrategy::Always,
// in the future we might change it based on number of disputes initiated:
// <https://github.com/paritytech/polkadot/issues/5946>
SlashingOffenceKind::AgainstValid => DisableStrategy::Never,
}
}
fn slash_fraction(&self, _offenders: u32) -> Perbill {
self.slash_fraction
}
}
impl<KeyOwnerIdentification> SlashingOffence<KeyOwnerIdentification> {
fn new(
session_index: SessionIndex,
candidate_hash: CandidateHash,
validator_set_count: ValidatorSetCount,
offenders: Vec<KeyOwnerIdentification>,
kind: SlashingOffenceKind,
) -> Self {
let time_slot = DisputesTimeSlot::new(session_index, candidate_hash);
let slash_fraction = match kind {
SlashingOffenceKind::ForInvalid => SLASH_FOR_INVALID,
SlashingOffenceKind::AgainstValid => SLASH_AGAINST_VALID,
};
Self { time_slot, validator_set_count, offenders, slash_fraction, kind }
}
}
/// This type implements `SlashingHandler`.
pub struct SlashValidatorsForDisputes<C> {
_phantom: sp_std::marker::PhantomData<C>,
}
impl<C> Default for SlashValidatorsForDisputes<C> {
fn default() -> Self {
Self { _phantom: Default::default() }
}
}
impl<T> SlashValidatorsForDisputes<Pallet<T>>
where
T: Config<KeyOwnerIdentification = IdentificationTuple<T>>,
{
/// If in the current session, returns the identified validators. `None`
/// otherwise.
fn maybe_identify_validators(
session_index: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
) -> Option<Vec<IdentificationTuple<T>>> {
// We use `ValidatorSet::session_index` and not
// `shared::Pallet<T>::session_index()` because at the first block of a new era,
// the `IdentificationOf` of a validator in the previous session might be
// missing, while `shared` pallet would return the same session index as being
// updated at the end of the block.
let current_session = T::ValidatorSet::session_index();
if session_index == current_session {
let account_keys = crate::session_info::Pallet::<T>::account_keys(session_index);
let account_ids = account_keys.defensive_unwrap_or_default();
let fully_identified = validators
.into_iter()
.flat_map(|i| account_ids.get(i.0 as usize).cloned())
.filter_map(|id| {
<T::ValidatorSet as ValidatorSetWithIdentification<T::AccountId>>::IdentificationOf::convert(
id.clone()
).map(|full_id| (id, full_id))
})
.collect::<Vec<IdentificationTuple<T>>>();
return Some(fully_identified)
}
None
}
fn do_punish(
session_index: SessionIndex,
candidate_hash: CandidateHash,
kind: SlashingOffenceKind,
losers: impl IntoIterator<Item = ValidatorIndex>,
) {
let losers: Vec<ValidatorIndex> = losers.into_iter().collect();
if losers.is_empty() {
// Nothing to do
return
}
let session_info = crate::session_info::Pallet::<T>::session_info(session_index);
let session_info = match session_info.defensive_proof(DEFENSIVE_PROOF) {
Some(info) => info,
None => return,
};
let maybe = Self::maybe_identify_validators(session_index, losers.iter().cloned());
if let Some(offenders) = maybe {
let validator_set_count = session_info.discovery_keys.len() as ValidatorSetCount;
let offence = SlashingOffence::new(
session_index,
candidate_hash,
validator_set_count,
offenders,
kind,
);
// This is the first time we report an offence for this dispute,
// so it is not a duplicate.
let _ = T::HandleReports::report_offence(offence);
return
}
let keys = losers
.into_iter()
.filter_map(|i| session_info.validators.get(i.0 as usize).cloned().map(|id| (i, id)))
.collect();
let unapplied = PendingSlashes { keys, kind };
<UnappliedSlashes<T>>::insert(session_index, candidate_hash, unapplied);
}
}
impl<T> disputes::SlashingHandler<T::BlockNumber> for SlashValidatorsForDisputes<Pallet<T>>
where
T: Config<KeyOwnerIdentification = IdentificationTuple<T>>,
{
fn punish_for_invalid(
session_index: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
) {
let kind = SlashingOffenceKind::ForInvalid;
Self::do_punish(session_index, candidate_hash, kind, losers);
}
fn punish_against_valid(
session_index: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
) {
let kind = SlashingOffenceKind::AgainstValid;
Self::do_punish(session_index, candidate_hash, kind, losers);
}
fn initializer_initialize(now: T::BlockNumber) -> Weight {
Pallet::<T>::initializer_initialize(now)
}
fn initializer_finalize() {
Pallet::<T>::initializer_finalize()
}
fn initializer_on_new_session(session_index: SessionIndex) {
Pallet::<T>::initializer_on_new_session(session_index)
}
}
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum SlashingOffenceKind {
#[codec(index = 0)]
ForInvalid,
#[codec(index = 1)]
AgainstValid,
}
/// We store most of the information about a lost dispute on chain. This struct
/// is required to identify and verify it.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct DisputeProof {
/// Time slot when the dispute occured.
pub time_slot: DisputesTimeSlot,
/// The dispute outcome.
pub kind: SlashingOffenceKind,
/// The index of the validator who lost a dispute.
pub validator_index: ValidatorIndex,
/// The parachain session key of the validator.
pub validator_id: ValidatorId,
}
/// Slashes that are waiting to be applied once we have validator key
/// identification.
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct PendingSlashes {
/// Indices and keys of the validators who lost a dispute and are pending
/// slashes.
pub keys: BTreeMap<ValidatorIndex, ValidatorId>,
/// The dispute outcome.
pub kind: SlashingOffenceKind,
}
/// A trait that defines methods to report an offence (after the slashing report
/// has been validated) and for submitting a transaction to report a slash (from
/// an offchain context).
pub trait HandleReports<T: Config> {
/// The longevity, in blocks, that the offence report is valid for. When
/// using the staking pallet this should be equal to the bonding duration
/// (in blocks, not eras).
type ReportLongevity: Get<u64>;
/// Report an offence.
fn report_offence(
offence: SlashingOffence<T::KeyOwnerIdentification>,
) -> Result<(), OffenceError>;
/// Returns true if the offenders at the given time slot has already been
/// reported.
fn is_known_offence(
offenders: &[T::KeyOwnerIdentification],
time_slot: &DisputesTimeSlot,
) -> bool;
/// Create and dispatch a slashing report extrinsic.
/// This should be called offchain.
fn submit_unsigned_slashing_report(
dispute_proof: DisputeProof,
key_owner_proof: T::KeyOwnerProof,
) -> DispatchResult;
}
impl<T: Config> HandleReports<T> for () {
type ReportLongevity = ();
fn report_offence(
_offence: SlashingOffence<T::KeyOwnerIdentification>,
) -> Result<(), OffenceError> {
Ok(())
}
fn is_known_offence(
_offenders: &[T::KeyOwnerIdentification],
_time_slot: &DisputesTimeSlot,
) -> bool {
true
}
fn submit_unsigned_slashing_report(
_dispute_proof: DisputeProof,
_key_owner_proof: T::KeyOwnerProof,
) -> DispatchResult {
Ok(())
}
}
pub trait WeightInfo {
fn report_dispute_lost(validator_count: ValidatorSetCount) -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn report_dispute_lost(_validator_count: ValidatorSetCount) -> Weight {
Weight::zero()
}
}
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config + crate::disputes::Config {
/// The proof of key ownership, used for validating slashing reports.
/// The proof must include the session index and validator count of the
/// session at which the offence occurred.
type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount;
/// The identification of a key owner, used when reporting slashes.
type KeyOwnerIdentification: Parameter;
/// A system for proving ownership of keys, i.e. that a given key was
/// part of a validator set, needed for validating slashing reports.
type KeyOwnerProofSystem: KeyOwnerProofSystem<
(KeyTypeId, ValidatorId),
Proof = Self::KeyOwnerProof,
IdentificationTuple = Self::KeyOwnerIdentification,
>;
/// The slashing report handling subsystem, defines methods to report an
/// offence (after the slashing report has been validated) and for
/// submitting a transaction to report a slash (from an offchain
/// context). NOTE: when enabling slashing report handling (i.e. this
/// type isn't set to `()`) you must use this pallet's
/// `ValidateUnsigned` in the runtime definition.
type HandleReports: HandleReports<Self>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// Benchmarking configuration.
type BenchmarkingConfig: BenchmarkingConfiguration;
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
/// Validators pending dispute slashes.
#[pallet::storage]
pub(super) type UnappliedSlashes<T> = StorageDoubleMap<
_,
Twox64Concat,
SessionIndex,
Blake2_128Concat,
CandidateHash,
PendingSlashes,
>;
/// `ValidatorSetCount` per session.
#[pallet::storage]
pub(super) type ValidatorSetCounts<T> =
StorageMap<_, Twox64Concat, SessionIndex, ValidatorSetCount>;
#[pallet::error]
pub enum Error<T> {
/// The key ownership proof is invalid.
InvalidKeyOwnershipProof,
/// The session index is too old or invalid.
InvalidSessionIndex,
/// The candidate hash is invalid.
InvalidCandidateHash,
/// There is no pending slash for the given validator index and time
/// slot.
InvalidValidatorIndex,
/// The validator index does not match the validator id.
ValidatorIndexIdMismatch,
/// The given slashing report is valid but already previously reported.
DuplicateSlashingReport,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(<T as Config>::WeightInfo::report_dispute_lost(
key_owner_proof.validator_count()
))]
pub fn report_dispute_lost_unsigned(
origin: OriginFor<T>,
// box to decrease the size of the call
dispute_proof: Box<DisputeProof>,
key_owner_proof: T::KeyOwnerProof,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
// check the membership proof to extract the offender's id
let key = (primitives::v2::PARACHAIN_KEY_TYPE_ID, dispute_proof.validator_id.clone());
let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof)
.ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
let session_index = dispute_proof.time_slot.session_index;
let validator_set_count = crate::session_info::Pallet::<T>::session_info(session_index)
.ok_or(Error::<T>::InvalidSessionIndex)?
.discovery_keys
.len() as ValidatorSetCount;
// check that there is a pending slash for the given
// validator index and candidate hash
let candidate_hash = dispute_proof.time_slot.candidate_hash;
let try_remove = |v: &mut Option<PendingSlashes>| -> Result<(), DispatchError> {
let pending = v.as_mut().ok_or(Error::<T>::InvalidCandidateHash)?;
if pending.kind != dispute_proof.kind {
return Err(Error::<T>::InvalidCandidateHash.into())
}
match pending.keys.entry(dispute_proof.validator_index) {
Entry::Vacant(_) => return Err(Error::<T>::InvalidValidatorIndex.into()),
// check that `validator_index` matches `validator_id`
Entry::Occupied(e) if e.get() != &dispute_proof.validator_id =>
return Err(Error::<T>::ValidatorIndexIdMismatch.into()),
Entry::Occupied(e) => {
e.remove(); // the report is correct
},
}
// if the last validator is slashed for this dispute, clean up the storage
if pending.keys.is_empty() {
*v = None;
}
Ok(())
};
<UnappliedSlashes<T>>::try_mutate_exists(&session_index, &candidate_hash, try_remove)?;
let offence = SlashingOffence::new(
session_index,
candidate_hash,
validator_set_count,
vec![offender],
dispute_proof.kind,
);
<T::HandleReports as HandleReports<T>>::report_offence(offence)
.map_err(|_| Error::<T>::DuplicateSlashingReport)?;
Ok(Pays::No.into())
}
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
Self::validate_unsigned(source, call)
}
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
Self::pre_dispatch(call)
}
}
}
impl<T: Config> Pallet<T> {
/// Called by the initializer to initialize the disputes slashing module.
fn initializer_initialize(_now: T::BlockNumber) -> Weight {
Weight::zero()
}
/// Called by the initializer to finalize the disputes slashing pallet.
fn initializer_finalize() {}
/// Called by the initializer to note a new session in the disputes slashing
/// pallet.
fn initializer_on_new_session(session_index: SessionIndex) {
// This should be small, as disputes are limited by spam slots, so no limit is
// fine.
const REMOVE_LIMIT: u32 = u32::MAX;
let config = <crate::configuration::Pallet<T>>::config();
if session_index <= config.dispute_period + 1 {
return
}
let old_session = session_index - config.dispute_period - 1;
let _ = <UnappliedSlashes<T>>::clear_prefix(old_session, REMOVE_LIMIT, None);
}
}
/// Methods for the `ValidateUnsigned` implementation:
///
/// It restricts calls to `report_dispute_lost_unsigned` to local calls (i.e.
/// extrinsics generated on this node) or that already in a block. This
/// guarantees that only block authors can include unsigned slashing reports.
impl<T: Config> Pallet<T> {
pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
if let Call::report_dispute_lost_unsigned { dispute_proof, key_owner_proof } = call {
// discard slashing report not coming from the local node
match source {
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
_ => {
log::warn!(
target: LOG_TARGET,
"rejecting unsigned transaction because it is not local/in-block."
);
return InvalidTransaction::Call.into()
},
}
// check report staleness
is_known_offence::<T>(dispute_proof, key_owner_proof)?;
let longevity = <T::HandleReports as HandleReports<T>>::ReportLongevity::get();
let tag_prefix = match dispute_proof.kind {
SlashingOffenceKind::ForInvalid => "DisputeForInvalid",
SlashingOffenceKind::AgainstValid => "DisputeAgainstValid",
};
ValidTransaction::with_tag_prefix(tag_prefix)
// We assign the maximum priority for any report.
.priority(TransactionPriority::max_value())
// Only one report for the same offender at the same slot.
.and_provides((dispute_proof.time_slot.clone(), dispute_proof.validator_id.clone()))
.longevity(longevity)
// We don't propagate this. This can never be included on a remote node.
.propagate(false)
.build()
} else {
InvalidTransaction::Call.into()
}
}
pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
if let Call::report_dispute_lost_unsigned { dispute_proof, key_owner_proof } = call {
is_known_offence::<T>(dispute_proof, key_owner_proof)
} else {
Err(InvalidTransaction::Call.into())
}
}
}
fn is_known_offence<T: Config>(
dispute_proof: &DisputeProof,
key_owner_proof: &T::KeyOwnerProof,
) -> Result<(), TransactionValidityError> {
// check the membership proof to extract the offender's id
let key = (primitives::v2::PARACHAIN_KEY_TYPE_ID, dispute_proof.validator_id.clone());
let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof.clone())
.ok_or(InvalidTransaction::BadProof)?;
// check if the offence has already been reported,
// and if so then we can discard the report.
let is_known_offence = <T::HandleReports as HandleReports<T>>::is_known_offence(
&[offender],
&dispute_proof.time_slot,
);
if is_known_offence {
Err(InvalidTransaction::Stale.into())
} else {
Ok(())
}
}
/// Actual `HandleReports` implemention.
///
/// When configured properly, should be instantiated with
/// `T::KeyOwnerIdentification, Offences, ReportLongevity` parameters.
pub struct SlashingReportHandler<I, R, L> {
_phantom: sp_std::marker::PhantomData<(I, R, L)>,
}
impl<I, R, L> Default for SlashingReportHandler<I, R, L> {
fn default() -> Self {
Self { _phantom: Default::default() }
}
}
impl<T, R, L> HandleReports<T> for SlashingReportHandler<T::KeyOwnerIdentification, R, L>
where
T: Config + frame_system::offchain::SendTransactionTypes<Call<T>>,
R: ReportOffence<
T::AccountId,
T::KeyOwnerIdentification,
SlashingOffence<T::KeyOwnerIdentification>,
>,
L: Get<u64>,
{
type ReportLongevity = L;
fn report_offence(
offence: SlashingOffence<T::KeyOwnerIdentification>,
) -> Result<(), OffenceError> {
let reporters = Vec::new();
R::report_offence(reporters, offence)
}
fn is_known_offence(
offenders: &[T::KeyOwnerIdentification],
time_slot: &DisputesTimeSlot,
) -> bool {
<R as ReportOffence<
T::AccountId,
T::KeyOwnerIdentification,
SlashingOffence<T::KeyOwnerIdentification>,
>>::is_known_offence(offenders, time_slot)
}
fn submit_unsigned_slashing_report(
dispute_proof: DisputeProof,
key_owner_proof: <T as Config>::KeyOwnerProof,
) -> DispatchResult {
use frame_system::offchain::SubmitTransaction;
let session_index = dispute_proof.time_slot.session_index;
let validator_index = dispute_proof.validator_index.0;
let kind = dispute_proof.kind;
let call = Call::report_dispute_lost_unsigned {
dispute_proof: Box::new(dispute_proof),
key_owner_proof,
};
match SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
Ok(()) => log::info!(
target: LOG_TARGET,
"Submitted dispute slashing report, session({}), index({}), kind({:?})",
session_index,
validator_index,
kind,
),
Err(()) => log::error!(
target: LOG_TARGET,
"Error submitting dispute slashing report, session({}), index({}), kind({:?})",
session_index,
validator_index,
kind,
),
}
Ok(())
}
}
@@ -0,0 +1,156 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{disputes::SlashingHandler, initializer, shared};
use frame_benchmarking::{benchmarks, whitelist_account};
use frame_support::traits::{OnFinalize, OnInitialize};
use frame_system::RawOrigin;
use pallet_staking::testing_utils::create_validators;
use primitives::v2::{Hash, PARACHAIN_KEY_TYPE_ID};
use sp_runtime::traits::{One, StaticLookup};
use sp_session::MembershipProof;
// Candidate hash of the disputed candidate.
const CANDIDATE_HASH: CandidateHash = CandidateHash(Hash::zero());
pub trait Config:
pallet_session::Config
+ pallet_session::historical::Config
+ pallet_staking::Config
+ super::Config
+ shared::Config
+ initializer::Config
{
}
fn setup_validator_set<T>(n: u32) -> (SessionIndex, MembershipProof, ValidatorId)
where
T: Config,
{
pallet_staking::ValidatorCount::<T>::put(n);
let balance_factor = 1000;
// create validators and set random session keys
for (n, who) in create_validators::<T>(n, balance_factor).unwrap().into_iter().enumerate() {
use rand::{RngCore, SeedableRng};
let validator = T::Lookup::lookup(who).unwrap();
let controller = pallet_staking::Pallet::<T>::bonded(validator).unwrap();
let keys = {
const NUM_SESSION_KEYS: usize = 6;
const SESSION_KEY_LEN: usize = 32;
let mut keys = [0u8; NUM_SESSION_KEYS * SESSION_KEY_LEN];
let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(n as u64);
rng.fill_bytes(&mut keys);
keys
};
let keys: T::Keys = Decode::decode(&mut &keys[..]).expect("wrong number of session keys?");
let proof: Vec<u8> = vec![];
whitelist_account!(controller);
pallet_session::Pallet::<T>::set_keys(RawOrigin::Signed(controller).into(), keys, proof)
.expect("session::set_keys should work");
}
pallet_session::Pallet::<T>::on_initialize(T::BlockNumber::one());
initializer::Pallet::<T>::on_initialize(T::BlockNumber::one());
// skip sessions until the new validator set is enacted
while pallet_session::Pallet::<T>::validators().len() < n as usize {
pallet_session::Pallet::<T>::rotate_session();
}
initializer::Pallet::<T>::on_finalize(T::BlockNumber::one());
let session_index = crate::shared::Pallet::<T>::session_index();
let session_info = crate::session_info::Pallet::<T>::session_info(session_index);
let session_info = session_info.unwrap();
let validator_id = session_info.validators[0].clone();
let key = (PARACHAIN_KEY_TYPE_ID, validator_id.clone());
let key_owner_proof = pallet_session::historical::Pallet::<T>::prove(key).unwrap();
// rotate a session to make sure `key_owner_proof` is historical
initializer::Pallet::<T>::on_initialize(T::BlockNumber::one());
pallet_session::Pallet::<T>::rotate_session();
initializer::Pallet::<T>::on_finalize(T::BlockNumber::one());
let idx = crate::shared::Pallet::<T>::session_index();
assert!(
idx > session_index,
"session rotation should work for parachain pallets: {} <= {}",
idx,
session_index,
);
(session_index, key_owner_proof, validator_id)
}
fn setup_dispute<T>(session_index: SessionIndex, validator_id: ValidatorId) -> DisputeProof
where
T: Config,
{
let current_session = T::ValidatorSet::session_index();
assert_ne!(session_index, current_session);
let validator_index = ValidatorIndex(0);
let losers = [validator_index].into_iter();
T::SlashingHandler::punish_against_valid(session_index, CANDIDATE_HASH, losers);
let unapplied = <UnappliedSlashes<T>>::get(session_index, CANDIDATE_HASH);
assert_eq!(unapplied.unwrap().keys.len(), 1);
dispute_proof(session_index, validator_id, validator_index)
}
fn dispute_proof(
session_index: SessionIndex,
validator_id: ValidatorId,
validator_index: ValidatorIndex,
) -> DisputeProof {
let kind = SlashingOffenceKind::AgainstValid;
let time_slot = DisputesTimeSlot::new(session_index, CANDIDATE_HASH);
DisputeProof { time_slot, kind, validator_index, validator_id }
}
benchmarks! {
where_clause {
where T: Config<KeyOwnerProof = MembershipProof>,
}
// in this setup we have a single `AgainstValid` dispute
// submitted for a past session
report_dispute_lost {
let n in 4..<<T as super::Config>::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS;
let origin = RawOrigin::None.into();
let (session_index, key_owner_proof, validator_id) = setup_validator_set::<T>(n);
let dispute_proof = setup_dispute::<T>(session_index, validator_id);
}: {
let result = Pallet::<T>::report_dispute_lost_unsigned(
origin,
Box::new(dispute_proof),
key_owner_proof,
);
assert!(result.is_ok());
} verify {
let unapplied = <UnappliedSlashes<T>>::get(session_index, CANDIDATE_HASH);
assert!(unapplied.is_none());
}
}
@@ -20,8 +20,7 @@ use crate::{
disputes::DisputesHandler,
mock::{
new_test_ext, AccountId, AllPalletsWithSystem, Initializer, MockGenesisConfig, System,
Test, PUNISH_VALIDATORS_AGAINST, PUNISH_VALIDATORS_FOR, PUNISH_VALIDATORS_INCONCLUSIVE,
REWARD_VALIDATORS,
Test, PUNISH_VALIDATORS_AGAINST, PUNISH_VALIDATORS_FOR, REWARD_VALIDATORS,
},
};
use assert_matches::assert_matches;
@@ -500,9 +499,9 @@ fn dispute_statement_becoming_onesided_due_to_spamslots_is_accepted() {
});
}
// Test that punish_inconclusive is correctly called.
// Test that dispute timeout is handled correctly.
#[test]
fn test_initializer_initialize() {
fn test_dispute_timeout() {
let dispute_conclusion_by_time_out_period = 3;
let start = 10;
@@ -602,10 +601,6 @@ fn test_initializer_initialize() {
// Run to timeout + 1 in order to executive on_finalize(timeout)
run_to_block(start + dispute_conclusion_by_time_out_period + 1, |_| None);
assert_eq!(SpamSlots::<Test>::get(start - 1), Some(vec![0, 0, 0, 0, 0, 0, 0]));
assert_eq!(
PUNISH_VALIDATORS_INCONCLUSIVE.with(|r| r.borrow()[0].clone()),
(9, vec![ValidatorIndex(0), ValidatorIndex(6)]),
);
});
}
@@ -21,7 +21,7 @@
use crate::{
configuration::{self, HostConfiguration},
disputes::DisputesHandler,
disputes::{self, DisputesHandler as _, SlashingHandler as _},
dmp, hrmp, inclusion, paras, scheduler, session_info, shared, ump,
};
use frame_support::{
@@ -59,6 +59,9 @@ pub struct SessionChangeNotification<BlockNumber> {
pub session_index: SessionIndex,
}
/// Number of validators (not only parachain) in a session.
pub type ValidatorSetCount = u32;
impl<BlockNumber: Default + From<u32>> Default for SessionChangeNotification<BlockNumber> {
fn default() -> Self {
Self {
@@ -109,6 +112,7 @@ pub mod pallet {
+ scheduler::Config
+ inclusion::Config
+ session_info::Config
+ disputes::Config
+ dmp::Config
+ ump::Config
+ hrmp::Config
@@ -163,6 +167,7 @@ pub mod pallet {
inclusion::Pallet::<T>::initializer_initialize(now) +
session_info::Pallet::<T>::initializer_initialize(now) +
T::DisputesHandler::initializer_initialize(now) +
T::SlashingHandler::initializer_initialize(now) +
dmp::Pallet::<T>::initializer_initialize(now) +
ump::Pallet::<T>::initializer_initialize(now) +
hrmp::Pallet::<T>::initializer_initialize(now);
@@ -177,6 +182,7 @@ pub mod pallet {
hrmp::Pallet::<T>::initializer_finalize();
ump::Pallet::<T>::initializer_finalize();
dmp::Pallet::<T>::initializer_finalize();
T::SlashingHandler::initializer_finalize();
T::DisputesHandler::initializer_finalize();
session_info::Pallet::<T>::initializer_finalize();
inclusion::Pallet::<T>::initializer_finalize();
@@ -260,6 +266,7 @@ impl<T: Config> Pallet<T> {
inclusion::Pallet::<T>::initializer_on_new_session(&notification);
session_info::Pallet::<T>::initializer_on_new_session(&notification);
T::DisputesHandler::initializer_on_new_session(&notification);
T::SlashingHandler::initializer_on_new_session(session_index);
dmp::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
ump::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
hrmp::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
+16 -16
View File
@@ -31,8 +31,8 @@ use frame_support::{
use frame_support_test::TestRandomness;
use parity_scale_codec::Decode;
use primitives::v2::{
AuthorityDiscoveryId, Balance, BlockNumber, Header, Moment, SessionIndex, UpwardMessage,
ValidatorIndex,
AuthorityDiscoveryId, Balance, BlockNumber, CandidateHash, Header, Moment, SessionIndex,
UpwardMessage, ValidatorIndex,
};
use sp_core::H256;
use sp_io::TestExternalities;
@@ -243,7 +243,7 @@ impl crate::hrmp::Config for Test {
impl crate::disputes::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = Self;
type PunishValidators = Self;
type SlashingHandler = Self;
type WeightInfo = crate::disputes::TestWeightInfo;
}
@@ -251,7 +251,6 @@ thread_local! {
pub static REWARD_VALIDATORS: RefCell<Vec<(SessionIndex, Vec<ValidatorIndex>)>> = RefCell::new(Vec::new());
pub static PUNISH_VALIDATORS_FOR: RefCell<Vec<(SessionIndex, Vec<ValidatorIndex>)>> = RefCell::new(Vec::new());
pub static PUNISH_VALIDATORS_AGAINST: RefCell<Vec<(SessionIndex, Vec<ValidatorIndex>)>> = RefCell::new(Vec::new());
pub static PUNISH_VALIDATORS_INCONCLUSIVE: RefCell<Vec<(SessionIndex, Vec<ValidatorIndex>)>> = RefCell::new(Vec::new());
}
impl crate::disputes::RewardValidators for Test {
@@ -263,30 +262,31 @@ impl crate::disputes::RewardValidators for Test {
}
}
impl crate::disputes::PunishValidators for Test {
impl crate::disputes::SlashingHandler<BlockNumber> for Test {
fn punish_for_invalid(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
_: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
) {
PUNISH_VALIDATORS_FOR
.with(|r| r.borrow_mut().push((session, validators.into_iter().collect())))
PUNISH_VALIDATORS_FOR.with(|r| r.borrow_mut().push((session, losers.into_iter().collect())))
}
fn punish_against_valid(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
_: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
) {
PUNISH_VALIDATORS_AGAINST
.with(|r| r.borrow_mut().push((session, validators.into_iter().collect())))
.with(|r| r.borrow_mut().push((session, losers.into_iter().collect())))
}
fn punish_inconclusive(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
) {
PUNISH_VALIDATORS_INCONCLUSIVE
.with(|r| r.borrow_mut().push((session, validators.into_iter().collect())))
fn initializer_initialize(_now: BlockNumber) -> Weight {
Weight::zero()
}
fn initializer_finalize() {}
fn initializer_on_new_session(_: SessionIndex) {}
}
impl crate::scheduler::Config for Test {}
+1 -1
View File
@@ -1260,7 +1260,7 @@ impl parachains_initializer::Config for Runtime {
impl parachains_disputes::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = ();
type PunishValidators = ();
type SlashingHandler = ();
type WeightInfo = weights::runtime_parachains_disputes::WeightInfo<Runtime>;
}
+22 -3
View File
@@ -37,8 +37,9 @@ use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*};
use runtime_parachains::{
configuration as parachains_configuration, disputes as parachains_disputes,
dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion,
initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras,
disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp,
inclusion as parachains_inclusion, initializer as parachains_initializer,
origin as parachains_origin, paras as parachains_paras,
paras_inherent as parachains_paras_inherent,
runtime_api_impl::v2 as parachains_runtime_api_impl, scheduler as parachains_scheduler,
session_info as parachains_session_info, shared as parachains_shared, ump as parachains_ump,
@@ -1091,10 +1092,27 @@ impl parachains_initializer::Config for Runtime {
impl parachains_disputes::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = ();
type PunishValidators = ();
type SlashingHandler = parachains_slashing::SlashValidatorsForDisputes<ParasSlashing>;
type WeightInfo = weights::runtime_parachains_disputes::WeightInfo<Runtime>;
}
impl parachains_slashing::Config for Runtime {
type KeyOwnerProofSystem = Historical;
type KeyOwnerProof =
<Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, ValidatorId)>>::Proof;
type KeyOwnerIdentification = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
KeyTypeId,
ValidatorId,
)>>::IdentificationTuple;
type HandleReports = parachains_slashing::SlashingReportHandler<
Self::KeyOwnerIdentification,
Offences,
ReportLongevity,
>;
type WeightInfo = parachains_slashing::TestWeightInfo;
type BenchmarkingConfig = parachains_slashing::BenchConfig<200>;
}
parameter_types! {
pub const ParaDeposit: Balance = 40 * UNITS;
}
@@ -1374,6 +1392,7 @@ construct_runtime! {
Hrmp: parachains_hrmp::{Pallet, Call, Storage, Event<T>, Config} = 60,
ParaSessionInfo: parachains_session_info::{Pallet, Storage} = 61,
ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event<T>} = 62,
ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 63,
// Parachain Onboarding Pallets. Start indices at 70 to leave room.
Registrar: paras_registrar::{Pallet, Call, Storage, Event<T>, Config} = 70,
+1 -1
View File
@@ -485,7 +485,7 @@ impl parachains_inclusion::Config for Runtime {
impl parachains_disputes::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = ();
type PunishValidators = ();
type SlashingHandler = ();
type WeightInfo = parachains_disputes::TestWeightInfo;
}
+26 -5
View File
@@ -49,8 +49,9 @@ use runtime_common::{
};
use runtime_parachains::{
configuration as parachains_configuration, disputes as parachains_disputes,
dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion,
initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras,
disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp,
inclusion as parachains_inclusion, initializer as parachains_initializer,
origin as parachains_origin, paras as parachains_paras,
paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points,
runtime_api_impl::v2 as parachains_runtime_api_impl, scheduler as parachains_scheduler,
session_info as parachains_session_info, shared as parachains_shared, ump as parachains_ump,
@@ -431,9 +432,9 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type OffchainRepeat = OffchainRepeat;
type MinerTxPriority = NposSolutionPriority;
type DataProvider = Staking;
#[cfg(feature = "fast-runtime")]
#[cfg(any(feature = "fast-runtime", feature = "runtime-benchmarks"))]
type Fallback = onchain::UnboundedExecution<OnChainSeqPhragmen>;
#[cfg(not(feature = "fast-runtime"))]
#[cfg(not(any(feature = "fast-runtime", feature = "runtime-benchmarks")))]
type Fallback = pallet_election_provider_multi_phase::NoFallback<Self>;
type GovernanceFallback = onchain::UnboundedExecution<OnChainSeqPhragmen>;
type Solver = SequentialPhragmen<
@@ -947,10 +948,27 @@ impl assigned_slots::Config for Runtime {
impl parachains_disputes::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints<Runtime>;
type PunishValidators = ();
type SlashingHandler = parachains_slashing::SlashValidatorsForDisputes<ParasSlashing>;
type WeightInfo = weights::runtime_parachains_disputes::WeightInfo<Runtime>;
}
impl parachains_slashing::Config for Runtime {
type KeyOwnerProofSystem = Historical;
type KeyOwnerProof =
<Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, ValidatorId)>>::Proof;
type KeyOwnerIdentification = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
KeyTypeId,
ValidatorId,
)>>::IdentificationTuple;
type HandleReports = parachains_slashing::SlashingReportHandler<
Self::KeyOwnerIdentification,
Offences,
ReportLongevity,
>;
type WeightInfo = weights::runtime_parachains_disputes_slashing::WeightInfo<Runtime>;
type BenchmarkingConfig = parachains_slashing::BenchConfig<300>;
}
parameter_types! {
pub const ParaDeposit: Balance = 2000 * CENTS;
pub const DataDepositPerByte: Balance = deposit(0, 1);
@@ -1121,6 +1139,7 @@ construct_runtime! {
Hrmp: parachains_hrmp::{Pallet, Call, Storage, Event<T>, Config} = 51,
ParaSessionInfo: parachains_session_info::{Pallet, Storage} = 52,
ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event<T>} = 53,
ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 54,
// Parachain Onboarding Pallets. Start indices at 60 to leave room.
Registrar: paras_registrar::{Pallet, Call, Storage, Event<T>, Config} = 60,
@@ -1202,6 +1221,7 @@ mod benches {
[runtime_common::slots, Slots]
[runtime_parachains::configuration, Configuration]
[runtime_parachains::disputes, ParasDisputes]
[runtime_parachains::disputes::slashing, ParasSlashing]
[runtime_parachains::hrmp, Hrmp]
[runtime_parachains::initializer, Initializer]
[runtime_parachains::paras, Paras]
@@ -1665,6 +1685,7 @@ sp_api::impl_runtime_apis! {
impl pallet_election_provider_support_benchmarking::Config for Runtime {}
impl frame_system_benchmarking::Config for Runtime {}
impl pallet_nomination_pools_benchmarking::Config for Runtime {}
impl runtime_parachains::disputes::slashing::benchmarking::Config for Runtime {}
use xcm::latest::{
AssetId::*, Fungibility::*, Junctions::*, MultiAsset, MultiAssets, MultiLocation,
@@ -39,6 +39,7 @@ pub mod runtime_common_paras_registrar;
pub mod runtime_common_slots;
pub mod runtime_parachains_configuration;
pub mod runtime_parachains_disputes;
pub mod runtime_parachains_disputes_slashing;
pub mod runtime_parachains_hrmp;
pub mod runtime_parachains_initializer;
pub mod runtime_parachains_paras;
@@ -0,0 +1,70 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Autogenerated weights for `runtime_parachains::disputes::slashing`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-08-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024
// Executed Command:
// /home/benchbot/cargo_target_dir/production/polkadot
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
// --pallet=runtime_parachains::disputes::slashing
// --chain=westend-dev
// --header=./file_header.txt
// --output=./runtime/westend/src/weights/runtime_parachains_disputes_slashing.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::Weight};
use sp_std::marker::PhantomData;
/// Weight functions for `runtime_parachains::disputes::slashing`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> runtime_parachains::disputes::slashing::WeightInfo for WeightInfo<T> {
// Storage: Session CurrentIndex (r:1 w:0)
// Storage: Historical HistoricalSessions (r:1 w:0)
// Storage: ParaSessionInfo Sessions (r:1 w:0)
// Storage: ParasSlashing UnappliedSlashes (r:1 w:1)
// Storage: Authorship Author (r:1 w:0)
// Storage: System Digest (r:1 w:0)
// Storage: Offences ReportsByKindIndex (r:1 w:1)
// Storage: Offences ConcurrentReportsIndex (r:1 w:1)
// Storage: Offences Reports (r:1 w:1)
// Storage: Staking SlashRewardFraction (r:1 w:0)
// Storage: Staking ActiveEra (r:1 w:0)
// Storage: Staking ErasStartSessionIndex (r:1 w:0)
// Storage: Staking Invulnerables (r:1 w:0)
// Storage: Staking ValidatorSlashInEra (r:1 w:0)
/// The range of component `n` is `[4, 300]`.
fn report_dispute_lost(n: u32, ) -> Weight {
Weight::from_ref_time(97_366_000 as u64)
// Standard Error: 2_000
.saturating_add(Weight::from_ref_time(467_000 as u64).saturating_mul(n as u64))
.saturating_add(T::DbWeight::get().reads(14 as u64))
.saturating_add(T::DbWeight::get().writes(4 as u64))
}
}
+2
View File
@@ -51,6 +51,7 @@ Debian/M
decodable/MS
decrement
deduplicated
deduplication
deinitializing
dequeue/SD
dequeuing
@@ -283,6 +284,7 @@ typesystem
ubuntu/M
UDP
UI
unapplied
unassign
unconcluded
unfinalize/B
@@ -48,6 +48,9 @@ eve: reports parachain_candidate_disputes_total is at least 10 within 15 seconds
eve: reports parachain_candidate_dispute_concluded{validity="valid"} is at least 10 within 15 seconds
eve: reports parachain_candidate_dispute_concluded{validity="invalid"} is 0 within 15 seconds
# Check there is an offence report
alice: system event contains "There is an offence reported" within 60 seconds
# Check lag - approval
alice: reports polkadot_parachain_approval_checking_finality_lag is 0
bob: reports polkadot_parachain_approval_checking_finality_lag is 0
@@ -26,12 +26,10 @@ requests = { memory = "2G", cpu = "1" }
name = "bob"
command = "malus dispute-ancestor --fake-validation approval-invalid"
args = [ "--bob", "-lparachain=debug,MALUS=trace"]
[[relaychain.nodes]]
image = "{{MALUS_IMAGE}}"
name = "charlie"
command = "malus dispute-ancestor --fake-validation approval-invalid"
args = [ "--charlie", "-lparachain=debug,MALUS=trace" ]
args = [ "--charlie", "-lparachain=debug" ]
[[relaychain.nodes]]
name = "dave"
@@ -2,10 +2,10 @@ Description: Deregister / Register Validator Smoke
Network: ./0003-deregister-register-validator-smoke.toml
Creds: config
alice: is up
bob: is up
charlie: is up
dave: is up
alice: is up within 30 seconds
bob: is up within 30 seconds
charlie: is up within 30 seconds
dave: is up within 30 seconds
# ensure is in the validator set
dave: reports polkadot_node_is_parachain_validator is 1 within 240 secs