1use crate::{
20 configuration, initializer::SessionChangeNotification, metrics::METRICS, session_info,
21};
22use alloc::{collections::btree_set::BTreeSet, vec::Vec};
23use bitvec::{bitvec, order::Lsb0 as BitOrderLsb0};
24use codec::{Decode, DecodeWithMemTracking, Encode};
25use core::cmp::Ordering;
26use frame_support::{ensure, weights::Weight};
27use frame_system::pallet_prelude::*;
28use polkadot_primitives::{
29 byzantine_threshold, supermajority_threshold, ApprovalVote, ApprovalVoteMultipleCandidates,
30 CandidateHash, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CompactStatement,
31 ConsensusLog, DisputeState, DisputeStatement, DisputeStatementSet, ExplicitDisputeStatement,
32 InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex, SigningContext,
33 ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
34};
35use polkadot_runtime_metrics::get_current_time;
36use scale_info::TypeInfo;
37use sp_runtime::{
38 traits::{AppVerify, One, Saturating, Zero},
39 DispatchError, RuntimeDebug, SaturatedConversion,
40};
41
42#[cfg(test)]
43#[allow(unused_imports)]
44pub(crate) use self::tests::run_to_block;
45
46pub mod slashing;
47#[cfg(test)]
48mod tests;
49
50#[cfg(feature = "runtime-benchmarks")]
51mod benchmarking;
52
53pub mod migration;
54
55const LOG_TARGET: &str = "runtime::disputes";
56
57#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
59pub enum DisputeLocation {
60 Local,
61 Remote,
62}
63
64#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
66pub enum DisputeResult {
67 Valid,
68 Invalid,
69}
70
71pub trait RewardValidators {
73 fn reward_dispute_statement(
75 session: SessionIndex,
76 validators: impl IntoIterator<Item = ValidatorIndex>,
77 );
78}
79
80impl RewardValidators for () {
81 fn reward_dispute_statement(_: SessionIndex, _: impl IntoIterator<Item = ValidatorIndex>) {}
82}
83
84pub trait SlashingHandler<BlockNumber> {
86 fn punish_for_invalid(
90 session: SessionIndex,
91 candidate_hash: CandidateHash,
92 losers: impl IntoIterator<Item = ValidatorIndex>,
93 backers: impl IntoIterator<Item = ValidatorIndex>,
94 );
95
96 fn punish_against_valid(
99 session: SessionIndex,
100 candidate_hash: CandidateHash,
101 losers: impl IntoIterator<Item = ValidatorIndex>,
102 backers: impl IntoIterator<Item = ValidatorIndex>,
103 );
104
105 fn initializer_initialize(now: BlockNumber) -> Weight;
107
108 fn initializer_finalize();
110
111 fn initializer_on_new_session(session_index: SessionIndex);
113}
114
115impl<BlockNumber> SlashingHandler<BlockNumber> for () {
116 fn punish_for_invalid(
117 _: SessionIndex,
118 _: CandidateHash,
119 _: impl IntoIterator<Item = ValidatorIndex>,
120 _: impl IntoIterator<Item = ValidatorIndex>,
121 ) {
122 }
123
124 fn punish_against_valid(
125 _: SessionIndex,
126 _: CandidateHash,
127 _: impl IntoIterator<Item = ValidatorIndex>,
128 _: impl IntoIterator<Item = ValidatorIndex>,
129 ) {
130 }
131
132 fn initializer_initialize(_now: BlockNumber) -> Weight {
133 Weight::zero()
134 }
135
136 fn initializer_finalize() {}
137
138 fn initializer_on_new_session(_: SessionIndex) {}
139}
140
141fn dispute_ordering_compare<T: DisputesHandler<BlockNumber>, BlockNumber: Ord>(
146 a: &DisputeStatementSet,
147 b: &DisputeStatementSet,
148) -> Ordering
149where
150 T: ?Sized,
151{
152 let a_local_block =
153 <T as DisputesHandler<BlockNumber>>::included_state(a.session, a.candidate_hash);
154 let b_local_block =
155 <T as DisputesHandler<BlockNumber>>::included_state(b.session, b.candidate_hash);
156 match (a_local_block, b_local_block) {
157 (None, Some(_)) => Ordering::Greater,
159 (Some(_), None) => Ordering::Less,
160 (Some(a_height), Some(b_height)) =>
162 a_height.cmp(&b_height).then_with(|| a.candidate_hash.cmp(&b.candidate_hash)),
163 (None, None) => {
165 let session_ord = a.session.cmp(&b.session);
166 if session_ord == Ordering::Equal {
167 a.candidate_hash.cmp(&b.candidate_hash)
169 } else {
170 session_ord
171 }
172 },
173 }
174}
175
176pub trait DisputesHandler<BlockNumber: Ord> {
181 fn is_frozen() -> bool;
184
185 fn deduplicate_and_sort_dispute_data(
193 statement_sets: &mut MultiDisputeStatementSet,
194 ) -> Result<(), ()> {
195 let n = statement_sets.len();
199
200 statement_sets.sort_by(dispute_ordering_compare::<Self, BlockNumber>);
201 statement_sets
202 .dedup_by(|a, b| a.session == b.session && a.candidate_hash == b.candidate_hash);
203
204 if n == statement_sets.len() {
206 Ok(())
207 } else {
208 Err(())
209 }
210 }
211
212 fn filter_dispute_data(
217 statement_set: DisputeStatementSet,
218 post_conclusion_acceptance_period: BlockNumber,
219 ) -> Option<CheckedDisputeStatementSet>;
220
221 fn process_checked_multi_dispute_data(
224 statement_sets: &CheckedMultiDisputeStatementSet,
225 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError>;
226
227 fn note_included(
229 session: SessionIndex,
230 candidate_hash: CandidateHash,
231 included_in: BlockNumber,
232 );
233
234 fn included_state(session: SessionIndex, candidate_hash: CandidateHash) -> Option<BlockNumber>;
237
238 fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool;
240
241 fn initializer_initialize(now: BlockNumber) -> Weight;
243
244 fn initializer_finalize();
246
247 fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumber>);
249}
250
251impl<BlockNumber: Ord> DisputesHandler<BlockNumber> for () {
252 fn is_frozen() -> bool {
253 false
254 }
255
256 fn deduplicate_and_sort_dispute_data(
257 statement_sets: &mut MultiDisputeStatementSet,
258 ) -> Result<(), ()> {
259 statement_sets.clear();
260 Ok(())
261 }
262
263 fn filter_dispute_data(
264 _set: DisputeStatementSet,
265 _post_conclusion_acceptance_period: BlockNumber,
266 ) -> Option<CheckedDisputeStatementSet> {
267 None
268 }
269
270 fn process_checked_multi_dispute_data(
271 _statement_sets: &CheckedMultiDisputeStatementSet,
272 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
273 Ok(Vec::new())
274 }
275
276 fn note_included(
277 _session: SessionIndex,
278 _candidate_hash: CandidateHash,
279 _included_in: BlockNumber,
280 ) {
281 }
282
283 fn included_state(
284 _session: SessionIndex,
285 _candidate_hash: CandidateHash,
286 ) -> Option<BlockNumber> {
287 None
288 }
289
290 fn concluded_invalid(_session: SessionIndex, _candidate_hash: CandidateHash) -> bool {
291 false
292 }
293
294 fn initializer_initialize(_now: BlockNumber) -> Weight {
295 Weight::zero()
296 }
297
298 fn initializer_finalize() {}
299
300 fn initializer_on_new_session(_notification: &SessionChangeNotification<BlockNumber>) {}
301}
302
303impl<T: Config> DisputesHandler<BlockNumberFor<T>> for pallet::Pallet<T>
304where
305 BlockNumberFor<T>: Ord,
306{
307 fn is_frozen() -> bool {
308 pallet::Pallet::<T>::is_frozen()
309 }
310
311 fn filter_dispute_data(
312 set: DisputeStatementSet,
313 post_conclusion_acceptance_period: BlockNumberFor<T>,
314 ) -> Option<CheckedDisputeStatementSet> {
315 pallet::Pallet::<T>::filter_dispute_data(&set, post_conclusion_acceptance_period)
316 .filter_statement_set(set)
317 }
318
319 fn process_checked_multi_dispute_data(
320 statement_sets: &CheckedMultiDisputeStatementSet,
321 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
322 pallet::Pallet::<T>::process_checked_multi_dispute_data(statement_sets)
323 }
324
325 fn note_included(
326 session: SessionIndex,
327 candidate_hash: CandidateHash,
328 included_in: BlockNumberFor<T>,
329 ) {
330 pallet::Pallet::<T>::note_included(session, candidate_hash, included_in)
331 }
332
333 fn included_state(
334 session: SessionIndex,
335 candidate_hash: CandidateHash,
336 ) -> Option<BlockNumberFor<T>> {
337 pallet::Pallet::<T>::included_state(session, candidate_hash)
338 }
339
340 fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
341 pallet::Pallet::<T>::concluded_invalid(session, candidate_hash)
342 }
343
344 fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
345 pallet::Pallet::<T>::initializer_initialize(now)
346 }
347
348 fn initializer_finalize() {
349 pallet::Pallet::<T>::initializer_finalize()
350 }
351
352 fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
353 pallet::Pallet::<T>::initializer_on_new_session(notification)
354 }
355}
356
357pub trait WeightInfo {
358 fn force_unfreeze() -> Weight;
359}
360
361pub struct TestWeightInfo;
362impl WeightInfo for TestWeightInfo {
363 fn force_unfreeze() -> Weight {
364 Weight::zero()
365 }
366}
367
368pub use pallet::*;
369#[frame_support::pallet]
370pub mod pallet {
371 use super::*;
372 use frame_support::pallet_prelude::*;
373
374 #[pallet::config]
375 pub trait Config: frame_system::Config + configuration::Config + session_info::Config {
376 #[allow(deprecated)]
377 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
378 type RewardValidators: RewardValidators;
379 type SlashingHandler: SlashingHandler<BlockNumberFor<Self>>;
380
381 type WeightInfo: WeightInfo;
383 }
384
385 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
387
388 #[pallet::pallet]
389 #[pallet::without_storage_info]
390 #[pallet::storage_version(STORAGE_VERSION)]
391 pub struct Pallet<T>(_);
392
393 #[pallet::storage]
396 pub(super) type LastPrunedSession<T> = StorageValue<_, SessionIndex>;
397
398 #[pallet::storage]
400 pub(super) type Disputes<T: Config> = StorageDoubleMap<
401 _,
402 Twox64Concat,
403 SessionIndex,
404 Blake2_128Concat,
405 CandidateHash,
406 DisputeState<BlockNumberFor<T>>,
407 >;
408
409 #[pallet::storage]
412 pub(super) type BackersOnDisputes<T: Config> = StorageDoubleMap<
413 _,
414 Twox64Concat,
415 SessionIndex,
416 Blake2_128Concat,
417 CandidateHash,
418 BTreeSet<ValidatorIndex>,
419 >;
420
421 #[pallet::storage]
424 pub(super) type Included<T: Config> = StorageDoubleMap<
425 _,
426 Twox64Concat,
427 SessionIndex,
428 Blake2_128Concat,
429 CandidateHash,
430 BlockNumberFor<T>,
431 >;
432
433 #[pallet::storage]
438 pub type Frozen<T: Config> = StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
439
440 #[pallet::event]
441 #[pallet::generate_deposit(pub fn deposit_event)]
442 pub enum Event<T: Config> {
443 DisputeInitiated(CandidateHash, DisputeLocation),
445 DisputeConcluded(CandidateHash, DisputeResult),
448 Revert(BlockNumberFor<T>),
453 }
454
455 #[pallet::error]
456 pub enum Error<T> {
457 DuplicateDisputeStatementSets,
459 AncientDisputeStatement,
461 ValidatorIndexOutOfBounds,
463 InvalidSignature,
465 DuplicateStatement,
467 SingleSidedDispute,
469 MaliciousBacker,
471 MissingBackingVotes,
473 UnconfirmedDispute,
475 }
476
477 #[pallet::call]
478 impl<T: Config> Pallet<T> {
479 #[pallet::call_index(0)]
480 #[pallet::weight(<T as Config>::WeightInfo::force_unfreeze())]
481 pub fn force_unfreeze(origin: OriginFor<T>) -> DispatchResult {
482 ensure_root(origin)?;
483 Frozen::<T>::set(None);
484 Ok(())
485 }
486 }
487}
488
489bitflags::bitflags! {
490 #[derive(Default)]
491 struct DisputeStateFlags: u8 {
492 const CONFIRMED = 0b0001;
494 const FOR_SUPERMAJORITY = 0b0010;
496 const AGAINST_SUPERMAJORITY = 0b0100;
498 const AGAINST_BYZANTINE = 0b1000;
500 }
501}
502
503impl DisputeStateFlags {
504 fn from_state<BlockNumber>(state: &DisputeState<BlockNumber>) -> Self {
505 let n = state.validators_for.len();
508
509 let byzantine_threshold = byzantine_threshold(n);
510 let supermajority_threshold = supermajority_threshold(n);
511
512 let mut flags = DisputeStateFlags::default();
513 let all_participants = state.validators_for.clone() | state.validators_against.clone();
514 if all_participants.count_ones() > byzantine_threshold {
515 flags |= DisputeStateFlags::CONFIRMED;
516 }
517
518 if state.validators_for.count_ones() >= supermajority_threshold {
519 flags |= DisputeStateFlags::FOR_SUPERMAJORITY;
520 }
521
522 if state.validators_against.count_ones() > byzantine_threshold {
523 flags |= DisputeStateFlags::AGAINST_BYZANTINE;
524 }
525
526 if state.validators_against.count_ones() >= supermajority_threshold {
527 flags |= DisputeStateFlags::AGAINST_SUPERMAJORITY;
528 }
529
530 flags
531 }
532}
533
534struct ImportSummary<BlockNumber> {
535 state: DisputeState<BlockNumber>,
537 backers: BTreeSet<ValidatorIndex>,
539 slash_against: Vec<ValidatorIndex>,
541 slash_for: Vec<ValidatorIndex>,
543 new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
545 new_flags: DisputeStateFlags,
547}
548
549#[derive(RuntimeDebug, PartialEq, Eq)]
550enum VoteImportError {
551 ValidatorIndexOutOfBounds,
553 DuplicateStatement,
555 MaliciousBacker,
559}
560
561#[derive(RuntimeDebug, Copy, Clone, PartialEq, Eq)]
562enum VoteKind {
563 Backing,
565 ExplicitValid,
567 Invalid,
569}
570
571impl From<&DisputeStatement> for VoteKind {
572 fn from(statement: &DisputeStatement) -> Self {
573 if statement.is_backing() {
574 Self::Backing
575 } else if statement.indicates_validity() {
576 Self::ExplicitValid
577 } else {
578 Self::Invalid
579 }
580 }
581}
582
583impl VoteKind {
584 fn is_valid(&self) -> bool {
585 match self {
586 Self::Backing | Self::ExplicitValid => true,
587 Self::Invalid => false,
588 }
589 }
590
591 fn is_backing(&self) -> bool {
592 match self {
593 Self::Backing => true,
594 Self::Invalid | Self::ExplicitValid => false,
595 }
596 }
597}
598
599impl<T: Config> From<VoteImportError> for Error<T> {
600 fn from(e: VoteImportError) -> Self {
601 match e {
602 VoteImportError::ValidatorIndexOutOfBounds => Error::<T>::ValidatorIndexOutOfBounds,
603 VoteImportError::DuplicateStatement => Error::<T>::DuplicateStatement,
604 VoteImportError::MaliciousBacker => Error::<T>::MaliciousBacker,
605 }
606 }
607}
608
609#[derive(RuntimeDebug, PartialEq, Eq)]
611struct ImportUndo {
612 validator_index: ValidatorIndex,
614 vote_kind: VoteKind,
616 new_participant: bool,
619}
620
621struct DisputeStateImporter<BlockNumber> {
622 state: DisputeState<BlockNumber>,
623 backers: BTreeSet<ValidatorIndex>,
624 now: BlockNumber,
625 new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
626 pre_flags: DisputeStateFlags,
627 pre_state: DisputeState<BlockNumber>,
628 pre_backers: BTreeSet<ValidatorIndex>,
636}
637
638impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
639 fn new(
640 state: DisputeState<BlockNumber>,
641 backers: BTreeSet<ValidatorIndex>,
642 now: BlockNumber,
643 ) -> Self {
644 let pre_flags = DisputeStateFlags::from_state(&state);
645 let new_participants = bitvec::bitvec![u8, BitOrderLsb0; 0; state.validators_for.len()];
646 for i in backers.iter() {
648 debug_assert_eq!(state.validators_for.get(i.0 as usize).map(|b| *b), Some(true));
649 }
650 let pre_state = state.clone();
651 let pre_backers = backers.clone();
652
653 DisputeStateImporter {
654 state,
655 backers,
656 now,
657 new_participants,
658 pre_flags,
659 pre_state,
660 pre_backers,
661 }
662 }
663
664 fn import(
665 &mut self,
666 validator: ValidatorIndex,
667 kind: VoteKind,
668 ) -> Result<ImportUndo, VoteImportError> {
669 let (bits, other_bits) = if kind.is_valid() {
670 (&mut self.state.validators_for, &mut self.state.validators_against)
671 } else {
672 (&mut self.state.validators_against, &mut self.state.validators_for)
673 };
674
675 match bits.get(validator.0 as usize).map(|b| *b) {
677 None => return Err(VoteImportError::ValidatorIndexOutOfBounds),
678 Some(true) => {
679 match (kind.is_backing(), self.backers.contains(&validator)) {
682 (true, true) | (false, false) =>
683 return Err(VoteImportError::DuplicateStatement),
684 (false, true) => return Err(VoteImportError::MaliciousBacker),
685 (true, false) => {},
686 }
687 },
688 Some(false) => {},
689 }
690
691 debug_assert!((validator.0 as usize) < self.new_participants.len());
693
694 let mut undo =
695 ImportUndo { validator_index: validator, vote_kind: kind, new_participant: false };
696
697 bits.set(validator.0 as usize, true);
698 if kind.is_backing() {
699 let is_new = self.backers.insert(validator);
700 debug_assert!(is_new);
702 }
703
704 if other_bits.get(validator.0 as usize).map_or(false, |b| !*b) {
708 undo.new_participant = true;
709 self.new_participants.set(validator.0 as usize, true);
710 }
711
712 Ok(undo)
713 }
714
715 fn undo(&mut self, undo: ImportUndo) {
717 if undo.vote_kind.is_valid() {
718 self.state.validators_for.set(undo.validator_index.0 as usize, false);
719 } else {
720 self.state.validators_against.set(undo.validator_index.0 as usize, false);
721 }
722
723 if undo.vote_kind.is_backing() {
724 self.backers.remove(&undo.validator_index);
725 }
726
727 if undo.new_participant {
728 self.new_participants.set(undo.validator_index.0 as usize, false);
729 }
730 }
731
732 fn finish(mut self) -> ImportSummary<BlockNumber> {
734 let pre_flags = self.pre_flags;
735 let post_flags = DisputeStateFlags::from_state(&self.state);
736
737 let pre_post_contains = |flags| (pre_flags.contains(flags), post_flags.contains(flags));
738
739 let slash_against = match pre_post_contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
741 (false, true) => {
742 if self.state.concluded_at.is_none() {
743 self.state.concluded_at = Some(self.now.clone());
744 }
745
746 self.state
748 .validators_against
749 .iter_ones()
750 .map(|i| ValidatorIndex(i as _))
751 .collect()
752 },
753 (true, true) => {
754 self.state
756 .validators_against
757 .iter_ones()
758 .filter(|i| self.pre_state.validators_against.get(*i).map_or(false, |b| !*b))
759 .map(|i| ValidatorIndex(i as _))
760 .collect()
761 },
762 (true, false) => {
763 log::error!("Dispute statements are never removed. This is a bug");
764 Vec::new()
765 },
766 (false, false) => Vec::new(),
767 };
768
769 let slash_for = match pre_post_contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
771 (false, true) => {
772 if self.state.concluded_at.is_none() {
773 self.state.concluded_at = Some(self.now.clone());
774 }
775
776 self.state.validators_for.iter_ones().map(|i| ValidatorIndex(i as _)).collect()
778 },
779 (true, true) => {
780 let new_backing_vote = |i: &ValidatorIndex| -> bool {
783 !self.pre_backers.contains(i) && self.backers.contains(i)
784 };
785 self.state
786 .validators_for
787 .iter_ones()
788 .filter(|i| {
789 self.pre_state.validators_for.get(*i).map_or(false, |b| !*b) ||
790 new_backing_vote(&ValidatorIndex(*i as _))
791 })
792 .map(|i| ValidatorIndex(i as _))
793 .collect()
794 },
795 (true, false) => {
796 log::error!("Dispute statements are never removed. This is a bug");
797 Vec::new()
798 },
799 (false, false) => Vec::new(),
800 };
801
802 ImportSummary {
803 state: self.state,
804 backers: self.backers,
805 slash_against,
806 slash_for,
807 new_participants: self.new_participants,
808 new_flags: post_flags - pre_flags,
809 }
810 }
811}
812
813#[derive(PartialEq)]
815#[cfg_attr(test, derive(Debug))]
816enum StatementSetFilter {
817 RemoveAll,
819 RemoveIndices(Vec<usize>),
821}
822
823impl StatementSetFilter {
824 fn filter_statement_set(
825 self,
826 mut statement_set: DisputeStatementSet,
827 ) -> Option<CheckedDisputeStatementSet> {
828 match self {
829 StatementSetFilter::RemoveAll => None,
830 StatementSetFilter::RemoveIndices(mut indices) => {
831 indices.sort();
832 indices.dedup();
833
834 for index in indices.into_iter().rev() {
836 statement_set.statements.swap_remove(index);
838 }
839
840 if statement_set.statements.is_empty() {
841 None
842 } else {
843 Some(CheckedDisputeStatementSet::unchecked_from_unchecked(statement_set))
845 }
846 },
847 }
848 }
849
850 fn remove_index(&mut self, i: usize) {
851 if let StatementSetFilter::RemoveIndices(ref mut indices) = *self {
852 indices.push(i)
853 }
854 }
855}
856
857impl<T: Config> Pallet<T> {
858 pub(crate) fn initializer_initialize(_now: BlockNumberFor<T>) -> Weight {
860 Weight::zero()
861 }
862
863 pub(crate) fn initializer_finalize() {}
865
866 pub(crate) fn initializer_on_new_session(
868 notification: &SessionChangeNotification<BlockNumberFor<T>>,
869 ) {
870 let config = configuration::ActiveConfig::<T>::get();
871
872 if notification.session_index <= config.dispute_period + 1 {
873 return
874 }
875
876 let pruning_target = notification.session_index - config.dispute_period - 1;
877
878 LastPrunedSession::<T>::mutate(|last_pruned| {
879 let to_prune = if let Some(last_pruned) = last_pruned {
880 *last_pruned + 1..=pruning_target
881 } else {
882 pruning_target..=pruning_target
883 };
884
885 for to_prune in to_prune {
886 #[allow(deprecated)]
888 Disputes::<T>::remove_prefix(to_prune, None);
889 #[allow(deprecated)]
890 BackersOnDisputes::<T>::remove_prefix(to_prune, None);
891
892 #[allow(deprecated)]
895 Included::<T>::remove_prefix(to_prune, None);
896 }
897
898 *last_pruned = Some(pruning_target);
899 });
900 }
901
902 pub(crate) fn process_checked_multi_dispute_data(
913 statement_sets: &CheckedMultiDisputeStatementSet,
914 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
915 let config = configuration::ActiveConfig::<T>::get();
916
917 let mut fresh = Vec::with_capacity(statement_sets.len());
918 for statement_set in statement_sets {
919 let dispute_target = {
920 let statement_set = statement_set.as_ref();
921 (statement_set.session, statement_set.candidate_hash)
922 };
923 if Self::process_checked_dispute_data(
924 statement_set,
925 config.dispute_post_conclusion_acceptance_period,
926 )? {
927 fresh.push(dispute_target);
928 }
929 }
930
931 Ok(fresh)
932 }
933
934 fn filter_dispute_data(
941 set: &DisputeStatementSet,
942 post_conclusion_acceptance_period: BlockNumberFor<T>,
943 ) -> StatementSetFilter {
944 let mut filter = StatementSetFilter::RemoveIndices(Vec::new());
945
946 let now = frame_system::Pallet::<T>::block_number();
949 let oldest_accepted = now.saturating_sub(post_conclusion_acceptance_period);
950
951 let session_info = match session_info::Sessions::<T>::get(set.session) {
953 Some(s) => s,
954 None => return StatementSetFilter::RemoveAll,
955 };
956
957 let config = configuration::ActiveConfig::<T>::get();
958
959 let n_validators = session_info.validators.len();
960
961 let dispute_state = {
963 if let Some(dispute_state) = Disputes::<T>::get(&set.session, &set.candidate_hash) {
964 if dispute_state.concluded_at.as_ref().map_or(false, |c| c < &oldest_accepted) {
965 return StatementSetFilter::RemoveAll
966 }
967
968 dispute_state
969 } else {
970 DisputeState {
972 validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
973 validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
974 start: now,
975 concluded_at: None,
976 }
977 }
978 };
979
980 let backers =
981 BackersOnDisputes::<T>::get(&set.session, &set.candidate_hash).unwrap_or_default();
982
983 let summary = {
985 let mut importer = DisputeStateImporter::new(dispute_state, backers, now);
986 for (i, (statement, validator_index, signature)) in set.statements.iter().enumerate() {
987 let validator_public = match session_info.validators.get(*validator_index) {
990 None => {
991 filter.remove_index(i);
992 continue
993 },
994 Some(v) => v,
995 };
996
997 let kind = VoteKind::from(statement);
998
999 let undo = match importer.import(*validator_index, kind) {
1000 Ok(u) => u,
1001 Err(_) => {
1002 filter.remove_index(i);
1003 continue
1004 },
1005 };
1006
1007 if let Err(()) = check_signature(
1017 &validator_public,
1018 set.candidate_hash,
1019 set.session,
1020 statement,
1021 signature,
1022 config.approval_voting_params.max_approval_coalesce_count > 1,
1027 ) {
1028 log::warn!("Failed to check dispute signature");
1029
1030 importer.undo(undo);
1031 filter.remove_index(i);
1032 continue
1033 };
1034 }
1035
1036 importer.finish()
1037 };
1038
1039 if summary.state.validators_for.count_ones() == 0 ||
1041 summary.state.validators_against.count_ones() == 0
1042 {
1043 return StatementSetFilter::RemoveAll
1044 }
1045
1046 if (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() <=
1048 byzantine_threshold(summary.state.validators_for.len())
1049 {
1050 return StatementSetFilter::RemoveAll
1051 }
1052
1053 filter
1054 }
1055
1056 fn process_checked_dispute_data(
1061 set: &CheckedDisputeStatementSet,
1062 dispute_post_conclusion_acceptance_period: BlockNumberFor<T>,
1063 ) -> Result<bool, DispatchError> {
1064 let now = frame_system::Pallet::<T>::block_number();
1067 let oldest_accepted = now.saturating_sub(dispute_post_conclusion_acceptance_period);
1068
1069 let set = set.as_ref();
1070
1071 let session_info = match session_info::Sessions::<T>::get(set.session) {
1073 Some(s) => s,
1074 None => return Err(Error::<T>::AncientDisputeStatement.into()),
1075 };
1076
1077 let n_validators = session_info.validators.len();
1078
1079 let (fresh, dispute_state) = {
1081 if let Some(dispute_state) = Disputes::<T>::get(&set.session, &set.candidate_hash) {
1082 ensure!(
1083 dispute_state.concluded_at.as_ref().map_or(true, |c| c >= &oldest_accepted),
1084 Error::<T>::AncientDisputeStatement,
1085 );
1086
1087 (false, dispute_state)
1088 } else {
1089 (
1090 true,
1091 DisputeState {
1092 validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
1093 validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
1094 start: now,
1095 concluded_at: None,
1096 },
1097 )
1098 }
1099 };
1100
1101 let backers =
1102 BackersOnDisputes::<T>::get(&set.session, &set.candidate_hash).unwrap_or_default();
1103
1104 let summary = {
1106 let mut importer = DisputeStateImporter::new(dispute_state, backers, now);
1107 for (statement, validator_index, _signature) in &set.statements {
1108 let kind = VoteKind::from(statement);
1109
1110 importer.import(*validator_index, kind).map_err(Error::<T>::from)?;
1111 }
1112
1113 importer.finish()
1114 };
1115
1116 ensure!(
1118 summary.state.validators_for.count_ones() > 0 &&
1119 summary.state.validators_against.count_ones() > 0,
1120 Error::<T>::SingleSidedDispute,
1121 );
1122
1123 ensure!(
1125 (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() >
1126 byzantine_threshold(summary.state.validators_for.len()),
1127 Error::<T>::UnconfirmedDispute,
1128 );
1129 let backers = summary.backers;
1130 ensure!(!backers.is_empty(), Error::<T>::MissingBackingVotes);
1132 BackersOnDisputes::<T>::insert(&set.session, &set.candidate_hash, backers.clone());
1133 let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
1136 let session = *session;
1137 let candidate_hash = *candidate_hash;
1138
1139 if fresh {
1140 let is_local = Included::<T>::contains_key(&session, &candidate_hash);
1141
1142 Self::deposit_event(Event::DisputeInitiated(
1143 candidate_hash,
1144 if is_local { DisputeLocation::Local } else { DisputeLocation::Remote },
1145 ));
1146 }
1147
1148 {
1149 if summary.new_flags.contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
1150 Self::deposit_event(Event::DisputeConcluded(candidate_hash, DisputeResult::Valid));
1151 }
1152
1153 if summary.new_flags.contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
1158 Self::deposit_event(Event::DisputeConcluded(
1159 candidate_hash,
1160 DisputeResult::Invalid,
1161 ));
1162 }
1163 }
1164
1165 T::RewardValidators::reward_dispute_statement(
1167 session,
1168 summary.new_participants.iter_ones().map(|i| ValidatorIndex(i as _)),
1169 );
1170
1171 {
1173 T::SlashingHandler::punish_against_valid(
1175 session,
1176 candidate_hash,
1177 summary.slash_against,
1178 backers.clone(),
1179 );
1180
1181 T::SlashingHandler::punish_for_invalid(
1183 session,
1184 candidate_hash,
1185 summary.slash_for,
1186 backers,
1187 );
1188 }
1189
1190 Disputes::<T>::insert(&session, &candidate_hash, &summary.state);
1191
1192 if summary.new_flags.contains(DisputeStateFlags::AGAINST_BYZANTINE) {
1195 if let Some(revert_to) = Included::<T>::get(&session, &candidate_hash) {
1196 Self::revert_and_freeze(revert_to);
1197 }
1198 }
1199
1200 Ok(fresh)
1201 }
1202
1203 #[allow(unused)]
1204 pub(crate) fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumberFor<T>>)>
1205 {
1206 Disputes::<T>::iter().collect()
1207 }
1208
1209 pub(crate) fn note_included(
1210 session: SessionIndex,
1211 candidate_hash: CandidateHash,
1212 included_in: BlockNumberFor<T>,
1213 ) {
1214 if included_in.is_zero() {
1215 return
1216 }
1217
1218 let revert_to = included_in - One::one();
1219
1220 Included::<T>::insert(&session, &candidate_hash, revert_to);
1221
1222 if let Some(state) = Disputes::<T>::get(&session, candidate_hash) {
1223 if has_supermajority_against(&state) {
1224 Self::revert_and_freeze(revert_to);
1225 }
1226 }
1227 }
1228
1229 pub(crate) fn included_state(
1230 session: SessionIndex,
1231 candidate_hash: CandidateHash,
1232 ) -> Option<BlockNumberFor<T>> {
1233 Included::<T>::get(session, candidate_hash)
1234 }
1235
1236 pub(crate) fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
1237 Disputes::<T>::get(&session, &candidate_hash).map_or(false, |dispute| {
1238 has_supermajority_against(&dispute)
1240 })
1241 }
1242
1243 pub(crate) fn is_frozen() -> bool {
1244 Frozen::<T>::get().is_some()
1245 }
1246
1247 pub(crate) fn revert_and_freeze(revert_to: BlockNumberFor<T>) {
1248 if Frozen::<T>::get().map_or(true, |last| last > revert_to) {
1249 Frozen::<T>::set(Some(revert_to));
1250
1251 let revert = revert_to + One::one();
1255 Self::deposit_event(Event::Revert(revert));
1256 frame_system::Pallet::<T>::deposit_log(
1257 ConsensusLog::Revert(revert.saturated_into()).into(),
1258 );
1259 }
1260 }
1261}
1262
1263fn has_supermajority_against<BlockNumber>(dispute: &DisputeState<BlockNumber>) -> bool {
1264 let supermajority_threshold = supermajority_threshold(dispute.validators_against.len());
1265 dispute.validators_against.count_ones() >= supermajority_threshold
1266}
1267
1268fn check_signature(
1269 validator_public: &ValidatorId,
1270 candidate_hash: CandidateHash,
1271 session: SessionIndex,
1272 statement: &DisputeStatement,
1273 validator_signature: &ValidatorSignature,
1274 approval_multiple_candidates_enabled: bool,
1275) -> Result<(), ()> {
1276 let payload = match statement {
1277 DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) =>
1278 ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(),
1279 DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(inclusion_parent)) =>
1280 CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
1281 session_index: session,
1282 parent_hash: *inclusion_parent,
1283 }),
1284 DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) =>
1285 CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
1286 session_index: session,
1287 parent_hash: *inclusion_parent,
1288 }),
1289 DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) =>
1290 ApprovalVote(candidate_hash).signing_payload(session),
1291 DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(
1292 candidates,
1293 )) =>
1294 if approval_multiple_candidates_enabled && candidates.contains(&candidate_hash) {
1295 ApprovalVoteMultipleCandidates(candidates).signing_payload(session)
1296 } else {
1297 return Err(())
1298 },
1299 DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) =>
1300 ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(),
1301 };
1302
1303 let start = get_current_time();
1304
1305 let res =
1306 if validator_signature.verify(&payload[..], &validator_public) { Ok(()) } else { Err(()) };
1307
1308 let end = get_current_time();
1309
1310 METRICS.on_signature_check_complete(end.saturating_sub(start)); res
1313}
1314
1315#[cfg(all(not(feature = "runtime-benchmarks"), test))]
1316pub(crate) fn clear_dispute_storage<T: Config>() {
1318 let _ = Disputes::<T>::clear(u32::MAX, None);
1319 let _ = BackersOnDisputes::<T>::clear(u32::MAX, None);
1320 let _ = Included::<T>::clear(u32::MAX, None);
1321}