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 },
344 ValidSolution::Y => {
345 QueuedSolutionY::<T>::insert(Self::round(), page, support)
346 },
347 }
348 }
349 QueuedSolutionScore::<T>::insert(Self::round(), score);
350 });
351 }
352
353 pub(crate) fn kill() {
357 Self::mutate_checked(|| {
358 clear_round_based_map!(QueuedSolutionX::<T>, Self::round());
359 clear_round_based_map!(QueuedSolutionY::<T>, Self::round());
360 QueuedValidVariant::<T>::remove(Self::round());
361 clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
362 QueuedSolutionScore::<T>::remove(Self::round());
363 })
364 }
365
366 pub(crate) fn compute_invalid_score() -> Result<(ElectionScore, u32), FeasibilityError> {
383 let mut total_supports: BTreeMap<T::AccountId, PartialBackings> = Default::default();
384 for (who, PartialBackings { backers, total }) in
385 QueuedSolutionBackings::<T>::iter_prefix(Self::round()).flat_map(|(_, pb)| pb)
386 {
387 let entry = total_supports.entry(who).or_default();
388 entry.total = entry.total.saturating_add(total);
389 entry.backers = entry.backers.saturating_add(backers);
390
391 if entry.backers > T::MaxBackersPerWinnerFinal::get() {
392 return Err(FeasibilityError::FailedToBoundSupport);
393 }
394 }
395
396 let winner_count = total_supports.len() as u32;
397 let score = evaluate_support(total_supports.into_values());
398
399 Ok((score, winner_count))
400 }
401
402 pub(crate) fn queued_score() -> Option<ElectionScore> {
404 QueuedSolutionScore::<T>::get(Self::round())
405 }
406
407 pub(crate) fn get_queued_solution_page(
409 page: PageIndex,
410 ) -> Option<SupportsOfVerifier<Pallet<T>>> {
411 match Self::valid() {
412 ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
413 ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
414 }
415 }
416
417 fn valid() -> ValidSolution {
418 QueuedValidVariant::<T>::get(Self::round())
419 }
420
421 fn invalid() -> ValidSolution {
422 Self::valid().other()
423 }
424 }
425
426 #[allow(unused)]
427 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime", debug_assertions))]
428 impl<T: Config> QueuedSolution<T> {
429 pub(crate) fn valid_iter(
430 ) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
431 match Self::valid() {
432 ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
433 ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
434 }
435 }
436
437 pub(crate) fn invalid_iter(
438 ) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
439 match Self::invalid() {
440 ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
441 ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
442 }
443 }
444
445 pub(crate) fn get_valid_page(page: PageIndex) -> Option<SupportsOfVerifier<Pallet<T>>> {
446 match Self::valid() {
447 ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
448 ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
449 }
450 }
451
452 pub(crate) fn backing_iter() -> impl Iterator<
453 Item = (PageIndex, BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>),
454 > {
455 QueuedSolutionBackings::<T>::iter_prefix(Self::round())
456 }
457
458 pub(crate) fn assert_killed() {
461 use frame_support::assert_storage_noop;
462 assert_storage_noop!(Self::kill());
463 }
464
465 pub(crate) fn sanity_check() -> Result<(), sp_runtime::DispatchError> {
467 ensure!(
469 Pallet::<T>::minimum_score()
470 .zip(Self::queued_score())
471 .map_or(true, |(min_score, score)| score.strict_better(min_score)),
472 "queued solution has weak score (min-score)"
473 );
474
475 if let Some(queued_score) = Self::queued_score() {
476 let mut backing_map: BTreeMap<T::AccountId, PartialBackings> = BTreeMap::new();
477 Self::valid_iter()
478 .flat_map(|(_, supports)| supports)
479 .for_each(|(who, support)| {
480 let entry = backing_map.entry(who).or_default();
481 entry.total = entry.total.saturating_add(support.total);
482 });
483 let real_score = evaluate_support(backing_map.into_values());
484 ensure!(real_score == queued_score, "queued solution has wrong score");
485 } else {
486 assert!(Self::valid_iter().count() == 0, "nothing should be stored if no score");
487 }
488
489 ensure!(
492 QueuedSolutionBackings::<T>::iter_prefix(Self::round()).count() ==
493 Self::invalid_iter().count(),
494 "incorrect number of backings pages",
495 );
496
497 if let Status::Nothing = StatusStorage::<T>::get() {
498 ensure!(Self::invalid_iter().count() == 0, "dangling data in invalid variant");
499 }
500
501 Ok(())
502 }
503 }
504
505 #[pallet::storage]
515 type QueuedSolutionX<T: Config> = StorageDoubleMap<
516 _,
517 Twox64Concat,
518 u32,
519 Twox64Concat,
520 PageIndex,
521 SupportsOfVerifier<Pallet<T>>,
522 >;
523
524 #[pallet::storage]
526 type QueuedSolutionY<T: Config> = StorageDoubleMap<
527 _,
528 Twox64Concat,
529 u32,
530 Twox64Concat,
531 PageIndex,
532 SupportsOfVerifier<Pallet<T>>,
533 >;
534 #[pallet::storage]
538 type QueuedValidVariant<T: Config> =
539 StorageMap<_, Twox64Concat, u32, ValidSolution, ValueQuery>;
540
541 #[pallet::storage]
550 type QueuedSolutionBackings<T: Config> = StorageDoubleMap<
551 _,
552 Twox64Concat,
553 u32,
554 Twox64Concat,
555 PageIndex,
556 BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>,
557 >;
558
559 #[pallet::storage]
563 type QueuedSolutionScore<T: Config> = StorageMap<_, Twox64Concat, u32, ElectionScore>;
564
565 #[pallet::storage]
569 #[pallet::getter(fn minimum_score)]
570 pub(crate) type MinimumScore<T: Config> = StorageValue<_, ElectionScore>;
571
572 #[pallet::storage]
574 #[pallet::getter(fn status_storage)]
575 pub(crate) type StatusStorage<T: Config> = StorageValue<_, Status, ValueQuery>;
576
577 #[pallet::pallet]
578 pub struct Pallet<T>(PhantomData<T>);
579
580 #[pallet::genesis_config]
581 #[derive(frame_support::DefaultNoBound)]
582 pub struct GenesisConfig<T: Config> {
583 pub(crate) minimum_score: ElectionScore,
585 _marker: PhantomData<T>,
586 }
587
588 #[pallet::genesis_build]
589 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
590 fn build(&self) {
591 MinimumScore::<T>::put(self.minimum_score);
592 }
593 }
594
595 #[pallet::call]
596 impl<T: Config> Pallet<T> {}
597
598 #[pallet::hooks]
599 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
600 fn integrity_test() {
601 assert_eq!(T::MaxWinnersPerPage::get(), <Self as Verifier>::MaxWinnersPerPage::get());
604 assert_eq!(
605 T::MaxBackersPerWinner::get(),
606 <Self as Verifier>::MaxBackersPerWinner::get()
607 );
608 assert!(T::MaxBackersPerWinner::get() <= T::MaxBackersPerWinnerFinal::get());
609 }
610
611 #[cfg(feature = "try-runtime")]
612 fn try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
613 Self::do_try_state(_now)
614 }
615 }
616}
617
618impl<T: Config> Pallet<T> {
619 fn do_per_block_exec() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
620 let Status::Ongoing(current_page) = Self::status_storage() else {
621 let weight = T::DbWeight::get().reads(1);
622 return (weight, Box::new(move |meter: &mut WeightMeter| meter.consume(weight)));
623 };
624
625 let worst_case_weight = VerifierWeightsOf::<T>::verification_valid_non_terminal()
627 .max(VerifierWeightsOf::<T>::verification_valid_terminal())
628 .max(VerifierWeightsOf::<T>::verification_invalid_non_terminal(T::Pages::get()))
629 .max(VerifierWeightsOf::<T>::verification_invalid_terminal());
630
631 let execute = Box::new(move |meter: &mut WeightMeter| {
632 let page_solution =
633 <T::SolutionDataProvider as SolutionDataProvider>::get_page(current_page);
634 let maybe_supports = Self::feasibility_check_page_inner(page_solution, current_page);
635
636 sublog!(
637 debug,
638 "verifier",
639 "verified page {} of a solution, outcome = {:?}",
640 current_page,
641 maybe_supports.as_ref().map(|s| s.len())
642 );
643 match maybe_supports {
644 Ok(supports) => {
645 Self::deposit_event(Event::<T>::Verified(current_page, supports.len() as u32));
646 QueuedSolution::<T>::set_invalid_page(current_page, supports);
647
648 if current_page > crate::Pallet::<T>::lsp() {
649 StatusStorage::<T>::put(Status::Ongoing(
651 current_page.defensive_saturating_sub(1),
652 ));
653 meter.consume(VerifierWeightsOf::<T>::verification_valid_non_terminal())
654 } else {
655 let claimed_score = T::SolutionDataProvider::get_score();
657
658 StatusStorage::<T>::put(Status::Nothing);
661
662 match Self::finalize_async_verification(claimed_score) {
663 Ok(_) => {
664 T::SolutionDataProvider::report_result(VerificationResult::Queued);
665 meter.consume(VerifierWeightsOf::<T>::verification_valid_terminal())
666 },
667 Err(_) => {
668 T::SolutionDataProvider::report_result(
669 VerificationResult::Rejected,
670 );
671 QueuedSolution::<T>::clear_invalid_and_backings();
673 meter
674 .consume(VerifierWeightsOf::<T>::verification_invalid_terminal())
675 },
676 }
677 }
678 },
679 Err(err) => {
680 Self::deposit_event(Event::<T>::VerificationFailed(current_page, err));
682
683 sublog!(warn, "verifier", "Clearing any ongoing unverified solution.");
684 QueuedSolution::<T>::clear_invalid_and_backings_unchecked();
687
688 let was_ongoing = matches!(StatusStorage::<T>::get(), Status::Ongoing(_));
690 StatusStorage::<T>::put(Status::Nothing);
691
692 if was_ongoing {
693 T::SolutionDataProvider::report_result(VerificationResult::Rejected);
694 }
695 let wasted_pages = T::Pages::get().saturating_sub(current_page);
696 meter.consume(VerifierWeightsOf::<T>::verification_invalid_non_terminal(
697 wasted_pages,
698 ))
699 },
700 }
701 });
702
703 (worst_case_weight, execute)
704 }
705
706 fn do_verify_synchronous_multi(
707 partial_solutions: Vec<SolutionOf<T::MinerConfig>>,
708 solution_pages: Vec<PageIndex>,
709 claimed_score: ElectionScore,
710 ) -> Result<(), (PageIndex, FeasibilityError)> {
711 let first_page = solution_pages.first().cloned().unwrap_or_default();
712 let last_page = solution_pages.last().cloned().unwrap_or_default();
713 let _ = Self::ensure_score_quality(claimed_score).map_err(|fe| (first_page, fe))?;
715 ensure!(
716 partial_solutions.len() == solution_pages.len(),
717 (first_page, FeasibilityError::Incomplete)
718 );
719
720 let mut backings =
722 sp_std::collections::btree_map::BTreeMap::<T::AccountId, PartialBackings>::new();
723 let mut linked_supports = Vec::with_capacity(partial_solutions.len());
724
725 for (solution_page, page) in partial_solutions.into_iter().zip(solution_pages.iter()) {
726 let page_supports = Self::feasibility_check_page_inner(solution_page, *page)
727 .map_err(|fe| (*page, fe))?;
728
729 linked_supports.push(page_supports.clone());
730 let support_len = page_supports.len() as u32;
731 for (who, support) in page_supports.into_iter() {
732 let entry = backings.entry(who).or_default();
733 entry.total = entry.total.saturating_add(support.total);
734 entry.backers = entry.backers.saturating_add(support.voters.len() as u32);
737 if entry.backers > T::MaxBackersPerWinnerFinal::get() {
738 return Err((*page, FeasibilityError::FailedToBoundSupport));
739 }
740 }
741
742 Self::deposit_event(Event::<T>::Verified(*page, support_len));
743 }
744
745 let desired_targets = crate::Snapshot::<T>::desired_targets()
747 .ok_or(FeasibilityError::SnapshotUnavailable)
748 .map_err(|fe| (last_page, fe))?;
749 ensure!(
750 backings.len() as u32 == desired_targets,
751 (last_page, FeasibilityError::WrongWinnerCount)
752 );
753
754 let truth_score = evaluate_support(backings.into_values());
756 ensure!(truth_score == claimed_score, (last_page, FeasibilityError::InvalidScore));
757
758 let maybe_current_score = QueuedSolution::<T>::queued_score();
759
760 sublog!(
762 debug,
763 "verifier",
764 "queued sync solution with score {:?} for pages {:?}",
765 truth_score,
766 solution_pages
767 );
768 QueuedSolution::<T>::force_set_multi_page_valid(
769 solution_pages,
770 linked_supports,
771 truth_score,
772 );
773 Self::deposit_event(Event::<T>::Queued(truth_score, maybe_current_score));
774
775 Ok(())
776 }
777
778 fn finalize_async_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> {
788 let outcome = QueuedSolution::<T>::compute_invalid_score()
789 .and_then(|(final_score, winner_count)| {
790 let desired_targets =
791 crate::Snapshot::<T>::desired_targets().defensive_unwrap_or(u32::MAX);
792 match (final_score == claimed_score, winner_count == desired_targets) {
794 (true, true) => {
795 Self::deposit_event(Event::<T>::Queued(
798 final_score,
799 QueuedSolution::<T>::queued_score(), ));
802 QueuedSolution::<T>::finalize_correct(final_score);
803 Ok(())
804 },
805 (false, true) => Err(FeasibilityError::InvalidScore),
806 (true, false) => Err(FeasibilityError::WrongWinnerCount),
807 (false, false) => Err(FeasibilityError::InvalidScore),
808 }
809 })
810 .map_err(|err| {
811 sublog!(warn, "verifier", "Finalizing solution was invalid due to {:?}.", err);
812 Self::deposit_event(Event::<T>::VerificationFailed(0, err.clone()));
814 err
815 });
816 sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome);
817 outcome
818 }
819
820 pub(crate) fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> {
825 let is_improvement = <Self as Verifier>::queued_score()
826 .map_or(true, |best_score| score.strict_better(best_score));
827 ensure!(is_improvement, FeasibilityError::ScoreTooLow);
828
829 let is_greater_than_min_untrusted =
830 Self::minimum_score().map_or(true, |min_score| score.strict_better(min_score));
831 ensure!(is_greater_than_min_untrusted, FeasibilityError::ScoreTooLow);
832
833 Ok(())
834 }
835
836 pub(super) fn feasibility_check_page_inner(
843 partial_solution: SolutionOf<T::MinerConfig>,
844 page: PageIndex,
845 ) -> Result<SupportsOfVerifier<Self>, FeasibilityError> {
846 let snapshot_targets =
848 crate::Snapshot::<T>::targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
849 let snapshot_voters =
850 crate::Snapshot::<T>::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?;
851 let desired_targets =
852 crate::Snapshot::<T>::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
853
854 feasibility_check_page_inner_with_snapshot::<T::MinerConfig>(
855 partial_solution,
856 &snapshot_voters,
857 &snapshot_targets,
858 desired_targets,
859 )
860 .and_then(|miner_supports| {
861 SupportsOfVerifier::<Self>::try_from_other_bounds(miner_supports)
862 .defensive_map_err(|_| FeasibilityError::FailedToBoundSupport)
863 })
864 }
865
866 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
867 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
868 QueuedSolution::<T>::sanity_check()
869 }
870}
871
872pub fn feasibility_check_page_inner_with_snapshot<T: MinerConfig>(
877 partial_solution: SolutionOf<T>,
878 snapshot_voters: &BoundedVec<VoterOf<T>, T::VoterSnapshotPerBlock>,
879 snapshot_targets: &BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
880 desired_targets: u32,
881) -> Result<PageSupportsOfMiner<T>, FeasibilityError> {
882 let cache = helpers::generate_voter_cache::<T, _>(snapshot_voters);
884 let voter_at = helpers::voter_at_fn::<T>(snapshot_voters);
885 let target_at = helpers::target_at_fn::<T>(snapshot_targets);
886 let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
887
888 let assignments = partial_solution
892 .into_assignment(voter_at, target_at)
893 .map_err::<FeasibilityError, _>(Into::into)?;
894
895 let _ = assignments
897 .iter()
898 .map(|ref assignment| {
899 let snapshot_index =
907 voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
908 let (_voter, _stake, targets) =
910 snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
911 debug_assert!(*_voter == assignment.who);
912
913 if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) {
915 return Err(FeasibilityError::InvalidVote);
916 }
917 Ok(())
918 })
919 .collect::<Result<(), FeasibilityError>>()?;
920
921 let stake_of = helpers::stake_of_fn::<T, _>(&snapshot_voters, &cache);
923
924 let staked_assignments =
926 sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of)
927 .map_err::<FeasibilityError, _>(Into::into)?;
928
929 let supports = sp_npos_elections::to_supports(&staked_assignments);
930
931 ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount);
934
935 let bounded_supports =
939 supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?;
940 Ok(bounded_supports)
941}
942
943impl<T: Config> Verifier for Pallet<T> {
944 type AccountId = T::AccountId;
945 type Solution = SolutionOf<T::MinerConfig>;
946 type MaxBackersPerWinner = T::MaxBackersPerWinner;
947 type MaxWinnersPerPage = T::MaxWinnersPerPage;
948 type MaxBackersPerWinnerFinal = T::MaxBackersPerWinnerFinal;
949
950 fn set_minimum_score(score: ElectionScore) {
951 MinimumScore::<T>::put(score);
952 }
953
954 fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool {
955 Self::ensure_score_quality(claimed_score).is_ok()
956 }
957
958 fn queued_score() -> Option<ElectionScore> {
959 QueuedSolution::<T>::queued_score()
960 }
961
962 fn kill() {
963 QueuedSolution::<T>::kill();
964 <StatusStorage<T>>::put(Status::Nothing);
965 }
966
967 fn get_queued_solution_page(page: PageIndex) -> Option<SupportsOfVerifier<Self>> {
968 QueuedSolution::<T>::get_queued_solution_page(page)
969 }
970
971 fn verify_synchronous_multi(
972 partial_solutions: Vec<Self::Solution>,
973 solution_pages: Vec<PageIndex>,
974 claimed_score: ElectionScore,
975 ) -> Result<(), FeasibilityError> {
976 Self::do_verify_synchronous_multi(partial_solutions, solution_pages, claimed_score).map_err(
977 |(page, fe)| {
978 sublog!(
979 warn,
980 "verifier",
981 "sync verification of page {:?} failed due to {:?}.",
982 page,
983 fe
984 );
985 Self::deposit_event(Event::<T>::VerificationFailed(page, fe.clone()));
986 fe
987 },
988 )
989 }
990
991 fn force_set_single_page_valid(
992 partial_supports: SupportsOfVerifier<Self>,
993 page: PageIndex,
994 score: ElectionScore,
995 ) {
996 Self::deposit_event(Event::<T>::Queued(score, QueuedSolution::<T>::queued_score()));
997 QueuedSolution::<T>::force_set_single_page_valid(page, partial_supports, score);
998 }
999
1000 fn per_block_exec() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
1001 Self::do_per_block_exec()
1002 }
1003}
1004
1005impl<T: Config> AsynchronousVerifier for Pallet<T> {
1006 type SolutionDataProvider = T::SolutionDataProvider;
1007
1008 fn status() -> Status {
1009 Pallet::<T>::status_storage()
1010 }
1011
1012 fn start() -> Result<(), &'static str> {
1013 sublog!(debug, "verifier", "start signal received.");
1014 if let Status::Nothing = Self::status() {
1015 let claimed_score = Self::SolutionDataProvider::get_score();
1016 if Self::ensure_score_quality(claimed_score).is_err() {
1017 Self::deposit_event(Event::<T>::VerificationFailed(
1019 crate::Pallet::<T>::msp(),
1020 FeasibilityError::ScoreTooLow,
1021 ));
1022 T::SolutionDataProvider::report_result(VerificationResult::Rejected);
1023 Ok(())
1025 } else {
1026 StatusStorage::<T>::put(Status::Ongoing(crate::Pallet::<T>::msp()));
1028 Ok(())
1029 }
1030 } else {
1031 sublog!(warn, "verifier", "start signal received while busy. This will be ignored.");
1032 Err("verification ongoing")
1033 }
1034 }
1035}