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 Debug, DispatchError, 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, Debug, TypeInfo)]
59pub enum DisputeLocation {
60 Local,
61 Remote,
62}
63
64#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, 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 },
164 (None, None) => {
166 let session_ord = a.session.cmp(&b.session);
167 if session_ord == Ordering::Equal {
168 a.candidate_hash.cmp(&b.candidate_hash)
170 } else {
171 session_ord
172 }
173 },
174 }
175}
176
177pub trait DisputesHandler<BlockNumber: Ord> {
182 fn is_frozen() -> bool;
185
186 fn deduplicate_and_sort_dispute_data(
194 statement_sets: &mut MultiDisputeStatementSet,
195 ) -> Result<(), ()> {
196 let n = statement_sets.len();
200
201 statement_sets.sort_by(dispute_ordering_compare::<Self, BlockNumber>);
202 statement_sets
203 .dedup_by(|a, b| a.session == b.session && a.candidate_hash == b.candidate_hash);
204
205 if n == statement_sets.len() {
207 Ok(())
208 } else {
209 Err(())
210 }
211 }
212
213 fn filter_dispute_data(
218 statement_set: DisputeStatementSet,
219 post_conclusion_acceptance_period: BlockNumber,
220 ) -> Option<CheckedDisputeStatementSet>;
221
222 fn process_checked_multi_dispute_data(
225 statement_sets: &CheckedMultiDisputeStatementSet,
226 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError>;
227
228 fn note_included(
230 session: SessionIndex,
231 candidate_hash: CandidateHash,
232 included_in: BlockNumber,
233 );
234
235 fn included_state(session: SessionIndex, candidate_hash: CandidateHash) -> Option<BlockNumber>;
238
239 fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool;
241
242 fn initializer_initialize(now: BlockNumber) -> Weight;
244
245 fn initializer_finalize();
247
248 fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumber>);
250}
251
252impl<BlockNumber: Ord> DisputesHandler<BlockNumber> for () {
253 fn is_frozen() -> bool {
254 false
255 }
256
257 fn deduplicate_and_sort_dispute_data(
258 statement_sets: &mut MultiDisputeStatementSet,
259 ) -> Result<(), ()> {
260 statement_sets.clear();
261 Ok(())
262 }
263
264 fn filter_dispute_data(
265 _set: DisputeStatementSet,
266 _post_conclusion_acceptance_period: BlockNumber,
267 ) -> Option<CheckedDisputeStatementSet> {
268 None
269 }
270
271 fn process_checked_multi_dispute_data(
272 _statement_sets: &CheckedMultiDisputeStatementSet,
273 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
274 Ok(Vec::new())
275 }
276
277 fn note_included(
278 _session: SessionIndex,
279 _candidate_hash: CandidateHash,
280 _included_in: BlockNumber,
281 ) {
282 }
283
284 fn included_state(
285 _session: SessionIndex,
286 _candidate_hash: CandidateHash,
287 ) -> Option<BlockNumber> {
288 None
289 }
290
291 fn concluded_invalid(_session: SessionIndex, _candidate_hash: CandidateHash) -> bool {
292 false
293 }
294
295 fn initializer_initialize(_now: BlockNumber) -> Weight {
296 Weight::zero()
297 }
298
299 fn initializer_finalize() {}
300
301 fn initializer_on_new_session(_notification: &SessionChangeNotification<BlockNumber>) {}
302}
303
304impl<T: Config> DisputesHandler<BlockNumberFor<T>> for pallet::Pallet<T>
305where
306 BlockNumberFor<T>: Ord,
307{
308 fn is_frozen() -> bool {
309 pallet::Pallet::<T>::is_frozen()
310 }
311
312 fn filter_dispute_data(
313 set: DisputeStatementSet,
314 post_conclusion_acceptance_period: BlockNumberFor<T>,
315 ) -> Option<CheckedDisputeStatementSet> {
316 pallet::Pallet::<T>::filter_dispute_data(&set, post_conclusion_acceptance_period)
317 .filter_statement_set(set)
318 }
319
320 fn process_checked_multi_dispute_data(
321 statement_sets: &CheckedMultiDisputeStatementSet,
322 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
323 pallet::Pallet::<T>::process_checked_multi_dispute_data(statement_sets)
324 }
325
326 fn note_included(
327 session: SessionIndex,
328 candidate_hash: CandidateHash,
329 included_in: BlockNumberFor<T>,
330 ) {
331 pallet::Pallet::<T>::note_included(session, candidate_hash, included_in)
332 }
333
334 fn included_state(
335 session: SessionIndex,
336 candidate_hash: CandidateHash,
337 ) -> Option<BlockNumberFor<T>> {
338 pallet::Pallet::<T>::included_state(session, candidate_hash)
339 }
340
341 fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
342 pallet::Pallet::<T>::concluded_invalid(session, candidate_hash)
343 }
344
345 fn initializer_initialize(now: BlockNumberFor<T>) -> Weight {
346 pallet::Pallet::<T>::initializer_initialize(now)
347 }
348
349 fn initializer_finalize() {
350 pallet::Pallet::<T>::initializer_finalize()
351 }
352
353 fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
354 pallet::Pallet::<T>::initializer_on_new_session(notification)
355 }
356}
357
358pub trait WeightInfo {
359 fn force_unfreeze() -> Weight;
360}
361
362pub struct TestWeightInfo;
363impl WeightInfo for TestWeightInfo {
364 fn force_unfreeze() -> Weight {
365 Weight::zero()
366 }
367}
368
369pub use pallet::*;
370#[frame_support::pallet]
371pub mod pallet {
372 use super::*;
373 use frame_support::pallet_prelude::*;
374
375 #[pallet::config]
376 pub trait Config: frame_system::Config + configuration::Config + session_info::Config {
377 #[allow(deprecated)]
378 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
379 type RewardValidators: RewardValidators;
380 type SlashingHandler: SlashingHandler<BlockNumberFor<Self>>;
381
382 type WeightInfo: WeightInfo;
384 }
385
386 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
388
389 #[pallet::pallet]
390 #[pallet::without_storage_info]
391 #[pallet::storage_version(STORAGE_VERSION)]
392 pub struct Pallet<T>(_);
393
394 #[pallet::storage]
397 pub(super) type LastPrunedSession<T> = StorageValue<_, SessionIndex>;
398
399 #[pallet::storage]
401 pub(super) type Disputes<T: Config> = StorageDoubleMap<
402 _,
403 Twox64Concat,
404 SessionIndex,
405 Blake2_128Concat,
406 CandidateHash,
407 DisputeState<BlockNumberFor<T>>,
408 >;
409
410 #[pallet::storage]
413 pub(super) type BackersOnDisputes<T: Config> = StorageDoubleMap<
414 _,
415 Twox64Concat,
416 SessionIndex,
417 Blake2_128Concat,
418 CandidateHash,
419 BTreeSet<ValidatorIndex>,
420 >;
421
422 #[pallet::storage]
425 pub(super) type Included<T: Config> = StorageDoubleMap<
426 _,
427 Twox64Concat,
428 SessionIndex,
429 Blake2_128Concat,
430 CandidateHash,
431 BlockNumberFor<T>,
432 >;
433
434 #[pallet::storage]
439 pub type Frozen<T: Config> = StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
440
441 #[pallet::event]
442 #[pallet::generate_deposit(pub fn deposit_event)]
443 pub enum Event<T: Config> {
444 DisputeInitiated(CandidateHash, DisputeLocation),
446 DisputeConcluded(CandidateHash, DisputeResult),
449 Revert(BlockNumberFor<T>),
454 }
455
456 #[pallet::error]
457 pub enum Error<T> {
458 DuplicateDisputeStatementSets,
460 AncientDisputeStatement,
462 ValidatorIndexOutOfBounds,
464 InvalidSignature,
466 DuplicateStatement,
468 SingleSidedDispute,
470 MaliciousBacker,
472 MissingBackingVotes,
474 UnconfirmedDispute,
476 }
477
478 #[pallet::call]
479 impl<T: Config> Pallet<T> {
480 #[pallet::call_index(0)]
481 #[pallet::weight(<T as Config>::WeightInfo::force_unfreeze())]
482 pub fn force_unfreeze(origin: OriginFor<T>) -> DispatchResult {
483 ensure_root(origin)?;
484 Frozen::<T>::set(None);
485 Ok(())
486 }
487 }
488}
489
490bitflags::bitflags! {
491 #[derive(Default)]
492 struct DisputeStateFlags: u8 {
493 const CONFIRMED = 0b0001;
495 const FOR_SUPERMAJORITY = 0b0010;
497 const AGAINST_SUPERMAJORITY = 0b0100;
499 const AGAINST_BYZANTINE = 0b1000;
501 }
502}
503
504impl DisputeStateFlags {
505 fn from_state<BlockNumber>(state: &DisputeState<BlockNumber>) -> Self {
506 let n = state.validators_for.len();
509
510 let byzantine_threshold = byzantine_threshold(n);
511 let supermajority_threshold = supermajority_threshold(n);
512
513 let mut flags = DisputeStateFlags::default();
514 let all_participants = state.validators_for.clone() | state.validators_against.clone();
515 if all_participants.count_ones() > byzantine_threshold {
516 flags |= DisputeStateFlags::CONFIRMED;
517 }
518
519 if state.validators_for.count_ones() >= supermajority_threshold {
520 flags |= DisputeStateFlags::FOR_SUPERMAJORITY;
521 }
522
523 if state.validators_against.count_ones() > byzantine_threshold {
524 flags |= DisputeStateFlags::AGAINST_BYZANTINE;
525 }
526
527 if state.validators_against.count_ones() >= supermajority_threshold {
528 flags |= DisputeStateFlags::AGAINST_SUPERMAJORITY;
529 }
530
531 flags
532 }
533}
534
535struct ImportSummary<BlockNumber> {
536 state: DisputeState<BlockNumber>,
538 backers: BTreeSet<ValidatorIndex>,
540 slash_against: Vec<ValidatorIndex>,
542 slash_for: Vec<ValidatorIndex>,
544 new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
546 new_flags: DisputeStateFlags,
548}
549
550#[derive(Debug, PartialEq, Eq)]
551enum VoteImportError {
552 ValidatorIndexOutOfBounds,
554 DuplicateStatement,
556 MaliciousBacker,
560}
561
562#[derive(Debug, Copy, Clone, PartialEq, Eq)]
563enum VoteKind {
564 Backing,
566 ExplicitValid,
568 Invalid,
570}
571
572impl From<&DisputeStatement> for VoteKind {
573 fn from(statement: &DisputeStatement) -> Self {
574 if statement.is_backing() {
575 Self::Backing
576 } else if statement.indicates_validity() {
577 Self::ExplicitValid
578 } else {
579 Self::Invalid
580 }
581 }
582}
583
584impl VoteKind {
585 fn is_valid(&self) -> bool {
586 match self {
587 Self::Backing | Self::ExplicitValid => true,
588 Self::Invalid => false,
589 }
590 }
591
592 fn is_backing(&self) -> bool {
593 match self {
594 Self::Backing => true,
595 Self::Invalid | Self::ExplicitValid => false,
596 }
597 }
598}
599
600impl<T: Config> From<VoteImportError> for Error<T> {
601 fn from(e: VoteImportError) -> Self {
602 match e {
603 VoteImportError::ValidatorIndexOutOfBounds => Error::<T>::ValidatorIndexOutOfBounds,
604 VoteImportError::DuplicateStatement => Error::<T>::DuplicateStatement,
605 VoteImportError::MaliciousBacker => Error::<T>::MaliciousBacker,
606 }
607 }
608}
609
610#[derive(Debug, PartialEq, Eq)]
612struct ImportUndo {
613 validator_index: ValidatorIndex,
615 vote_kind: VoteKind,
617 new_participant: bool,
620}
621
622struct DisputeStateImporter<BlockNumber> {
623 state: DisputeState<BlockNumber>,
624 backers: BTreeSet<ValidatorIndex>,
625 now: BlockNumber,
626 new_participants: bitvec::vec::BitVec<u8, BitOrderLsb0>,
627 pre_flags: DisputeStateFlags,
628 pre_state: DisputeState<BlockNumber>,
629 pre_backers: BTreeSet<ValidatorIndex>,
637}
638
639impl<BlockNumber: Clone> DisputeStateImporter<BlockNumber> {
640 fn new(
641 state: DisputeState<BlockNumber>,
642 backers: BTreeSet<ValidatorIndex>,
643 now: BlockNumber,
644 ) -> Self {
645 let pre_flags = DisputeStateFlags::from_state(&state);
646 let new_participants = bitvec::bitvec![u8, BitOrderLsb0; 0; state.validators_for.len()];
647 for i in backers.iter() {
649 debug_assert_eq!(state.validators_for.get(i.0 as usize).map(|b| *b), Some(true));
650 }
651 let pre_state = state.clone();
652 let pre_backers = backers.clone();
653
654 DisputeStateImporter {
655 state,
656 backers,
657 now,
658 new_participants,
659 pre_flags,
660 pre_state,
661 pre_backers,
662 }
663 }
664
665 fn import(
666 &mut self,
667 validator: ValidatorIndex,
668 kind: VoteKind,
669 ) -> Result<ImportUndo, VoteImportError> {
670 let (bits, other_bits) = if kind.is_valid() {
671 (&mut self.state.validators_for, &mut self.state.validators_against)
672 } else {
673 (&mut self.state.validators_against, &mut self.state.validators_for)
674 };
675
676 match bits.get(validator.0 as usize).map(|b| *b) {
678 None => return Err(VoteImportError::ValidatorIndexOutOfBounds),
679 Some(true) => {
680 match (kind.is_backing(), self.backers.contains(&validator)) {
683 (true, true) | (false, false) => {
684 return Err(VoteImportError::DuplicateStatement)
685 },
686 (false, true) => return Err(VoteImportError::MaliciousBacker),
687 (true, false) => {},
688 }
689 },
690 Some(false) => {},
691 }
692
693 debug_assert!((validator.0 as usize) < self.new_participants.len());
695
696 let mut undo =
697 ImportUndo { validator_index: validator, vote_kind: kind, new_participant: false };
698
699 bits.set(validator.0 as usize, true);
700 if kind.is_backing() {
701 let is_new = self.backers.insert(validator);
702 debug_assert!(is_new);
704 }
705
706 if other_bits.get(validator.0 as usize).map_or(false, |b| !*b) {
710 undo.new_participant = true;
711 self.new_participants.set(validator.0 as usize, true);
712 }
713
714 Ok(undo)
715 }
716
717 fn undo(&mut self, undo: ImportUndo) {
719 if undo.vote_kind.is_valid() {
720 self.state.validators_for.set(undo.validator_index.0 as usize, false);
721 } else {
722 self.state.validators_against.set(undo.validator_index.0 as usize, false);
723 }
724
725 if undo.vote_kind.is_backing() {
726 self.backers.remove(&undo.validator_index);
727 }
728
729 if undo.new_participant {
730 self.new_participants.set(undo.validator_index.0 as usize, false);
731 }
732 }
733
734 fn finish(mut self) -> ImportSummary<BlockNumber> {
736 let pre_flags = self.pre_flags;
737 let post_flags = DisputeStateFlags::from_state(&self.state);
738
739 let pre_post_contains = |flags| (pre_flags.contains(flags), post_flags.contains(flags));
740
741 let slash_against = match pre_post_contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
743 (false, true) => {
744 if self.state.concluded_at.is_none() {
745 self.state.concluded_at = Some(self.now.clone());
746 }
747
748 self.state
750 .validators_against
751 .iter_ones()
752 .map(|i| ValidatorIndex(i as _))
753 .collect()
754 },
755 (true, true) => {
756 self.state
758 .validators_against
759 .iter_ones()
760 .filter(|i| self.pre_state.validators_against.get(*i).map_or(false, |b| !*b))
761 .map(|i| ValidatorIndex(i as _))
762 .collect()
763 },
764 (true, false) => {
765 log::error!("Dispute statements are never removed. This is a bug");
766 Vec::new()
767 },
768 (false, false) => Vec::new(),
769 };
770
771 let slash_for = match pre_post_contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
773 (false, true) => {
774 if self.state.concluded_at.is_none() {
775 self.state.concluded_at = Some(self.now.clone());
776 }
777
778 self.state.validators_for.iter_ones().map(|i| ValidatorIndex(i as _)).collect()
780 },
781 (true, true) => {
782 let new_backing_vote = |i: &ValidatorIndex| -> bool {
785 !self.pre_backers.contains(i) && self.backers.contains(i)
786 };
787 self.state
788 .validators_for
789 .iter_ones()
790 .filter(|i| {
791 self.pre_state.validators_for.get(*i).map_or(false, |b| !*b) ||
792 new_backing_vote(&ValidatorIndex(*i as _))
793 })
794 .map(|i| ValidatorIndex(i as _))
795 .collect()
796 },
797 (true, false) => {
798 log::error!("Dispute statements are never removed. This is a bug");
799 Vec::new()
800 },
801 (false, false) => Vec::new(),
802 };
803
804 ImportSummary {
805 state: self.state,
806 backers: self.backers,
807 slash_against,
808 slash_for,
809 new_participants: self.new_participants,
810 new_flags: post_flags - pre_flags,
811 }
812 }
813}
814
815#[derive(PartialEq)]
817#[cfg_attr(test, derive(Debug))]
818enum StatementSetFilter {
819 RemoveAll,
821 RemoveIndices(Vec<usize>),
823}
824
825impl StatementSetFilter {
826 fn filter_statement_set(
827 self,
828 mut statement_set: DisputeStatementSet,
829 ) -> Option<CheckedDisputeStatementSet> {
830 match self {
831 StatementSetFilter::RemoveAll => None,
832 StatementSetFilter::RemoveIndices(mut indices) => {
833 indices.sort();
834 indices.dedup();
835
836 for index in indices.into_iter().rev() {
838 statement_set.statements.swap_remove(index);
840 }
841
842 if statement_set.statements.is_empty() {
843 None
844 } else {
845 Some(CheckedDisputeStatementSet::unchecked_from_unchecked(statement_set))
847 }
848 },
849 }
850 }
851
852 fn remove_index(&mut self, i: usize) {
853 if let StatementSetFilter::RemoveIndices(ref mut indices) = *self {
854 indices.push(i)
855 }
856 }
857}
858
859impl<T: Config> Pallet<T> {
860 pub(crate) fn initializer_initialize(_now: BlockNumberFor<T>) -> Weight {
862 Weight::zero()
863 }
864
865 pub(crate) fn initializer_finalize() {}
867
868 pub(crate) fn initializer_on_new_session(
870 notification: &SessionChangeNotification<BlockNumberFor<T>>,
871 ) {
872 let config = configuration::ActiveConfig::<T>::get();
873
874 if notification.session_index <= config.dispute_period + 1 {
875 return;
876 }
877
878 let pruning_target = notification.session_index - config.dispute_period - 1;
879
880 LastPrunedSession::<T>::mutate(|last_pruned| {
881 let to_prune = if let Some(last_pruned) = last_pruned {
882 *last_pruned + 1..=pruning_target
883 } else {
884 pruning_target..=pruning_target
885 };
886
887 for to_prune in to_prune {
888 #[allow(deprecated)]
890 Disputes::<T>::remove_prefix(to_prune, None);
891 #[allow(deprecated)]
892 BackersOnDisputes::<T>::remove_prefix(to_prune, None);
893
894 #[allow(deprecated)]
897 Included::<T>::remove_prefix(to_prune, None);
898 }
899
900 *last_pruned = Some(pruning_target);
901 });
902 }
903
904 pub(crate) fn process_checked_multi_dispute_data(
915 statement_sets: &CheckedMultiDisputeStatementSet,
916 ) -> Result<Vec<(SessionIndex, CandidateHash)>, DispatchError> {
917 let config = configuration::ActiveConfig::<T>::get();
918
919 let mut fresh = Vec::with_capacity(statement_sets.len());
920 for statement_set in statement_sets {
921 let dispute_target = {
922 let statement_set = statement_set.as_ref();
923 (statement_set.session, statement_set.candidate_hash)
924 };
925 if Self::process_checked_dispute_data(
926 statement_set,
927 config.dispute_post_conclusion_acceptance_period,
928 )? {
929 fresh.push(dispute_target);
930 }
931 }
932
933 Ok(fresh)
934 }
935
936 fn filter_dispute_data(
943 set: &DisputeStatementSet,
944 post_conclusion_acceptance_period: BlockNumberFor<T>,
945 ) -> StatementSetFilter {
946 let mut filter = StatementSetFilter::RemoveIndices(Vec::new());
947
948 let now = frame_system::Pallet::<T>::block_number();
951 let oldest_accepted = now.saturating_sub(post_conclusion_acceptance_period);
952
953 let session_info = match session_info::Sessions::<T>::get(set.session) {
955 Some(s) => s,
956 None => return StatementSetFilter::RemoveAll,
957 };
958
959 let config = configuration::ActiveConfig::<T>::get();
960
961 let n_validators = session_info.validators.len();
962
963 let dispute_state = {
965 if let Some(dispute_state) = Disputes::<T>::get(&set.session, &set.candidate_hash) {
966 if dispute_state.concluded_at.as_ref().map_or(false, |c| c < &oldest_accepted) {
967 return StatementSetFilter::RemoveAll;
968 }
969
970 dispute_state
971 } else {
972 DisputeState {
974 validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
975 validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
976 start: now,
977 concluded_at: None,
978 }
979 }
980 };
981
982 let backers =
983 BackersOnDisputes::<T>::get(&set.session, &set.candidate_hash).unwrap_or_default();
984
985 let summary = {
987 let mut importer = DisputeStateImporter::new(dispute_state, backers, now);
988 for (i, (statement, validator_index, signature)) in set.statements.iter().enumerate() {
989 let validator_public = match session_info.validators.get(*validator_index) {
992 None => {
993 filter.remove_index(i);
994 continue;
995 },
996 Some(v) => v,
997 };
998
999 let kind = VoteKind::from(statement);
1000
1001 let undo = match importer.import(*validator_index, kind) {
1002 Ok(u) => u,
1003 Err(_) => {
1004 filter.remove_index(i);
1005 continue;
1006 },
1007 };
1008
1009 if let Err(()) = check_signature(
1019 &validator_public,
1020 set.candidate_hash,
1021 set.session,
1022 statement,
1023 signature,
1024 config.approval_voting_params.max_approval_coalesce_count > 1,
1029 ) {
1030 log::warn!("Failed to check dispute signature");
1031
1032 importer.undo(undo);
1033 filter.remove_index(i);
1034 continue;
1035 };
1036 }
1037
1038 importer.finish()
1039 };
1040
1041 if summary.state.validators_for.count_ones() == 0 ||
1043 summary.state.validators_against.count_ones() == 0
1044 {
1045 return StatementSetFilter::RemoveAll;
1046 }
1047
1048 if (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() <=
1050 byzantine_threshold(summary.state.validators_for.len())
1051 {
1052 return StatementSetFilter::RemoveAll;
1053 }
1054
1055 filter
1056 }
1057
1058 fn process_checked_dispute_data(
1063 set: &CheckedDisputeStatementSet,
1064 dispute_post_conclusion_acceptance_period: BlockNumberFor<T>,
1065 ) -> Result<bool, DispatchError> {
1066 let now = frame_system::Pallet::<T>::block_number();
1069 let oldest_accepted = now.saturating_sub(dispute_post_conclusion_acceptance_period);
1070
1071 let set = set.as_ref();
1072
1073 let session_info = match session_info::Sessions::<T>::get(set.session) {
1075 Some(s) => s,
1076 None => return Err(Error::<T>::AncientDisputeStatement.into()),
1077 };
1078
1079 let n_validators = session_info.validators.len();
1080
1081 let (fresh, dispute_state) = {
1083 if let Some(dispute_state) = Disputes::<T>::get(&set.session, &set.candidate_hash) {
1084 ensure!(
1085 dispute_state.concluded_at.as_ref().map_or(true, |c| c >= &oldest_accepted),
1086 Error::<T>::AncientDisputeStatement,
1087 );
1088
1089 (false, dispute_state)
1090 } else {
1091 (
1092 true,
1093 DisputeState {
1094 validators_for: bitvec![u8, BitOrderLsb0; 0; n_validators],
1095 validators_against: bitvec![u8, BitOrderLsb0; 0; n_validators],
1096 start: now,
1097 concluded_at: None,
1098 },
1099 )
1100 }
1101 };
1102
1103 let backers =
1104 BackersOnDisputes::<T>::get(&set.session, &set.candidate_hash).unwrap_or_default();
1105
1106 let summary = {
1108 let mut importer = DisputeStateImporter::new(dispute_state, backers, now);
1109 for (statement, validator_index, _signature) in &set.statements {
1110 let kind = VoteKind::from(statement);
1111
1112 importer.import(*validator_index, kind).map_err(Error::<T>::from)?;
1113 }
1114
1115 importer.finish()
1116 };
1117
1118 ensure!(
1120 summary.state.validators_for.count_ones() > 0 &&
1121 summary.state.validators_against.count_ones() > 0,
1122 Error::<T>::SingleSidedDispute,
1123 );
1124
1125 ensure!(
1127 (summary.state.validators_for.clone() | &summary.state.validators_against).count_ones() >
1128 byzantine_threshold(summary.state.validators_for.len()),
1129 Error::<T>::UnconfirmedDispute,
1130 );
1131 let backers = summary.backers;
1132 ensure!(!backers.is_empty(), Error::<T>::MissingBackingVotes);
1134 BackersOnDisputes::<T>::insert(&set.session, &set.candidate_hash, backers.clone());
1135 let DisputeStatementSet { ref session, ref candidate_hash, .. } = set;
1138 let session = *session;
1139 let candidate_hash = *candidate_hash;
1140
1141 if fresh {
1142 let is_local = Included::<T>::contains_key(&session, &candidate_hash);
1143
1144 Self::deposit_event(Event::DisputeInitiated(
1145 candidate_hash,
1146 if is_local { DisputeLocation::Local } else { DisputeLocation::Remote },
1147 ));
1148 }
1149
1150 {
1151 if summary.new_flags.contains(DisputeStateFlags::FOR_SUPERMAJORITY) {
1152 Self::deposit_event(Event::DisputeConcluded(candidate_hash, DisputeResult::Valid));
1153 }
1154
1155 if summary.new_flags.contains(DisputeStateFlags::AGAINST_SUPERMAJORITY) {
1160 Self::deposit_event(Event::DisputeConcluded(
1161 candidate_hash,
1162 DisputeResult::Invalid,
1163 ));
1164 }
1165 }
1166
1167 T::RewardValidators::reward_dispute_statement(
1169 session,
1170 summary.new_participants.iter_ones().map(|i| ValidatorIndex(i as _)),
1171 );
1172
1173 {
1175 T::SlashingHandler::punish_against_valid(
1177 session,
1178 candidate_hash,
1179 summary.slash_against,
1180 backers.clone(),
1181 );
1182
1183 T::SlashingHandler::punish_for_invalid(
1185 session,
1186 candidate_hash,
1187 summary.slash_for,
1188 backers,
1189 );
1190 }
1191
1192 Disputes::<T>::insert(&session, &candidate_hash, &summary.state);
1193
1194 if summary.new_flags.contains(DisputeStateFlags::AGAINST_BYZANTINE) {
1197 if let Some(revert_to) = Included::<T>::get(&session, &candidate_hash) {
1198 Self::revert_and_freeze(revert_to);
1199 }
1200 }
1201
1202 Ok(fresh)
1203 }
1204
1205 #[allow(unused)]
1206 pub(crate) fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumberFor<T>>)>
1207 {
1208 Disputes::<T>::iter().collect()
1209 }
1210
1211 pub(crate) fn note_included(
1212 session: SessionIndex,
1213 candidate_hash: CandidateHash,
1214 included_in: BlockNumberFor<T>,
1215 ) {
1216 if included_in.is_zero() {
1217 return;
1218 }
1219
1220 let revert_to = included_in - One::one();
1221
1222 Included::<T>::insert(&session, &candidate_hash, revert_to);
1223
1224 if let Some(state) = Disputes::<T>::get(&session, candidate_hash) {
1225 if has_supermajority_against(&state) {
1226 Self::revert_and_freeze(revert_to);
1227 }
1228 }
1229 }
1230
1231 pub(crate) fn included_state(
1232 session: SessionIndex,
1233 candidate_hash: CandidateHash,
1234 ) -> Option<BlockNumberFor<T>> {
1235 Included::<T>::get(session, candidate_hash)
1236 }
1237
1238 pub(crate) fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool {
1239 Disputes::<T>::get(&session, &candidate_hash).map_or(false, |dispute| {
1240 has_supermajority_against(&dispute)
1242 })
1243 }
1244
1245 pub(crate) fn is_frozen() -> bool {
1246 Frozen::<T>::get().is_some()
1247 }
1248
1249 pub(crate) fn revert_and_freeze(revert_to: BlockNumberFor<T>) {
1250 if Frozen::<T>::get().map_or(true, |last| last > revert_to) {
1251 Frozen::<T>::set(Some(revert_to));
1252
1253 let revert = revert_to + One::one();
1257 Self::deposit_event(Event::Revert(revert));
1258 frame_system::Pallet::<T>::deposit_log(
1259 ConsensusLog::Revert(revert.saturated_into()).into(),
1260 );
1261 }
1262 }
1263}
1264
1265fn has_supermajority_against<BlockNumber>(dispute: &DisputeState<BlockNumber>) -> bool {
1266 let supermajority_threshold = supermajority_threshold(dispute.validators_against.len());
1267 dispute.validators_against.count_ones() >= supermajority_threshold
1268}
1269
1270fn check_signature(
1271 validator_public: &ValidatorId,
1272 candidate_hash: CandidateHash,
1273 session: SessionIndex,
1274 statement: &DisputeStatement,
1275 validator_signature: &ValidatorSignature,
1276 approval_multiple_candidates_enabled: bool,
1277) -> Result<(), ()> {
1278 let payload = match statement {
1279 DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) => {
1280 ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload()
1281 },
1282 DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(inclusion_parent)) => {
1283 CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
1284 session_index: session,
1285 parent_hash: *inclusion_parent,
1286 })
1287 },
1288 DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) => {
1289 CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
1290 session_index: session,
1291 parent_hash: *inclusion_parent,
1292 })
1293 },
1294 DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) => {
1295 ApprovalVote(candidate_hash).signing_payload(session)
1296 },
1297 DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(
1298 candidates,
1299 )) => {
1300 if approval_multiple_candidates_enabled && candidates.contains(&candidate_hash) {
1301 ApprovalVoteMultipleCandidates(candidates).signing_payload(session)
1302 } else {
1303 return Err(());
1304 }
1305 },
1306 DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) => {
1307 ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload()
1308 },
1309 };
1310
1311 let start = get_current_time();
1312
1313 let res =
1314 if validator_signature.verify(&payload[..], &validator_public) { Ok(()) } else { Err(()) };
1315
1316 let end = get_current_time();
1317
1318 METRICS.on_signature_check_complete(end.saturating_sub(start)); res
1321}
1322
1323#[cfg(all(not(feature = "runtime-benchmarks"), test))]
1324pub(crate) fn clear_dispute_storage<T: Config>() {
1326 let _ = Disputes::<T>::clear(u32::MAX, None);
1327 let _ = BackersOnDisputes::<T>::clear(u32::MAX, None);
1328 let _ = Included::<T>::clear(u32::MAX, None);
1329}