1use super::*;
22use crate::{
23 helpers,
24 types::VoterOf,
25 unsigned::miner::{MinerConfig, PageSupportsOfMiner},
26 verifier::Verifier,
27 SolutionOf,
28};
29use codec::{Decode, Encode, MaxEncodedLen};
30use frame_election_provider_support::{
31 ExtendedBalance, NposSolution, PageIndex, TryFromOtherBounds,
32};
33use frame_support::{
34 ensure,
35 pallet_prelude::{ValueQuery, *},
36 traits::{defensive_prelude::*, Get},
37};
38use frame_system::pallet_prelude::*;
39use pallet::*;
40use sp_npos_elections::{evaluate_support, ElectionScore};
41use sp_runtime::Perbill;
42use sp_std::{collections::btree_map::BTreeMap, prelude::*};
43
44pub(crate) type SupportsOfVerifier<V> = frame_election_provider_support::BoundedSupports<
45 <V as Verifier>::AccountId,
46 <V as Verifier>::MaxWinnersPerPage,
47 <V as Verifier>::MaxBackersPerWinner,
48>;
49
50pub(crate) type VerifierWeightsOf<T> = <T as super::Config>::WeightInfo;
51
52#[derive(
54 Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, Debug, PartialEq, Eq,
55)]
56pub enum Status {
57 Ongoing(PageIndex),
60 Nothing,
62}
63
64impl Default for Status {
65 fn default() -> Self {
66 Self::Nothing
67 }
68}
69
70#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen)]
72enum ValidSolution {
73 X,
74 Y,
75}
76
77impl Default for ValidSolution {
78 fn default() -> Self {
79 ValidSolution::Y
80 }
81}
82
83impl ValidSolution {
84 fn other(&self) -> Self {
85 match *self {
86 ValidSolution::X => ValidSolution::Y,
87 ValidSolution::Y => ValidSolution::X,
88 }
89 }
90}
91
92#[derive(Default, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)]
98pub struct PartialBackings {
99 pub total: ExtendedBalance,
101 pub backers: u32,
103}
104
105impl sp_npos_elections::Backings for PartialBackings {
106 fn total(&self) -> ExtendedBalance {
107 self.total
108 }
109}
110
111#[frame_support::pallet]
112pub(crate) mod pallet {
113 use super::*;
114
115 #[pallet::config]
116 #[pallet::disable_frame_system_supertrait_check]
117 pub trait Config: crate::Config {
118 #[pallet::constant]
121 type SolutionImprovementThreshold: Get<Perbill>;
122
123 #[pallet::constant]
129 type MaxBackersPerWinnerFinal: Get<u32>;
130
131 #[pallet::constant]
133 type MaxBackersPerWinner: Get<u32>;
134
135 #[pallet::constant]
138 type MaxWinnersPerPage: Get<u32>;
139
140 type SolutionDataProvider: crate::verifier::SolutionDataProvider<
144 Solution = SolutionOf<Self::MinerConfig>,
145 >;
146
147 type WeightInfo: super::WeightInfo;
149 }
150
151 #[pallet::event]
152 #[pallet::generate_deposit(pub(super) fn deposit_event)]
153 pub enum Event<T> {
154 VerificationFailed(PageIndex, FeasibilityError),
159 Verified(PageIndex, u32),
162 Queued(ElectionScore, Option<ElectionScore>),
164 }
165
166 pub struct QueuedSolution<T: Config>(sp_std::marker::PhantomData<T>);
215 impl<T: Config> QueuedSolution<T> {
216 fn mutate_checked<R>(mutate: impl FnOnce() -> R) -> R {
218 let r = mutate();
219 #[cfg(debug_assertions)]
220 assert!(Self::sanity_check().is_ok());
221 r
222 }
223
224 fn round() -> u32 {
225 crate::Pallet::<T>::round()
226 }
227
228 pub(crate) fn finalize_correct(score: ElectionScore) {
237 sublog!(
238 info,
239 "verifier",
240 "finalizing verification a correct solution, replacing old score {:?} with {:?}",
241 QueuedSolutionScore::<T>::get(Self::round()),
242 score
243 );
244
245 Self::mutate_checked(|| {
246 QueuedValidVariant::<T>::mutate(Self::round(), |v| *v = v.other());
247 QueuedSolutionScore::<T>::insert(Self::round(), score);
248
249 Self::clear_invalid_and_backings_unchecked();
251 });
252 }
253
254 pub(crate) fn clear_invalid_and_backings() {
259 Self::mutate_checked(Self::clear_invalid_and_backings_unchecked)
260 }
261
262 pub(crate) fn clear_invalid_and_backings_unchecked() {
265 match Self::invalid() {
267 ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
268 ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
269 };
270 clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
271 }
272
273 pub(crate) fn set_invalid_page(page: PageIndex, supports: SupportsOfVerifier<Pallet<T>>) {
282 use frame_support::traits::TryCollect;
283 Self::mutate_checked(|| {
284 let backings: BoundedVec<_, _> = supports
285 .iter()
286 .map(|(x, s)| (x.clone(), PartialBackings { total: s.total, backers: s.voters.len() as u32 } ))
287 .try_collect()
288 .expect("`SupportsOfVerifier` is bounded by <Pallet<T> as Verifier>::MaxWinnersPerPage, which is assured to be the same as `T::MaxWinnersPerPage` in an integrity test");
289 QueuedSolutionBackings::<T>::insert(Self::round(), page, backings);
290
291 match Self::invalid() {
292 ValidSolution::X => QueuedSolutionX::<T>::insert(Self::round(), page, supports),
293 ValidSolution::Y => QueuedSolutionY::<T>::insert(Self::round(), page, supports),
294 }
295 })
296 }
297
298 pub(crate) fn force_set_single_page_valid(
305 page: PageIndex,
306 supports: SupportsOfVerifier<Pallet<T>>,
307 score: ElectionScore,
308 ) {
309 Self::mutate_checked(|| {
310 match Self::valid() {
312 ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
313 ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
314 };
315 QueuedSolutionScore::<T>::remove(Self::round());
316
317 match Self::valid() {
319 ValidSolution::X => QueuedSolutionX::<T>::insert(Self::round(), page, supports),
320 ValidSolution::Y => QueuedSolutionY::<T>::insert(Self::round(), page, supports),
321 }
322
323 QueuedSolutionScore::<T>::insert(Self::round(), score);
325 })
326 }
327
328 pub(crate) fn force_set_multi_page_valid(
329 pages: Vec<PageIndex>,
330 supports: Vec<SupportsOfVerifier<Pallet<T>>>,
331 score: ElectionScore,
332 ) {
333 debug_assert_eq!(pages.len(), supports.len());
334 Self::mutate_checked(|| {
336 match Self::valid() {
338 ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
339 ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
340 };
341 QueuedSolutionScore::<T>::remove(Self::round());
342
343 for (support, page) in supports.into_iter().zip(pages.iter()) {
345 match Self::valid() {
346 ValidSolution::X =>
347 QueuedSolutionX::<T>::insert(Self::round(), page, support),
348 ValidSolution::Y =>
349 QueuedSolutionY::<T>::insert(Self::round(), page, support),
350 }
351 }
352 QueuedSolutionScore::<T>::insert(Self::round(), score);
353 });
354 }
355
356 pub(crate) fn kill() {
360 Self::mutate_checked(|| {
361 clear_round_based_map!(QueuedSolutionX::<T>, Self::round());
362 clear_round_based_map!(QueuedSolutionY::<T>, Self::round());
363 QueuedValidVariant::<T>::remove(Self::round());
364 clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
365 QueuedSolutionScore::<T>::remove(Self::round());
366 })
367 }
368
369 pub(crate) fn compute_invalid_score() -> Result<(ElectionScore, u32), FeasibilityError> {
386 let mut total_supports: BTreeMap<T::AccountId, PartialBackings> = Default::default();
387 for (who, PartialBackings { backers, total }) in
388 QueuedSolutionBackings::<T>::iter_prefix(Self::round()).flat_map(|(_, pb)| pb)
389 {
390 let entry = total_supports.entry(who).or_default();
391 entry.total = entry.total.saturating_add(total);
392 entry.backers = entry.backers.saturating_add(backers);
393
394 if entry.backers > T::MaxBackersPerWinnerFinal::get() {
395 return Err(FeasibilityError::FailedToBoundSupport)
396 }
397 }
398
399 let winner_count = total_supports.len() as u32;
400 let score = evaluate_support(total_supports.into_values());
401
402 Ok((score, winner_count))
403 }
404
405 pub(crate) fn queued_score() -> Option<ElectionScore> {
407 QueuedSolutionScore::<T>::get(Self::round())
408 }
409
410 pub(crate) fn get_queued_solution_page(
412 page: PageIndex,
413 ) -> Option<SupportsOfVerifier<Pallet<T>>> {
414 match Self::valid() {
415 ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
416 ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
417 }
418 }
419
420 fn valid() -> ValidSolution {
421 QueuedValidVariant::<T>::get(Self::round())
422 }
423
424 fn invalid() -> ValidSolution {
425 Self::valid().other()
426 }
427 }
428
429 #[allow(unused)]
430 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime", debug_assertions))]
431 impl<T: Config> QueuedSolution<T> {
432 pub(crate) fn valid_iter(
433 ) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
434 match Self::valid() {
435 ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
436 ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
437 }
438 }
439
440 pub(crate) fn invalid_iter(
441 ) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
442 match Self::invalid() {
443 ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
444 ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
445 }
446 }
447
448 pub(crate) fn get_valid_page(page: PageIndex) -> Option<SupportsOfVerifier<Pallet<T>>> {
449 match Self::valid() {
450 ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
451 ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
452 }
453 }
454
455 pub(crate) fn backing_iter() -> impl Iterator<
456 Item = (PageIndex, BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>),
457 > {
458 QueuedSolutionBackings::<T>::iter_prefix(Self::round())
459 }
460
461 pub(crate) fn assert_killed() {
464 use frame_support::assert_storage_noop;
465 assert_storage_noop!(Self::kill());
466 }
467
468 pub(crate) fn sanity_check() -> Result<(), sp_runtime::DispatchError> {
470 ensure!(
472 Pallet::<T>::minimum_score()
473 .zip(Self::queued_score())
474 .map_or(true, |(min_score, score)| score
475 .strict_threshold_better(min_score, Perbill::zero())),
476 "queued solution has weak score (min-score)"
477 );
478
479 if let Some(queued_score) = Self::queued_score() {
480 let mut backing_map: BTreeMap<T::AccountId, PartialBackings> = BTreeMap::new();
481 Self::valid_iter()
482 .flat_map(|(_, supports)| supports)
483 .for_each(|(who, support)| {
484 let entry = backing_map.entry(who).or_default();
485 entry.total = entry.total.saturating_add(support.total);
486 });
487 let real_score = evaluate_support(backing_map.into_values());
488 ensure!(real_score == queued_score, "queued solution has wrong score");
489 } else {
490 assert!(Self::valid_iter().count() == 0, "nothing should be stored if no score");
491 }
492
493 ensure!(
496 QueuedSolutionBackings::<T>::iter_prefix(Self::round()).count() ==
497 Self::invalid_iter().count(),
498 "incorrect number of backings pages",
499 );
500
501 if let Status::Nothing = StatusStorage::<T>::get() {
502 ensure!(Self::invalid_iter().count() == 0, "dangling data in invalid variant");
503 }
504
505 Ok(())
506 }
507 }
508
509 #[pallet::storage]
519 type QueuedSolutionX<T: Config> = StorageDoubleMap<
520 _,
521 Twox64Concat,
522 u32,
523 Twox64Concat,
524 PageIndex,
525 SupportsOfVerifier<Pallet<T>>,
526 >;
527
528 #[pallet::storage]
530 type QueuedSolutionY<T: Config> = StorageDoubleMap<
531 _,
532 Twox64Concat,
533 u32,
534 Twox64Concat,
535 PageIndex,
536 SupportsOfVerifier<Pallet<T>>,
537 >;
538 #[pallet::storage]
542 type QueuedValidVariant<T: Config> =
543 StorageMap<_, Twox64Concat, u32, ValidSolution, ValueQuery>;
544
545 #[pallet::storage]
554 type QueuedSolutionBackings<T: Config> = StorageDoubleMap<
555 _,
556 Twox64Concat,
557 u32,
558 Twox64Concat,
559 PageIndex,
560 BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>,
561 >;
562
563 #[pallet::storage]
567 type QueuedSolutionScore<T: Config> = StorageMap<_, Twox64Concat, u32, ElectionScore>;
568
569 #[pallet::storage]
573 #[pallet::getter(fn minimum_score)]
574 pub(crate) type MinimumScore<T: Config> = StorageValue<_, ElectionScore>;
575
576 #[pallet::storage]
578 #[pallet::getter(fn status_storage)]
579 pub(crate) type StatusStorage<T: Config> = StorageValue<_, Status, ValueQuery>;
580
581 #[pallet::pallet]
582 pub struct Pallet<T>(PhantomData<T>);
583
584 #[pallet::genesis_config]
585 #[derive(frame_support::DefaultNoBound)]
586 pub struct GenesisConfig<T: Config> {
587 pub(crate) minimum_score: ElectionScore,
589 _marker: PhantomData<T>,
590 }
591
592 #[pallet::genesis_build]
593 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
594 fn build(&self) {
595 MinimumScore::<T>::put(self.minimum_score);
596 }
597 }
598
599 #[pallet::call]
600 impl<T: Config> Pallet<T> {}
601
602 #[pallet::hooks]
603 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
604 fn integrity_test() {
605 assert_eq!(T::MaxWinnersPerPage::get(), <Self as Verifier>::MaxWinnersPerPage::get());
608 assert_eq!(
609 T::MaxBackersPerWinner::get(),
610 <Self as Verifier>::MaxBackersPerWinner::get()
611 );
612 assert!(T::MaxBackersPerWinner::get() <= T::MaxBackersPerWinnerFinal::get());
613 }
614
615 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
616 Self::do_on_initialize()
617 }
618
619 #[cfg(feature = "try-runtime")]
620 fn try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
621 Self::do_try_state(_now)
622 }
623 }
624}
625
626impl<T: Config> Pallet<T> {
627 fn do_on_initialize() -> Weight {
628 if let Status::Ongoing(current_page) = Self::status_storage() {
629 let page_solution =
630 <T::SolutionDataProvider as SolutionDataProvider>::get_page(current_page);
631
632 let maybe_supports = Self::feasibility_check_page_inner(page_solution, current_page);
633
634 sublog!(
635 debug,
636 "verifier",
637 "verified page {} of a solution, outcome = {:?}",
638 current_page,
639 maybe_supports.as_ref().map(|s| s.len())
640 );
641
642 match maybe_supports {
643 Ok(supports) => {
644 Self::deposit_event(Event::<T>::Verified(current_page, supports.len() as u32));
645 QueuedSolution::<T>::set_invalid_page(current_page, supports);
646
647 if current_page > crate::Pallet::<T>::lsp() {
648 StatusStorage::<T>::put(Status::Ongoing(current_page.saturating_sub(1)));
650 VerifierWeightsOf::<T>::on_initialize_valid_non_terminal()
651 } else {
652 let claimed_score = T::SolutionDataProvider::get_score();
654
655 StatusStorage::<T>::put(Status::Nothing);
657
658 match Self::finalize_async_verification(claimed_score) {
659 Ok(_) => {
660 T::SolutionDataProvider::report_result(VerificationResult::Queued);
661 VerifierWeightsOf::<T>::on_initialize_valid_terminal()
662 },
663 Err(_) => {
664 T::SolutionDataProvider::report_result(
665 VerificationResult::Rejected,
666 );
667 QueuedSolution::<T>::clear_invalid_and_backings();
669 VerifierWeightsOf::<T>::on_initialize_invalid_terminal()
670 },
671 }
672 }
673 },
674 Err(err) => {
675 Self::deposit_event(Event::<T>::VerificationFailed(current_page, err));
677
678 sublog!(warn, "verifier", "Clearing any ongoing unverified solutions.");
679 QueuedSolution::<T>::clear_invalid_and_backings_unchecked();
682
683 let was_ongoing = matches!(StatusStorage::<T>::get(), Status::Ongoing(_));
685 StatusStorage::<T>::put(Status::Nothing);
686
687 if was_ongoing {
688 T::SolutionDataProvider::report_result(VerificationResult::Rejected);
689 }
690 let wasted_pages = T::Pages::get().saturating_sub(current_page);
691 VerifierWeightsOf::<T>::on_initialize_invalid_non_terminal(wasted_pages)
692 },
693 }
694 } else {
695 T::DbWeight::get().reads(1)
696 }
697 }
698
699 fn do_verify_synchronous_multi(
700 partial_solutions: Vec<SolutionOf<T::MinerConfig>>,
701 solution_pages: Vec<PageIndex>,
702 claimed_score: ElectionScore,
703 ) -> Result<(), (PageIndex, FeasibilityError)> {
704 let first_page = solution_pages.first().cloned().unwrap_or_default();
705 let last_page = solution_pages.last().cloned().unwrap_or_default();
706 let _ = Self::ensure_score_quality(claimed_score).map_err(|fe| (first_page, fe))?;
708 ensure!(
709 partial_solutions.len() == solution_pages.len(),
710 (first_page, FeasibilityError::Incomplete)
711 );
712
713 let mut backings =
715 sp_std::collections::btree_map::BTreeMap::<T::AccountId, PartialBackings>::new();
716 let mut linked_supports = Vec::with_capacity(partial_solutions.len());
717
718 for (solution_page, page) in partial_solutions.into_iter().zip(solution_pages.iter()) {
719 let page_supports = Self::feasibility_check_page_inner(solution_page, *page)
720 .map_err(|fe| (*page, fe))?;
721
722 linked_supports.push(page_supports.clone());
723 let support_len = page_supports.len() as u32;
724 for (who, support) in page_supports.into_iter() {
725 let entry = backings.entry(who).or_default();
726 entry.total = entry.total.saturating_add(support.total);
727 entry.backers = entry.backers.saturating_add(support.voters.len() as u32);
730 if entry.backers > T::MaxBackersPerWinnerFinal::get() {
731 return Err((*page, FeasibilityError::FailedToBoundSupport))
732 }
733 }
734
735 Self::deposit_event(Event::<T>::Verified(*page, support_len));
736 }
737
738 let desired_targets = crate::Snapshot::<T>::desired_targets()
740 .ok_or(FeasibilityError::SnapshotUnavailable)
741 .map_err(|fe| (last_page, fe))?;
742 ensure!(
743 backings.len() as u32 == desired_targets,
744 (last_page, FeasibilityError::WrongWinnerCount)
745 );
746
747 let truth_score = evaluate_support(backings.into_values());
749 ensure!(truth_score == claimed_score, (last_page, FeasibilityError::InvalidScore));
750
751 let maybe_current_score = QueuedSolution::<T>::queued_score();
752
753 sublog!(
755 debug,
756 "verifier",
757 "queued sync solution with score {:?} for pages {:?}",
758 truth_score,
759 solution_pages
760 );
761 QueuedSolution::<T>::force_set_multi_page_valid(
762 solution_pages,
763 linked_supports,
764 truth_score,
765 );
766 Self::deposit_event(Event::<T>::Queued(truth_score, maybe_current_score));
767
768 Ok(())
769 }
770
771 fn finalize_async_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> {
781 let outcome = QueuedSolution::<T>::compute_invalid_score()
782 .and_then(|(final_score, winner_count)| {
783 let desired_targets =
784 crate::Snapshot::<T>::desired_targets().defensive_unwrap_or(u32::MAX);
785 match (final_score == claimed_score, winner_count == desired_targets) {
787 (true, true) => {
788 Self::deposit_event(Event::<T>::Queued(
791 final_score,
792 QueuedSolution::<T>::queued_score(), ));
795 QueuedSolution::<T>::finalize_correct(final_score);
796 Ok(())
797 },
798 (false, true) => Err(FeasibilityError::InvalidScore),
799 (true, false) => Err(FeasibilityError::WrongWinnerCount),
800 (false, false) => Err(FeasibilityError::InvalidScore),
801 }
802 })
803 .map_err(|err| {
804 sublog!(warn, "verifier", "Finalizing solution was invalid due to {:?}.", err);
805 Self::deposit_event(Event::<T>::VerificationFailed(0, err.clone()));
807 err
808 });
809 sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome);
810 outcome
811 }
812
813 pub(crate) fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> {
818 let is_improvement = <Self as Verifier>::queued_score().map_or(true, |best_score| {
819 score.strict_threshold_better(best_score, T::SolutionImprovementThreshold::get())
820 });
821 ensure!(is_improvement, FeasibilityError::ScoreTooLow);
822
823 let is_greater_than_min_untrusted = Self::minimum_score()
824 .map_or(true, |min_score| score.strict_threshold_better(min_score, Perbill::zero()));
825 ensure!(is_greater_than_min_untrusted, FeasibilityError::ScoreTooLow);
826
827 Ok(())
828 }
829
830 pub(super) fn feasibility_check_page_inner(
837 partial_solution: SolutionOf<T::MinerConfig>,
838 page: PageIndex,
839 ) -> Result<SupportsOfVerifier<Self>, FeasibilityError> {
840 let snapshot_targets =
842 crate::Snapshot::<T>::targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
843 let snapshot_voters =
844 crate::Snapshot::<T>::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?;
845 let desired_targets =
846 crate::Snapshot::<T>::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
847
848 feasibility_check_page_inner_with_snapshot::<T::MinerConfig>(
849 partial_solution,
850 &snapshot_voters,
851 &snapshot_targets,
852 desired_targets,
853 )
854 .and_then(|miner_supports| {
855 SupportsOfVerifier::<Self>::try_from_other_bounds(miner_supports)
856 .defensive_map_err(|_| FeasibilityError::FailedToBoundSupport)
857 })
858 }
859
860 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
861 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
862 QueuedSolution::<T>::sanity_check()
863 }
864}
865
866pub fn feasibility_check_page_inner_with_snapshot<T: MinerConfig>(
871 partial_solution: SolutionOf<T>,
872 snapshot_voters: &BoundedVec<VoterOf<T>, T::VoterSnapshotPerBlock>,
873 snapshot_targets: &BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
874 desired_targets: u32,
875) -> Result<PageSupportsOfMiner<T>, FeasibilityError> {
876 let cache = helpers::generate_voter_cache::<T, _>(snapshot_voters);
878 let voter_at = helpers::voter_at_fn::<T>(snapshot_voters);
879 let target_at = helpers::target_at_fn::<T>(snapshot_targets);
880 let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
881
882 let assignments = partial_solution
886 .into_assignment(voter_at, target_at)
887 .map_err::<FeasibilityError, _>(Into::into)?;
888
889 let _ = assignments
891 .iter()
892 .map(|ref assignment| {
893 let snapshot_index =
901 voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
902 let (_voter, _stake, targets) =
904 snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
905 debug_assert!(*_voter == assignment.who);
906
907 if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) {
909 return Err(FeasibilityError::InvalidVote)
910 }
911 Ok(())
912 })
913 .collect::<Result<(), FeasibilityError>>()?;
914
915 let stake_of = helpers::stake_of_fn::<T, _>(&snapshot_voters, &cache);
917
918 let staked_assignments =
920 sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of)
921 .map_err::<FeasibilityError, _>(Into::into)?;
922
923 let supports = sp_npos_elections::to_supports(&staked_assignments);
924
925 ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount);
928
929 let bounded_supports =
933 supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?;
934 Ok(bounded_supports)
935}
936
937impl<T: Config> Verifier for Pallet<T> {
938 type AccountId = T::AccountId;
939 type Solution = SolutionOf<T::MinerConfig>;
940 type MaxBackersPerWinner = T::MaxBackersPerWinner;
941 type MaxWinnersPerPage = T::MaxWinnersPerPage;
942 type MaxBackersPerWinnerFinal = T::MaxBackersPerWinnerFinal;
943
944 fn set_minimum_score(score: ElectionScore) {
945 MinimumScore::<T>::put(score);
946 }
947
948 fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool {
949 Self::ensure_score_quality(claimed_score).is_ok()
950 }
951
952 fn queued_score() -> Option<ElectionScore> {
953 QueuedSolution::<T>::queued_score()
954 }
955
956 fn kill() {
957 QueuedSolution::<T>::kill();
958 <StatusStorage<T>>::put(Status::Nothing);
959 }
960
961 fn get_queued_solution_page(page: PageIndex) -> Option<SupportsOfVerifier<Self>> {
962 QueuedSolution::<T>::get_queued_solution_page(page)
963 }
964
965 fn verify_synchronous_multi(
966 partial_solutions: Vec<Self::Solution>,
967 solution_pages: Vec<PageIndex>,
968 claimed_score: ElectionScore,
969 ) -> Result<(), FeasibilityError> {
970 Self::do_verify_synchronous_multi(partial_solutions, solution_pages, claimed_score).map_err(
971 |(page, fe)| {
972 sublog!(
973 warn,
974 "verifier",
975 "sync verification of page {:?} failed due to {:?}.",
976 page,
977 fe
978 );
979 Self::deposit_event(Event::<T>::VerificationFailed(page, fe.clone()));
980 fe
981 },
982 )
983 }
984
985 fn force_set_single_page_valid(
986 partial_supports: SupportsOfVerifier<Self>,
987 page: PageIndex,
988 score: ElectionScore,
989 ) {
990 Self::deposit_event(Event::<T>::Queued(score, QueuedSolution::<T>::queued_score()));
991 QueuedSolution::<T>::force_set_single_page_valid(page, partial_supports, score);
992 }
993}
994
995impl<T: Config> AsynchronousVerifier for Pallet<T> {
996 type SolutionDataProvider = T::SolutionDataProvider;
997
998 fn status() -> Status {
999 Pallet::<T>::status_storage()
1000 }
1001
1002 fn start() -> Result<(), &'static str> {
1003 sublog!(debug, "verifier", "start signal received.");
1004 if let Status::Nothing = Self::status() {
1005 let claimed_score = Self::SolutionDataProvider::get_score();
1006 if Self::ensure_score_quality(claimed_score).is_err() {
1007 Self::deposit_event(Event::<T>::VerificationFailed(
1009 crate::Pallet::<T>::msp(),
1010 FeasibilityError::ScoreTooLow,
1011 ));
1012 T::SolutionDataProvider::report_result(VerificationResult::Rejected);
1013 Ok(())
1015 } else {
1016 StatusStorage::<T>::put(Status::Ongoing(crate::Pallet::<T>::msp()));
1018 Ok(())
1019 }
1020 } else {
1021 sublog!(warn, "verifier", "start signal received while busy. This will be ignored.");
1022 Err("verification ongoing")
1023 }
1024 }
1025}