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> SolutionDataProvider for Pallet<T> {
115	type Solution = SolutionOf<T::MinerConfig>;
116
117	fn get_page(page: PageIndex) -> Self::Solution {
122		let current_round = Self::current_round();
123		Submissions::<T>::leader(current_round)
124			.defensive()
125			.and_then(|(who, _score)| {
126				sublog!(
127					debug,
128					"signed",
129					"returning page {} of {:?}'s submission as leader.",
130					page,
131					who
132				);
133				Submissions::<T>::get_page_of(current_round, &who, page)
134			})
135			.unwrap_or_default()
136	}
137
138	fn get_score() -> ElectionScore {
140		let current_round = Self::current_round();
141		Submissions::<T>::leader(current_round)
142			.defensive()
143			.inspect(|(_who, score)| {
144				sublog!(
145					debug,
146					"signed",
147					"returning score {:?} of current leader for round {}.",
148					score,
149					current_round
150				);
151			})
152			.map(|(_who, score)| score)
153			.unwrap_or_default()
154	}
155
156	fn report_result(result: crate::verifier::VerificationResult) {
157		debug_assert!(matches!(<T::Verifier as AsynchronousVerifier>::status(), Status::Nothing));
159		let current_round = Self::current_round();
160
161		match result {
162			VerificationResult::Queued => {
163				if let Some((winner, metadata)) =
166					Submissions::<T>::take_leader_with_data(Self::current_round()).defensive()
167				{
168					let reward = metadata.reward.saturating_add(metadata.fee);
170					let _r = T::Currency::mint_into(&winner, reward);
171					debug_assert!(_r.is_ok());
172					Self::deposit_event(Event::<T>::Rewarded(
173						current_round,
174						winner.clone(),
175						reward,
176					));
177
178					let _res = T::Currency::release(
180						&HoldReason::SignedSubmission.into(),
181						&winner,
182						metadata.deposit,
183						Precision::BestEffort,
184					);
185					debug_assert!(_res.is_ok());
186				}
187			},
188			VerificationResult::Rejected => {
189				Self::handle_solution_rejection(current_round);
190			},
191		}
192	}
193}
194
195pub trait CalculateBaseDeposit<Balance> {
200	fn calculate_base_deposit(existing_submitters: usize) -> Balance;
201}
202
203impl<Balance, G: Get<Balance>> CalculateBaseDeposit<Balance> for G {
204	fn calculate_base_deposit(_existing_submitters: usize) -> Balance {
205		G::get()
206	}
207}
208
209pub trait CalculatePageDeposit<Balance> {
214	fn calculate_page_deposit(existing_submitters: usize, page_size: usize) -> Balance;
215}
216
217impl<Balance: From<u32> + Saturating, G: Get<Balance>> CalculatePageDeposit<Balance> for G {
218	fn calculate_page_deposit(_existing_submitters: usize, page_size: usize) -> Balance {
219		let page_size: Balance = (page_size as u32).into();
220		G::get().saturating_mul(page_size)
221	}
222}
223
224#[frame_support::pallet]
225pub mod pallet {
226	use super::*;
227
228	#[pallet::config]
229	#[pallet::disable_frame_system_supertrait_check]
230	pub trait Config: crate::Config {
231		type Currency: Inspect<Self::AccountId>
233			+ Mutate<Self::AccountId>
234			+ MutateHold<Self::AccountId, Reason: From<HoldReason>>;
235
236		type DepositBase: CalculateBaseDeposit<BalanceOf<Self>>;
238
239		type DepositPerPage: CalculatePageDeposit<BalanceOf<Self>>;
241
242		type InvulnerableDeposit: Get<BalanceOf<Self>>;
244
245		type RewardBase: Get<BalanceOf<Self>>;
247
248		type MaxSubmissions: Get<u32>;
251
252		type BailoutGraceRatio: Get<Perbill>;
258
259		type EjectGraceRatio: Get<Perbill>;
264
265		type EstimateCallFee: EstimateCallFee<Call<Self>, BalanceOf<Self>>;
268
269		type WeightInfo: WeightInfo;
271	}
272
273	#[pallet::composite_enum]
275	pub enum HoldReason {
276		#[codec(index = 0)]
278		SignedSubmission,
279	}
280
281	#[pallet::storage]
291	pub type Invulnerables<T: Config> =
292		StorageValue<_, BoundedVec<T::AccountId, ConstU32<16>>, ValueQuery>;
293
294	pub(crate) struct Submissions<T: Config>(sp_std::marker::PhantomData<T>);
329
330	#[pallet::storage]
331	pub type SortedScores<T: Config> = StorageMap<
332		_,
333		Twox64Concat,
334		u32,
335		BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions>,
336		ValueQuery,
337	>;
338
339	#[pallet::storage]
341	type SubmissionStorage<T: Config> = StorageNMap<
342		_,
343		(
344			NMapKey<Twox64Concat, u32>,
345			NMapKey<Twox64Concat, T::AccountId>,
346			NMapKey<Twox64Concat, PageIndex>,
347		),
348		SolutionOf<T::MinerConfig>,
349		OptionQuery,
350	>;
351
352	#[pallet::storage]
357	type SubmissionMetadataStorage<T: Config> =
358		StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata<T>>;
359
360	impl<T: Config> Submissions<T> {
361		fn mutate_checked<R, F: FnOnce() -> R>(_round: u32, mutate: F) -> R {
368			let result = mutate();
369
370			#[cfg(debug_assertions)]
371			{
372				assert!(Self::sanity_check_round(_round).is_ok());
373				assert!(Self::sanity_check_round(_round + 1).is_ok());
374				assert!(Self::sanity_check_round(_round.saturating_sub(1)).is_ok());
375			}
376
377			result
378		}
379
380		pub(crate) fn take_leader_with_data(
386			round: u32,
387		) -> Option<(T::AccountId, SubmissionMetadata<T>)> {
388			Self::mutate_checked(round, || {
389				SortedScores::<T>::mutate(round, |sorted| sorted.pop()).and_then(
390					|(submitter, _score)| {
391						let r: MultiRemovalResults = SubmissionStorage::<T>::clear_prefix(
393							(round, &submitter),
394							u32::MAX,
395							None,
396						);
397						debug_assert!(r.unique <= T::Pages::get());
398
399						SubmissionMetadataStorage::<T>::take(round, &submitter)
400							.map(|metadata| (submitter, metadata))
401					},
402				)
403			})
404		}
405
406		pub(crate) fn take_submission_with_data(
412			round: u32,
413			who: &T::AccountId,
414		) -> Option<SubmissionMetadata<T>> {
415			Self::mutate_checked(round, || {
416				let mut sorted_scores = SortedScores::<T>::get(round);
417				if let Some(index) = sorted_scores.iter().position(|(x, _)| x == who) {
418					sorted_scores.remove(index);
419				}
420				if sorted_scores.is_empty() {
421					SortedScores::<T>::remove(round);
422				} else {
423					SortedScores::<T>::insert(round, sorted_scores);
424				}
425
426				let r = SubmissionStorage::<T>::clear_prefix((round, who), u32::MAX, None);
428				debug_assert!(r.unique <= T::Pages::get());
429
430				SubmissionMetadataStorage::<T>::take(round, who)
431			})
432		}
433
434		fn try_register(
441			round: u32,
442			who: &T::AccountId,
443			metadata: SubmissionMetadata<T>,
444		) -> Result<bool, DispatchError> {
445			Self::mutate_checked(round, || Self::try_register_inner(round, who, metadata))
446		}
447
448		fn try_register_inner(
449			round: u32,
450			who: &T::AccountId,
451			metadata: SubmissionMetadata<T>,
452		) -> Result<bool, DispatchError> {
453			let mut sorted_scores = SortedScores::<T>::get(round);
454
455			let did_eject = if let Some(_) = sorted_scores.iter().position(|(x, _)| x == who) {
456				return Err(Error::<T>::Duplicate.into());
457			} else {
458				debug_assert!(!SubmissionMetadataStorage::<T>::contains_key(round, who));
460
461				let insert_idx = match sorted_scores
462					.binary_search_by_key(&metadata.claimed_score, |(_, y)| *y)
463				{
464					Ok(pos) => pos,
467					Err(pos) => pos,
469				};
470
471				let mut record = (who.clone(), metadata.claimed_score);
472				if sorted_scores.is_full() {
473					let remove_idx = sorted_scores
474						.iter()
475						.position(|(x, _)| !Pallet::<T>::is_invulnerable(x))
476						.ok_or(Error::<T>::QueueFull)?;
477					if insert_idx > remove_idx {
478						sp_std::mem::swap(&mut sorted_scores[remove_idx], &mut record);
480						sorted_scores[remove_idx..insert_idx].rotate_left(1);
486
487						let discarded = record.0;
488						let maybe_metadata =
489							SubmissionMetadataStorage::<T>::take(round, &discarded).defensive();
490						let _r = SubmissionStorage::<T>::clear_prefix(
492							(round, &discarded),
493							u32::MAX,
494							None,
495						);
496						debug_assert!(_r.unique <= T::Pages::get());
497
498						if let Some(metadata) = maybe_metadata {
499							Pallet::<T>::settle_deposit(
500								&discarded,
501								metadata.deposit,
502								T::EjectGraceRatio::get(),
503							);
504						}
505
506						Pallet::<T>::deposit_event(Event::<T>::Ejected(round, discarded));
507						true
508					} else {
509						return Err(Error::<T>::QueueFull.into())
511					}
512				} else {
513					sorted_scores
514						.try_insert(insert_idx, record)
515						.expect("length checked above; qed");
516					false
517				}
518			};
519
520			SortedScores::<T>::insert(round, sorted_scores);
521			SubmissionMetadataStorage::<T>::insert(round, who, metadata);
522			Ok(did_eject)
523		}
524
525		pub(crate) fn try_mutate_page(
533			round: u32,
534			who: &T::AccountId,
535			page: PageIndex,
536			maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
537		) -> DispatchResultWithPostInfo {
538			Self::mutate_checked(round, || {
539				Self::try_mutate_page_inner(round, who, page, maybe_solution)
540			})
541		}
542
543		fn deposit_for(who: &T::AccountId, pages: usize) -> BalanceOf<T> {
545			if Pallet::<T>::is_invulnerable(who) {
546				T::InvulnerableDeposit::get()
547			} else {
548				let round = Pallet::<T>::current_round();
549				let queue_size = Self::submitters_count(round);
550				let base = T::DepositBase::calculate_base_deposit(queue_size);
551				let pages = T::DepositPerPage::calculate_page_deposit(queue_size, pages);
552				base.saturating_add(pages)
553			}
554		}
555
556		fn try_mutate_page_inner(
557			round: u32,
558			who: &T::AccountId,
559			page: PageIndex,
560			maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
561		) -> DispatchResultWithPostInfo {
562			let mut metadata =
563				SubmissionMetadataStorage::<T>::get(round, who).ok_or(Error::<T>::NotRegistered)?;
564			ensure!(page < T::Pages::get(), Error::<T>::BadPageIndex);
565
566			if let Some(page_bit) = metadata.pages.get_mut(page as usize).defensive() {
569				*page_bit = maybe_solution.is_some();
570			}
571
572			let new_pages = metadata.pages.iter().filter(|x| **x).count();
574			let new_deposit = Self::deposit_for(&who, new_pages);
575			let old_deposit = metadata.deposit;
576			if new_deposit > old_deposit {
577				let to_reserve = new_deposit - old_deposit;
578				T::Currency::hold(&HoldReason::SignedSubmission.into(), who, to_reserve)?;
579			} else {
580				let to_unreserve = old_deposit - new_deposit;
581				let _res = T::Currency::release(
582					&HoldReason::SignedSubmission.into(),
583					who,
584					to_unreserve,
585					Precision::BestEffort,
586				);
587				debug_assert_eq!(_res, Ok(to_unreserve));
588			};
589			metadata.deposit = new_deposit;
590
591			if maybe_solution.is_some() {
595				let fee = T::EstimateCallFee::estimate_call_fee(
596					&Call::submit_page { page, maybe_solution: maybe_solution.clone() },
597					None.into(),
598				);
599				metadata.fee.saturating_accrue(fee);
600			}
601
602			SubmissionStorage::<T>::mutate_exists((round, who, page), |maybe_old_solution| {
603				*maybe_old_solution = maybe_solution.map(|s| *s)
604			});
605			SubmissionMetadataStorage::<T>::insert(round, who, metadata);
606			Ok(().into())
607		}
608
609		pub(crate) fn has_leader(round: u32) -> bool {
611			!SortedScores::<T>::get(round).is_empty()
612		}
613
614		pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> {
615			SortedScores::<T>::get(round).last().cloned()
616		}
617
618		pub(crate) fn submitters_count(round: u32) -> usize {
619			SortedScores::<T>::get(round).len()
620		}
621
622		pub(crate) fn get_page_of(
623			round: u32,
624			who: &T::AccountId,
625			page: PageIndex,
626		) -> Option<SolutionOf<T::MinerConfig>> {
627			SubmissionStorage::<T>::get((round, who, &page))
628		}
629	}
630
631	#[allow(unused)]
632	#[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks", debug_assertions))]
633	impl<T: Config> Submissions<T> {
634		pub(crate) fn sorted_submitters(round: u32) -> BoundedVec<T::AccountId, T::MaxSubmissions> {
635			use frame_support::traits::TryCollect;
636			SortedScores::<T>::get(round).into_iter().map(|(x, _)| x).try_collect().unwrap()
637		}
638
639		pub fn submissions_iter(
640			round: u32,
641		) -> impl Iterator<Item = (T::AccountId, PageIndex, SolutionOf<T::MinerConfig>)> {
642			SubmissionStorage::<T>::iter_prefix((round,)).map(|((x, y), z)| (x, y, z))
643		}
644
645		pub fn metadata_iter(
646			round: u32,
647		) -> impl Iterator<Item = (T::AccountId, SubmissionMetadata<T>)> {
648			SubmissionMetadataStorage::<T>::iter_prefix(round)
649		}
650
651		pub fn metadata_of(round: u32, who: T::AccountId) -> Option<SubmissionMetadata<T>> {
652			SubmissionMetadataStorage::<T>::get(round, who)
653		}
654
655		pub fn pages_of(
656			round: u32,
657			who: T::AccountId,
658		) -> impl Iterator<Item = (PageIndex, SolutionOf<T::MinerConfig>)> {
659			SubmissionStorage::<T>::iter_prefix((round, who))
660		}
661
662		pub fn leaderboard(
663			round: u32,
664		) -> BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions> {
665			SortedScores::<T>::get(round)
666		}
667
668		pub(crate) fn ensure_killed(round: u32) -> DispatchResult {
671			ensure!(Self::metadata_iter(round).count() == 0, "metadata_iter not cleared.");
672			ensure!(Self::submissions_iter(round).count() == 0, "submissions_iter not cleared.");
673			ensure!(Self::sorted_submitters(round).len() == 0, "sorted_submitters not cleared.");
674
675			Ok(())
676		}
677
678		pub(crate) fn ensure_killed_with(who: &T::AccountId, round: u32) -> DispatchResult {
680			ensure!(
681				SubmissionMetadataStorage::<T>::get(round, who).is_none(),
682				"metadata not cleared."
683			);
684			ensure!(
685				SubmissionStorage::<T>::iter_prefix((round, who)).count() == 0,
686				"submissions not cleared."
687			);
688			ensure!(
689				SortedScores::<T>::get(round).iter().all(|(x, _)| x != who),
690				"sorted_submitters not cleared."
691			);
692
693			Ok(())
694		}
695
696		pub(crate) fn sanity_check_round(round: u32) -> DispatchResult {
698			use sp_std::collections::btree_set::BTreeSet;
699			let sorted_scores = SortedScores::<T>::get(round);
700			assert_eq!(
701				sorted_scores.clone().into_iter().map(|(x, _)| x).collect::<BTreeSet<_>>().len(),
702				sorted_scores.len()
703			);
704
705			let _ = SubmissionMetadataStorage::<T>::iter_prefix(round)
706				.map(|(submitter, meta)| {
707					let mut matches = SortedScores::<T>::get(round)
708						.into_iter()
709						.filter(|(who, _score)| who == &submitter)
710						.collect::<Vec<_>>();
711
712					ensure!(
713						matches.len() == 1,
714						"item existing in metadata but missing in sorted list.",
715					);
716
717					let (_, score) = matches.pop().expect("checked; qed");
718					ensure!(score == meta.claimed_score, "score mismatch");
719					Ok(())
720				})
721				.collect::<Result<Vec<_>, &'static str>>()?;
722
723			ensure!(
724				SubmissionStorage::<T>::iter_key_prefix((round,)).map(|(k1, _k2)| k1).all(
725					|submitter| SubmissionMetadataStorage::<T>::contains_key(round, submitter)
726				),
727				"missing metadata of submitter"
728			);
729
730			for submitter in SubmissionStorage::<T>::iter_key_prefix((round,)).map(|(k1, _k2)| k1) {
731				let pages_count =
732					SubmissionStorage::<T>::iter_key_prefix((round, &submitter)).count();
733				let metadata = SubmissionMetadataStorage::<T>::get(round, submitter)
734					.expect("metadata checked to exist for all keys; qed");
735				let assumed_pages_count = metadata.pages.iter().filter(|x| **x).count();
736				ensure!(pages_count == assumed_pages_count, "wrong page count");
737			}
738
739			Ok(())
740		}
741	}
742
743	#[pallet::pallet]
744	pub struct Pallet<T>(PhantomData<T>);
745
746	#[pallet::event]
747	#[pallet::generate_deposit(pub(super) fn deposit_event)]
748	pub enum Event<T: Config> {
749		Registered(u32, T::AccountId, ElectionScore),
751		Stored(u32, T::AccountId, PageIndex),
753		Rewarded(u32, T::AccountId, BalanceOf<T>),
755		Slashed(u32, T::AccountId, BalanceOf<T>),
757		Ejected(u32, T::AccountId),
759		Discarded(u32, T::AccountId),
761		Bailed(u32, T::AccountId),
763	}
764
765	#[pallet::error]
766	pub enum Error<T> {
767		PhaseNotSigned,
769		Duplicate,
771		QueueFull,
773		BadPageIndex,
775		NotRegistered,
777		NoSubmission,
779		RoundNotOver,
781		BadWitnessData,
783		TooManyInvulnerables,
785	}
786
787	#[pallet::call]
788	impl<T: Config> Pallet<T> {
789		#[pallet::weight(SignedWeightsOf::<T>::register_eject())]
791		#[pallet::call_index(0)]
792		pub fn register(
793			origin: OriginFor<T>,
794			claimed_score: ElectionScore,
795		) -> DispatchResultWithPostInfo {
796			let who = ensure_signed(origin)?;
797			ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
798
799			let deposit = Submissions::<T>::deposit_for(&who, 0);
803			let reward = T::RewardBase::get();
804			let fee = T::EstimateCallFee::estimate_call_fee(
805				&Call::register { claimed_score },
806				None.into(),
807			);
808			let mut pages = BoundedVec::<_, _>::with_bounded_capacity(T::Pages::get() as usize);
809			pages.bounded_resize(T::Pages::get() as usize, false);
810
811			let new_metadata = SubmissionMetadata { claimed_score, deposit, reward, fee, pages };
812
813			T::Currency::hold(&HoldReason::SignedSubmission.into(), &who, deposit)?;
814			let round = Self::current_round();
815			let discarded = Submissions::<T>::try_register(round, &who, new_metadata)?;
816			Self::deposit_event(Event::<T>::Registered(round, who, claimed_score));
817
818			if discarded {
820				Ok(().into())
821			} else {
822				Ok(Some(SignedWeightsOf::<T>::register_not_full()).into())
823			}
824		}
825
826		#[pallet::weight(SignedWeightsOf::<T>::submit_page())]
835		#[pallet::call_index(1)]
836		pub fn submit_page(
837			origin: OriginFor<T>,
838			page: PageIndex,
839			maybe_solution: Option<Box<SolutionOf<T::MinerConfig>>>,
840		) -> DispatchResultWithPostInfo {
841			let who = ensure_signed(origin)?;
842			ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
843			let is_set = maybe_solution.is_some();
844
845			let round = Self::current_round();
846			Submissions::<T>::try_mutate_page(round, &who, page, maybe_solution)?;
847			Self::deposit_event(Event::<T>::Stored(round, who, page));
848
849			if is_set {
851				Ok(().into())
852			} else {
853				Ok(Some(SignedWeightsOf::<T>::unset_page()).into())
854			}
855		}
856
857		#[pallet::weight(SignedWeightsOf::<T>::bail())]
863		#[pallet::call_index(2)]
864		pub fn bail(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
865			let who = ensure_signed(origin)?;
866			ensure!(crate::Pallet::<T>::current_phase().is_signed(), Error::<T>::PhaseNotSigned);
867			let round = Self::current_round();
868			let metadata = Submissions::<T>::take_submission_with_data(round, &who)
869				.ok_or(Error::<T>::NoSubmission)?;
870
871			let deposit = metadata.deposit;
872			Self::settle_deposit(&who, deposit, T::BailoutGraceRatio::get());
873			Self::deposit_event(Event::<T>::Bailed(round, who));
874
875			Ok(None.into())
876		}
877
878		#[pallet::call_index(3)]
885		#[pallet::weight(SignedWeightsOf::<T>::clear_old_round_data(*witness_pages))]
886		pub fn clear_old_round_data(
887			origin: OriginFor<T>,
888			round: u32,
889			witness_pages: u32,
890		) -> DispatchResultWithPostInfo {
891			let discarded = ensure_signed(origin)?;
892
893			let current_round = Self::current_round();
894			ensure!(round < current_round, Error::<T>::RoundNotOver);
896
897			let metadata = Submissions::<T>::take_submission_with_data(round, &discarded)
898				.ok_or(Error::<T>::NoSubmission)?;
899			ensure!(
900				metadata.pages.iter().filter(|p| **p).count() as u32 <= witness_pages,
901				Error::<T>::BadWitnessData
902			);
903
904			let _res = T::Currency::release(
906				&HoldReason::SignedSubmission.into(),
907				&discarded,
908				metadata.deposit,
909				Precision::BestEffort,
910			);
911			debug_assert_eq!(_res, Ok(metadata.deposit));
912
913			if Self::is_invulnerable(&discarded) {
915				let _r = T::Currency::mint_into(&discarded, metadata.fee);
916				debug_assert!(_r.is_ok());
917			}
918
919			Self::deposit_event(Event::<T>::Discarded(round, discarded));
920
921			Ok(None.into())
923		}
924
925		#[pallet::call_index(4)]
929		#[pallet::weight(T::DbWeight::get().writes(1))]
930		pub fn set_invulnerables(origin: OriginFor<T>, inv: Vec<T::AccountId>) -> DispatchResult {
931			<T as crate::Config>::AdminOrigin::ensure_origin(origin)?;
932			let bounded: BoundedVec<_, ConstU32<16>> =
933				inv.try_into().map_err(|_| Error::<T>::TooManyInvulnerables)?;
934			Invulnerables::<T>::set(bounded);
935			Ok(())
936		}
937	}
938
939	#[pallet::view_functions]
940	impl<T: Config> Pallet<T> {
941		pub fn deposit_for(who: T::AccountId, pages: u32) -> BalanceOf<T> {
947			Submissions::<T>::deposit_for(&who, pages as usize)
948		}
949	}
950
951	#[pallet::hooks]
952	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
953		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
954			let weight_taken_into_account: Weight = Default::default();
957
958			if crate::Pallet::<T>::current_phase().is_signed_validation_opened_now() {
959				let maybe_leader = Submissions::<T>::leader(Self::current_round());
960				sublog!(
961					debug,
962					"signed",
963					"signed validation started, sending validation start signal? {:?}",
964					maybe_leader.is_some()
965				);
966
967				if maybe_leader.is_some() {
969					let _ = <T::Verifier as AsynchronousVerifier>::start().defensive();
972				}
973			}
974
975			weight_taken_into_account
976		}
977
978		#[cfg(feature = "try-runtime")]
979		fn try_state(n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
980			Self::do_try_state(n)
981		}
982	}
983}
984
985impl<T: Config> Pallet<T> {
986	#[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks"))]
987	pub(crate) fn do_try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
988		Submissions::<T>::sanity_check_round(Self::current_round())
989	}
990
991	fn current_round() -> u32 {
992		crate::Pallet::<T>::round()
993	}
994
995	fn is_invulnerable(who: &T::AccountId) -> bool {
996		Invulnerables::<T>::get().contains(who)
997	}
998
999	fn settle_deposit(who: &T::AccountId, deposit: BalanceOf<T>, grace: Perbill) {
1000		let to_refund = grace * deposit;
1001		let to_slash = deposit.defensive_saturating_sub(to_refund);
1002
1003		let _res = T::Currency::release(
1004			&HoldReason::SignedSubmission.into(),
1005			who,
1006			to_refund,
1007			Precision::BestEffort,
1008		)
1009		.defensive();
1010		debug_assert_eq!(_res, Ok(to_refund));
1011
1012		let _res = T::Currency::burn_held(
1013			&HoldReason::SignedSubmission.into(),
1014			who,
1015			to_slash,
1016			Precision::BestEffort,
1017			Fortitude::Force,
1018		)
1019		.defensive();
1020		debug_assert_eq!(_res, Ok(to_slash));
1021	}
1022
1023	fn handle_solution_rejection(current_round: u32) {
1025		if let Some((loser, metadata)) =
1026			Submissions::<T>::take_leader_with_data(current_round).defensive()
1027		{
1028			let slash = metadata.deposit;
1030			let _res = T::Currency::burn_held(
1031				&HoldReason::SignedSubmission.into(),
1032				&loser,
1033				slash,
1034				Precision::BestEffort,
1035				Fortitude::Force,
1036			);
1037			debug_assert_eq!(_res, Ok(slash));
1038			Self::deposit_event(Event::<T>::Slashed(current_round, loser.clone(), slash));
1039			Invulnerables::<T>::mutate(|x| x.retain(|y| y != &loser));
1040
1041			if let crate::types::Phase::SignedValidation(remaining_blocks) =
1043				crate::Pallet::<T>::current_phase()
1044			{
1045				let actual_blocks_remaining = remaining_blocks.saturating_add(One::one());
1048				if actual_blocks_remaining >= T::Pages::get().into() {
1049					if Submissions::<T>::has_leader(current_round) {
1050						let _ = <T::Verifier as AsynchronousVerifier>::start().defensive();
1053					}
1054				} else {
1055					sublog!(
1056						warn,
1057						"signed",
1058						"SignedValidation phase has {:?} blocks remaining, which are insufficient for {} pages",
1059						actual_blocks_remaining,
1060						T::Pages::get()
1061					);
1062				}
1063			}
1064		} else {
1065			sublog!(
1067				warn,
1068				"signed",
1069				"Tried to slash but no leader was present for round {}",
1070				current_round
1071			);
1072		}
1073	}
1074}