mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 07:58:00 +00:00
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:
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(¬ification);
|
||||
session_info::Pallet::<T>::initializer_on_new_session(¬ification);
|
||||
T::DisputesHandler::initializer_on_new_session(¬ification);
|
||||
T::SlashingHandler::initializer_on_new_session(session_index);
|
||||
dmp::Pallet::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
ump::Pallet::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
hrmp::Pallet::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user