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::*, DefensiveSaturating, Get},
37};
38use frame_system::pallet_prelude::*;
39use pallet::*;
40use sp_npos_elections::{evaluate_support, ElectionScore};
41use sp_std::{collections::btree_map::BTreeMap, prelude::*};
42
43pub(crate) type SupportsOfVerifier<V> = frame_election_provider_support::BoundedSupports<
44 <V as Verifier>::AccountId,
45 <V as Verifier>::MaxWinnersPerPage,
46 <V as Verifier>::MaxBackersPerWinner,
47>;
48
49pub(crate) type VerifierWeightsOf<T> = <T as super::Config>::WeightInfo;
50
51#[derive(
53 Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, Debug, PartialEq, Eq,
54)]
55pub enum Status {
56 Ongoing(PageIndex),
59 Nothing,
61}
62
63impl Default for Status {
64 fn default() -> Self {
65 Self::Nothing
66 }
67}
68
69#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen)]
71enum ValidSolution {
72 X,
73 Y,
74}
75
76impl Default for ValidSolution {
77 fn default() -> Self {
78 ValidSolution::Y
79 }
80}
81
82impl ValidSolution {
83 fn other(&self) -> Self {
84 match *self {
85 ValidSolution::X => ValidSolution::Y,
86 ValidSolution::Y => ValidSolution::X,
87 }
88 }
89}
90
91#[derive(Default, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)]
97pub struct PartialBackings {
98 pub total: ExtendedBalance,
100 pub backers: u32,
102}
103
104impl sp_npos_elections::Backings for PartialBackings {
105 fn total(&self) -> ExtendedBalance {
106 self.total
107 }
108}
109
110#[frame_support::pallet]
111pub(crate) mod pallet {
112 use super::*;
113
114 #[pallet::config]
115 #[pallet::disable_frame_system_supertrait_check]
116 pub trait Config: crate::Config {
117 #[pallet::constant]
123 type MaxBackersPerWinnerFinal: Get<u32>;
124
125 #[pallet::constant]
127 type MaxBackersPerWinner: Get<u32>;
128
129 #[pallet::constant]
132 type MaxWinnersPerPage: Get<u32>;
133
134 type SolutionDataProvider: crate::verifier::SolutionDataProvider<
138 Solution = SolutionOf<Self::MinerConfig>,
139 >;
140
141 type WeightInfo: super::WeightInfo;
143 }
144
145 #[pallet::event]
146 #[pallet::generate_deposit(pub(super) fn deposit_event)]
147 pub enum Event<T> {
148 VerificationFailed(PageIndex, FeasibilityError),
153 Verified(PageIndex, u32),
156 Queued(ElectionScore, Option<ElectionScore>),
158 }
159
160 pub struct QueuedSolution<T: Config>(sp_std::marker::PhantomData<T>);
208 impl<T: Config> QueuedSolution<T> {
209 fn mutate_checked<R>(mutate: impl FnOnce() -> R) -> R {
211 let r = mutate();
212 #[cfg(debug_assertions)]
213 assert!(Self::sanity_check()
214 .inspect_err(|e| {
215 sublog!(error, "verifier", "sanity check failed: {:?}", e);
216 })
217 .is_ok());
218 r
219 }
220
221 fn round() -> u32 {
222 crate::Pallet::<T>::round()
223 }
224
225 pub(crate) fn finalize_correct(score: ElectionScore) {
234 sublog!(
235 info,
236 "verifier",
237 "finalizing verification a correct solution, replacing old score {:?} with {:?}",
238 QueuedSolutionScore::<T>::get(Self::round()),
239 score
240 );
241
242 Self::mutate_checked(|| {
243 QueuedValidVariant::<T>::mutate(Self::round(), |v| *v = v.other());
244 QueuedSolutionScore::<T>::insert(Self::round(), score);
245
246 Self::clear_invalid_and_backings_unchecked();
248 });
249 }
250
251 pub(crate) fn clear_invalid_and_backings() {
256 Self::mutate_checked(Self::clear_invalid_and_backings_unchecked)
257 }
258
259 pub(crate) fn clear_invalid_and_backings_unchecked() {
262 match Self::invalid() {
264 ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
265 ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
266 };
267 clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
268 }
269
270 pub(crate) fn set_invalid_page(page: PageIndex, supports: SupportsOfVerifier<Pallet<T>>) {
279 use frame_support::traits::TryCollect;
280 Self::mutate_checked(|| {
281 let backings: BoundedVec<_, _> = supports
282 .iter()
283 .map(|(x, s)| (x.clone(), PartialBackings { total: s.total, backers: s.voters.len() as u32 } ))
284 .try_collect()
285 .expect("`SupportsOfVerifier` is bounded by <Pallet<T> as Verifier>::MaxWinnersPerPage, which is assured to be the same as `T::MaxWinnersPerPage` in an integrity test");
286 QueuedSolutionBackings::<T>::insert(Self::round(), page, backings);
287
288 match Self::invalid() {
289 ValidSolution::X => QueuedSolutionX::<T>::insert(Self::round(), page, supports),
290 ValidSolution::Y => QueuedSolutionY::<T>::insert(Self::round(), page, supports),
291 }
292 })
293 }
294
295 pub(crate) fn force_set_single_page_valid(
302 page: PageIndex,
303 supports: SupportsOfVerifier<Pallet<T>>,
304 score: ElectionScore,
305 ) {
306 Self::mutate_checked(|| {
307 match Self::valid() {
309 ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
310 ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
311 };
312 QueuedSolutionScore::<T>::remove(Self::round());
313
314 match Self::valid() {
316 ValidSolution::X => QueuedSolutionX::<T>::insert(Self::round(), page, supports),
317 ValidSolution::Y => QueuedSolutionY::<T>::insert(Self::round(), page, supports),
318 }
319 QueuedSolutionScore::<T>::insert(Self::round(), score);
320 })
321 }
322
323 pub(crate) fn force_set_multi_page_valid(
324 pages: Vec<PageIndex>,
325 supports: Vec<SupportsOfVerifier<Pallet<T>>>,
326 score: ElectionScore,
327 ) {
328 debug_assert_eq!(pages.len(), supports.len());
329 Self::mutate_checked(|| {
331 match Self::valid() {
333 ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
334 ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
335 };
336 QueuedSolutionScore::<T>::remove(Self::round());
337
338 for (support, page) in supports.into_iter().zip(pages.iter()) {
340 match Self::valid() {
341 ValidSolution::X =>
342 QueuedSolutionX::<T>::insert(Self::round(), page, support),
343 ValidSolution::Y =>
344 QueuedSolutionY::<T>::insert(Self::round(), page, support),
345 }
346 }
347 QueuedSolutionScore::<T>::insert(Self::round(), score);
348 });
349 }
350
351 pub(crate) fn kill() {
355 Self::mutate_checked(|| {
356 clear_round_based_map!(QueuedSolutionX::<T>, Self::round());
357 clear_round_based_map!(QueuedSolutionY::<T>, Self::round());
358 QueuedValidVariant::<T>::remove(Self::round());
359 clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
360 QueuedSolutionScore::<T>::remove(Self::round());
361 })
362 }
363
364 pub(crate) fn compute_invalid_score() -> Result<(ElectionScore, u32), FeasibilityError> {
381 let mut total_supports: BTreeMap<T::AccountId, PartialBackings> = Default::default();
382 for (who, PartialBackings { backers, total }) in
383 QueuedSolutionBackings::<T>::iter_prefix(Self::round()).flat_map(|(_, pb)| pb)
384 {
385 let entry = total_supports.entry(who).or_default();
386 entry.total = entry.total.saturating_add(total);
387 entry.backers = entry.backers.saturating_add(backers);
388
389 if entry.backers > T::MaxBackersPerWinnerFinal::get() {
390 return Err(FeasibilityError::FailedToBoundSupport)
391 }
392 }
393
394 let winner_count = total_supports.len() as u32;
395 let score = evaluate_support(total_supports.into_values());
396
397 Ok((score, winner_count))
398 }
399
400 pub(crate) fn queued_score() -> Option<ElectionScore> {
402 QueuedSolutionScore::<T>::get(Self::round())
403 }
404
405 pub(crate) fn get_queued_solution_page(
407 page: PageIndex,
408 ) -> Option<SupportsOfVerifier<Pallet<T>>> {
409 match Self::valid() {
410 ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
411 ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
412 }
413 }
414
415 fn valid() -> ValidSolution {
416 QueuedValidVariant::<T>::get(Self::round())
417 }
418
419 fn invalid() -> ValidSolution {
420 Self::valid().other()
421 }
422 }
423
424 #[allow(unused)]
425 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime", debug_assertions))]
426 impl<T: Config> QueuedSolution<T> {
427 pub(crate) fn valid_iter(
428 ) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
429 match Self::valid() {
430 ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
431 ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
432 }
433 }
434
435 pub(crate) fn invalid_iter(
436 ) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
437 match Self::invalid() {
438 ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
439 ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
440 }
441 }
442
443 pub(crate) fn get_valid_page(page: PageIndex) -> Option<SupportsOfVerifier<Pallet<T>>> {
444 match Self::valid() {
445 ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
446 ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
447 }
448 }
449
450 pub(crate) fn backing_iter() -> impl Iterator<
451 Item = (PageIndex, BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>),
452 > {
453 QueuedSolutionBackings::<T>::iter_prefix(Self::round())
454 }
455
456 pub(crate) fn assert_killed() {
459 use frame_support::assert_storage_noop;
460 assert_storage_noop!(Self::kill());
461 }
462
463 pub(crate) fn sanity_check() -> Result<(), sp_runtime::DispatchError> {
465 ensure!(
467 Pallet::<T>::minimum_score()
468 .zip(Self::queued_score())
469 .map_or(true, |(min_score, score)| score.strict_better(min_score)),
470 "queued solution has weak score (min-score)"
471 );
472
473 if let Some(queued_score) = Self::queued_score() {
474 let mut backing_map: BTreeMap<T::AccountId, PartialBackings> = BTreeMap::new();
475 Self::valid_iter()
476 .flat_map(|(_, supports)| supports)
477 .for_each(|(who, support)| {
478 let entry = backing_map.entry(who).or_default();
479 entry.total = entry.total.saturating_add(support.total);
480 });
481 let real_score = evaluate_support(backing_map.into_values());
482 ensure!(real_score == queued_score, "queued solution has wrong score");
483 } else {
484 assert!(Self::valid_iter().count() == 0, "nothing should be stored if no score");
485 }
486
487 ensure!(
490 QueuedSolutionBackings::<T>::iter_prefix(Self::round()).count() ==
491 Self::invalid_iter().count(),
492 "incorrect number of backings pages",
493 );
494
495 if let Status::Nothing = StatusStorage::<T>::get() {
496 ensure!(Self::invalid_iter().count() == 0, "dangling data in invalid variant");
497 }
498
499 Ok(())
500 }
501 }
502
503 #[pallet::storage]
513 type QueuedSolutionX<T: Config> = StorageDoubleMap<
514 _,
515 Twox64Concat,
516 u32,
517 Twox64Concat,
518 PageIndex,
519 SupportsOfVerifier<Pallet<T>>,
520 >;
521
522 #[pallet::storage]
524 type QueuedSolutionY<T: Config> = StorageDoubleMap<
525 _,
526 Twox64Concat,
527 u32,
528 Twox64Concat,
529 PageIndex,
530 SupportsOfVerifier<Pallet<T>>,
531 >;
532 #[pallet::storage]
536 type QueuedValidVariant<T: Config> =
537 StorageMap<_, Twox64Concat, u32, ValidSolution, ValueQuery>;
538
539 #[pallet::storage]
548 type QueuedSolutionBackings<T: Config> = StorageDoubleMap<
549 _,
550 Twox64Concat,
551 u32,
552 Twox64Concat,
553 PageIndex,
554 BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>,
555 >;
556
557 #[pallet::storage]
561 type QueuedSolutionScore<T: Config> = StorageMap<_, Twox64Concat, u32, ElectionScore>;
562
563 #[pallet::storage]
567 #[pallet::getter(fn minimum_score)]
568 pub(crate) type MinimumScore<T: Config> = StorageValue<_, ElectionScore>;
569
570 #[pallet::storage]
572 #[pallet::getter(fn status_storage)]
573 pub(crate) type StatusStorage<T: Config> = StorageValue<_, Status, ValueQuery>;
574
575 #[pallet::pallet]
576 pub struct Pallet<T>(PhantomData<T>);
577
578 #[pallet::genesis_config]
579 #[derive(frame_support::DefaultNoBound)]
580 pub struct GenesisConfig<T: Config> {
581 pub(crate) minimum_score: ElectionScore,
583 _marker: PhantomData<T>,
584 }
585
586 #[pallet::genesis_build]
587 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
588 fn build(&self) {
589 MinimumScore::<T>::put(self.minimum_score);
590 }
591 }
592
593 #[pallet::call]
594 impl<T: Config> Pallet<T> {}
595
596 #[pallet::hooks]
597 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
598 fn integrity_test() {
599 assert_eq!(T::MaxWinnersPerPage::get(), <Self as Verifier>::MaxWinnersPerPage::get());
602 assert_eq!(
603 T::MaxBackersPerWinner::get(),
604 <Self as Verifier>::MaxBackersPerWinner::get()
605 );
606 assert!(T::MaxBackersPerWinner::get() <= T::MaxBackersPerWinnerFinal::get());
607 }
608
609 #[cfg(feature = "try-runtime")]
610 fn try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
611 Self::do_try_state(_now)
612 }
613 }
614}
615
616impl<T: Config> Pallet<T> {
617 fn do_per_block_exec() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
618 let Status::Ongoing(current_page) = Self::status_storage() else {
619 let weight = T::DbWeight::get().reads(1);
620 return (weight, Box::new(move |meter: &mut WeightMeter| meter.consume(weight)))
621 };
622
623 let worst_case_weight = VerifierWeightsOf::<T>::verification_valid_non_terminal()
625 .max(VerifierWeightsOf::<T>::verification_valid_terminal())
626 .max(VerifierWeightsOf::<T>::verification_invalid_non_terminal(T::Pages::get()))
627 .max(VerifierWeightsOf::<T>::verification_invalid_terminal());
628
629 let execute = Box::new(move |meter: &mut WeightMeter| {
630 let page_solution =
631 <T::SolutionDataProvider as SolutionDataProvider>::get_page(current_page);
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 match maybe_supports {
642 Ok(supports) => {
643 Self::deposit_event(Event::<T>::Verified(current_page, supports.len() as u32));
644 QueuedSolution::<T>::set_invalid_page(current_page, supports);
645
646 if current_page > crate::Pallet::<T>::lsp() {
647 StatusStorage::<T>::put(Status::Ongoing(
649 current_page.defensive_saturating_sub(1),
650 ));
651 meter.consume(VerifierWeightsOf::<T>::verification_valid_non_terminal())
652 } else {
653 let claimed_score = T::SolutionDataProvider::get_score();
655
656 StatusStorage::<T>::put(Status::Nothing);
659
660 match Self::finalize_async_verification(claimed_score) {
661 Ok(_) => {
662 T::SolutionDataProvider::report_result(VerificationResult::Queued);
663 meter.consume(VerifierWeightsOf::<T>::verification_valid_terminal())
664 },
665 Err(_) => {
666 T::SolutionDataProvider::report_result(
667 VerificationResult::Rejected,
668 );
669 QueuedSolution::<T>::clear_invalid_and_backings();
671 meter
672 .consume(VerifierWeightsOf::<T>::verification_invalid_terminal())
673 },
674 }
675 }
676 },
677 Err(err) => {
678 Self::deposit_event(Event::<T>::VerificationFailed(current_page, err));
680
681 sublog!(warn, "verifier", "Clearing any ongoing unverified solution.");
682 QueuedSolution::<T>::clear_invalid_and_backings_unchecked();
685
686 let was_ongoing = matches!(StatusStorage::<T>::get(), Status::Ongoing(_));
688 StatusStorage::<T>::put(Status::Nothing);
689
690 if was_ongoing {
691 T::SolutionDataProvider::report_result(VerificationResult::Rejected);
692 }
693 let wasted_pages = T::Pages::get().saturating_sub(current_page);
694 meter.consume(VerifierWeightsOf::<T>::verification_invalid_non_terminal(
695 wasted_pages,
696 ))
697 },
698 }
699 });
700
701 (worst_case_weight, execute)
702 }
703
704 fn do_verify_synchronous_multi(
705 partial_solutions: Vec<SolutionOf<T::MinerConfig>>,
706 solution_pages: Vec<PageIndex>,
707 claimed_score: ElectionScore,
708 ) -> Result<(), (PageIndex, FeasibilityError)> {
709 let first_page = solution_pages.first().cloned().unwrap_or_default();
710 let last_page = solution_pages.last().cloned().unwrap_or_default();
711 let _ = Self::ensure_score_quality(claimed_score).map_err(|fe| (first_page, fe))?;
713 ensure!(
714 partial_solutions.len() == solution_pages.len(),
715 (first_page, FeasibilityError::Incomplete)
716 );
717
718 let mut backings =
720 sp_std::collections::btree_map::BTreeMap::<T::AccountId, PartialBackings>::new();
721 let mut linked_supports = Vec::with_capacity(partial_solutions.len());
722
723 for (solution_page, page) in partial_solutions.into_iter().zip(solution_pages.iter()) {
724 let page_supports = Self::feasibility_check_page_inner(solution_page, *page)
725 .map_err(|fe| (*page, fe))?;
726
727 linked_supports.push(page_supports.clone());
728 let support_len = page_supports.len() as u32;
729 for (who, support) in page_supports.into_iter() {
730 let entry = backings.entry(who).or_default();
731 entry.total = entry.total.saturating_add(support.total);
732 entry.backers = entry.backers.saturating_add(support.voters.len() as u32);
735 if entry.backers > T::MaxBackersPerWinnerFinal::get() {
736 return Err((*page, FeasibilityError::FailedToBoundSupport))
737 }
738 }
739
740 Self::deposit_event(Event::<T>::Verified(*page, support_len));
741 }
742
743 let desired_targets = crate::Snapshot::<T>::desired_targets()
745 .ok_or(FeasibilityError::SnapshotUnavailable)
746 .map_err(|fe| (last_page, fe))?;
747 ensure!(
748 backings.len() as u32 == desired_targets,
749 (last_page, FeasibilityError::WrongWinnerCount)
750 );
751
752 let truth_score = evaluate_support(backings.into_values());
754 ensure!(truth_score == claimed_score, (last_page, FeasibilityError::InvalidScore));
755
756 let maybe_current_score = QueuedSolution::<T>::queued_score();
757
758 sublog!(
760 debug,
761 "verifier",
762 "queued sync solution with score {:?} for pages {:?}",
763 truth_score,
764 solution_pages
765 );
766 QueuedSolution::<T>::force_set_multi_page_valid(
767 solution_pages,
768 linked_supports,
769 truth_score,
770 );
771 Self::deposit_event(Event::<T>::Queued(truth_score, maybe_current_score));
772
773 Ok(())
774 }
775
776 fn finalize_async_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> {
786 let outcome = QueuedSolution::<T>::compute_invalid_score()
787 .and_then(|(final_score, winner_count)| {
788 let desired_targets =
789 crate::Snapshot::<T>::desired_targets().defensive_unwrap_or(u32::MAX);
790 match (final_score == claimed_score, winner_count == desired_targets) {
792 (true, true) => {
793 Self::deposit_event(Event::<T>::Queued(
796 final_score,
797 QueuedSolution::<T>::queued_score(), ));
800 QueuedSolution::<T>::finalize_correct(final_score);
801 Ok(())
802 },
803 (false, true) => Err(FeasibilityError::InvalidScore),
804 (true, false) => Err(FeasibilityError::WrongWinnerCount),
805 (false, false) => Err(FeasibilityError::InvalidScore),
806 }
807 })
808 .map_err(|err| {
809 sublog!(warn, "verifier", "Finalizing solution was invalid due to {:?}.", err);
810 Self::deposit_event(Event::<T>::VerificationFailed(0, err.clone()));
812 err
813 });
814 sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome);
815 outcome
816 }
817
818 pub(crate) fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> {
823 let is_improvement = <Self as Verifier>::queued_score()
824 .map_or(true, |best_score| score.strict_better(best_score));
825 ensure!(is_improvement, FeasibilityError::ScoreTooLow);
826
827 let is_greater_than_min_untrusted =
828 Self::minimum_score().map_or(true, |min_score| score.strict_better(min_score));
829 ensure!(is_greater_than_min_untrusted, FeasibilityError::ScoreTooLow);
830
831 Ok(())
832 }
833
834 pub(super) fn feasibility_check_page_inner(
841 partial_solution: SolutionOf<T::MinerConfig>,
842 page: PageIndex,
843 ) -> Result<SupportsOfVerifier<Self>, FeasibilityError> {
844 let snapshot_targets =
846 crate::Snapshot::<T>::targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
847 let snapshot_voters =
848 crate::Snapshot::<T>::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?;
849 let desired_targets =
850 crate::Snapshot::<T>::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
851
852 feasibility_check_page_inner_with_snapshot::<T::MinerConfig>(
853 partial_solution,
854 &snapshot_voters,
855 &snapshot_targets,
856 desired_targets,
857 )
858 .and_then(|miner_supports| {
859 SupportsOfVerifier::<Self>::try_from_other_bounds(miner_supports)
860 .defensive_map_err(|_| FeasibilityError::FailedToBoundSupport)
861 })
862 }
863
864 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
865 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
866 QueuedSolution::<T>::sanity_check()
867 }
868}
869
870pub fn feasibility_check_page_inner_with_snapshot<T: MinerConfig>(
875 partial_solution: SolutionOf<T>,
876 snapshot_voters: &BoundedVec<VoterOf<T>, T::VoterSnapshotPerBlock>,
877 snapshot_targets: &BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
878 desired_targets: u32,
879) -> Result<PageSupportsOfMiner<T>, FeasibilityError> {
880 let cache = helpers::generate_voter_cache::<T, _>(snapshot_voters);
882 let voter_at = helpers::voter_at_fn::<T>(snapshot_voters);
883 let target_at = helpers::target_at_fn::<T>(snapshot_targets);
884 let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
885
886 let assignments = partial_solution
890 .into_assignment(voter_at, target_at)
891 .map_err::<FeasibilityError, _>(Into::into)?;
892
893 let _ = assignments
895 .iter()
896 .map(|ref assignment| {
897 let snapshot_index =
905 voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
906 let (_voter, _stake, targets) =
908 snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
909 debug_assert!(*_voter == assignment.who);
910
911 if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) {
913 return Err(FeasibilityError::InvalidVote)
914 }
915 Ok(())
916 })
917 .collect::<Result<(), FeasibilityError>>()?;
918
919 let stake_of = helpers::stake_of_fn::<T, _>(&snapshot_voters, &cache);
921
922 let staked_assignments =
924 sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of)
925 .map_err::<FeasibilityError, _>(Into::into)?;
926
927 let supports = sp_npos_elections::to_supports(&staked_assignments);
928
929 ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount);
932
933 let bounded_supports =
937 supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?;
938 Ok(bounded_supports)
939}
940
941impl<T: Config> Verifier for Pallet<T> {
942 type AccountId = T::AccountId;
943 type Solution = SolutionOf<T::MinerConfig>;
944 type MaxBackersPerWinner = T::MaxBackersPerWinner;
945 type MaxWinnersPerPage = T::MaxWinnersPerPage;
946 type MaxBackersPerWinnerFinal = T::MaxBackersPerWinnerFinal;
947
948 fn set_minimum_score(score: ElectionScore) {
949 MinimumScore::<T>::put(score);
950 }
951
952 fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool {
953 Self::ensure_score_quality(claimed_score).is_ok()
954 }
955
956 fn queued_score() -> Option<ElectionScore> {
957 QueuedSolution::<T>::queued_score()
958 }
959
960 fn kill() {
961 QueuedSolution::<T>::kill();
962 <StatusStorage<T>>::put(Status::Nothing);
963 }
964
965 fn get_queued_solution_page(page: PageIndex) -> Option<SupportsOfVerifier<Self>> {
966 QueuedSolution::<T>::get_queued_solution_page(page)
967 }
968
969 fn verify_synchronous_multi(
970 partial_solutions: Vec<Self::Solution>,
971 solution_pages: Vec<PageIndex>,
972 claimed_score: ElectionScore,
973 ) -> Result<(), FeasibilityError> {
974 Self::do_verify_synchronous_multi(partial_solutions, solution_pages, claimed_score).map_err(
975 |(page, fe)| {
976 sublog!(
977 warn,
978 "verifier",
979 "sync verification of page {:?} failed due to {:?}.",
980 page,
981 fe
982 );
983 Self::deposit_event(Event::<T>::VerificationFailed(page, fe.clone()));
984 fe
985 },
986 )
987 }
988
989 fn force_set_single_page_valid(
990 partial_supports: SupportsOfVerifier<Self>,
991 page: PageIndex,
992 score: ElectionScore,
993 ) {
994 Self::deposit_event(Event::<T>::Queued(score, QueuedSolution::<T>::queued_score()));
995 QueuedSolution::<T>::force_set_single_page_valid(page, partial_supports, score);
996 }
997
998 fn per_block_exec() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
999 Self::do_per_block_exec()
1000 }
1001}
1002
1003impl<T: Config> AsynchronousVerifier for Pallet<T> {
1004 type SolutionDataProvider = T::SolutionDataProvider;
1005
1006 fn status() -> Status {
1007 Pallet::<T>::status_storage()
1008 }
1009
1010 fn start() -> Result<(), &'static str> {
1011 sublog!(debug, "verifier", "start signal received.");
1012 if let Status::Nothing = Self::status() {
1013 let claimed_score = Self::SolutionDataProvider::get_score();
1014 if Self::ensure_score_quality(claimed_score).is_err() {
1015 Self::deposit_event(Event::<T>::VerificationFailed(
1017 crate::Pallet::<T>::msp(),
1018 FeasibilityError::ScoreTooLow,
1019 ));
1020 T::SolutionDataProvider::report_result(VerificationResult::Rejected);
1021 Ok(())
1023 } else {
1024 StatusStorage::<T>::put(Status::Ongoing(crate::Pallet::<T>::msp()));
1026 Ok(())
1027 }
1028 } else {
1029 sublog!(warn, "verifier", "start signal received while busy. This will be ignored.");
1030 Err("verification ongoing")
1031 }
1032 }
1033}