mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 09:21:04 +00:00
Disputes runtime (#2947)
* disputes module skeleton and storage * implement dispute module initialization logic * implement disputes session change logic * provide dispute skeletons * deduplication & ancient check * fix a couple of warnings * begin provide_dispute_data impl * flesh out statement set import somewhat * move ApprovalVote to shared primitives * add a signing-payload API to explicit dispute statements * implement statement signature checking * some bitflags glue for observing changes in disputes * implement dispute vote import logic * flesh out everything except slashing * guide: tweaks * declare and use punishment trait * punish validators for inconclusive disputes * guide: tiny fix * guide: update docs * add disputes getter fn * guide: small change to spam slots handling * improve spam slots handling and fix some bugs * finish API of disputes runtime * define and deposit `RevertTo` log * begin integrating disputes into para_inherent * use precomputed slash_for/against * return candidate hash from process_bitfields * implement inclusion::collect_disputed * finish integration into rest of runtime * add Disputes to initializer * address suggestions * use pallet macro * fix typo * Update runtime/parachains/src/disputes.rs * add test: fix pruning * document specific behavior * deposit events on dispute changes * add an allow(unused) on fn disputes * add a dummy PunishValidators implementation * add disputes module to Rococo * add disputes module to westend runtime * add disputes module to test runtime * add disputes module to kusama runtime * guide: prepare for runtime API for checking frozenness * remove revert digests in favor of state variable * merge reversions * Update runtime/parachains/src/disputes.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Update runtime/parachains/src/disputes.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Update runtime/parachains/src/disputes.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * add byzantine_threshold and supermajority_threshold utilities to primitives * use primitive helpers * deposit revert event when freezing chain * deposit revert log when freezing chain * test revert event and log are generated when freezing * add trait to decouple disputes handling from paras inherent handling * runtime: fix compilation and setup dispute handler * disputes: add hook for filtering out dispute statements * disputes: add initializer hooks to DisputesHandler * runtime: remove disputes pallet from all runtimes * tag TODOs * don't import any dispute statements just yet... * address grumbles * fix spellcheck, hopefully * maybe now? * last spellcheck round * fix runtime tests * fix test-runtime Co-authored-by: thiolliere <gui.thiolliere@gmail.com> Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
committed by
GitHub
parent
4636daa7ce
commit
ec98120f0e
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ use parity_scale_codec::{Encode, Decode};
|
||||
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
|
||||
use sp_runtime::{DispatchError, traits::{One, Saturating}};
|
||||
|
||||
use crate::{configuration, paras, dmp, ump, hrmp, shared, scheduler::CoreAssignment};
|
||||
use crate::{configuration, disputes, paras, dmp, ump, hrmp, shared, scheduler::CoreAssignment};
|
||||
|
||||
/// A bitfield signed by a validator indicating that it is keeping its piece of the erasure-coding
|
||||
/// for any backed candidates referred to by a `1` bit available.
|
||||
@@ -118,6 +118,7 @@ pub trait Config:
|
||||
+ configuration::Config
|
||||
{
|
||||
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
|
||||
type DisputesHandler: disputes::DisputesHandler<Self::BlockNumber>;
|
||||
type RewardValidators: RewardValidators;
|
||||
}
|
||||
|
||||
@@ -238,7 +239,7 @@ impl<T: Config> Module<T> {
|
||||
expected_bits: usize,
|
||||
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||
core_lookup: impl Fn(CoreIndex) -> Option<ParaId>,
|
||||
) -> Result<Vec<CoreIndex>, DispatchError> {
|
||||
) -> Result<Vec<(CoreIndex, CandidateHash)>, DispatchError> {
|
||||
let validators = shared::Module::<T>::active_validator_keys();
|
||||
let session_index = shared::Module::<T>::session_index();
|
||||
|
||||
@@ -247,7 +248,6 @@ impl<T: Config> Module<T> {
|
||||
.map(|core_para| core_para.map(|p| (p, PendingAvailability::<T>::get(&p))))
|
||||
.collect();
|
||||
|
||||
|
||||
// do sanity checks on the bitfields:
|
||||
// 1. no more than one bitfield per validator
|
||||
// 2. bitfields are ascending by validator index.
|
||||
@@ -368,15 +368,12 @@ impl<T: Config> Module<T> {
|
||||
pending_availability.backing_group,
|
||||
);
|
||||
|
||||
freed_cores.push(pending_availability.core);
|
||||
freed_cores.push((pending_availability.core, pending_availability.hash));
|
||||
} else {
|
||||
<PendingAvailability<T>>::insert(¶_id, &pending_availability);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: pass available candidates onwards to validity module once implemented.
|
||||
// https://github.com/paritytech/polkadot/issues/1251
|
||||
|
||||
Ok(freed_cores)
|
||||
}
|
||||
|
||||
@@ -754,6 +751,28 @@ impl<T: Config> Module<T> {
|
||||
cleaned_up_cores
|
||||
}
|
||||
|
||||
/// Cleans up all paras pending availability that are in the given list of disputed candidates.
|
||||
///
|
||||
/// Returns a vector of cleaned-up core IDs.
|
||||
pub(crate) fn collect_disputed(disputed: Vec<CandidateHash>) -> Vec<CoreIndex> {
|
||||
let mut cleaned_up_ids = Vec::new();
|
||||
let mut cleaned_up_cores = Vec::new();
|
||||
|
||||
for (para_id, pending_record) in <PendingAvailability<T>>::iter() {
|
||||
if disputed.contains(&pending_record.hash) {
|
||||
cleaned_up_ids.push(para_id);
|
||||
cleaned_up_cores.push(pending_record.core);
|
||||
}
|
||||
}
|
||||
|
||||
for para_id in cleaned_up_ids {
|
||||
let _ = <PendingAvailability<T>>::take(¶_id);
|
||||
let _ = <PendingAvailabilityCommitments>::take(¶_id);
|
||||
}
|
||||
|
||||
cleaned_up_cores
|
||||
}
|
||||
|
||||
/// Forcibly enact the candidate with the given ID as though it had been deemed available
|
||||
/// by bitfields.
|
||||
///
|
||||
@@ -2553,4 +2572,6 @@ mod tests {
|
||||
assert!(<PendingAvailabilityCommitments>::iter().collect::<Vec<_>>().is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
// TODO [now]: test `collect_disputed`
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use frame_support::traits::{Randomness, OneSessionHandler};
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use crate::{
|
||||
configuration::{self, HostConfiguration},
|
||||
disputes::DisputesHandler,
|
||||
shared, paras, scheduler, inclusion, session_info, dmp, ump, hrmp,
|
||||
};
|
||||
|
||||
@@ -127,7 +128,7 @@ pub mod pallet {
|
||||
// - Scheduler
|
||||
// - Inclusion
|
||||
// - SessionInfo
|
||||
// - Validity
|
||||
// - Disputes
|
||||
// - DMP
|
||||
// - UMP
|
||||
// - HRMP
|
||||
@@ -137,6 +138,7 @@ pub mod pallet {
|
||||
scheduler::Module::<T>::initializer_initialize(now) +
|
||||
inclusion::Module::<T>::initializer_initialize(now) +
|
||||
session_info::Module::<T>::initializer_initialize(now) +
|
||||
T::DisputesHandler::initializer_initialize(now) +
|
||||
dmp::Module::<T>::initializer_initialize(now) +
|
||||
ump::Module::<T>::initializer_initialize(now) +
|
||||
hrmp::Module::<T>::initializer_initialize(now);
|
||||
@@ -151,6 +153,7 @@ pub mod pallet {
|
||||
hrmp::Module::<T>::initializer_finalize();
|
||||
ump::Module::<T>::initializer_finalize();
|
||||
dmp::Module::<T>::initializer_finalize();
|
||||
T::DisputesHandler::initializer_finalize();
|
||||
session_info::Module::<T>::initializer_finalize();
|
||||
inclusion::Module::<T>::initializer_finalize();
|
||||
scheduler::Module::<T>::initializer_finalize();
|
||||
@@ -234,6 +237,7 @@ impl<T: Config> Pallet<T> {
|
||||
scheduler::Module::<T>::initializer_on_new_session(¬ification);
|
||||
inclusion::Module::<T>::initializer_on_new_session(¬ification);
|
||||
session_info::Module::<T>::initializer_on_new_session(¬ification);
|
||||
T::DisputesHandler::initializer_on_new_session(¬ification);
|
||||
dmp::Module::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
ump::Module::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
hrmp::Module::<T>::initializer_on_new_session(¬ification, &outgoing_paras);
|
||||
@@ -268,6 +272,20 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Allow to trigger on_new_session in tests, this is needed as long as pallet_session is not
|
||||
// implemented in mock.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn test_trigger_on_new_session<'a, I: 'a>(
|
||||
changed: bool,
|
||||
session_index: SessionIndex,
|
||||
validators: I,
|
||||
queued: Option<I>,
|
||||
)
|
||||
where I: Iterator<Item=(&'a T::AccountId, ValidatorId)>
|
||||
{
|
||||
Self::on_new_session(changed, session_index, validators, queued)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod configuration;
|
||||
pub mod disputes;
|
||||
pub mod shared;
|
||||
pub mod inclusion;
|
||||
pub mod initializer;
|
||||
|
||||
@@ -21,7 +21,9 @@ use sp_core::H256;
|
||||
use sp_runtime::traits::{
|
||||
BlakeTwo256, IdentityLookup,
|
||||
};
|
||||
use primitives::v1::{AuthorityDiscoveryId, Balance, BlockNumber, Header, ValidatorIndex};
|
||||
use primitives::v1::{
|
||||
AuthorityDiscoveryId, Balance, BlockNumber, Header, ValidatorIndex, SessionIndex,
|
||||
};
|
||||
use frame_support::parameter_types;
|
||||
use frame_support::traits::GenesisBuild;
|
||||
use frame_support_test::TestRandomness;
|
||||
@@ -29,7 +31,7 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use crate::{
|
||||
inclusion, scheduler, dmp, ump, hrmp, session_info, paras, configuration,
|
||||
initializer, shared,
|
||||
initializer, shared, disputes,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
@@ -53,6 +55,7 @@ frame_support::construct_runtime!(
|
||||
Ump: ump::{Pallet, Call, Storage, Event},
|
||||
Hrmp: hrmp::{Pallet, Call, Storage, Event},
|
||||
SessionInfo: session_info::{Pallet, Call, Storage},
|
||||
Disputes: disputes::{Pallet, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -62,6 +65,8 @@ parameter_types! {
|
||||
frame_system::limits::BlockWeights::simple_max(4 * 1024 * 1024);
|
||||
}
|
||||
|
||||
pub type AccountId = u64;
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::AllowAll;
|
||||
type BlockWeights = BlockWeights;
|
||||
@@ -136,10 +141,59 @@ impl crate::hrmp::Config for Test {
|
||||
type Currency = pallet_balances::Pallet<Test>;
|
||||
}
|
||||
|
||||
impl crate::disputes::Config for Test {
|
||||
type Event = Event;
|
||||
type RewardValidators = Self;
|
||||
type PunishValidators = Self;
|
||||
}
|
||||
|
||||
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 {
|
||||
fn reward_dispute_statement(
|
||||
session: SessionIndex,
|
||||
validators: impl IntoIterator<Item=ValidatorIndex>
|
||||
) {
|
||||
REWARD_VALIDATORS.with(|r| r.borrow_mut().push((session, validators.into_iter().collect())))
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::disputes::PunishValidators for Test {
|
||||
fn punish_for_invalid(
|
||||
session: SessionIndex,
|
||||
validators: impl IntoIterator<Item=ValidatorIndex>,
|
||||
) {
|
||||
PUNISH_VALIDATORS_FOR
|
||||
.with(|r| r.borrow_mut().push((session, validators.into_iter().collect())))
|
||||
}
|
||||
|
||||
fn punish_against_valid(
|
||||
session: SessionIndex,
|
||||
validators: impl IntoIterator<Item=ValidatorIndex>,
|
||||
) {
|
||||
PUNISH_VALIDATORS_AGAINST
|
||||
.with(|r| r.borrow_mut().push((session, validators.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())))
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::scheduler::Config for Test { }
|
||||
|
||||
impl crate::inclusion::Config for Test {
|
||||
type Event = Event;
|
||||
type DisputesHandler = Disputes;
|
||||
type RewardValidators = TestRewardValidators;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,10 @@ use frame_support::{
|
||||
};
|
||||
use frame_system::ensure_none;
|
||||
use crate::{
|
||||
disputes::DisputesHandler,
|
||||
inclusion,
|
||||
scheduler::{self, FreedReason},
|
||||
shared,
|
||||
ump,
|
||||
};
|
||||
|
||||
@@ -68,6 +70,8 @@ decl_error! {
|
||||
/// The hash of the submitted parent header doesn't correspond to the saved block hash of
|
||||
/// the parent.
|
||||
InvalidParentHeader,
|
||||
/// Potentially invalid candidate.
|
||||
CandidateCouldBeInvalid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +103,7 @@ decl_module! {
|
||||
bitfields: signed_bitfields,
|
||||
backed_candidates,
|
||||
parent_header,
|
||||
disputes: _,
|
||||
disputes,
|
||||
} = data;
|
||||
|
||||
ensure_none(origin)?;
|
||||
@@ -112,6 +116,36 @@ decl_module! {
|
||||
Error::<T>::InvalidParentHeader,
|
||||
);
|
||||
|
||||
// Handle disputes logic.
|
||||
let current_session = <shared::Module<T>>::session_index();
|
||||
let freed_disputed: Vec<(_, FreedReason)> = {
|
||||
let fresh_disputes = T::DisputesHandler::provide_multi_dispute_data(disputes)?;
|
||||
if T::DisputesHandler::is_frozen() {
|
||||
// The relay chain we are currently on is invalid. Proceed no further on parachains.
|
||||
Included::set(Some(()));
|
||||
return Ok(Some(
|
||||
MINIMAL_INCLUSION_INHERENT_WEIGHT
|
||||
).into());
|
||||
}
|
||||
|
||||
let any_current_session_disputes = fresh_disputes.iter()
|
||||
.any(|(s, _)| s == ¤t_session);
|
||||
|
||||
if any_current_session_disputes {
|
||||
let current_session_disputes: Vec<_> = fresh_disputes.iter()
|
||||
.filter(|(s, _)| s == ¤t_session)
|
||||
.map(|(_, c)| *c)
|
||||
.collect();
|
||||
|
||||
<inclusion::Module<T>>::collect_disputed(current_session_disputes)
|
||||
.into_iter()
|
||||
.map(|core| (core, FreedReason::Concluded))
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
// Process new availability bitfields, yielding any availability cores whose
|
||||
// work has now concluded.
|
||||
let expected_bits = <scheduler::Module<T>>::availability_cores().len();
|
||||
@@ -121,6 +155,12 @@ decl_module! {
|
||||
<scheduler::Module<T>>::core_para,
|
||||
)?;
|
||||
|
||||
// Inform the disputes module of all included candidates.
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
for (_, candidate_hash) in &freed_concluded {
|
||||
T::DisputesHandler::note_included(current_session, *candidate_hash, now);
|
||||
}
|
||||
|
||||
// Handle timeouts for any availability core work.
|
||||
let availability_pred = <scheduler::Module<T>>::availability_timeout_predicate();
|
||||
let freed_timeout = if let Some(pred) = availability_pred {
|
||||
@@ -130,8 +170,12 @@ decl_module! {
|
||||
};
|
||||
|
||||
// Schedule paras again, given freed cores, and reasons for freeing.
|
||||
let freed = freed_concluded.into_iter().map(|c| (c, FreedReason::Concluded))
|
||||
.chain(freed_timeout.into_iter().map(|c| (c, FreedReason::TimedOut)));
|
||||
let mut freed = freed_disputed.into_iter()
|
||||
.chain(freed_concluded.into_iter().map(|(c, _hash)| (c, FreedReason::Concluded)))
|
||||
.chain(freed_timeout.into_iter().map(|c| (c, FreedReason::TimedOut)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
freed.sort_unstable_by_key(|pair| pair.0); // sort by core index
|
||||
|
||||
<scheduler::Module<T>>::clear();
|
||||
<scheduler::Module<T>>::schedule(
|
||||
@@ -142,6 +186,17 @@ decl_module! {
|
||||
let backed_candidates = limit_backed_candidates::<T>(backed_candidates);
|
||||
let backed_candidates_len = backed_candidates.len() as Weight;
|
||||
|
||||
// Refuse to back any candidates that are disputed or invalid.
|
||||
for candidate in &backed_candidates {
|
||||
ensure!(
|
||||
!T::DisputesHandler::could_be_invalid(
|
||||
current_session,
|
||||
candidate.candidate.hash(),
|
||||
),
|
||||
Error::<T>::CandidateCouldBeInvalid,
|
||||
);
|
||||
}
|
||||
|
||||
// Process backed candidates according to scheduled cores.
|
||||
let parent_storage_root = parent_header.state_root().clone();
|
||||
let occupied = <inclusion::Module<T>>::process_candidates(
|
||||
@@ -216,7 +271,7 @@ impl<T: Config> ProvideInherent for Module<T> {
|
||||
const INHERENT_IDENTIFIER: InherentIdentifier = PARACHAINS_INHERENT_IDENTIFIER;
|
||||
|
||||
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
|
||||
let inherent_data: ParachainsInherentData<T::Header>
|
||||
let mut inherent_data: ParachainsInherentData<T::Header>
|
||||
= match data.get_data(&Self::INHERENT_IDENTIFIER)
|
||||
{
|
||||
Ok(Some(d)) => d,
|
||||
@@ -231,6 +286,9 @@ impl<T: Config> ProvideInherent for Module<T> {
|
||||
}
|
||||
};
|
||||
|
||||
// filter out any unneeded dispute statements
|
||||
T::DisputesHandler::filter_multi_dispute_data(&mut inherent_data.disputes);
|
||||
|
||||
// Sanity check: session changes can invalidate an inherent, and we _really_ don't want that to happen.
|
||||
// See github.com/paritytech/polkadot/issues/1327
|
||||
let inherent_data = match Self::enter(
|
||||
|
||||
Reference in New Issue
Block a user