1use crate::{
57 types::SolutionOf,
58 verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult},
59};
60use codec::{Decode, Encode, MaxEncodedLen};
61use frame_election_provider_support::PageIndex;
62use frame_support::{
63 dispatch::{DispatchResultWithPostInfo, GetDispatchInfo},
64 pallet_prelude::{StorageDoubleMap, ValueQuery, *},
65 traits::{
66 tokens::{
67 fungible::{Inspect, Mutate, MutateHold},
68 Fortitude, Precision,
69 },
70 Defensive, DefensiveSaturating, EstimateCallFee, EstimateFee,
71 },
72 BoundedVec, Twox64Concat,
73};
74use frame_system::{ensure_signed, pallet_prelude::*};
75use scale_info::TypeInfo;
76use sp_io::MultiRemovalResults;
77use sp_npos_elections::ElectionScore;
78use sp_runtime::{traits::Saturating, Perbill};
79use sp_std::prelude::*;
80
81pub use crate::weights::traits::pallet_election_provider_multi_block_signed::*;
83pub use pallet::*;
85
86#[cfg(feature = "runtime-benchmarks")]
87mod benchmarking;
88
89pub(crate) type SignedWeightsOf<T> = <T as crate::signed::Config>::WeightInfo;
90
91#[cfg(test)]
92mod tests;
93
94type BalanceOf<T> =
95 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
96
97#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, DebugNoBound)]
99#[cfg_attr(test, derive(frame_support::PartialEqNoBound, frame_support::EqNoBound))]
100#[codec(mel_bound(T: Config))]
101#[scale_info(skip_type_params(T))]
102pub struct SubmissionMetadata<T: Config> {
103 deposit: BalanceOf<T>,
105 fee: BalanceOf<T>,
107 reward: BalanceOf<T>,
109 claimed_score: ElectionScore,
111 pages: BoundedVec<bool, T::Pages>,
113}
114
115impl<T: Config> crate::types::SignedInterface for Pallet<T> {
116 fn has_leader(round: u32) -> bool {
117 Submissions::<T>::has_leader(round)
118 }
119}
120
121impl<T: Config> SolutionDataProvider for Pallet<T> {
122 type Solution = SolutionOf<T::MinerConfig>;
123
124 fn get_page(page: PageIndex) -> Self::Solution {
129 let current_round = Self::current_round();
130 Submissions::<T>::leader(current_round)
131 .defensive()
132 .and_then(|(who, _score)| {
133 sublog!(
134 debug,
135 "signed",
136 "returning page {} of {:?}'s submission as leader.",
137 page,
138 who
139 );
140 Submissions::<T>::get_page_of(current_round, &who, page)
141 })
142 .unwrap_or_default()
143 }
144
145 fn get_score() -> ElectionScore {
147 let current_round = Self::current_round();
148 Submissions::<T>::leader(current_round)
149 .defensive()
150 .inspect(|(_who, score)| {
151 sublog!(
152 debug,
153 "signed",
154 "returning score {:?} of current leader for round {}.",
155 score,
156 current_round
157 );
158 })
159 .map(|(_who, score)| score)
160 .unwrap_or_default()
161 }
162
163 fn report_result(result: crate::verifier::VerificationResult) {
164 debug_assert!(matches!(<T::Verifier as AsynchronousVerifier>::status(), Status::Nothing));
166 let current_round = Self::current_round();
167
168 match result {
169 VerificationResult::Queued => {
170 if let Some((winner, metadata)) =
173 Submissions::<T>::take_leader_with_data(Self::current_round()).defensive()
174 {
175 let reward =
177 metadata.reward.saturating_add(metadata.fee.min(T::MaxFeeRefund::get()));
178 let _r = T::Currency::mint_into(&winner, reward);
180 debug_assert!(_r.is_ok());
181 Self::deposit_event(Event::<T>::Rewarded(
182 current_round,
183 winner.clone(),
184 reward,
185 ));
186
187 let _res = T::Currency::release(
189 &HoldReason::SignedSubmission.into(),
190 &winner,
191 metadata.deposit,
192 Precision::BestEffort,
193 );
194 debug_assert!(_res.is_ok());
195 }
196 },
197 VerificationResult::Rejected => {
198 Self::handle_solution_rejection(current_round);
199 },
200 }
201 }
202}
203
204pub trait CalculateBaseDeposit<Balance> {
209 fn calculate_base_deposit(existing_submitters: usize) -> Balance;
210}
211
212impl<Balance, G: Get<Balance>> CalculateBaseDeposit<Balance> for G {
213 fn calculate_base_deposit(_existing_submitters: usize) -> Balance {
214 G::get()
215 }
216}
217
218pub trait CalculatePageDeposit<Balance> {
223 fn calculate_page_deposit(existing_submitters: usize, page_size: usize) -> Balance;
224}
225
226impl<Balance: From<u32> + Saturating, G: Get<Balance>> CalculatePageDeposit<Balance> for G {
227 fn calculate_page_deposit(_existing_submitters: usize, page_size: usize) -> Balance {
228 let page_size: Balance = (page_size as u32).into();
229 G::get().saturating_mul(page_size)
230 }
231}
232
233pub struct FullSubmissionFee<T, F>(PhantomData<(T, F)>);
241
242impl<T: Config, F: EstimateFee<BalanceOf<T>>> Get<BalanceOf<T>> for FullSubmissionFee<T, F> {
243 fn get() -> BalanceOf<T> {
244 let page_call = Call::<T>::submit_page { page: 0, maybe_solution: None };
246 let page_len = page_call
247 .encoded_size()
248 .saturating_add(SolutionOf::<T::MinerConfig>::max_encoded_len());
249 let per_page = F::estimate_fee(page_len as u32, &page_call.get_dispatch_info());
250
251 let register_call = Call::<T>::register { claimed_score: Default::default() };
252 let register = F::estimate_fee(
253 register_call.encoded_size() as u32,
254 ®ister_call.get_dispatch_info(),
255 );
256
257 register.saturating_add(per_page.saturating_mul(T::Pages::get().into()))
258 }
259}
260
261#[frame_support::pallet]
262pub mod pallet {
263 use super::*;
264
265 #[pallet::config]
266 #[pallet::disable_frame_system_supertrait_check]
267 pub trait Config: crate::Config {
268 type Currency: Inspect<Self::AccountId>
270 + Mutate<Self::AccountId>
271 + MutateHold<Self::AccountId, Reason: From<HoldReason>>;
272
273 type DepositBase: CalculateBaseDeposit<BalanceOf<Self>>;
275
276 type DepositPerPage: CalculatePageDeposit<BalanceOf<Self>>;
278
279 type InvulnerableDeposit: Get<BalanceOf<Self>>;
281
282 type RewardBase: Get<BalanceOf<Self>>;
284
285 type MaxSubmissions: Get<u32>;
288
289 type BailoutGraceRatio: Get<Perbill>;
295
296 type EjectGraceRatio: Get<Perbill>;
301
302 type EstimateCallFee: EstimateCallFee<Call<Self>, BalanceOf<Self>>;
305
306 type MaxFeeRefund: Get<BalanceOf<Self>>;
316
317 type WeightInfo: WeightInfo;
319 }
320
321 #[pallet::composite_enum]
323 pub enum HoldReason {
324 #[codec(index = 0)]
326 SignedSubmission,
327 }
328
329 #[pallet::storage]
340 pub type Invulnerables<T: Config> =
341 StorageValue<_, BoundedVec<T::AccountId, ConstU32<16>>, ValueQuery>;
342
343 pub(crate) struct Submissions<T: Config>(sp_std::marker::PhantomData<T>);
378
379 #[pallet::storage]
380 pub type SortedScores<T: Config> = StorageMap<
381 _,
382 Twox64Concat,
383 u32,
384 BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions>,
385 ValueQuery,
386 >;
387
388 #[pallet::storage]
390 type SubmissionStorage<T: Config> = StorageNMap<
391 _,
392 (
393 NMapKey<Twox64Concat, u32>,
394 NMapKey<Twox64Concat, T::AccountId>,
395 NMapKey<Twox64Concat, PageIndex>,
396 ),
397 SolutionOf<T::MinerConfig>,
398 OptionQuery,
399 >;
400
401 #[pallet::storage]
406 type SubmissionMetadataStorage<T: Config> =
407 StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata<T>>;
408
409 impl<T: Config> Submissions<T> {
410 fn mutate_checked<R, F: FnOnce() -> R>(_round: u32, mutate: F) -> R {
417 let result = mutate();
418
419 #[cfg(debug_assertions)]
420 {
421 assert!(Self::sanity_check_round(_round).is_ok());
422 assert!(Self::sanity_check_round(_round + 1).is_ok());
423 assert!(Self::sanity_check_round(_round.saturating_sub(1)).is_ok());
424 }
425
426 result
427 }
428
429 pub(crate) fn take_leader_with_data(
435 round: u32,
436 ) -> Option<(T::AccountId, SubmissionMetadata<T>)> {
437 Self::mutate_checked(round, || {
438 SortedScores::<T>::mutate(round, |sorted| sorted.pop()).and_then(
439 |(submitter, _score)| {
440 let r: MultiRemovalResults = SubmissionStorage::<T>::clear_prefix(
442 (round, &submitter),
443 u32::MAX,
444 None,
445 );
446 debug_assert!(r.unique <= T::Pages::get());
447
448 SubmissionMetadataStorage::<T>::take(round, &submitter)
449 .map(|metadata| (submitter, metadata))
450 },
451 )
452 })
453 }
454
455 pub(crate) fn take_submission_with_data(
461 round: u32,
462 who: &T::AccountId,
463 ) -> Option<SubmissionMetadata<T>> {
464 Self::mutate_checked(round, || {
465 let mut sorted_scores = SortedScores::<T>::get(round);
466 if let Some(index) = sorted_scores.iter().position(|(x, _)| x == who) {
467 sorted_scores.remove(index);
468 }
469 if sorted_scores.is_empty() {
470 SortedScores::<T>::remove(round);
471 } else {
472 SortedScores::<T>::insert(round, sorted_scores);
473 }
474
475 let r = SubmissionStorage::<T>::clear_prefix((round, who), u32::MAX, None);
477 debug_assert!(r.unique <= T::Pages::get());
478
479 SubmissionMetadataStorage::<T>::take(round, who)
480 })
481 }
482
483 fn try_register(
490 round: u32,
491 who: &T::AccountId,
492 metadata: SubmissionMetadata<T>,
493 ) -> Result<bool, DispatchError> {
494 Self::mutate_checked(round, || Self::try_register_inner(round, who, metadata))
495 }
496
497 fn try_register_inner(
498 round: u32,
499 who: &T::AccountId,
500 metadata: SubmissionMetadata<T>,
501 ) -> Result<bool, DispatchError> {
502 let mut sorted_scores = SortedScores::<T>::get(round);
503
504 let did_eject = if let Some(_) = sorted_scores.iter().position(|(x, _)| x == who) {
505 return Err(Error::<T>::Duplicate.into());
506 } else {
507 debug_assert!(!SubmissionMetadataStorage::<T>::contains_key(round, who));
509
510 let insert_idx = match sorted_scores
511 .binary_search_by_key(&metadata.claimed_score, |(_, y)| *y)
512 {
513 Ok(pos) => pos,
516 Err(pos) => pos,
518 };
519
520 let mut record = (who.clone(), metadata.claimed_score);
521 if sorted_scores.is_full() {
522 let remove_idx = sorted_scores
523 .iter()
524 .position(|(x, _)| !Pallet::<T>::is_invulnerable(x))
525 .ok_or(Error::<T>::QueueFull)?;
526 if insert_idx > remove_idx {
527 sp_std::mem::swap(&mut sorted_scores[remove_idx], &mut record);
529 sorted_scores[remove_idx..insert_idx].rotate_left(1);
535
536 let discarded = record.0;
537 let maybe_metadata =
538 SubmissionMetadataStorage::<T>::take(round, &discarded).defensive();
539 let _r = SubmissionStorage::<T>::clear_prefix(
541 (round, &discarded),
542 u32::MAX,
543 None,
544 );
545 debug_assert!(_r.unique <= T::Pages::get());
546
547 if let Some(metadata) = maybe_metadata {
548 Pallet::<T>::settle_deposit(
549 &discarded,
550 metadata.deposit,
551 T::EjectGraceRatio::get(),
552 );
553 }
554
555 Pallet::<T>::deposit_event(Event::<T>::Ejected(round, discarded));
556 true
557 } else {
558 return Err(Error::<T>::QueueFull.into());
560 }
561 } else {
562 sorted_scores
563 .try_insert(insert_idx, record)
564 .expect("length checked above; qed");
565 false
566 }
567 };
568
569 SortedScores::<T>::insert(round, sorted_scores);
570 SubmissionMetadataStorage::<T>::insert(round, who, metadata);
571 Ok(did_eject)
572 }
573
574 pub(crate) fn try_mutate_page(
582 round: u32,
583 who: &T::AccountId,
584 page: PageIndex,
585 maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
586 ) -> DispatchResultWithPostInfo {
587 Self::mutate_checked(round, || {
588 Self::try_mutate_page_inner(round, who, page, maybe_solution)
589 })
590 }
591
592 fn deposit_for(who: &T::AccountId, pages: usize) -> BalanceOf<T> {
594 if Pallet::<T>::is_invulnerable(who) {
595 T::InvulnerableDeposit::get()
596 } else {
597 let round = Pallet::<T>::current_round();
598 let queue_size = Self::submitters_count(round);
599 let base = T::DepositBase::calculate_base_deposit(queue_size);
600 let pages = T::DepositPerPage::calculate_page_deposit(queue_size, pages);
601 base.saturating_add(pages)
602 }
603 }
604
605 fn try_mutate_page_inner(
606 round: u32,
607 who: &T::AccountId,
608 page: PageIndex,
609 maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
610 ) -> DispatchResultWithPostInfo {
611 let mut metadata =
612 SubmissionMetadataStorage::<T>::get(round, who).ok_or(Error::<T>::NotRegistered)?;
613 ensure!(page < T::Pages::get(), Error::<T>::BadPageIndex);
614
615 let was_set = metadata.pages.get(page as usize).copied().unwrap_or_default();
616
617 if let Some(page_bit) = metadata.pages.get_mut(page as usize).defensive() {
620 *page_bit = maybe_solution.is_some();
621 }
622
623 let new_pages = metadata.pages.iter().filter(|x| **x).count();
625 let new_deposit = Self::deposit_for(&who, new_pages);
626 let old_deposit = metadata.deposit;
627 if new_deposit > old_deposit {
628 let to_reserve = new_deposit - old_deposit;
629 T::Currency::hold(&HoldReason::SignedSubmission.into(), who, to_reserve)?;
630 } else {
631 let to_unreserve = old_deposit - new_deposit;
632 let _res = T::Currency::release(
633 &HoldReason::SignedSubmission.into(),
634 who,
635 to_unreserve,
636 Precision::BestEffort,
637 );
638 debug_assert_eq!(_res, Ok(to_unreserve));
639 };
640 metadata.deposit = new_deposit;
641
642 if maybe_solution.is_some() && !was_set {
648 let fee = T::EstimateCallFee::estimate_call_fee(
649 &Call::submit_page { page, maybe_solution: maybe_solution.clone() },
650 None.into(),
651 );
652 metadata.fee.saturating_accrue(fee);
653 }
654
655 SubmissionStorage::<T>::mutate_exists((round, who, page), |maybe_old_solution| {
656 *maybe_old_solution = maybe_solution.map(|s| *s)
657 });
658 SubmissionMetadataStorage::<T>::insert(round, who, metadata);
659 Ok(().into())
660 }
661
662 pub(crate) fn has_leader(round: u32) -> bool {
664 !SortedScores::<T>::get(round).is_empty()
665 }
666
667 pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> {
668 SortedScores::<T>::get(round).last().cloned()
669 }
670
671 pub(crate) fn submitters_count(round: u32) -> usize {
672 SortedScores::<T>::get(round).len()
673 }
674
675 pub(crate) fn get_page_of(
676 round: u32,
677 who: &T::AccountId,
678 page: PageIndex,
679 ) -> Option<SolutionOf<T::MinerConfig>> {
680 SubmissionStorage::<T>::get((round, who, &page))
681 }
682 }
683
684 #[allow(unused)]
685 #[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks", debug_assertions))]
686 impl<T: Config> Submissions<T> {
687 pub(crate) fn sorted_submitters(round: u32) -> BoundedVec<T::AccountId, T::MaxSubmissions> {
688 use frame_support::traits::TryCollect;
689 SortedScores::<T>::get(round).into_iter().map(|(x, _)| x).try_collect().unwrap()
690 }
691
692 pub fn submissions_iter(
693 round: u32,
694 ) -> impl Iterator<Item = (T::AccountId, PageIndex, SolutionOf<T::MinerConfig>)> {
695 SubmissionStorage::<T>::iter_prefix((round,)).map(|((x, y), z)| (x, y, z))
696 }
697
698 pub fn metadata_iter(
699 round: u32,
700 ) -> impl Iterator<Item = (T::AccountId, SubmissionMetadata<T>)> {
701 SubmissionMetadataStorage::<T>::iter_prefix(round)
702 }
703
704 pub fn metadata_of(round: u32, who: T::AccountId) -> Option<SubmissionMetadata<T>> {
705 SubmissionMetadataStorage::<T>::get(round, who)
706 }
707
708 pub fn pages_of(
709 round: u32,
710 who: T::AccountId,
711 ) -> impl Iterator<Item = (PageIndex, SolutionOf<T::MinerConfig>)> {
712 SubmissionStorage::<T>::iter_prefix((round, who))
713 }
714
715 pub fn leaderboard(
716 round: u32,
717 ) -> BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions> {
718 SortedScores::<T>::get(round)
719 }
720
721 pub(crate) fn ensure_killed(round: u32) -> DispatchResult {
724 ensure!(Self::metadata_iter(round).count() == 0, "metadata_iter not cleared.");
725 ensure!(Self::submissions_iter(round).count() == 0, "submissions_iter not cleared.");
726 ensure!(Self::sorted_submitters(round).len() == 0, "sorted_submitters not cleared.");
727
728 Ok(())
729 }
730
731 pub(crate) fn ensure_killed_with(who: &T::AccountId, round: u32) -> DispatchResult {
733 ensure!(
734 SubmissionMetadataStorage::<T>::get(round, who).is_none(),
735 "metadata not cleared."
736 );
737 ensure!(
738 SubmissionStorage::<T>::iter_prefix((round, who)).count() == 0,
739 "submissions not cleared."
740 );
741 ensure!(
742 SortedScores::<T>::get(round).iter().all(|(x, _)| x != who),
743 "sorted_submitters not cleared."
744 );
745
746 Ok(())
747 }
748
749 pub(crate) fn sanity_check_round(round: u32) -> DispatchResult {
751 use sp_std::collections::btree_set::BTreeSet;
752 let sorted_scores = SortedScores::<T>::get(round);
753 assert_eq!(
754 sorted_scores.clone().into_iter().map(|(x, _)| x).collect::<BTreeSet<_>>().len(),
755 sorted_scores.len()
756 );
757
758 let _ = SubmissionMetadataStorage::<T>::iter_prefix(round)
759 .map(|(submitter, meta)| {
760 let mut matches = SortedScores::<T>::get(round)
761 .into_iter()
762 .filter(|(who, _score)| who == &submitter)
763 .collect::<Vec<_>>();
764
765 ensure!(
766 matches.len() == 1,
767 "item existing in metadata but missing in sorted list.",
768 );
769
770 let (_, score) = matches.pop().expect("checked; qed");
771 ensure!(score == meta.claimed_score, "score mismatch");
772 Ok(())
773 })
774 .collect::<Result<Vec<_>, &'static str>>()?;
775
776 ensure!(
777 SubmissionStorage::<T>::iter_key_prefix((round,)).map(|(k1, _k2)| k1).all(
778 |submitter| SubmissionMetadataStorage::<T>::contains_key(round, submitter)
779 ),
780 "missing metadata of submitter"
781 );
782
783 for submitter in SubmissionStorage::<T>::iter_key_prefix((round,)).map(|(k1, _k2)| k1) {
784 let pages_count =
785 SubmissionStorage::<T>::iter_key_prefix((round, &submitter)).count();
786 let metadata = SubmissionMetadataStorage::<T>::get(round, submitter)
787 .expect("metadata checked to exist for all keys; qed");
788 let assumed_pages_count = metadata.pages.iter().filter(|x| **x).count();
789 ensure!(pages_count == assumed_pages_count, "wrong page count");
790 }
791
792 Ok(())
793 }
794 }
795
796 #[pallet::pallet]
797 pub struct Pallet<T>(PhantomData<T>);
798
799 #[pallet::event]
800 #[pallet::generate_deposit(pub(super) fn deposit_event)]
801 pub enum Event<T: Config> {
802 Registered(u32, T::AccountId, ElectionScore),
804 Stored(u32, T::AccountId, PageIndex),
806 Rewarded(u32, T::AccountId, BalanceOf<T>),
808 Slashed(u32, T::AccountId, BalanceOf<T>),
810 Ejected(u32, T::AccountId),
812 Discarded(u32, T::AccountId),
814 Bailed(u32, T::AccountId),
816 }
817
818 #[pallet::error]
819 pub enum Error<T> {
820 PhaseNotSigned,
822 Duplicate,
824 QueueFull,
826 BadPageIndex,
828 NotRegistered,
830 NoSubmission,
832 RoundNotOver,
834 BadWitnessData,
836 TooManyInvulnerables,
838 }
839
840 #[pallet::call]
841 impl<T: Config> Pallet<T> {
842 #[pallet::weight(SignedWeightsOf::<T>::register_eject())]
844 #[pallet::call_index(0)]
845 pub fn register(
846 origin: OriginFor<T>,
847 claimed_score: ElectionScore,
848 ) -> DispatchResultWithPostInfo {
849 let who = ensure_signed(origin)?;
850 ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
851
852 let deposit = Submissions::<T>::deposit_for(&who, 0);
856 let reward = T::RewardBase::get();
857 let fee = T::EstimateCallFee::estimate_call_fee(
858 &Call::register { claimed_score },
859 None.into(),
860 );
861 let mut pages = BoundedVec::<_, _>::with_bounded_capacity(T::Pages::get() as usize);
862 pages.bounded_resize(T::Pages::get() as usize, false);
863
864 let new_metadata = SubmissionMetadata { claimed_score, deposit, reward, fee, pages };
865
866 T::Currency::hold(&HoldReason::SignedSubmission.into(), &who, deposit)?;
867 let round = Self::current_round();
868 let discarded = Submissions::<T>::try_register(round, &who, new_metadata)?;
869 Self::deposit_event(Event::<T>::Registered(round, who, claimed_score));
870
871 if discarded {
873 Ok(().into())
874 } else {
875 Ok(Some(SignedWeightsOf::<T>::register_not_full()).into())
876 }
877 }
878
879 #[pallet::weight(SignedWeightsOf::<T>::submit_page())]
888 #[pallet::call_index(1)]
889 pub fn submit_page(
890 origin: OriginFor<T>,
891 page: PageIndex,
892 maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
893 ) -> DispatchResultWithPostInfo {
894 let who = ensure_signed(origin)?;
895 ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
896 let is_set = maybe_solution.is_some();
897
898 let round = Self::current_round();
899 Submissions::<T>::try_mutate_page(round, &who, page, maybe_solution)?;
900 Self::deposit_event(Event::<T>::Stored(round, who, page));
901
902 if is_set {
904 Ok(().into())
905 } else {
906 Ok(Some(SignedWeightsOf::<T>::unset_page()).into())
907 }
908 }
909
910 #[pallet::weight(SignedWeightsOf::<T>::bail())]
916 #[pallet::call_index(2)]
917 pub fn bail(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
918 let who = ensure_signed(origin)?;
919 ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
920 let round = Self::current_round();
921 let metadata = Submissions::<T>::take_submission_with_data(round, &who)
922 .ok_or(Error::<T>::NoSubmission)?;
923
924 let deposit = metadata.deposit;
925 Self::settle_deposit(&who, deposit, T::BailoutGraceRatio::get());
926 Self::deposit_event(Event::<T>::Bailed(round, who));
927
928 Ok(None.into())
929 }
930
931 #[pallet::call_index(3)]
938 #[pallet::weight(SignedWeightsOf::<T>::clear_old_round_data(*witness_pages))]
939 pub fn clear_old_round_data(
940 origin: OriginFor<T>,
941 round: u32,
942 witness_pages: u32,
943 ) -> DispatchResultWithPostInfo {
944 let discarded = ensure_signed(origin)?;
945
946 let current_round = Self::current_round();
947 ensure!(round < current_round, Error::<T>::RoundNotOver);
949
950 let metadata = Submissions::<T>::take_submission_with_data(round, &discarded)
951 .ok_or(Error::<T>::NoSubmission)?;
952 ensure!(
953 metadata.pages.iter().filter(|p| **p).count() as u32 <= witness_pages,
954 Error::<T>::BadWitnessData
955 );
956
957 let _res = T::Currency::release(
959 &HoldReason::SignedSubmission.into(),
960 &discarded,
961 metadata.deposit,
962 Precision::BestEffort,
963 );
964 debug_assert_eq!(_res, Ok(metadata.deposit));
965
966 if Self::is_invulnerable(&discarded) {
968 let refund = metadata.fee.min(T::MaxFeeRefund::get());
969 let _r = T::Currency::mint_into(&discarded, refund);
971 debug_assert!(_r.is_ok());
972 }
973
974 Self::deposit_event(Event::<T>::Discarded(round, discarded));
975
976 Ok(None.into())
978 }
979
980 #[pallet::call_index(4)]
984 #[pallet::weight(T::DbWeight::get().writes(1))]
985 pub fn set_invulnerables(origin: OriginFor<T>, inv: Vec<T::AccountId>) -> DispatchResult {
986 <T as crate::Config>::AdminOrigin::ensure_origin(origin)?;
987 let bounded: BoundedVec<_, ConstU32<16>> =
988 inv.try_into().map_err(|_| Error::<T>::TooManyInvulnerables)?;
989 Invulnerables::<T>::set(bounded);
990 Ok(())
991 }
992 }
993
994 #[pallet::view_functions]
995 impl<T: Config> Pallet<T> {
996 pub fn deposit_for(who: T::AccountId, pages: u32) -> BalanceOf<T> {
1002 Submissions::<T>::deposit_for(&who, pages as usize)
1003 }
1004 }
1005
1006 #[pallet::hooks]
1007 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
1008 #[cfg(feature = "try-runtime")]
1009 fn try_state(n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
1010 Self::do_try_state(n)
1011 }
1012 }
1013}
1014
1015impl<T: Config> Pallet<T> {
1016 #[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks"))]
1017 pub(crate) fn do_try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
1018 Submissions::<T>::sanity_check_round(Self::current_round())
1019 }
1020
1021 fn current_round() -> u32 {
1022 crate::Pallet::<T>::round()
1023 }
1024
1025 fn is_invulnerable(who: &T::AccountId) -> bool {
1026 Invulnerables::<T>::get().contains(who)
1027 }
1028
1029 fn settle_deposit(who: &T::AccountId, deposit: BalanceOf<T>, grace: Perbill) {
1030 let to_refund = grace * deposit;
1031 let to_slash = deposit.defensive_saturating_sub(to_refund);
1032
1033 let _res = T::Currency::release(
1034 &HoldReason::SignedSubmission.into(),
1035 who,
1036 to_refund,
1037 Precision::BestEffort,
1038 )
1039 .defensive();
1040 debug_assert_eq!(_res, Ok(to_refund));
1041
1042 let _res = T::Currency::burn_held(
1043 &HoldReason::SignedSubmission.into(),
1044 who,
1045 to_slash,
1046 Precision::BestEffort,
1047 Fortitude::Force,
1048 )
1049 .defensive();
1050 debug_assert_eq!(_res, Ok(to_slash));
1051 }
1052
1053 fn handle_solution_rejection(current_round: u32) {
1055 if let Some((loser, metadata)) =
1056 Submissions::<T>::take_leader_with_data(current_round).defensive()
1057 {
1058 let slash = metadata.deposit;
1064 let _res = T::Currency::burn_held(
1065 &HoldReason::SignedSubmission.into(),
1066 &loser,
1067 slash,
1068 Precision::BestEffort,
1069 Fortitude::Force,
1070 );
1071 debug_assert_eq!(_res, Ok(slash));
1072 Self::deposit_event(Event::<T>::Slashed(current_round, loser.clone(), slash));
1073
1074 if let crate::types::Phase::SignedValidation(remaining_blocks) =
1076 crate::Pallet::<T>::current_phase()
1077 {
1078 if remaining_blocks >= T::Pages::get().into() {
1081 if Submissions::<T>::has_leader(current_round) {
1082 let _ = <T::Verifier as AsynchronousVerifier>::start().defensive();
1085 }
1086 } else {
1087 sublog!(
1088 warn,
1089 "signed",
1090 "SignedValidation phase has {:?} blocks remaining, which are insufficient for {} pages",
1091 remaining_blocks,
1092 T::Pages::get()
1093 );
1094 }
1095 }
1096 } else {
1097 sublog!(
1099 warn,
1100 "signed",
1101 "Tried to slash but no leader was present for round {}",
1102 current_round
1103 );
1104 }
1105 }
1106}