use crate::{configuration, initializer::SessionChangeNotification, session_info};
use bitvec::{bitvec, order::Lsb0 as BitOrderLsb0};
use frame_support::{ensure, traits::Get, weights::Weight};
use frame_system::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use primitives::v2::{
byzantine_threshold, supermajority_threshold, ApprovalVote, CandidateHash,
CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CompactStatement, ConsensusLog,
DisputeState, DisputeStatement, DisputeStatementSet, ExplicitDisputeStatement,
InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex, SigningContext,
ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{AppVerify, One, Saturating, Zero},
DispatchError, RuntimeDebug, SaturatedConversion,
};
use sp_std::{cmp::Ordering, prelude::*};
#[cfg(test)]
#[allow(unused_imports)]
pub(crate) use self::tests::run_to_block;
pub mod slashing;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
const LOG_TARGET: &str = "runtime::disputes";
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DisputeLocation {
Local,
Remote,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DisputeResult {
Valid,
Invalid,
}
pub trait RewardValidators {
fn reward_dispute_statement(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
);
}
impl RewardValidators for () {
fn reward_dispute_statement(_: SessionIndex, _: impl IntoIterator<Item = ValidatorIndex>) {}
}
pub trait SlashingHandler<BlockNumber> {
fn punish_for_invalid(
session: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
);
fn punish_against_valid(
session: SessionIndex,
candidate_hash: CandidateHash,
losers: impl IntoIterator<Item = ValidatorIndex>,
);
fn initializer_initialize(now: BlockNumber) -> Weight;
fn initializer_finalize();
fn initializer_on_new_session(session_index: SessionIndex);
}
impl<BlockNumber> SlashingHandler<BlockNumber> for () {
fn punish_for_invalid(
_: SessionIndex,
_: CandidateHash,
_: impl IntoIterator<Item = ValidatorIndex>,
) {
}
fn punish_against_valid(
_: SessionIndex,
_: CandidateHash,
_: impl IntoIterator<Item = ValidatorIndex>,
) {
}
fn initializer_initialize(_now: BlockNumber) -> Weight {
Weight::zero()
}
fn initializer_finalize() {}
fn initializer_on_new_session(_: SessionIndex) {}
}
#[derive(Clone, Copy)]
pub enum VerifyDisputeSignatures {
Yes,
Skip,
}
fn dispute_ordering_compare<T: DisputesHandler<BlockNumber>, BlockNumber: Ord>(
a: &DisputeStatementSet,
b: &DisputeStatementSet,
) -> Ordering
where
T: ?Sized,
{
let a_local_block =
<T as DisputesHandler<BlockNumber>>::included_state(a.session, a.candidate_hash);
let b_local_block =
<T as DisputesHandler<BlockNumber>>::included_state(b.session, b.candidate_hash);
match (a_local_block, b_local_block) {
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(a_height), Some(b_height)) =>
a_height.cmp(&b_height).then_with(|| a.candidate_hash.cmp(&b.candidate_hash)),
(None, None) => {
let session_ord = a.session.cmp(&b.session);
if session_ord == Ordering::Equal {
a.candidate_hash.cmp(&b.candidate_hash)
} else {
session_ord
}
},
}
}
use super::paras_inherent::IsSortedBy;
fn contains_duplicates_in_sorted_iter<
'a,
T: 'a,
I: 'a + IntoIterator<Item = &'a T>,
C: 'static + FnMut(&T, &T) -> bool,
>(
iter: I,
mut check_equal: C,
) -> bool {
let mut iter = iter.into_iter();
if let Some(mut previous) = iter.next() {
while let Some(current) = iter.next() {
if check_equal(previous, current) {
return true
}
previous = current;
}
}
return false
}
pub trait DisputesHandler<BlockNumber: Ord> {
fn is_frozen() -> bool;
fn assure_deduplicated_and_sorted(statement_sets: &MultiDisputeStatementSet) -> Result<(), ()> {
if !IsSortedBy::is_sorted_by(
statement_sets.as_slice(),
dispute_ordering_compare::<Self, BlockNumber>,
) {
return Err(())
}
if contains_duplicates_in_sorted_iter(statement_sets, |previous, current| {
current.session == previous.session && current.candidate_hash == previous.candidate_hash
}) {
return Err(())
}
Ok(())
}
fn deduplicate_and_sort_dispute_data(
statement_sets: &mut MultiDisputeStatementSet,
) -> Result<(), ()> {
let n = statement_sets.len();
statement_sets.sort_by(dispute_ordering_compare::<Self, BlockNumber>);
statement_sets
.dedup_by(|a, b| a.session == b.session && a.candidate_hash == b.candidate_hash);
if n == statement_sets.len() {
Ok(())
} else {
Err(())
}
}
fn filter_dispute_data(
statement_set: DisputeStatementSet,
post_conclusion_acceptance_period: BlockNumber,
verify_sigs: VerifyDisputeSignatures,
) -> Option<CheckedDisputeStatementSet>;
fn process_checked_multi_dispute_data(
statement_sets: CheckedMultiDisputeStatementSet,
) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError>;
fn note_included(
session: SessionIndex,
candidate_hash: CandidateHash,
included_in: BlockNumber,
);
fn included_state(session: SessionIndex, candidate_hash: CandidateHash) -> Option<BlockNumber>;
fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool;
fn initializer_initialize(now: BlockNumber) -> Weight;
fn initializer_finalize();
fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumber>);
}
impl<BlockNumber: Ord> DisputesHandler<BlockNumber> for () {
fn is_frozen() -> bool {
false
}
fn deduplicate_and_sort_dispute_data(
statement_sets: &mut MultiDisputeStatementSet,
) -> Result<(), ()> {
statement_sets.clear();
Ok(())
}
fn filter_dispute_data(
_set: DisputeStatementSet,
_post_conclusion_acceptance_period: BlockNumber,
_verify_sigs: VerifyDisputeSignatures,
) -> Option<CheckedDisputeStatementSet> {
None
}
fn process_checked_multi_dispute_data(
_statement_sets: CheckedMultiDisputeStatementSet,
) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
Ok(Vec::new())
}
fn note_included(
_session: SessionIndex,
_candidate_hash: CandidateHash,
_included_in: BlockNumber,
) {
}
fn included_state(
_session: SessionIndex,
_candidate_hash: CandidateHash,
) -> Option<BlockNumber> {
None
}
fn concluded_invalid(_session: SessionIndex, _candidate_hash: CandidateHash) -> bool {
false
}
fn initializer_initialize(_now: BlockNumber) -> Weight {
Weight::zero()
}
fn initializer_finalize() {}
fn initializer_on_new_session(_notification: &SessionChangeNotification<BlockNumber>) {}
}
impl<T: Config> DisputesHandler<T::BlockNumber> for pallet::Pallet<T>
where
T::BlockNumber: Ord,
{
fn is_frozen() -> bool {
pallet::Pallet::<T>::is_frozen()
}
fn filter_dispute_data(
set: DisputeStatementSet,
post_conclusion_acceptance_period: T::BlockNumber,
verify_sigs: VerifyDisputeSignatures,
) -> Option<CheckedDisputeStatementSet> {
pallet::Pallet::<T>::filter_dispute_data(
&set,
post_conclusion_acceptance_period,
verify_sigs,
)
.filter_statement_set(set)
}
fn process_checked_multi_dispute_data(
statement_sets: CheckedMultiDisputeStatementSet,
) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
pallet::Pallet::<T>::process_checked_multi_dispute_data(statement_sets)
}
fn note_included(
session: SessionIndex,
candidate_hash: CandidateHash,
included_in: T::BlockNumber,
) {
pallet::Pallet::<T>::note_included(session, candidate_hash, included_in)
}
fn included_state(
session: SessionIndex,
candidate_hash: CandidateHash,
) -> Option<T::BlockNumber> {
pallet::Pallet::<T>::included_state(session, candidate_hash)
}
fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
pallet::Pallet::<T>::concluded_invalid(session, candidate_hash)
}
fn initializer_initialize(now: T::BlockNumber) -> Weight {
pallet::Pallet::<T>::initializer_initialize(now)
}
fn initializer_finalize() {
pallet::Pallet::<T>::initializer_finalize()
}
fn initializer_on_new_session(notification: &SessionChangeNotification<T::BlockNumber>) {
pallet::Pallet::<T>::initializer_on_new_session(notification)
}
}
pub trait WeightInfo {
fn force_unfreeze() -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn force_unfreeze() -> Weight {
Weight::zero()
}
}
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
#[pallet::config]
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 SlashingHandler: SlashingHandler<Self::BlockNumber>;
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::storage]
pub(super) type LastPrunedSession<T> = StorageValue<_, SessionIndex>;
#[pallet::storage]
pub(super) type Disputes<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
SessionIndex,
Blake2_128Concat,
CandidateHash,
DisputeState<T::BlockNumber>,
>;
#[pallet::storage]
pub(super) type Included<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
SessionIndex,
Blake2_128Concat,
CandidateHash,
T::BlockNumber,
>;
#[pallet::storage]
#[pallet::getter(fn last_valid_block)]
pub(super) type Frozen<T: Config> = StorageValue<_, Option<T::BlockNumber>, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub fn deposit_event)]
pub enum Event<T: Config> {
DisputeInitiated(CandidateHash, DisputeLocation),
DisputeConcluded(CandidateHash, DisputeResult),
DisputeTimedOut(CandidateHash),
Revert(T::BlockNumber),
}
#[pallet::error]
pub enum Error<T> {
DuplicateDisputeStatementSets,
AncientDisputeStatement,
ValidatorIndexOutOfBounds,
InvalidSignature,
DuplicateStatement,
SingleSidedDispute,
UnconfirmedDispute,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::force_unfreeze())]
pub fn force_unfreeze(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
Frozen::<T>::set(None);
Ok(())
}
}
}
bitflags::bitflags! {
#[derive(Default)]
struct DisputeStateFlags: u8 {
const CONFIRMED = 0b0001;
const FOR_SUPERMAJORITY = 0b0010;
const AGAINST_SUPERMAJORITY = 0b0100;
}
}
impl DisputeStateFlags {
fn from_state<BlockNumber>(state: &DisputeState<BlockNumber>) -> Self {
let n = state.validators_for.len();
let byzantine_threshold = byzantine_threshold(n);
let supermajority_threshold = supermajority_threshold(n);
let mut flags = DisputeStateFlags::default();
let all_participants = state.validators_for.clone() | state.validators_against.clone();
if all_participants.count_ones() > byzantine_threshold {
flags |= DisputeStateFlags::CONFIRMED;
}
if state.validators_for.count_ones() >= supermajority_threshold {
flags |= DisputeStateFlags::FOR_SUPERMAJORITY;
}
if state.validators_against.count_ones() >= supermajority_threshold {
flags |= DisputeStateFlags::AGAINST_SUPERMAJORITY;
}
flags
}
}
struct ImportSummary<BlockNumber> {
state: DisputeState<BlockNumber>,
slash_against: Vec<ValidatorIndex>,
slash_for: Vec<ValidatorIndex>,
new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
new_flags: DisputeStateFlags,
}
#[derive(RuntimeDebug, PartialEq, Eq)]
enum VoteImportError {
ValidatorIndexOutOfBounds,
DuplicateStatement,
}
impl<T: Config> From<VoteImportError> for Error<T> {
fn from(e: VoteImportError) -> Self {
match e {
VoteImportError::ValidatorIndexOutOfBounds => Error::<T>::ValidatorIndexOutOfBounds,
VoteImportError::DuplicateStatement => Error::<T>::DuplicateStatement,
}
}
}
#[derive(RuntimeDebug, PartialEq, Eq)]
struct ImportUndo {
validator_index: ValidatorIndex,
valid: bool,
new_participant: bool,
}
struct DisputeStateImporter<BlockNumber> {
state: DisputeState<BlockNumber>,
now: BlockNumber,
new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
pre_flags: DisputeStateFlags,
}
impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
fn new(state: DisputeState<BlockNumber>, now: BlockNumber) -> Self {
let pre_flags = DisputeStateFlags::from_state(&state);
let new_participants = bitvec::bitvec![u8, BitOrderLsb0; 0; state.validators_for.len()];
DisputeStateImporter { state, now, new_participants, pre_flags }
}
fn import(
&mut self,
validator: ValidatorIndex,
valid: bool,
) -> Result<ImportUndo, VoteImportError> {
let (bits, other_bits) = if valid {
(&mut self.state.validators_for, &mut self.state.validators_against)
} else {
(&mut self.state.validators_against, &mut self.state.validators_for)
};
match bits.get(validator.0 as usize).map(|b| *b) {
None => return Err(VoteImportError::ValidatorIndexOutOfBounds),
Some(true) => return Err(VoteImportError::DuplicateStatement),
Some(false) => {},
}
if validator.0 as usize >= self.new_participants.len() {
return Err(VoteImportError::ValidatorIndexOutOfBounds)
}
let mut undo = ImportUndo { validator_index: validator, valid, new_participant: false };
bits.set(validator.0 as usize, true);
if other_bits.get(validator.0 as usize).map_or(false, |b| !*b) {
undo.new_participant = true;
self.new_participants.set(validator.0 as usize, true);
}
Ok(undo)
}
fn undo(&mut self, undo: ImportUndo) {
if undo.valid {
self.state.validators_for.set(undo.validator_index.0 as usize, false);
} else {
self.state.validators_against.set(undo.validator_index.0 as usize, false);
}
if undo.new_participant {
self.new_participants.set(undo.validator_index.0 as usize, false);
}
}
fn finish(mut self) -> ImportSummary<BlockNumber> {
let pre_flags = self.pre_flags;
let post_flags = DisputeStateFlags::from_state(&self.state);
let pre_post_contains = |flags| (pre_flags.contains(flags), post_flags.contains(flags));
let slash_against =
if let (false, true) = pre_post_contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
if self.state.concluded_at.is_none() {
self.state.concluded_at = Some(self.now.clone());
}
self.state
.validators_against
.iter_ones()
.map(|i| ValidatorIndex(i as _))
.collect()
} else {
Vec::new()
};
let slash_for =
if let (false, true) = pre_post_contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
if self.state.concluded_at.is_none() {
self.state.concluded_at = Some(self.now.clone());
}
self.state.validators_for.iter_ones().map(|i| ValidatorIndex(i as _)).collect()
} else {
Vec::new()
};
ImportSummary {
state: self.state,
slash_against,
slash_for,
new_participants: self.new_participants,
new_flags: post_flags - pre_flags,
}
}
}
#[derive(PartialEq)]
#[cfg_attr(test, derive(Debug))]
enum StatementSetFilter {
RemoveAll,
RemoveIndices(Vec<usize>),
}
impl StatementSetFilter {
fn filter_statement_set(
self,
mut statement_set: DisputeStatementSet,
) -> Option<CheckedDisputeStatementSet> {
match self {
StatementSetFilter::RemoveAll => None,
StatementSetFilter::RemoveIndices(mut indices) => {
indices.sort();
indices.dedup();
for index in indices.into_iter().rev() {
statement_set.statements.swap_remove(index);
}
if statement_set.statements.is_empty() {
None
} else {
Some(CheckedDisputeStatementSet::unchecked_from_unchecked(statement_set))
}
},
}
}
fn remove_index(&mut self, i: usize) {
if let StatementSetFilter::RemoveIndices(ref mut indices) = *self {
indices.push(i)
}
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn initializer_initialize(now: T::BlockNumber) -> Weight {
let config = <configuration::Pallet<T>>::config();
let mut weight = Weight::zero();
for (session_index, candidate_hash, mut dispute) in <Disputes<T>>::iter() {
weight += T::DbWeight::get().reads_writes(1, 0);
if dispute.concluded_at.is_none() &&
dispute.start + config.dispute_conclusion_by_time_out_period < now
{
Self::deposit_event(Event::DisputeTimedOut(candidate_hash));
dispute.concluded_at = Some(now);
<Disputes<T>>::insert(session_index, candidate_hash, &dispute);
weight += T::DbWeight::get().writes(1);
}
}
weight
}
pub(crate) fn initializer_finalize() {}
pub(crate) fn initializer_on_new_session(
notification: &SessionChangeNotification<T::BlockNumber>,
) {
let config = <configuration::Pallet<T>>::config();
if notification.session_index <= config.dispute_period + 1 {
return
}
let pruning_target = notification.session_index - config.dispute_period - 1;
LastPrunedSession::<T>::mutate(|last_pruned| {
let to_prune = if let Some(last_pruned) = last_pruned {
*last_pruned + 1..=pruning_target
} else {
pruning_target..=pruning_target
};
for to_prune in to_prune {
#[allow(deprecated)]
<Disputes<T>>::remove_prefix(to_prune, None);
#[allow(deprecated)]
<Included<T>>::remove_prefix(to_prune, None);
}
*last_pruned = Some(pruning_target);
});
}
pub(crate) fn process_checked_multi_dispute_data(
statement_sets: CheckedMultiDisputeStatementSet,
) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
let config = <configuration::Pallet<T>>::config();
let mut fresh = Vec::with_capacity(statement_sets.len());
for statement_set in statement_sets {
let dispute_target = {
let statement_set: &DisputeStatementSet = statement_set.as_ref();
(statement_set.session, statement_set.candidate_hash)
};
if Self::process_checked_dispute_data(
statement_set,
config.dispute_post_conclusion_acceptance_period,
)? {
fresh.push(dispute_target);
}
}
Ok(fresh)
}
fn filter_dispute_data(
set: &DisputeStatementSet,
post_conclusion_acceptance_period: <T as frame_system::Config>::BlockNumber,
verify_sigs: VerifyDisputeSignatures,
) -> StatementSetFilter {
let mut filter = StatementSetFilter::RemoveIndices(Vec::new());
let now = <frame_system::Pallet<T>>::block_number();
let oldest_accepted = now.saturating_sub(post_conclusion_acceptance_period);
let session_info = match <session_info::Pallet<T>>::session_info(set.session) {
Some(s) => s,
None => return StatementSetFilter::RemoveAll,
};
let n_validators = session_info.validators.len();
let dispute_state = {
if let Some(dispute_state) = <Disputes<T>>::get(&set.session, &set.candidate_hash) {
if dispute_state.concluded_at.as_ref().map_or(false, |c| c < &oldest_accepted) {
return StatementSetFilter::RemoveAll
}
dispute_state
} else {
DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
start: now,
concluded_at: None,
}
}
};
let summary = {
let mut importer = DisputeStateImporter::new(dispute_state, now);
for (i, (statement, validator_index, signature)) in set.statements.iter().enumerate() {
let validator_public = match session_info.validators.get(*validator_index) {
None => {
filter.remove_index(i);
continue
},
Some(v) => v,
};
let valid = statement.indicates_validity();
let undo = match importer.import(*validator_index, valid) {
Ok(u) => u,
Err(_) => {
filter.remove_index(i);
continue
},
};
if let VerifyDisputeSignatures::Yes = verify_sigs {
if let Err(()) = check_signature(
&validator_public,
set.candidate_hash,
set.session,
statement,
signature,
) {
importer.undo(undo);
filter.remove_index(i);
continue
}
}
}
importer.finish()
};
if summary.state.validators_for.count_ones() == 0 ||
summary.state.validators_against.count_ones() == 0
{
return StatementSetFilter::RemoveAll
}
if (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() <=
byzantine_threshold(summary.state.validators_for.len())
{
return StatementSetFilter::RemoveAll
}
filter
}
fn process_checked_dispute_data(
set: CheckedDisputeStatementSet,
dispute_post_conclusion_acceptance_period: T::BlockNumber,
) -> Result<bool, DispatchError> {
let now = <frame_system::Pallet<T>>::block_number();
let oldest_accepted = now.saturating_sub(dispute_post_conclusion_acceptance_period);
let set = set.as_ref();
let session_info = match <session_info::Pallet<T>>::session_info(set.session) {
Some(s) => s,
None => return Err(Error::<T>::AncientDisputeStatement.into()),
};
let n_validators = session_info.validators.len();
let (fresh, dispute_state) = {
if let Some(dispute_state) = <Disputes<T>>::get(&set.session, &set.candidate_hash) {
ensure!(
dispute_state.concluded_at.as_ref().map_or(true, |c| c >= &oldest_accepted),
Error::<T>::AncientDisputeStatement,
);
(false, dispute_state)
} else {
(
true,
DisputeState {
validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
start: now,
concluded_at: None,
},
)
}
};
let summary = {
let mut importer = DisputeStateImporter::new(dispute_state, now);
for (statement, validator_index, _signature) in &set.statements {
let valid = statement.indicates_validity();
importer.import(*validator_index, valid).map_err(Error::<T>::from)?;
}
importer.finish()
};
ensure!(
summary.state.validators_for.count_ones() > 0 &&
summary.state.validators_against.count_ones() > 0,
Error::<T>::SingleSidedDispute,
);
ensure!(
(summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() >
byzantine_threshold(summary.state.validators_for.len()),
Error::<T>::UnconfirmedDispute,
);
let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
let session = *session;
let candidate_hash = *candidate_hash;
if fresh {
let is_local = <Included<T>>::contains_key(&session, &candidate_hash);
Self::deposit_event(Event::DisputeInitiated(
candidate_hash,
if is_local { DisputeLocation::Local } else { DisputeLocation::Remote },
));
}
{
if summary.new_flags.contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
Self::deposit_event(Event::DisputeConcluded(candidate_hash, DisputeResult::Valid));
}
if summary.new_flags.contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
Self::deposit_event(Event::DisputeConcluded(
candidate_hash,
DisputeResult::Invalid,
));
}
}
T::RewardValidators::reward_dispute_statement(
session,
summary.new_participants.iter_ones().map(|i| ValidatorIndex(i as _)),
);
{
T::SlashingHandler::punish_against_valid(
session,
candidate_hash,
summary.slash_against,
);
T::SlashingHandler::punish_for_invalid(session, candidate_hash, summary.slash_for);
}
<Disputes<T>>::insert(&session, &candidate_hash, &summary.state);
if summary.new_flags.contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
if let Some(revert_to) = <Included<T>>::get(&session, &candidate_hash) {
Self::revert_and_freeze(revert_to);
}
}
Ok(fresh)
}
#[allow(unused)]
pub(crate) fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<T::BlockNumber>)> {
<Disputes<T>>::iter().collect()
}
pub(crate) fn note_included(
session: SessionIndex,
candidate_hash: CandidateHash,
included_in: T::BlockNumber,
) {
if included_in.is_zero() {
return
}
let revert_to = included_in - One::one();
<Included<T>>::insert(&session, &candidate_hash, revert_to);
if let Some(state) = <Disputes<T>>::get(&session, candidate_hash) {
if has_supermajority_against(&state) {
Self::revert_and_freeze(revert_to);
}
}
}
pub(crate) fn included_state(
session: SessionIndex,
candidate_hash: CandidateHash,
) -> Option<T::BlockNumber> {
<Included<T>>::get(session, candidate_hash)
}
pub(crate) fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
<Disputes<T>>::get(&session, &candidate_hash).map_or(false, |dispute| {
has_supermajority_against(&dispute)
})
}
pub(crate) fn is_frozen() -> bool {
Self::last_valid_block().is_some()
}
pub(crate) fn revert_and_freeze(revert_to: T::BlockNumber) {
if Self::last_valid_block().map_or(true, |last| last > revert_to) {
Frozen::<T>::set(Some(revert_to));
let revert = revert_to + One::one();
Self::deposit_event(Event::Revert(revert));
frame_system::Pallet::<T>::deposit_log(
ConsensusLog::Revert(revert.saturated_into()).into(),
);
}
}
}
fn has_supermajority_against<BlockNumber>(dispute: &DisputeState<BlockNumber>) -> bool {
let supermajority_threshold = supermajority_threshold(dispute.validators_against.len());
dispute.validators_against.count_ones() >= supermajority_threshold
}
fn check_signature(
validator_public: &ValidatorId,
candidate_hash: CandidateHash,
session: SessionIndex,
statement: &DisputeStatement,
validator_signature: &ValidatorSignature,
) -> Result<(), ()> {
let payload = match *statement {
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) =>
ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(),
DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(inclusion_parent)) =>
CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
session_index: session,
parent_hash: inclusion_parent,
}),
DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) =>
CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
session_index: session,
parent_hash: inclusion_parent,
}),
DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) =>
ApprovalVote(candidate_hash).signing_payload(session),
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) =>
ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(),
};
if validator_signature.verify(&payload[..], &validator_public) {
Ok(())
} else {
Err(())
}
}