Unverified Commit 219f0a4e authored by asynchronous rob's avatar asynchronous rob Committed by GitHub
Browse files

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: default avatarAndré Silva <123550+andresilva@users.noreply.github.com>

* Update runtime/parachains/src/disputes.rs
Co-authored-by: default avatarAndré Silva <123550+andresilva@users.noreply.github.com>

* Update runtime/parachains/src/disputes.rs
Co-authored-by: default avatarAndré 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's avatarthiolliere <gui.thiolliere@gmail.com>
Co-authored-by: default avatarAndré Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: default avatarAndré Silva <andrerfosilva@gmail.com>
parent 7f798974
Pipeline #147933 canceled with stages
in 23 minutes and 31 seconds
......@@ -6853,6 +6853,7 @@ dependencies = [
name = "polkadot-runtime-parachains"
version = "0.9.8"
dependencies = [
"bitflags",
"bitvec",
"derive_more",
"frame-benchmarking",
......
......@@ -1186,7 +1186,7 @@ pub struct DisputeStatementSet {
pub type MultiDisputeStatementSet = Vec<DisputeStatementSet>;
/// The entire state of a dispute.
#[derive(Encode, Decode, Clone, RuntimeDebug)]
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq)]
pub struct DisputeState<N = BlockNumber> {
/// A bitfield indicating all validators for the candidate.
pub validators_for: BitVec<bitvec::order::Lsb0, u8>, // one bit per validator.
......
......@@ -6,7 +6,7 @@ However, this isn't the end of the story. We are working in a forkful blockchain
1. For security, validators that misbehave shouldn't only be slashed on one fork, but on all possible forks. Validators that misbehave shouldn't be able to create a new fork of the chain when caught and get away with their misbehavior.
1. It is possible (and likely) that the parablock being contested has not appeared on all forks.
1. If a block author believes that there is a disputed parablock on a specific fork that will resolve to a reversion of the fork, that block author is better incentivized to build on a different fork which does not include that parablock.
1. If a block author believes that there is a disputed parablock on a specific fork that will resolve to a reversion of the fork, that block author has more incentive to build on a different fork which does not include that parablock.
This means that in all likelihood, there is the possibility of disputes that are started on one fork of the relay chain, and as soon as the dispute resolution process starts to indicate that the parablock is indeed invalid, that fork of the relay chain will be abandoned and the dispute will never be fully resolved on that chain.
......@@ -42,11 +42,12 @@ Included: double_map (SessionIndex, CandidateHash) -> Option<BlockNumber>,
// fewer than `byzantine_threshold + 1` validators.
//
// The i'th entry of the vector corresponds to the i'th validator in the session.
SpamSlots: map SessionIndex -> Vec<u32>,
// Whether the chain is frozen or not. Starts as `false`. When this is `true`,
// the chain will not accept any new parachain blocks for backing or inclusion.
// It can only be set back to `false` by governance intervention.
Frozen: bool,
SpamSlots: map SessionIndex -> Option<Vec<u32>>,
// Whether the chain is frozen or not. Starts as `None`. When this is `Some`,
// the chain will not accept any new parachain blocks for backing or inclusion,
// and its value indicates the last valid block number in the chain.
// It can only be set back to `None` by governance intervention.
Frozen: Option<BlockNumber>,
```
> `byzantine_threshold` refers to the maximum number `f` of validators which may be byzantine. The total number of validators is `n = 3f + e` where `e in { 1, 2, 3 }`.
......@@ -54,7 +55,8 @@ Frozen: bool,
## Session Change
1. If the current session is not greater than `config.dispute_period + 1`, nothing to do here.
1. Set `pruning_target = current_session - config.dispute_period - 1`. We add the extra `1` because we want to keep things for `config.dispute_period` _full_ sessions. The stuff at the end of the most recent session has been around for ~0 sessions, not ~1.
1. Set `pruning_target = current_session - config.dispute_period - 1`. We add the extra `1` because we want to keep things for `config.dispute_period` _full_ sessions.
The stuff at the end of the most recent session has been around for a little over 0 sessions, not a little over 1.
1. If `LastPrunedSession` is `None`, then set `LastPrunedSession` to `Some(pruning_target)` and return.
1. Otherwise, clear out all disputes, included candidates, and `SpamSlots` entries in the range `last_pruned..=pruning_target` and set `LastPrunedSession` to `Some(pruning_target)`.
......@@ -65,7 +67,6 @@ Frozen: bool,
## Routines
* `provide_multi_dispute_data(MultiDisputeStatementSet) -> Vec<(SessionIndex, Hash)>`:
1. Fail if any disputes in the set are duplicate or concluded before the `config.dispute_post_conclusion_acceptance_period` window relative to now.
1. Pass on each dispute statement set to `provide_dispute_data`, propagating failure.
1. Return a list of all candidates who just had disputes initiated.
......@@ -75,29 +76,30 @@ Frozen: bool,
1. If there is no dispute under `Disputes`, create a new `DisputeState` with blank bitfields.
1. If `concluded_at` is `Some`, and is `concluded_at + config.post_conclusion_acceptance_period < now`, return false.
1. If the overlap of the validators in the `DisputeStatementSet` and those already present in the `DisputeState` is fewer in number than `byzantine_threshold + 1` and the candidate is not present in the `Included` map
1. increment `SpamSlots` for each validator in the `DisputeStatementSet` which is not already in the `DisputeState`. Initialize the `SpamSlots` to a zeroed vector first, if necessary.
1. If the value for any spam slot exceeds `config.dispute_max_spam_slots`, return false.
1. If the overlap of the validators in the `DisputeStatementSet` and those already present in the `DisputeState` is at least `byzantine_threshold + 1`, the `DisputeState` has fewer than `byzantine_threshold + 1` validators, and the candidate is not present in the `Included` map, decrement `SpamSlots` for each validator in the `DisputeState`.
1. Import all statements into the dispute. This should fail if any statements are duplicate; if the corresponding bit for the corresponding validator is set in the dispute already.
1. If `concluded_at` is `None`, reward all statements slightly less.
1. increment `SpamSlots` for each validator in the `DisputeStatementSet` which is not already in the `DisputeState`. Initialize the `SpamSlots` to a zeroed vector first, if necessary. do not increment `SpamSlots` if the candidate is local.
1. If the value for any spam slot exceeds `config.dispute_max_spam_slots`, return false.
1. If the overlap of the validators in the `DisputeStatementSet` and those already present in the `DisputeState` is at least `byzantine_threshold + 1`, the `DisputeState` has fewer than `byzantine_threshold + 1` validators, and the candidate is not present in the `Included` map, then decrease `SpamSlots` by 1 for each validator in the `DisputeState`.
1. Import all statements into the dispute. This should fail if any statements are duplicate or if the corresponding bit for the corresponding validator is set in the dispute already.
1. If `concluded_at` is `None`, reward all statements.
1. If `concluded_at` is `Some`, reward all statements slightly less.
1. If either side now has supermajority, slash the other side. This may be both sides, and we support this possibility in code, but note that this requires validators to participate on both sides which has negative expected value. Set `concluded_at` to `Some(now)`.
1. If either side now has supermajority and did not previously, slash the other side. This may be both sides, and we support this possibility in code, but note that this requires validators to participate on both sides which has negative expected value. Set `concluded_at` to `Some(now)` if it was `None`.
1. If just concluded against the candidate and the `Included` map contains `(session, candidate)`: invoke `revert_and_freeze` with the stored block number.
1. Return true if just initiated, false otherwise.
* `disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState)>`: Get a list of all disputes and info about dispute state.
1. Iterate over all disputes in `Disputes`. Set the flag according to `concluded`.
1. Iterate over all disputes in `Disputes` and collect into a vector.
* `note_included(SessionIndex, CandidateHash, included_in: BlockNumber)`:
1. Add `(SessionIndex, CandidateHash)` to the `Included` map with `included_in - 1` as the value.
1. If there is a dispute under `(Sessionindex, CandidateHash)` with fewer than `byzantine_threshold + 1` participating validators, decrement `SpamSlots` for each validator in the `DisputeState`.
1. If there is a dispute under `(Sessionindex, CandidateHash)` with fewer than `byzantine_threshold + 1` participating validators, decrease `SpamSlots` by 1 for each validator in the `DisputeState`.
1. If there is a dispute under `(SessionIndex, CandidateHash)` that has concluded against the candidate, invoke `revert_and_freeze` with the stored block number.
* `could_be_invalid(SessionIndex, CandidateHash) -> bool`: Returns whether a candidate has a live dispute ongoing or a dispute which has already concluded in the negative.
* `is_frozen()`: Load the value of `Frozen` from storage.
* `is_frozen()`: Load the value of `Frozen` from storage. Return true if `Some` and false if `None`.
* `revert_and_freeze(BlockNumber):
* `last_valid_block()`: Load the value of `Frozen` from storage and return. None indicates that all blocks in the chain are potentially valid.
* `revert_and_freeze(BlockNumber)`:
1. If `is_frozen()` return.
1. issue a digest in the block header which indicates the chain is to be abandoned back to the stored block number.
1. Set `Frozen` to true.
1. Set `Frozen` to `Some(BlockNumber)` to indicate a rollback to the given block number is necessary.
# Disputes
## DisputeStatementSet
## `DisputeStatementSet`
```rust
/// A set of statements about a specific candidate.
......@@ -11,7 +11,7 @@ struct DisputeStatementSet {
}
```
## DisputeStatement
## `DisputeStatement`
```rust
/// A statement about a candidate, to be used within some dispute resolution process.
......@@ -33,8 +33,8 @@ Kinds of dispute statements. Each of these can be combined with a candidate hash
```rust
enum ValidDisputeStatementKind {
Explicit,
BackingSeconded,
BackingValid,
BackingSeconded(Hash),
BackingValid(Hash),
ApprovalChecking,
}
......@@ -43,7 +43,7 @@ enum InvalidDisputeStatementKind {
}
```
## ExplicitDisputeStatement
## `ExplicitDisputeStatement`
```rust
struct ExplicitDisputeStatement {
......@@ -53,7 +53,7 @@ struct ExplicitDisputeStatement {
}
```
## MultiDisputeStatementSet
## `MultiDisputeStatementSet`
Sets of statements for many (zero or more) disputes.
......@@ -61,7 +61,7 @@ Sets of statements for many (zero or more) disputes.
type MultiDisputeStatementSet = Vec<DisputeStatementSet>;
```
## DisputeState
## `DisputeState`
```rust
struct DisputeState {
......
......@@ -1092,6 +1092,7 @@ impl parachains_session_info::Config for Runtime {}
impl parachains_inclusion::Config for Runtime {
type Event = Event;
type DisputesHandler = ();
type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints<Runtime>;
}
......
......@@ -11,6 +11,7 @@ log = { version = "0.4.14", default-features = false }
rustc-hex = { version = "2.1.0", default-features = false }
serde = { version = "1.0.123", features = [ "derive" ], optional = true }
derive_more = "0.99.14"
bitflags = "1"
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
inherents = { package = "sp-inherents", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
......
This diff is collapsed.
......@@ -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(&para_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(&para_id);
let _ = <PendingAvailabilityCommitments>::take(&para_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(&notification);
inclusion::Module::<T>::initializer_on_new_session(&notification);
session_info::Module::<T>::initializer_on_new_session(&notification);
T::DisputesHandler::initializer_on_new_session(&notification);
dmp::Module::<T>::initializer_on_new_session(&notification, &outgoing_paras);
ump::Module::<T>::initializer_on_new_session(&notification, &outgoing_paras);
hrmp::Module::<T>::initializer_on_new_session(&notification, &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 == &current_session);
if any_current_session_disputes {
let current_session_disputes: Vec<_> = fresh_disputes.iter()
.filter(|(s, _)| s == &current_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(
......
......@@ -589,6 +589,7 @@ impl runtime_parachains::inclusion::RewardValidators for RewardValidators {
impl parachains_inclusion::Config for Runtime {
type Event = Event;
type DisputesHandler = ();
type RewardValidators = RewardValidators;
}
......
......@@ -36,6 +36,7 @@ use polkadot_runtime_parachains::dmp as parachains_dmp;
use polkadot_runtime_parachains::ump as parachains_ump;
use polkadot_runtime_parachains::hrmp as parachains_hrmp;
use polkadot_runtime_parachains::scheduler as parachains_scheduler;
use polkadot_runtime_parachains::disputes as parachains_disputes;
use polkadot_runtime_parachains::runtime_api_impl::v1 as runtime_impl;
use primitives::v1::{
......@@ -455,9 +456,16 @@ impl parachains_shared::Config for Runtime {}
impl parachains_inclusion::Config for Runtime {
type Event = Event;
type DisputesHandler = ParasDisputes;
type RewardValidators = RewardValidatorsWithEraPoints<Runtime>;
}
impl parachains_disputes::Config for Runtime {
type Event = Event;
type RewardValidators = ();
type PunishValidators = ();
}
impl parachains_paras_inherent::Config for Runtime {}
impl parachains_initializer::Config for Runtime {
......@@ -537,6 +545,7 @@ construct_runtime! {
SessionInfo: parachains_session_info::{Pallet, Call, Storage},
Hrmp: parachains_hrmp::{Pallet, Call, Storage, Event},
Ump: parachains_ump::{Pallet, Call, Storage, Event},
ParasDisputes: parachains_disputes::{Pallet, Storage, Event<T>},
Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>},
}
......
......@@ -765,6 +765,7 @@ impl parachains_session_info::Config for Runtime {}
impl parachains_inclusion::Config for Runtime {
type Event = Event;