1use crate::{
56 types::SolutionOf,
57 verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult},
58};
59use codec::{Decode, Encode, MaxEncodedLen};
60use frame_election_provider_support::PageIndex;
61use frame_support::{
62 dispatch::DispatchResultWithPostInfo,
63 pallet_prelude::{StorageDoubleMap, ValueQuery, *},
64 traits::{
65 tokens::{
66 fungible::{Inspect, Mutate, MutateHold},
67 Fortitude, Precision,
68 },
69 Defensive, DefensiveSaturating, EstimateCallFee,
70 },
71 BoundedVec, Twox64Concat,
72};
73use frame_system::{ensure_signed, pallet_prelude::*};
74use scale_info::TypeInfo;
75use sp_io::MultiRemovalResults;
76use sp_npos_elections::ElectionScore;
77use sp_runtime::{traits::Saturating, Perbill};
78use sp_std::prelude::*;
79
80pub use crate::weights::traits::pallet_election_provider_multi_block_signed::*;
82pub use pallet::*;
84
85#[cfg(feature = "runtime-benchmarks")]
86mod benchmarking;
87
88pub(crate) type SignedWeightsOf<T> = <T as crate::signed::Config>::WeightInfo;
89
90#[cfg(test)]
91mod tests;
92
93type BalanceOf<T> =
94 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
95
96#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, DebugNoBound)]
98#[cfg_attr(test, derive(frame_support::PartialEqNoBound, frame_support::EqNoBound))]
99#[codec(mel_bound(T: Config))]
100#[scale_info(skip_type_params(T))]
101pub struct SubmissionMetadata<T: Config> {
102 deposit: BalanceOf<T>,
104 fee: BalanceOf<T>,
106 reward: BalanceOf<T>,
108 claimed_score: ElectionScore,
110 pages: BoundedVec<bool, T::Pages>,
112}
113
114impl<T: Config> crate::types::SignedInterface for Pallet<T> {
115 fn has_leader(round: u32) -> bool {
116 Submissions::<T>::has_leader(round)
117 }
118}
119
120impl<T: Config> SolutionDataProvider for Pallet<T> {
121 type Solution = SolutionOf<T::MinerConfig>;
122
123 fn get_page(page: PageIndex) -> Self::Solution {
128 let current_round = Self::current_round();
129 Submissions::<T>::leader(current_round)
130 .defensive()
131 .and_then(|(who, _score)| {
132 sublog!(
133 debug,
134 "signed",
135 "returning page {} of {:?}'s submission as leader.",
136 page,
137 who
138 );
139 Submissions::<T>::get_page_of(current_round, &who, page)
140 })
141 .unwrap_or_default()
142 }
143
144 fn get_score() -> ElectionScore {
146 let current_round = Self::current_round();
147 Submissions::<T>::leader(current_round)
148 .defensive()
149 .inspect(|(_who, score)| {
150 sublog!(
151 debug,
152 "signed",
153 "returning score {:?} of current leader for round {}.",
154 score,
155 current_round
156 );
157 })
158 .map(|(_who, score)| score)
159 .unwrap_or_default()
160 }
161
162 fn report_result(result: crate::verifier::VerificationResult) {
163 debug_assert!(matches!(<T::Verifier as AsynchronousVerifier>::status(), Status::Nothing));
165 let current_round = Self::current_round();
166
167 match result {
168 VerificationResult::Queued => {
169 if let Some((winner, metadata)) =
172 Submissions::<T>::take_leader_with_data(Self::current_round()).defensive()
173 {
174 let reward = metadata.reward.saturating_add(metadata.fee);
176 let _r = T::Currency::mint_into(&winner, reward);
177 debug_assert!(_r.is_ok());
178 Self::deposit_event(Event::<T>::Rewarded(
179 current_round,
180 winner.clone(),
181 reward,
182 ));
183
184 let _res = T::Currency::release(
186 &HoldReason::SignedSubmission.into(),
187 &winner,
188 metadata.deposit,
189 Precision::BestEffort,
190 );
191 debug_assert!(_res.is_ok());
192 }
193 },
194 VerificationResult::Rejected => {
195 Self::handle_solution_rejection(current_round);
196 },
197 }
198 }
199}
200
201pub trait CalculateBaseDeposit<Balance> {
206 fn calculate_base_deposit(existing_submitters: usize) -> Balance;
207}
208
209impl<Balance, G: Get<Balance>> CalculateBaseDeposit<Balance> for G {
210 fn calculate_base_deposit(_existing_submitters: usize) -> Balance {
211 G::get()
212 }
213}
214
215pub trait CalculatePageDeposit<Balance> {
220 fn calculate_page_deposit(existing_submitters: usize, page_size: usize) -> Balance;
221}
222
223impl<Balance: From<u32> + Saturating, G: Get<Balance>> CalculatePageDeposit<Balance> for G {
224 fn calculate_page_deposit(_existing_submitters: usize, page_size: usize) -> Balance {
225 let page_size: Balance = (page_size as u32).into();
226 G::get().saturating_mul(page_size)
227 }
228}
229
230#[frame_support::pallet]
231pub mod pallet {
232 use super::*;
233
234 #[pallet::config]
235 #[pallet::disable_frame_system_supertrait_check]
236 pub trait Config: crate::Config {
237 type Currency: Inspect<Self::AccountId>
239 + Mutate<Self::AccountId>
240 + MutateHold<Self::AccountId, Reason: From<HoldReason>>;
241
242 type DepositBase: CalculateBaseDeposit<BalanceOf<Self>>;
244
245 type DepositPerPage: CalculatePageDeposit<BalanceOf<Self>>;
247
248 type InvulnerableDeposit: Get<BalanceOf<Self>>;
250
251 type RewardBase: Get<BalanceOf<Self>>;
253
254 type MaxSubmissions: Get<u32>;
257
258 type BailoutGraceRatio: Get<Perbill>;
264
265 type EjectGraceRatio: Get<Perbill>;
270
271 type EstimateCallFee: EstimateCallFee<Call<Self>, BalanceOf<Self>>;
274
275 type WeightInfo: WeightInfo;
277 }
278
279 #[pallet::composite_enum]
281 pub enum HoldReason {
282 #[codec(index = 0)]
284 SignedSubmission,
285 }
286
287 #[pallet::storage]
297 pub type Invulnerables<T: Config> =
298 StorageValue<_, BoundedVec<T::AccountId, ConstU32<16>>, ValueQuery>;
299
300 pub(crate) struct Submissions<T: Config>(sp_std::marker::PhantomData<T>);
335
336 #[pallet::storage]
337 pub type SortedScores<T: Config> = StorageMap<
338 _,
339 Twox64Concat,
340 u32,
341 BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions>,
342 ValueQuery,
343 >;
344
345 #[pallet::storage]
347 type SubmissionStorage<T: Config> = StorageNMap<
348 _,
349 (
350 NMapKey<Twox64Concat, u32>,
351 NMapKey<Twox64Concat, T::AccountId>,
352 NMapKey<Twox64Concat, PageIndex>,
353 ),
354 SolutionOf<T::MinerConfig>,
355 OptionQuery,
356 >;
357
358 #[pallet::storage]
363 type SubmissionMetadataStorage<T: Config> =
364 StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata<T>>;
365
366 impl<T: Config> Submissions<T> {
367 fn mutate_checked<R, F: FnOnce() -> R>(_round: u32, mutate: F) -> R {
374 let result = mutate();
375
376 #[cfg(debug_assertions)]
377 {
378 assert!(Self::sanity_check_round(_round).is_ok());
379 assert!(Self::sanity_check_round(_round + 1).is_ok());
380 assert!(Self::sanity_check_round(_round.saturating_sub(1)).is_ok());
381 }
382
383 result
384 }
385
386 pub(crate) fn take_leader_with_data(
392 round: u32,
393 ) -> Option<(T::AccountId, SubmissionMetadata<T>)> {
394 Self::mutate_checked(round, || {
395 SortedScores::<T>::mutate(round, |sorted| sorted.pop()).and_then(
396 |(submitter, _score)| {
397 let r: MultiRemovalResults = SubmissionStorage::<T>::clear_prefix(
399 (round, &submitter),
400 u32::MAX,
401 None,
402 );
403 debug_assert!(r.unique <= T::Pages::get());
404
405 SubmissionMetadataStorage::<T>::take(round, &submitter)
406 .map(|metadata| (submitter, metadata))
407 },
408 )
409 })
410 }
411
412 pub(crate) fn take_submission_with_data(
418 round: u32,
419 who: &T::AccountId,
420 ) -> Option<SubmissionMetadata<T>> {
421 Self::mutate_checked(round, || {
422 let mut sorted_scores = SortedScores::<T>::get(round);
423 if let Some(index) = sorted_scores.iter().position(|(x, _)| x == who) {
424 sorted_scores.remove(index);
425 }
426 if sorted_scores.is_empty() {
427 SortedScores::<T>::remove(round);
428 } else {
429 SortedScores::<T>::insert(round, sorted_scores);
430 }
431
432 let r = SubmissionStorage::<T>::clear_prefix((round, who), u32::MAX, None);
434 debug_assert!(r.unique <= T::Pages::get());
435
436 SubmissionMetadataStorage::<T>::take(round, who)
437 })
438 }
439
440 fn try_register(
447 round: u32,
448 who: &T::AccountId,
449 metadata: SubmissionMetadata<T>,
450 ) -> Result<bool, DispatchError> {
451 Self::mutate_checked(round, || Self::try_register_inner(round, who, metadata))
452 }
453
454 fn try_register_inner(
455 round: u32,
456 who: &T::AccountId,
457 metadata: SubmissionMetadata<T>,
458 ) -> Result<bool, DispatchError> {
459 let mut sorted_scores = SortedScores::<T>::get(round);
460
461 let did_eject = if let Some(_) = sorted_scores.iter().position(|(x, _)| x == who) {
462 return Err(Error::<T>::Duplicate.into());
463 } else {
464 debug_assert!(!SubmissionMetadataStorage::<T>::contains_key(round, who));
466
467 let insert_idx = match sorted_scores
468 .binary_search_by_key(&metadata.claimed_score, |(_, y)| *y)
469 {
470 Ok(pos) => pos,
473 Err(pos) => pos,
475 };
476
477 let mut record = (who.clone(), metadata.claimed_score);
478 if sorted_scores.is_full() {
479 let remove_idx = sorted_scores
480 .iter()
481 .position(|(x, _)| !Pallet::<T>::is_invulnerable(x))
482 .ok_or(Error::<T>::QueueFull)?;
483 if insert_idx > remove_idx {
484 sp_std::mem::swap(&mut sorted_scores[remove_idx], &mut record);
486 sorted_scores[remove_idx..insert_idx].rotate_left(1);
492
493 let discarded = record.0;
494 let maybe_metadata =
495 SubmissionMetadataStorage::<T>::take(round, &discarded).defensive();
496 let _r = SubmissionStorage::<T>::clear_prefix(
498 (round, &discarded),
499 u32::MAX,
500 None,
501 );
502 debug_assert!(_r.unique <= T::Pages::get());
503
504 if let Some(metadata) = maybe_metadata {
505 Pallet::<T>::settle_deposit(
506 &discarded,
507 metadata.deposit,
508 T::EjectGraceRatio::get(),
509 );
510 }
511
512 Pallet::<T>::deposit_event(Event::<T>::Ejected(round, discarded));
513 true
514 } else {
515 return Err(Error::<T>::QueueFull.into())
517 }
518 } else {
519 sorted_scores
520 .try_insert(insert_idx, record)
521 .expect("length checked above; qed");
522 false
523 }
524 };
525
526 SortedScores::<T>::insert(round, sorted_scores);
527 SubmissionMetadataStorage::<T>::insert(round, who, metadata);
528 Ok(did_eject)
529 }
530
531 pub(crate) fn try_mutate_page(
539 round: u32,
540 who: &T::AccountId,
541 page: PageIndex,
542 maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
543 ) -> DispatchResultWithPostInfo {
544 Self::mutate_checked(round, || {
545 Self::try_mutate_page_inner(round, who, page, maybe_solution)
546 })
547 }
548
549 fn deposit_for(who: &T::AccountId, pages: usize) -> BalanceOf<T> {
551 if Pallet::<T>::is_invulnerable(who) {
552 T::InvulnerableDeposit::get()
553 } else {
554 let round = Pallet::<T>::current_round();
555 let queue_size = Self::submitters_count(round);
556 let base = T::DepositBase::calculate_base_deposit(queue_size);
557 let pages = T::DepositPerPage::calculate_page_deposit(queue_size, pages);
558 base.saturating_add(pages)
559 }
560 }
561
562 fn try_mutate_page_inner(
563 round: u32,
564 who: &T::AccountId,
565 page: PageIndex,
566 maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
567 ) -> DispatchResultWithPostInfo {
568 let mut metadata =
569 SubmissionMetadataStorage::<T>::get(round, who).ok_or(Error::<T>::NotRegistered)?;
570 ensure!(page < T::Pages::get(), Error::<T>::BadPageIndex);
571
572 if let Some(page_bit) = metadata.pages.get_mut(page as usize).defensive() {
575 *page_bit = maybe_solution.is_some();
576 }
577
578 let new_pages = metadata.pages.iter().filter(|x| **x).count();
580 let new_deposit = Self::deposit_for(&who, new_pages);
581 let old_deposit = metadata.deposit;
582 if new_deposit > old_deposit {
583 let to_reserve = new_deposit - old_deposit;
584 T::Currency::hold(&HoldReason::SignedSubmission.into(), who, to_reserve)?;
585 } else {
586 let to_unreserve = old_deposit - new_deposit;
587 let _res = T::Currency::release(
588 &HoldReason::SignedSubmission.into(),
589 who,
590 to_unreserve,
591 Precision::BestEffort,
592 );
593 debug_assert_eq!(_res, Ok(to_unreserve));
594 };
595 metadata.deposit = new_deposit;
596
597 if maybe_solution.is_some() {
601 let fee = T::EstimateCallFee::estimate_call_fee(
602 &Call::submit_page { page, maybe_solution: maybe_solution.clone() },
603 None.into(),
604 );
605 metadata.fee.saturating_accrue(fee);
606 }
607
608 SubmissionStorage::<T>::mutate_exists((round, who, page), |maybe_old_solution| {
609 *maybe_old_solution = maybe_solution.map(|s| *s)
610 });
611 SubmissionMetadataStorage::<T>::insert(round, who, metadata);
612 Ok(().into())
613 }
614
615 pub(crate) fn has_leader(round: u32) -> bool {
617 !SortedScores::<T>::get(round).is_empty()
618 }
619
620 pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> {
621 SortedScores::<T>::get(round).last().cloned()
622 }
623
624 pub(crate) fn submitters_count(round: u32) -> usize {
625 SortedScores::<T>::get(round).len()
626 }
627
628 pub(crate) fn get_page_of(
629 round: u32,
630 who: &T::AccountId,
631 page: PageIndex,
632 ) -> Option<SolutionOf<T::MinerConfig>> {
633 SubmissionStorage::<T>::get((round, who, &page))
634 }
635 }
636
637 #[allow(unused)]
638 #[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks", debug_assertions))]
639 impl<T: Config> Submissions<T> {
640 pub(crate) fn sorted_submitters(round: u32) -> BoundedVec<T::AccountId, T::MaxSubmissions> {
641 use frame_support::traits::TryCollect;
642 SortedScores::<T>::get(round).into_iter().map(|(x, _)| x).try_collect().unwrap()
643 }
644
645 pub fn submissions_iter(
646 round: u32,
647 ) -> impl Iterator<Item = (T::AccountId, PageIndex, SolutionOf<T::MinerConfig>)> {
648 SubmissionStorage::<T>::iter_prefix((round,)).map(|((x, y), z)| (x, y, z))
649 }
650
651 pub fn metadata_iter(
652 round: u32,
653 ) -> impl Iterator<Item = (T::AccountId, SubmissionMetadata<T>)> {
654 SubmissionMetadataStorage::<T>::iter_prefix(round)
655 }
656
657 pub fn metadata_of(round: u32, who: T::AccountId) -> Option<SubmissionMetadata<T>> {
658 SubmissionMetadataStorage::<T>::get(round, who)
659 }
660
661 pub fn pages_of(
662 round: u32,
663 who: T::AccountId,
664 ) -> impl Iterator<Item = (PageIndex, SolutionOf<T::MinerConfig>)> {
665 SubmissionStorage::<T>::iter_prefix((round, who))
666 }
667
668 pub fn leaderboard(
669 round: u32,
670 ) -> BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions> {
671 SortedScores::<T>::get(round)
672 }
673
674 pub(crate) fn ensure_killed(round: u32) -> DispatchResult {
677 ensure!(Self::metadata_iter(round).count() == 0, "metadata_iter not cleared.");
678 ensure!(Self::submissions_iter(round).count() == 0, "submissions_iter not cleared.");
679 ensure!(Self::sorted_submitters(round).len() == 0, "sorted_submitters not cleared.");
680
681 Ok(())
682 }
683
684 pub(crate) fn ensure_killed_with(who: &T::AccountId, round: u32) -> DispatchResult {
686 ensure!(
687 SubmissionMetadataStorage::<T>::get(round, who).is_none(),
688 "metadata not cleared."
689 );
690 ensure!(
691 SubmissionStorage::<T>::iter_prefix((round, who)).count() == 0,
692 "submissions not cleared."
693 );
694 ensure!(
695 SortedScores::<T>::get(round).iter().all(|(x, _)| x != who),
696 "sorted_submitters not cleared."
697 );
698
699 Ok(())
700 }
701
702 pub(crate) fn sanity_check_round(round: u32) -> DispatchResult {
704 use sp_std::collections::btree_set::BTreeSet;
705 let sorted_scores = SortedScores::<T>::get(round);
706 assert_eq!(
707 sorted_scores.clone().into_iter().map(|(x, _)| x).collect::<BTreeSet<_>>().len(),
708 sorted_scores.len()
709 );
710
711 let _ = SubmissionMetadataStorage::<T>::iter_prefix(round)
712 .map(|(submitter, meta)| {
713 let mut matches = SortedScores::<T>::get(round)
714 .into_iter()
715 .filter(|(who, _score)| who == &submitter)
716 .collect::<Vec<_>>();
717
718 ensure!(
719 matches.len() == 1,
720 "item existing in metadata but missing in sorted list.",
721 );
722
723 let (_, score) = matches.pop().expect("checked; qed");
724 ensure!(score == meta.claimed_score, "score mismatch");
725 Ok(())
726 })
727 .collect::<Result<Vec<_>, &'static str>>()?;
728
729 ensure!(
730 SubmissionStorage::<T>::iter_key_prefix((round,)).map(|(k1, _k2)| k1).all(
731 |submitter| SubmissionMetadataStorage::<T>::contains_key(round, submitter)
732 ),
733 "missing metadata of submitter"
734 );
735
736 for submitter in SubmissionStorage::<T>::iter_key_prefix((round,)).map(|(k1, _k2)| k1) {
737 let pages_count =
738 SubmissionStorage::<T>::iter_key_prefix((round, &submitter)).count();
739 let metadata = SubmissionMetadataStorage::<T>::get(round, submitter)
740 .expect("metadata checked to exist for all keys; qed");
741 let assumed_pages_count = metadata.pages.iter().filter(|x| **x).count();
742 ensure!(pages_count == assumed_pages_count, "wrong page count");
743 }
744
745 Ok(())
746 }
747 }
748
749 #[pallet::pallet]
750 pub struct Pallet<T>(PhantomData<T>);
751
752 #[pallet::event]
753 #[pallet::generate_deposit(pub(super) fn deposit_event)]
754 pub enum Event<T: Config> {
755 Registered(u32, T::AccountId, ElectionScore),
757 Stored(u32, T::AccountId, PageIndex),
759 Rewarded(u32, T::AccountId, BalanceOf<T>),
761 Slashed(u32, T::AccountId, BalanceOf<T>),
763 Ejected(u32, T::AccountId),
765 Discarded(u32, T::AccountId),
767 Bailed(u32, T::AccountId),
769 }
770
771 #[pallet::error]
772 pub enum Error<T> {
773 PhaseNotSigned,
775 Duplicate,
777 QueueFull,
779 BadPageIndex,
781 NotRegistered,
783 NoSubmission,
785 RoundNotOver,
787 BadWitnessData,
789 TooManyInvulnerables,
791 }
792
793 #[pallet::call]
794 impl<T: Config> Pallet<T> {
795 #[pallet::weight(SignedWeightsOf::<T>::register_eject())]
797 #[pallet::call_index(0)]
798 pub fn register(
799 origin: OriginFor<T>,
800 claimed_score: ElectionScore,
801 ) -> DispatchResultWithPostInfo {
802 let who = ensure_signed(origin)?;
803 ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
804
805 let deposit = Submissions::<T>::deposit_for(&who, 0);
809 let reward = T::RewardBase::get();
810 let fee = T::EstimateCallFee::estimate_call_fee(
811 &Call::register { claimed_score },
812 None.into(),
813 );
814 let mut pages = BoundedVec::<_, _>::with_bounded_capacity(T::Pages::get() as usize);
815 pages.bounded_resize(T::Pages::get() as usize, false);
816
817 let new_metadata = SubmissionMetadata { claimed_score, deposit, reward, fee, pages };
818
819 T::Currency::hold(&HoldReason::SignedSubmission.into(), &who, deposit)?;
820 let round = Self::current_round();
821 let discarded = Submissions::<T>::try_register(round, &who, new_metadata)?;
822 Self::deposit_event(Event::<T>::Registered(round, who, claimed_score));
823
824 if discarded {
826 Ok(().into())
827 } else {
828 Ok(Some(SignedWeightsOf::<T>::register_not_full()).into())
829 }
830 }
831
832 #[pallet::weight(SignedWeightsOf::<T>::submit_page())]
841 #[pallet::call_index(1)]
842 pub fn submit_page(
843 origin: OriginFor<T>,
844 page: PageIndex,
845 maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
846 ) -> DispatchResultWithPostInfo {
847 let who = ensure_signed(origin)?;
848 ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
849 let is_set = maybe_solution.is_some();
850
851 let round = Self::current_round();
852 Submissions::<T>::try_mutate_page(round, &who, page, maybe_solution)?;
853 Self::deposit_event(Event::<T>::Stored(round, who, page));
854
855 if is_set {
857 Ok(().into())
858 } else {
859 Ok(Some(SignedWeightsOf::<T>::unset_page()).into())
860 }
861 }
862
863 #[pallet::weight(SignedWeightsOf::<T>::bail())]
869 #[pallet::call_index(2)]
870 pub fn bail(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
871 let who = ensure_signed(origin)?;
872 ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
873 let round = Self::current_round();
874 let metadata = Submissions::<T>::take_submission_with_data(round, &who)
875 .ok_or(Error::<T>::NoSubmission)?;
876
877 let deposit = metadata.deposit;
878 Self::settle_deposit(&who, deposit, T::BailoutGraceRatio::get());
879 Self::deposit_event(Event::<T>::Bailed(round, who));
880
881 Ok(None.into())
882 }
883
884 #[pallet::call_index(3)]
891 #[pallet::weight(SignedWeightsOf::<T>::clear_old_round_data(*witness_pages))]
892 pub fn clear_old_round_data(
893 origin: OriginFor<T>,
894 round: u32,
895 witness_pages: u32,
896 ) -> DispatchResultWithPostInfo {
897 let discarded = ensure_signed(origin)?;
898
899 let current_round = Self::current_round();
900 ensure!(round < current_round, Error::<T>::RoundNotOver);
902
903 let metadata = Submissions::<T>::take_submission_with_data(round, &discarded)
904 .ok_or(Error::<T>::NoSubmission)?;
905 ensure!(
906 metadata.pages.iter().filter(|p| **p).count() as u32 <= witness_pages,
907 Error::<T>::BadWitnessData
908 );
909
910 let _res = T::Currency::release(
912 &HoldReason::SignedSubmission.into(),
913 &discarded,
914 metadata.deposit,
915 Precision::BestEffort,
916 );
917 debug_assert_eq!(_res, Ok(metadata.deposit));
918
919 if Self::is_invulnerable(&discarded) {
921 let _r = T::Currency::mint_into(&discarded, metadata.fee);
922 debug_assert!(_r.is_ok());
923 }
924
925 Self::deposit_event(Event::<T>::Discarded(round, discarded));
926
927 Ok(None.into())
929 }
930
931 #[pallet::call_index(4)]
935 #[pallet::weight(T::DbWeight::get().writes(1))]
936 pub fn set_invulnerables(origin: OriginFor<T>, inv: Vec<T::AccountId>) -> DispatchResult {
937 <T as crate::Config>::AdminOrigin::ensure_origin(origin)?;
938 let bounded: BoundedVec<_, ConstU32<16>> =
939 inv.try_into().map_err(|_| Error::<T>::TooManyInvulnerables)?;
940 Invulnerables::<T>::set(bounded);
941 Ok(())
942 }
943 }
944
945 #[pallet::view_functions]
946 impl<T: Config> Pallet<T> {
947 pub fn deposit_for(who: T::AccountId, pages: u32) -> BalanceOf<T> {
953 Submissions::<T>::deposit_for(&who, pages as usize)
954 }
955 }
956
957 #[pallet::hooks]
958 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
959 #[cfg(feature = "try-runtime")]
960 fn try_state(n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
961 Self::do_try_state(n)
962 }
963 }
964}
965
966impl<T: Config> Pallet<T> {
967 #[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks"))]
968 pub(crate) fn do_try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
969 Submissions::<T>::sanity_check_round(Self::current_round())
970 }
971
972 fn current_round() -> u32 {
973 crate::Pallet::<T>::round()
974 }
975
976 fn is_invulnerable(who: &T::AccountId) -> bool {
977 Invulnerables::<T>::get().contains(who)
978 }
979
980 fn settle_deposit(who: &T::AccountId, deposit: BalanceOf<T>, grace: Perbill) {
981 let to_refund = grace * deposit;
982 let to_slash = deposit.defensive_saturating_sub(to_refund);
983
984 let _res = T::Currency::release(
985 &HoldReason::SignedSubmission.into(),
986 who,
987 to_refund,
988 Precision::BestEffort,
989 )
990 .defensive();
991 debug_assert_eq!(_res, Ok(to_refund));
992
993 let _res = T::Currency::burn_held(
994 &HoldReason::SignedSubmission.into(),
995 who,
996 to_slash,
997 Precision::BestEffort,
998 Fortitude::Force,
999 )
1000 .defensive();
1001 debug_assert_eq!(_res, Ok(to_slash));
1002 }
1003
1004 fn handle_solution_rejection(current_round: u32) {
1006 if let Some((loser, metadata)) =
1007 Submissions::<T>::take_leader_with_data(current_round).defensive()
1008 {
1009 let slash = metadata.deposit;
1015 let _res = T::Currency::burn_held(
1016 &HoldReason::SignedSubmission.into(),
1017 &loser,
1018 slash,
1019 Precision::BestEffort,
1020 Fortitude::Force,
1021 );
1022 debug_assert_eq!(_res, Ok(slash));
1023 Self::deposit_event(Event::<T>::Slashed(current_round, loser.clone(), slash));
1024
1025 if let crate::types::Phase::SignedValidation(remaining_blocks) =
1027 crate::Pallet::<T>::current_phase()
1028 {
1029 if remaining_blocks >= T::Pages::get().into() {
1032 if Submissions::<T>::has_leader(current_round) {
1033 let _ = <T::Verifier as AsynchronousVerifier>::start().defensive();
1036 }
1037 } else {
1038 sublog!(
1039 warn,
1040 "signed",
1041 "SignedValidation phase has {:?} blocks remaining, which are insufficient for {} pages",
1042 remaining_blocks,
1043 T::Pages::get()
1044 );
1045 }
1046 }
1047 } else {
1048 sublog!(
1050 warn,
1051 "signed",
1052 "Tried to slash but no leader was present for round {}",
1053 current_round
1054 );
1055 }
1056 }
1057}