1#![cfg_attr(not(feature = "std"), no_std)]
198
199#[cfg(any(feature = "runtime-benchmarks", test))]
200use crate::signed::{CalculateBaseDeposit, CalculatePageDeposit};
201use crate::verifier::{AsynchronousVerifier, Verifier};
202use codec::{Decode, Encode, MaxEncodedLen};
203use frame_election_provider_support::{
204 onchain, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
205 InstantElectionProvider,
206};
207use frame_support::{
208 dispatch::PostDispatchInfo,
209 pallet_prelude::*,
210 traits::{Defensive, EnsureOrigin},
211 weights::WeightMeter,
212 DebugNoBound, Twox64Concat,
213};
214use frame_system::pallet_prelude::*;
215use scale_info::TypeInfo;
216use sp_arithmetic::{
217 traits::{CheckedAdd, Zero},
218 PerThing, UpperOf,
219};
220use sp_npos_elections::{EvaluateSupport, VoteWeight};
221use sp_runtime::{
222 traits::{Hash, Saturating},
223 SaturatedConversion,
224};
225use sp_std::{borrow::ToOwned, boxed::Box, prelude::*};
226
227#[cfg(test)]
228mod mock;
229#[macro_use]
230pub mod helpers;
231#[cfg(feature = "runtime-benchmarks")]
232pub mod benchmarking;
233
234pub const LOG_PREFIX: &'static str = "runtime::multiblock-election";
236
237macro_rules! clear_round_based_map {
238 ($map: ty, $round: expr) => {{
239 let __r = <$map>::clear_prefix($round, u32::MAX, None);
240 debug_assert!(__r.unique <= T::Pages::get(), "clearing map caused too many removals")
241 }};
242}
243
244pub mod signed;
246pub mod types;
248pub mod unsigned;
250pub mod verifier;
252pub mod weights;
254
255pub use pallet::*;
256pub use types::*;
257pub use weights::traits::pallet_election_provider_multi_block::WeightInfo;
258
259pub struct InitiateEmergencyPhase<T>(sp_std::marker::PhantomData<T>);
261impl<T: Config> ElectionProvider for InitiateEmergencyPhase<T> {
262 type AccountId = T::AccountId;
263 type BlockNumber = BlockNumberFor<T>;
264 type DataProvider = T::DataProvider;
265 type Error = &'static str;
266 type Pages = T::Pages;
267 type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
268 type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
269 type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
270
271 fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
272 Pallet::<T>::phase_transition(Phase::Emergency);
273 Err("Emergency phase started.")
274 }
275
276 fn status() -> Result<Option<Weight>, ()> {
277 Ok(Some(Default::default()))
278 }
279
280 fn start() -> Result<(), Self::Error> {
281 Ok(())
282 }
283
284 fn duration() -> Self::BlockNumber {
285 Zero::zero()
286 }
287}
288
289impl<T: Config> InstantElectionProvider for InitiateEmergencyPhase<T> {
290 fn instant_elect(
291 _voters: Vec<VoterOf<T::MinerConfig>>,
292 _targets: Vec<Self::AccountId>,
293 _desired_targets: u32,
294 ) -> Result<BoundedSupportsOf<Self>, Self::Error> {
295 Self::elect(0)
296 }
297
298 fn bother() -> bool {
299 false
300 }
301}
302
303pub struct Continue<T>(sp_std::marker::PhantomData<T>);
307impl<T: Config> ElectionProvider for Continue<T> {
308 type AccountId = T::AccountId;
309 type BlockNumber = BlockNumberFor<T>;
310 type DataProvider = T::DataProvider;
311 type Error = &'static str;
312 type Pages = T::Pages;
313 type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
314 type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
315 type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
316
317 fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
318 Err("'Continue' fallback will do nothing")
319 }
320
321 fn start() -> Result<(), Self::Error> {
322 Ok(())
323 }
324
325 fn duration() -> Self::BlockNumber {
326 Zero::zero()
327 }
328
329 fn status() -> Result<Option<Weight>, ()> {
330 Ok(Some(Default::default()))
331 }
332}
333
334impl<T: Config> InstantElectionProvider for Continue<T> {
335 fn instant_elect(
336 _voters: Vec<VoterOf<T::MinerConfig>>,
337 _targets: Vec<Self::AccountId>,
338 _desired_targets: u32,
339 ) -> Result<BoundedSupportsOf<Self>, Self::Error> {
340 Self::elect(0)
341 }
342
343 fn bother() -> bool {
344 false
345 }
346}
347
348pub struct IfSolutionQueuedElse<T, Queued, NotQueued>(
356 sp_std::marker::PhantomData<(T, Queued, NotQueued)>,
357);
358
359pub struct GetDone<T>(sp_std::marker::PhantomData<T>);
361impl<T: Config> Get<Phase<T>> for GetDone<T> {
362 fn get() -> Phase<T> {
363 Phase::Done
364 }
365}
366
367pub struct GetSigned<T>(sp_std::marker::PhantomData<T>);
369impl<T: Config> Get<Phase<T>> for GetSigned<T> {
370 fn get() -> Phase<T> {
371 Phase::Signed(T::SignedPhase::get().saturating_sub(1u32.into()))
372 }
373}
374
375pub type ProceedRegardlessOf<T> = IfSolutionQueuedElse<T, GetDone<T>, GetDone<T>>;
377
378pub type RevertToSignedIfNotQueuedOf<T> = IfSolutionQueuedElse<T, GetDone<T>, GetSigned<T>>;
381
382impl<T: Config, Queued, NotQueued> IfSolutionQueuedElse<T, Queued, NotQueued> {
383 fn something_queued() -> bool {
384 let queued_score = <T::Verifier as verifier::Verifier>::queued_score().is_some();
385 #[cfg(debug_assertions)]
386 {
387 let any_pages_queued = (Pallet::<T>::lsp()..=Pallet::<T>::msp()).any(|p| {
388 <T::Verifier as verifier::Verifier>::get_queued_solution_page(p).is_some()
389 });
390 assert_eq!(
391 queued_score, any_pages_queued,
392 "queued score ({}) and queued pages ({}) must match",
393 queued_score, any_pages_queued
394 );
395 }
396 queued_score
397 }
398}
399
400impl<T: Config, Queued: Get<Phase<T>>, NotQueued: Get<Phase<T>>> Get<Phase<T>>
401 for IfSolutionQueuedElse<T, Queued, NotQueued>
402{
403 fn get() -> Phase<T> {
404 if Self::something_queued() {
405 Queued::get()
406 } else {
407 NotQueued::get()
408 }
409 }
410}
411
412#[derive(
416 frame_support::DebugNoBound, frame_support::PartialEqNoBound, frame_support::EqNoBound,
417)]
418pub enum ElectionError<T: Config> {
419 Feasibility(verifier::FeasibilityError),
421 Fallback(FallbackErrorOf<T>),
423 OnChain(onchain::Error),
425 DataProvider(&'static str),
427 SupportPageNotAvailable,
429 NotOngoing,
431 Ongoing,
433 OutOfOrder,
435 Other(&'static str),
437}
438
439impl<T: Config> From<onchain::Error> for ElectionError<T> {
440 fn from(e: onchain::Error) -> Self {
441 ElectionError::OnChain(e)
442 }
443}
444
445impl<T: Config> From<verifier::FeasibilityError> for ElectionError<T> {
446 fn from(e: verifier::FeasibilityError) -> Self {
447 ElectionError::Feasibility(e)
448 }
449}
450
451#[derive(
453 Encode,
454 Decode,
455 DecodeWithMemTracking,
456 MaxEncodedLen,
457 TypeInfo,
458 DebugNoBound,
459 CloneNoBound,
460 PartialEqNoBound,
461 EqNoBound,
462)]
463#[codec(mel_bound(T: Config))]
464#[scale_info(skip_type_params(T))]
465pub enum AdminOperation<T: Config> {
466 EmergencySetSolution(Box<BoundedSupportsOf<Pallet<T>>>, ElectionScore),
470 SetMinUntrustedScore(ElectionScore),
476}
477
478#[derive(
481 Encode,
482 Decode,
483 DecodeWithMemTracking,
484 MaxEncodedLen,
485 TypeInfo,
486 DebugNoBound,
487 CloneNoBound,
488 PartialEqNoBound,
489 EqNoBound,
490)]
491#[codec(mel_bound(T: Config))]
492#[scale_info(skip_type_params(T))]
493pub enum ManagerOperation<T: Config> {
494 ForceRotateRound,
496 ForceSetPhase(Phase<T>),
500 EmergencyFallback,
505}
506
507pub trait OnRoundRotation {
509 fn on_round_rotation(ending: u32);
511}
512
513impl OnRoundRotation for () {
514 fn on_round_rotation(_: u32) {}
515}
516
517pub struct CleanRound<T>(core::marker::PhantomData<T>);
523impl<T: Config> OnRoundRotation for CleanRound<T> {
524 fn on_round_rotation(_ending: u32) {
525 T::Verifier::kill();
527
528 pallet::Snapshot::<T>::kill();
530
531 }
533}
534
535#[frame_support::pallet]
536pub mod pallet {
537 use super::*;
538
539 #[pallet::config]
540 pub trait Config: frame_system::Config {
541 #[pallet::constant]
543 type UnsignedPhase: Get<BlockNumberFor<Self>>;
544 #[pallet::constant]
546 type SignedPhase: Get<BlockNumberFor<Self>>;
547 #[pallet::constant]
552 type SignedValidationPhase: Get<BlockNumberFor<Self>>;
553
554 #[pallet::constant]
556 type VoterSnapshotPerBlock: Get<u32>;
557
558 #[pallet::constant]
560 type TargetSnapshotPerBlock: Get<u32>;
561
562 #[pallet::constant]
569 type Pages: Get<PageIndex>;
570
571 type DataProvider: ElectionDataProvider<
573 AccountId = Self::AccountId,
574 BlockNumber = BlockNumberFor<Self>,
575 >;
576
577 type MinerConfig: crate::unsigned::miner::MinerConfig<
582 Pages = Self::Pages,
583 AccountId = <Self as frame_system::Config>::AccountId,
584 MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
585 VoterSnapshotPerBlock = Self::VoterSnapshotPerBlock,
586 TargetSnapshotPerBlock = Self::TargetSnapshotPerBlock,
587 MaxBackersPerWinner = <Self::Verifier as verifier::Verifier>::MaxBackersPerWinner,
588 MaxWinnersPerPage = <Self::Verifier as verifier::Verifier>::MaxWinnersPerPage,
589 >;
590
591 type Fallback: InstantElectionProvider<
593 AccountId = Self::AccountId,
594 BlockNumber = BlockNumberFor<Self>,
595 DataProvider = Self::DataProvider,
596 MaxBackersPerWinner = <Self::Verifier as verifier::Verifier>::MaxBackersPerWinner,
597 MaxWinnersPerPage = <Self::Verifier as verifier::Verifier>::MaxWinnersPerPage,
598 >;
599
600 type Verifier: verifier::Verifier<
602 Solution = SolutionOf<Self::MinerConfig>,
603 AccountId = Self::AccountId,
604 > + verifier::AsynchronousVerifier;
605
606 type Signed: SignedInterface;
608
609 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
614
615 type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
618
619 type AreWeDone: Get<Phase<Self>>;
624
625 type WeightInfo: WeightInfo;
627
628 type OnRoundRotation: super::OnRoundRotation;
631 }
632
633 #[pallet::call]
634 impl<T: Config> Pallet<T> {
635 #[pallet::weight(T::WeightInfo::manage_fallback().max(T::WeightInfo::export_terminal()))]
641 #[pallet::call_index(0)]
642 pub fn manage(origin: OriginFor<T>, op: ManagerOperation<T>) -> DispatchResultWithPostInfo {
643 T::ManagerOrigin::ensure_origin(origin.clone()).map(|_| ()).or_else(|_| {
644 T::AdminOrigin::ensure_origin(origin).map(|_| ())
646 })?;
647 match op {
648 ManagerOperation::EmergencyFallback => {
649 ensure!(Self::current_phase() == Phase::Emergency, Error::<T>::UnexpectedPhase);
650 let voters = Snapshot::<T>::voters(Self::msp()).ok_or(Error::<T>::Snapshot)?;
653 let targets = Snapshot::<T>::targets().ok_or(Error::<T>::Snapshot)?;
654 let desired_targets =
655 Snapshot::<T>::desired_targets().ok_or(Error::<T>::Snapshot)?;
656 let fallback = T::Fallback::instant_elect(
657 voters.into_inner(),
658 targets.into_inner(),
659 desired_targets,
660 )
661 .map_err(|e| {
662 log!(warn, "Fallback failed: {:?}", e);
663 Error::<T>::Fallback
664 })?;
665 let score = fallback.evaluate();
666 T::Verifier::force_set_single_page_valid(fallback, 0, score);
667 Ok(PostDispatchInfo {
668 actual_weight: Some(T::WeightInfo::manage_fallback()),
669 pays_fee: Pays::No,
670 })
671 },
672 ManagerOperation::ForceSetPhase(phase) => {
673 Self::phase_transition(phase);
674 Ok(PostDispatchInfo {
675 actual_weight: Some(T::DbWeight::get().reads_writes(1, 1)),
676 pays_fee: Pays::No,
677 })
678 },
679 ManagerOperation::ForceRotateRound => {
680 Self::rotate_round();
681 Ok(PostDispatchInfo {
682 actual_weight: Some(T::WeightInfo::export_terminal()),
683 pays_fee: Pays::No,
684 })
685 },
686 }
687 }
688
689 #[pallet::call_index(1)]
690 #[pallet::weight(T::WeightInfo::admin_set())]
691 pub fn admin(origin: OriginFor<T>, op: AdminOperation<T>) -> DispatchResultWithPostInfo {
692 T::AdminOrigin::ensure_origin(origin)?;
693 match op {
694 AdminOperation::EmergencySetSolution(supports, score) => {
695 ensure!(Self::current_phase() == Phase::Emergency, Error::<T>::UnexpectedPhase);
696 T::Verifier::force_set_single_page_valid(*supports, 0, score);
697 Ok(PostDispatchInfo {
698 actual_weight: Some(T::WeightInfo::admin_set()),
699 pays_fee: Pays::No,
700 })
701 },
702 AdminOperation::SetMinUntrustedScore(score) => {
703 T::Verifier::set_minimum_score(score);
704 Ok(PostDispatchInfo {
705 actual_weight: Some(T::DbWeight::get().reads_writes(1, 1)),
706 pays_fee: Pays::No,
707 })
708 },
709 }
710 }
711 }
712
713 #[pallet::hooks]
714 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
715 fn on_poll(_now: BlockNumberFor<T>, weight_meter: &mut WeightMeter) {
716 if !weight_meter.can_consume(T::DbWeight::get().reads(1)) {
718 Self::deposit_event(Event::UnexpectedPhaseTransitionHalt {
719 required: T::DbWeight::get().reads(1),
720 had: weight_meter.remaining(),
721 });
722 return;
723 }
724
725 let current_phase = Self::current_phase();
727 weight_meter.consume(T::DbWeight::get().reads(1));
728
729 let (self_weight, self_exec) = Self::per_block_exec(current_phase);
730 let (verifier_weight, verifier_exc) = T::Verifier::per_block_exec();
731
732 let (combined_weight, combined_exec) = (
736 self_weight.saturating_add(verifier_weight),
738 Box::new(move |meter: &mut WeightMeter| {
740 self_exec(meter);
741 verifier_exc(meter);
742 }),
743 );
744
745 log!(
746 trace,
747 "worst-case required weight for transition from {:?} to {:?} is {:?}, has {:?}",
748 current_phase,
749 current_phase.next(),
750 combined_weight,
751 weight_meter.remaining()
752 );
753 if weight_meter.can_consume(combined_weight) {
754 combined_exec(weight_meter);
755 } else {
756 Self::deposit_event(Event::UnexpectedPhaseTransitionOutOfWeight {
757 from: current_phase,
758 to: current_phase.next(),
759 required: combined_weight,
760 had: weight_meter.remaining(),
761 });
762 }
763
764 #[cfg(test)]
766 {
767 if _now > 200u32.into() {
768 panic!("Looping to death: in case of errors in election start time in tests, we might loop \
769 infinitely. This panic is preventing you from that. Double check `mock::ElectionStart` or increase \
770 the 200 limit");
771 }
772 let test_election_start: BlockNumberFor<T> =
773 (crate::mock::ElectionStart::get() as u32).into();
774 if _now == test_election_start {
775 crate::log!(info, "TESTING: Starting election at block {}", _now);
776 crate::mock::MultiBlock::start().unwrap();
777 }
778 }
779 }
780
781 fn integrity_test() {
782 use sp_std::mem::size_of;
783 assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
786 assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
787
788 assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<u32>());
791 assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<u32>());
792
793 assert!(T::Pages::get() > 0);
795
796 let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
798
799 let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T::MinerConfig>>> = (0..
801 max_vote)
802 .map(|_| {
803 <UpperOf<SolutionAccuracyOf<T::MinerConfig>>>::from(
804 <SolutionAccuracyOf<T::MinerConfig>>::one().deconstruct(),
805 )
806 })
807 .collect();
808 let _: UpperOf<SolutionAccuracyOf<T::MinerConfig>> = maximum_chain_accuracy
809 .iter()
810 .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap());
811
812 assert_eq!(
818 <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
819 <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
820 );
821
822 let has_signed = !T::SignedPhase::get().is_zero();
824 let signed_validation = T::SignedValidationPhase::get();
825 let has_signed_validation = !signed_validation.is_zero();
826 let has_unsigned = !T::UnsignedPhase::get().is_zero();
827 assert!(
828 has_signed == has_signed_validation,
829 "Signed phase not set correct -- both should be set or unset"
830 );
831 assert!(
832 signed_validation.is_zero() ||
833 signed_validation % T::Pages::get().into() == Zero::zero(),
834 "signed validation phase should be a multiple of the number of pages."
835 );
836
837 assert!(has_signed || has_unsigned, "either signed or unsigned phase must be set");
838 }
839
840 #[cfg(feature = "try-runtime")]
841 fn try_state(now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
842 Self::do_try_state(now).map_err(Into::into)
843 }
844 }
845
846 #[pallet::event]
847 #[pallet::generate_deposit(pub(super) fn deposit_event)]
848 pub enum Event<T: Config> {
849 PhaseTransitioned {
852 from: Phase<T>,
854 to: Phase<T>,
856 },
857 UnexpectedTargetSnapshotFailed,
859 UnexpectedVoterSnapshotFailed,
861 UnexpectedPhaseTransitionOutOfWeight {
863 from: Phase<T>,
864 to: Phase<T>,
865 required: Weight,
866 had: Weight,
867 },
868 UnexpectedPhaseTransitionHalt { required: Weight, had: Weight },
870 }
871
872 #[pallet::error]
874 pub enum Error<T> {
875 Fallback,
877 UnexpectedPhase,
879 Snapshot,
881 }
882
883 #[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)]
885 pub enum CommonError {
886 EarlySubmission,
888 WrongRound,
890 WeakSubmission,
892 WrongPageCount,
894 WrongWinnerCount,
896 WrongFingerprint,
898 Snapshot,
900 }
901
902 #[pallet::storage]
909 #[pallet::getter(fn round)]
910 pub type Round<T: Config> = StorageValue<_, u32, ValueQuery>;
911
912 #[pallet::storage]
914 #[pallet::getter(fn current_phase)]
915 pub type CurrentPhase<T: Config> = StorageValue<_, Phase<T>, ValueQuery>;
916
917 pub(crate) struct Snapshot<T>(sp_std::marker::PhantomData<T>);
954 impl<T: Config> Snapshot<T> {
955 pub(crate) fn set_desired_targets(d: u32) {
957 DesiredTargets::<T>::insert(Self::round(), d);
958 }
959
960 pub(crate) fn set_targets(targets: BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>) {
961 let hash = Self::write_storage_with_pre_allocate(
962 &PagedTargetSnapshot::<T>::hashed_key_for(Self::round(), Pallet::<T>::msp()),
963 targets,
964 );
965 PagedTargetSnapshotHash::<T>::insert(Self::round(), Pallet::<T>::msp(), hash);
966 }
967
968 pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf<T::MinerConfig>) {
969 let hash = Self::write_storage_with_pre_allocate(
970 &PagedVoterSnapshot::<T>::hashed_key_for(Self::round(), page),
971 voters,
972 );
973 PagedVoterSnapshotHash::<T>::insert(Self::round(), page, hash);
974 }
975
976 pub(crate) fn kill() {
980 DesiredTargets::<T>::remove(Self::round());
981 clear_round_based_map!(PagedVoterSnapshot::<T>, Self::round());
982 clear_round_based_map!(PagedVoterSnapshotHash::<T>, Self::round());
983 clear_round_based_map!(PagedTargetSnapshot::<T>, Self::round());
984 clear_round_based_map!(PagedTargetSnapshotHash::<T>, Self::round());
985 }
986
987 pub(crate) fn desired_targets() -> Option<u32> {
989 DesiredTargets::<T>::get(Self::round())
990 }
991
992 pub(crate) fn voters(page: PageIndex) -> Option<VoterPageOf<T::MinerConfig>> {
993 PagedVoterSnapshot::<T>::get(Self::round(), page)
994 }
995
996 pub(crate) fn targets() -> Option<BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>> {
997 PagedTargetSnapshot::<T>::get(Self::round(), Pallet::<T>::msp())
999 }
1000
1001 pub fn fingerprint() -> T::Hash {
1008 let mut hashed_target_and_voters =
1009 Self::targets_hash().unwrap_or_default().as_ref().to_vec();
1010 let hashed_voters = (Pallet::<T>::msp()..=Pallet::<T>::lsp())
1011 .map(|i| PagedVoterSnapshotHash::<T>::get(Self::round(), i).unwrap_or_default())
1012 .flat_map(|hash| <T::Hash as AsRef<[u8]>>::as_ref(&hash).to_owned())
1013 .collect::<Vec<u8>>();
1014 hashed_target_and_voters.extend(hashed_voters);
1015 T::Hashing::hash(&hashed_target_and_voters)
1016 }
1017
1018 fn write_storage_with_pre_allocate<E: Encode>(key: &[u8], data: E) -> T::Hash {
1019 let size = data.encoded_size();
1020 let mut buffer = Vec::with_capacity(size);
1021 data.encode_to(&mut buffer);
1022
1023 let hash = T::Hashing::hash(&buffer);
1024
1025 debug_assert_eq!(buffer, data.encode());
1027 debug_assert!(buffer.len() == size && size == buffer.capacity());
1029 sp_io::storage::set(key, &buffer);
1030
1031 hash
1032 }
1033
1034 pub(crate) fn targets_hash() -> Option<T::Hash> {
1035 PagedTargetSnapshotHash::<T>::get(Self::round(), Pallet::<T>::msp())
1036 }
1037
1038 fn round() -> u32 {
1039 Pallet::<T>::round()
1040 }
1041 }
1042
1043 #[allow(unused)]
1044 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
1045 impl<T: Config> Snapshot<T> {
1046 pub(crate) fn ensure_target_snapshot(exists: bool) -> Result<(), &'static str> {
1048 ensure!(exists ^ Self::desired_targets().is_none(), "desired target mismatch");
1049 ensure!(exists ^ Self::targets().is_none(), "targets mismatch");
1050 ensure!(exists ^ Self::targets_hash().is_none(), "targets hash mismatch");
1051
1052 if let Some(targets) = Self::targets() {
1054 let hash = Self::targets_hash().expect("must exist; qed");
1055 ensure!(hash == T::Hashing::hash(&targets.encode()), "targets hash mismatch");
1056 }
1057 Ok(())
1058 }
1059
1060 pub(crate) fn ensure_voter_snapshot(
1062 exists: bool,
1063 mut up_to_page: PageIndex,
1064 ) -> Result<(), &'static str> {
1065 up_to_page = up_to_page.min(T::Pages::get());
1066 let mut sum_existing_voters: usize = 0;
1068 for p in (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp())
1069 .rev()
1070 .take(up_to_page as usize)
1071 {
1072 ensure!(
1073 (exists ^ Self::voters(p).is_none()) &&
1074 (exists ^ Self::voters_hash(p).is_none()),
1075 "voter page existence mismatch"
1076 );
1077
1078 if let Some(voters_page) = Self::voters(p) {
1079 sum_existing_voters = sum_existing_voters.saturating_add(voters_page.len());
1080 let hash = Self::voters_hash(p).expect("must exist; qed");
1081 ensure!(hash == T::Hashing::hash(&voters_page.encode()), "voter hash mismatch");
1082 }
1083 }
1084
1085 for p in (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp())
1087 .take((T::Pages::get() - up_to_page) as usize)
1088 {
1089 ensure!(
1090 (exists ^ Self::voters(p).is_some()) &&
1091 (exists ^ Self::voters_hash(p).is_some()),
1092 "voter page non-existence mismatch"
1093 );
1094 }
1095 Ok(())
1096 }
1097
1098 pub(crate) fn ensure_snapshot(
1099 exists: bool,
1100 mut up_to_page: PageIndex,
1101 ) -> Result<(), &'static str> {
1102 Self::ensure_target_snapshot(exists)
1103 .and_then(|_| Self::ensure_voter_snapshot(exists, up_to_page))
1104 }
1105
1106 pub(crate) fn ensure_full_snapshot() -> Result<(), &'static str> {
1107 ensure!(Self::desired_targets().is_some(), "desired target mismatch");
1109 ensure!(Self::targets_hash().is_some(), "targets hash mismatch");
1110 ensure!(
1111 Self::targets_decode_len().unwrap_or_default() as u32 ==
1112 T::TargetSnapshotPerBlock::get(),
1113 "targets decode length mismatch"
1114 );
1115
1116 for p in crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp() {
1118 ensure!(
1119 Self::voters_hash(p).is_some() &&
1120 Self::voters_decode_len(p).unwrap_or_default() as u32 ==
1121 T::VoterSnapshotPerBlock::get(),
1122 "voter page existence mismatch"
1123 );
1124 }
1125
1126 Ok(())
1127 }
1128
1129 pub(crate) fn voters_decode_len(page: PageIndex) -> Option<usize> {
1130 PagedVoterSnapshot::<T>::decode_len(Self::round(), page)
1131 }
1132
1133 pub(crate) fn targets_decode_len() -> Option<usize> {
1134 PagedTargetSnapshot::<T>::decode_len(Self::round(), Pallet::<T>::msp())
1135 }
1136
1137 pub(crate) fn voters_hash(page: PageIndex) -> Option<T::Hash> {
1138 PagedVoterSnapshotHash::<T>::get(Self::round(), page)
1139 }
1140
1141 pub(crate) fn sanity_check() -> Result<(), &'static str> {
1142 let phase = Pallet::<T>::current_phase();
1145 let _ = match phase {
1146 Phase::Off => Self::ensure_snapshot(false, T::Pages::get()),
1148
1149 Phase::Snapshot(p) if p == T::Pages::get() => {
1151 Self::ensure_snapshot(false, T::Pages::get())
1152 },
1153 Phase::Snapshot(p) if p < T::Pages::get() && p > 0 => {
1155 Self::ensure_snapshot(true, T::Pages::get() - p - 1)
1156 },
1157 Phase::Snapshot(_) => Ok(()),
1159
1160 Phase::Emergency |
1162 Phase::Signed(_) |
1163 Phase::SignedValidation(_) |
1164 Phase::Export(_) |
1165 Phase::Done |
1166 Phase::Unsigned(_) => Self::ensure_snapshot(true, T::Pages::get()),
1167 }?;
1168
1169 Ok(())
1170 }
1171 }
1172
1173 #[cfg(test)]
1174 impl<T: Config> Snapshot<T> {
1175 pub(crate) fn voter_pages() -> PageIndex {
1176 use sp_runtime::SaturatedConversion;
1177 PagedVoterSnapshot::<T>::iter().count().saturated_into::<PageIndex>()
1178 }
1179
1180 pub(crate) fn target_pages() -> PageIndex {
1181 use sp_runtime::SaturatedConversion;
1182 PagedTargetSnapshot::<T>::iter().count().saturated_into::<PageIndex>()
1183 }
1184
1185 pub(crate) fn voters_iter_flattened() -> impl Iterator<Item = VoterOf<T::MinerConfig>> {
1186 let key_range =
1187 (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp()).collect::<Vec<_>>();
1188 key_range
1189 .into_iter()
1190 .flat_map(|k| PagedVoterSnapshot::<T>::get(Self::round(), k).unwrap_or_default())
1191 }
1192
1193 pub(crate) fn remove_voter_page(page: PageIndex) {
1194 PagedVoterSnapshot::<T>::remove(Self::round(), page);
1195 }
1196
1197 pub(crate) fn kill_desired_targets() {
1198 DesiredTargets::<T>::remove(Self::round());
1199 }
1200
1201 pub(crate) fn remove_target_page() {
1202 PagedTargetSnapshot::<T>::remove(Self::round(), Pallet::<T>::msp());
1203 }
1204
1205 pub(crate) fn remove_target(at: usize) {
1206 PagedTargetSnapshot::<T>::mutate(
1207 Self::round(),
1208 crate::Pallet::<T>::msp(),
1209 |maybe_targets| {
1210 if let Some(targets) = maybe_targets {
1211 targets.remove(at);
1212 PagedTargetSnapshotHash::<T>::insert(
1214 Self::round(),
1215 crate::Pallet::<T>::msp(),
1216 T::Hashing::hash(&targets.encode()),
1217 )
1218 } else {
1219 unreachable!();
1220 }
1221 },
1222 )
1223 }
1224 }
1225
1226 #[pallet::storage]
1228 pub type DesiredTargets<T> = StorageMap<_, Twox64Concat, u32, u32>;
1229 #[pallet::storage]
1231 pub type PagedVoterSnapshot<T: Config> = StorageDoubleMap<
1232 _,
1233 Twox64Concat,
1234 u32,
1235 Twox64Concat,
1236 PageIndex,
1237 VoterPageOf<T::MinerConfig>,
1238 >;
1239 #[pallet::storage]
1243 pub type PagedVoterSnapshotHash<T: Config> =
1244 StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, PageIndex, T::Hash>;
1245 #[pallet::storage]
1249 pub type PagedTargetSnapshot<T: Config> = StorageDoubleMap<
1250 _,
1251 Twox64Concat,
1252 u32,
1253 Twox64Concat,
1254 PageIndex,
1255 BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
1256 >;
1257 #[pallet::storage]
1261 pub type PagedTargetSnapshotHash<T: Config> =
1262 StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, PageIndex, T::Hash>;
1263
1264 #[pallet::pallet]
1265 pub struct Pallet<T>(PhantomData<T>);
1266}
1267
1268impl<T: Config> Pallet<T> {
1269 fn msp() -> PageIndex {
1273 T::Pages::get().checked_sub(1).defensive_unwrap_or_default()
1274 }
1275
1276 fn lsp() -> PageIndex {
1280 Zero::zero()
1281 }
1282
1283 fn per_block_exec(current_phase: Phase<T>) -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
1349 type ExecuteFn = Box<dyn Fn(&mut WeightMeter)>;
1350 let next_phase = current_phase.next();
1351
1352 let just_next_phase: (Weight, ExecuteFn) = (
1353 T::WeightInfo::per_block_nothing(),
1354 Box::new(move |_| {
1355 Self::phase_transition(next_phase);
1357 }),
1358 );
1359
1360 match current_phase {
1361 Phase::Snapshot(x) if x == T::Pages::get() => {
1362 let weight = T::WeightInfo::per_block_snapshot_msp();
1364 let exec: ExecuteFn = Box::new(move |meter: &mut WeightMeter| {
1365 Self::create_targets_snapshot();
1366 Self::phase_transition(next_phase);
1367 meter.consume(weight)
1368 });
1369 (weight, exec)
1370 },
1371
1372 Phase::Snapshot(x) => {
1373 let weight = T::WeightInfo::per_block_snapshot_rest();
1375 let exec: ExecuteFn = Box::new(move |meter: &mut WeightMeter| {
1376 Self::create_voters_snapshot_paged(x);
1377 Self::phase_transition(next_phase);
1378 meter.consume(weight)
1379 });
1380 (weight, exec)
1381 },
1382 Phase::Signed(x) => {
1383 if x.is_zero() && T::Signed::has_leader(Self::round()) {
1386 let weight = T::WeightInfo::per_block_start_signed_validation();
1387 let exec: ExecuteFn = Box::new(move |meter: &mut WeightMeter| {
1388 let _ = T::Verifier::start().defensive();
1391 Self::phase_transition(next_phase);
1392 meter.consume(weight)
1393 });
1394 (weight, exec)
1395 } else {
1396 just_next_phase
1397 }
1398 },
1399 Phase::SignedValidation(_) |
1400 Phase::Unsigned(_) |
1401 Phase::Off |
1402 Phase::Emergency |
1403 Phase::Done |
1404 Phase::Export(_) => just_next_phase,
1405 }
1406 }
1407
1408 pub fn msp_range_for(length: usize) -> Vec<PageIndex> {
1414 (Self::lsp()..Self::msp() + 1).rev().take(length).rev().collect::<Vec<_>>()
1415 }
1416
1417 pub(crate) fn phase_transition(to: Phase<T>) {
1418 let from = Self::current_phase();
1419 if from == to {
1420 return;
1421 }
1422 use sp_std::mem::discriminant;
1423 if discriminant(&from) != discriminant(&to) {
1424 log!(debug, "transitioning phase from {:?} to {:?}", from, to);
1425 Self::deposit_event(Event::PhaseTransitioned { from, to });
1426 } else {
1427 log!(trace, "transitioning phase from {:?} to {:?}", from, to);
1428 }
1429 <CurrentPhase<T>>::put(to);
1430 }
1431
1432 pub(crate) fn snapshot_independent_checks(
1444 paged_solution: &PagedRawSolution<T::MinerConfig>,
1445 maybe_snapshot_fingerprint: Option<T::Hash>,
1446 ) -> Result<(), CommonError> {
1447 ensure!(Self::round() == paged_solution.round, CommonError::WrongRound);
1454
1455 ensure!(
1457 <T::Verifier as Verifier>::ensure_claimed_score_improves(paged_solution.score),
1458 CommonError::WeakSubmission,
1459 );
1460
1461 ensure!(
1463 paged_solution.solution_pages.len().saturated_into::<PageIndex>() <= T::Pages::get(),
1464 CommonError::WrongPageCount
1465 );
1466
1467 if let Some(desired_targets) = Snapshot::<T>::desired_targets() {
1469 ensure!(
1470 desired_targets == paged_solution.winner_count_single_page_target_snapshot() as u32,
1471 CommonError::WrongWinnerCount
1472 )
1473 }
1474
1475 ensure!(
1477 maybe_snapshot_fingerprint
1478 .map_or(true, |snapshot_fingerprint| Snapshot::<T>::fingerprint() ==
1479 snapshot_fingerprint),
1480 CommonError::WrongFingerprint
1481 );
1482
1483 Ok(())
1484 }
1485
1486 pub(crate) fn create_targets_snapshot() {
1491 let desired_targets = match T::DataProvider::desired_targets() {
1493 Ok(targets) => targets,
1494 Err(e) => {
1495 Self::deposit_event(Event::UnexpectedTargetSnapshotFailed);
1496 defensive!("Failed to get desired targets: {:?}", e);
1497 return;
1498 },
1499 };
1500 Snapshot::<T>::set_desired_targets(desired_targets);
1501
1502 let count = T::TargetSnapshotPerBlock::get();
1503 let bounds = DataProviderBounds { count: Some(count.into()), size: None };
1504 let targets: BoundedVec<_, T::TargetSnapshotPerBlock> =
1505 match T::DataProvider::electable_targets(bounds, 0)
1506 .and_then(|v| v.try_into().map_err(|_| "try-into failed"))
1507 {
1508 Ok(targets) => targets,
1509 Err(e) => {
1510 Self::deposit_event(Event::UnexpectedTargetSnapshotFailed);
1511 defensive!("Failed to create target snapshot: {:?}", e);
1512 return;
1513 },
1514 };
1515
1516 let count = targets.len() as u32;
1517 log!(debug, "created target snapshot with {} targets.", count);
1518 Snapshot::<T>::set_targets(targets);
1519 }
1520
1521 pub(crate) fn create_voters_snapshot_paged(remaining: PageIndex) {
1526 let count = T::VoterSnapshotPerBlock::get();
1527 let bounds = DataProviderBounds { count: Some(count.into()), size: None };
1528 let voters: BoundedVec<_, T::VoterSnapshotPerBlock> =
1529 match T::DataProvider::electing_voters(bounds, remaining)
1530 .and_then(|v| v.try_into().map_err(|_| "try-into failed"))
1531 {
1532 Ok(voters) => voters,
1533 Err(e) => {
1534 Self::deposit_event(Event::UnexpectedVoterSnapshotFailed);
1535 defensive!("Failed to create voter snapshot: {:?}", e);
1536 return;
1537 },
1538 };
1539
1540 let count = voters.len() as u32;
1541 Snapshot::<T>::set_voters(remaining, voters);
1542 log!(debug, "created voter snapshot with {} voters, {} remaining.", count, remaining);
1543 }
1544
1545 pub(crate) fn rotate_round() {
1551 <Round<T>>::mutate(|r| {
1553 T::OnRoundRotation::on_round_rotation(*r);
1555 *r += 1
1556 });
1557
1558 Self::phase_transition(Phase::Off);
1560 }
1561
1562 fn fallback_for_page(page: PageIndex) -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
1568 use frame_election_provider_support::InstantElectionProvider;
1569 let (voters, targets, desired_targets) = if T::Fallback::bother() {
1570 (
1571 Snapshot::<T>::voters(page).ok_or(ElectionError::Other("snapshot!"))?,
1572 Snapshot::<T>::targets().ok_or(ElectionError::Other("snapshot!"))?,
1573 Snapshot::<T>::desired_targets().ok_or(ElectionError::Other("snapshot!"))?,
1574 )
1575 } else {
1576 (Default::default(), Default::default(), Default::default())
1577 };
1578 T::Fallback::instant_elect(voters.into_inner(), targets.into_inner(), desired_targets)
1579 .map_err(|fe| ElectionError::Fallback(fe))
1580 }
1581
1582 pub fn average_election_duration() -> u32 {
1584 let signed: u32 = T::SignedPhase::get().saturated_into();
1585 let unsigned: u32 = T::UnsignedPhase::get().saturated_into();
1586 let signed_validation: u32 = T::SignedValidationPhase::get().saturated_into();
1587 let snapshot = T::Pages::get();
1588
1589 let _export = T::Pages::get();
1591
1592 snapshot + signed + signed_validation + unsigned
1593 }
1594
1595 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
1596 pub(crate) fn do_try_state(_: BlockNumberFor<T>) -> Result<(), &'static str> {
1597 Snapshot::<T>::sanity_check()
1598 }
1599}
1600
1601#[cfg(feature = "std")]
1602impl<T: Config> Pallet<T> {
1603 fn analyze_weight(
1604 op_name: &str,
1605 op_weight: Weight,
1606 limit_weight: Weight,
1607 maybe_max_ratio: Option<sp_runtime::Percent>,
1608 maybe_max_warn_ratio: Option<sp_runtime::Percent>,
1609 ) {
1610 use frame_support::weights::constants::{
1611 WEIGHT_PROOF_SIZE_PER_KB, WEIGHT_REF_TIME_PER_MILLIS,
1612 };
1613
1614 let ref_time_ms = op_weight.ref_time() / WEIGHT_REF_TIME_PER_MILLIS;
1615 let ref_time_ratio =
1616 sp_runtime::Percent::from_rational(op_weight.ref_time(), limit_weight.ref_time());
1617 let proof_size_kb = op_weight.proof_size() / WEIGHT_PROOF_SIZE_PER_KB;
1618 let proof_size_ratio =
1619 sp_runtime::Percent::from_rational(op_weight.proof_size(), limit_weight.proof_size());
1620 let limit_ms = limit_weight.ref_time() / WEIGHT_REF_TIME_PER_MILLIS;
1621 let limit_kb = limit_weight.proof_size() / WEIGHT_PROOF_SIZE_PER_KB;
1622 log::info!(
1623 target: crate::LOG_PREFIX,
1624 "weight of {op_name:?} is: ref-time: {ref_time_ms}ms, {ref_time_ratio:?} of total, proof-size: {proof_size_kb}KiB, {proof_size_ratio:?} of total (total: {limit_ms}ms, {limit_kb}KiB)",
1625 );
1626
1627 if let Some(max_ratio) = maybe_max_ratio {
1628 assert!(ref_time_ratio <= max_ratio && proof_size_ratio <= max_ratio,)
1629 }
1630 if let Some(warn_ratio) = maybe_max_warn_ratio {
1631 if ref_time_ratio > warn_ratio || proof_size_ratio > warn_ratio {
1632 log::warn!(
1633 target: crate::LOG_PREFIX,
1634 "weight of {op_name:?} is above {warn_ratio:?} of the block limit",
1635 );
1636 }
1637 }
1638 }
1639
1640 pub fn check_all_weights(
1660 limit_weight: Weight,
1661 maybe_max_ratio: Option<sp_runtime::Percent>,
1662 maybe_max_warn_ratio: Option<sp_runtime::Percent>,
1663 ) where
1664 T: crate::verifier::Config + crate::signed::Config + crate::unsigned::Config,
1665 {
1666 use crate::weights::traits::{
1667 pallet_election_provider_multi_block_signed::WeightInfo as _,
1668 pallet_election_provider_multi_block_unsigned::WeightInfo as _,
1669 pallet_election_provider_multi_block_verifier::WeightInfo as _,
1670 };
1671
1672 Self::analyze_weight(
1674 "snapshot_msp",
1675 <T as Config>::WeightInfo::per_block_snapshot_msp(),
1676 limit_weight,
1677 maybe_max_ratio,
1678 maybe_max_warn_ratio,
1679 );
1680
1681 Self::analyze_weight(
1682 "snapshot_rest",
1683 <T as Config>::WeightInfo::per_block_snapshot_rest(),
1684 limit_weight,
1685 maybe_max_ratio,
1686 maybe_max_warn_ratio,
1687 );
1688
1689 Self::analyze_weight(
1691 "signed_clear_all_pages",
1692 <T as crate::signed::Config>::WeightInfo::clear_old_round_data(T::Pages::get()),
1693 limit_weight,
1694 maybe_max_ratio,
1695 maybe_max_warn_ratio,
1696 );
1697 Self::analyze_weight(
1698 "signed_submit_single_pages",
1699 <T as crate::signed::Config>::WeightInfo::submit_page(),
1700 limit_weight,
1701 maybe_max_ratio,
1702 maybe_max_warn_ratio,
1703 );
1704
1705 Self::analyze_weight(
1707 "verify unsigned solution",
1708 <T as crate::unsigned::Config>::WeightInfo::submit_unsigned(),
1709 limit_weight,
1710 maybe_max_ratio,
1711 maybe_max_warn_ratio,
1712 );
1713
1714 Self::analyze_weight(
1716 "verifier valid terminal",
1717 <T as crate::verifier::Config>::WeightInfo::verification_valid_terminal(),
1718 limit_weight,
1719 maybe_max_ratio,
1720 maybe_max_warn_ratio,
1721 );
1722 Self::analyze_weight(
1723 "verifier invalid terminal",
1724 <T as crate::verifier::Config>::WeightInfo::verification_invalid_terminal(),
1725 limit_weight,
1726 maybe_max_ratio,
1727 maybe_max_warn_ratio,
1728 );
1729
1730 Self::analyze_weight(
1731 "verifier valid non terminal",
1732 <T as crate::verifier::Config>::WeightInfo::verification_valid_non_terminal(),
1733 limit_weight,
1734 maybe_max_ratio,
1735 maybe_max_warn_ratio,
1736 );
1737
1738 Self::analyze_weight(
1739 "verifier invalid non terminal",
1740 <T as crate::verifier::Config>::WeightInfo::verification_invalid_non_terminal(
1741 T::Pages::get(),
1742 ),
1743 limit_weight,
1744 maybe_max_ratio,
1745 maybe_max_warn_ratio,
1746 );
1747
1748 Self::analyze_weight(
1750 "export non-terminal",
1751 <T as Config>::WeightInfo::export_non_terminal(),
1752 limit_weight,
1753 maybe_max_ratio,
1754 maybe_max_warn_ratio,
1755 );
1756
1757 Self::analyze_weight(
1758 "export terminal",
1759 <T as Config>::WeightInfo::export_terminal(),
1760 limit_weight,
1761 maybe_max_ratio,
1762 maybe_max_warn_ratio,
1763 );
1764 }
1765}
1766
1767#[allow(unused)]
1768#[cfg(any(feature = "runtime-benchmarks", test))]
1769impl<T> Pallet<T>
1771where
1772 T: Config + crate::signed::Config + crate::unsigned::Config + crate::verifier::Config,
1773 BlockNumberFor<T>: From<u32>,
1774{
1775 pub(crate) fn roll_until_matches(criteria: impl FnOnce() -> bool + Copy) {
1777 loop {
1778 Self::roll_next(false);
1779 if criteria() {
1780 break;
1781 }
1782 }
1783 }
1784
1785 pub(crate) fn roll_until_before_matches(criteria: impl FnOnce() -> bool + Copy) {
1787 use frame_support::storage::TransactionOutcome;
1788 loop {
1789 let should_break = frame_support::storage::with_transaction(
1790 || -> TransactionOutcome<Result<_, DispatchError>> {
1791 Pallet::<T>::roll_next(false);
1792 if criteria() {
1793 TransactionOutcome::Rollback(Ok(true))
1794 } else {
1795 TransactionOutcome::Commit(Ok(false))
1796 }
1797 },
1798 )
1799 .unwrap();
1800
1801 if should_break {
1802 break;
1803 }
1804 }
1805 }
1806
1807 pub(crate) fn roll_to_signed_and_mine_full_solution() -> PagedRawSolution<T::MinerConfig> {
1808 use unsigned::miner::OffchainWorkerMiner;
1809 Self::roll_to_signed_and_mine_solution(T::Pages::get())
1810 }
1811
1812 pub(crate) fn roll_to_signed_and_mine_solution(
1813 pages: PageIndex,
1814 ) -> PagedRawSolution<T::MinerConfig> {
1815 use unsigned::miner::OffchainWorkerMiner;
1816 Self::roll_until_matches(|| Self::current_phase().is_signed());
1817 crate::Snapshot::<T>::ensure_full_snapshot().expect("Snapshot is not full");
1819 OffchainWorkerMiner::<T>::mine_solution(pages, false).expect("mine_solution failed")
1820 }
1821
1822 pub(crate) fn submit_full_solution(
1823 PagedRawSolution { score, solution_pages, .. }: PagedRawSolution<T::MinerConfig>,
1824 ) -> DispatchResultWithPostInfo {
1825 use frame_system::RawOrigin;
1826 use sp_std::boxed::Box;
1827 use types::Pagify;
1828
1829 let alice = crate::Pallet::<T>::funded_account("alice", 0);
1831 signed::Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
1832
1833 for (index, page) in solution_pages.pagify(T::Pages::get()) {
1835 signed::Pallet::<T>::submit_page(
1836 RawOrigin::Signed(alice.clone()).into(),
1837 index,
1838 Some(Box::new(page.clone())),
1839 )
1840 .inspect_err(|&e| {
1841 log!(error, "submit_page {:?} failed: {:?}", page, e);
1842 })?;
1843 }
1844
1845 Ok(().into())
1846 }
1847
1848 pub(crate) fn roll_to_signed_and_submit_full_solution() -> DispatchResultWithPostInfo {
1849 Self::submit_full_solution(Self::roll_to_signed_and_mine_full_solution())
1850 }
1851
1852 fn funded_account(seed: &'static str, index: u32) -> T::AccountId {
1853 use frame_benchmarking::whitelist;
1854 use frame_support::traits::fungible::{Inspect, Mutate};
1855 let who: T::AccountId = frame_benchmarking::account(seed, index, 777);
1856 whitelist!(who);
1857
1858 let worst_case_deposit = {
1864 let max_queue_size = T::MaxSubmissions::get() as usize;
1865 let base = T::DepositBase::calculate_base_deposit(max_queue_size);
1866 let pages =
1867 T::DepositPerPage::calculate_page_deposit(max_queue_size, T::Pages::get() as usize);
1868 base.saturating_add(pages)
1869 };
1870
1871 let min_balance = T::Currency::minimum_balance();
1874 let num_operations = 1u32.saturating_add(T::Pages::get()); let tx_fee_buffer = (min_balance / 100u32.into()).saturating_mul(num_operations.into());
1876
1877 let total_needed = worst_case_deposit
1878 .saturating_add(tx_fee_buffer)
1879 .saturating_add(T::Currency::minimum_balance());
1880
1881 T::Currency::mint_into(&who, total_needed).unwrap();
1882 who
1883 }
1884
1885 pub(crate) fn roll_to(n: BlockNumberFor<T>, try_state: bool) {
1887 let now = frame_system::Pallet::<T>::block_number();
1888 assert!(n > now, "cannot roll to current or past block");
1889 let one: BlockNumberFor<T> = 1u32.into();
1890 let mut i = now + one;
1891 while i <= n {
1892 frame_system::BlockWeight::<T>::kill();
1894
1895 frame_system::Pallet::<T>::set_block_number(i);
1896 let mut meter = frame_system::Pallet::<T>::remaining_block_weight();
1897 Pallet::<T>::on_poll(i, &mut meter);
1898
1899 frame_system::Pallet::<T>::register_extra_weight_unchecked(
1901 meter.consumed(),
1902 DispatchClass::Mandatory,
1903 );
1904
1905 if try_state {
1907 Pallet::<T>::do_try_state(i).unwrap();
1908 verifier::Pallet::<T>::do_try_state(i).unwrap();
1909 unsigned::Pallet::<T>::do_try_state(i).unwrap();
1910 signed::Pallet::<T>::do_try_state(i).unwrap();
1911 }
1912
1913 i += one;
1914 }
1915 }
1916
1917 pub(crate) fn roll_next(try_state: bool) {
1919 Self::roll_to(frame_system::Pallet::<T>::block_number() + 1u32.into(), try_state);
1920 }
1921}
1922
1923impl<T: Config> ElectionProvider for Pallet<T> {
1924 type AccountId = T::AccountId;
1925 type BlockNumber = BlockNumberFor<T>;
1926 type Error = ElectionError<T>;
1927 type DataProvider = T::DataProvider;
1928 type Pages = T::Pages;
1929 type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
1930 type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
1931 type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
1932
1933 fn elect(remaining: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
1934 match Self::status() {
1935 Ok(_) => (),
1937 Err(_) => return Err(ElectionError::NotOngoing),
1938 }
1939
1940 let current_phase = CurrentPhase::<T>::get();
1941 if let Phase::Export(expected) = current_phase {
1942 ensure!(expected == remaining, ElectionError::OutOfOrder);
1943 }
1944
1945 let result = T::Verifier::get_queued_solution_page(remaining)
1946 .ok_or(ElectionError::SupportPageNotAvailable)
1947 .or_else(|err: ElectionError<T>| {
1948 log!(
1949 debug,
1950 "primary election for page {} failed due to: {:?}, trying fallback",
1951 remaining,
1952 err,
1953 );
1954 Self::fallback_for_page(remaining)
1955 })
1956 .map_err(|err| {
1957 log!(debug, "fallback also ({:?}) failed for page {:?}", err, remaining);
1962 err
1963 })
1964 .map(|supports| {
1965 supports.into()
1967 });
1968
1969 if CurrentPhase::<T>::get().is_emergency() && result.is_err() {
1971 log!(error, "Emergency phase triggered, halting the election.");
1972 } else {
1973 if remaining.is_zero() {
1974 Self::rotate_round()
1975 } else {
1976 Self::phase_transition(Phase::Export(remaining - 1))
1977 }
1978 }
1979
1980 result
1981 }
1982
1983 fn start() -> Result<(), Self::Error> {
1984 match Self::status() {
1985 Err(()) => (),
1986 Ok(_) => return Err(ElectionError::Ongoing),
1987 }
1988
1989 Self::phase_transition(Phase::<T>::start_phase());
1990 Ok(())
1991 }
1992
1993 fn duration() -> Self::BlockNumber {
1994 Self::average_election_duration().into()
1995 }
1996
1997 fn status() -> Result<Option<Weight>, ()> {
1998 match <CurrentPhase<T>>::get() {
1999 Phase::Off => Err(()),
2001
2002 Phase::Signed(_) |
2004 Phase::SignedValidation(_) |
2005 Phase::Unsigned(_) |
2006 Phase::Snapshot(_) |
2007 Phase::Emergency => Ok(None),
2008
2009 Phase::Done => Ok(Some(T::WeightInfo::export_non_terminal())),
2011 Phase::Export(p) => {
2012 if p.is_zero() {
2013 Ok(Some(T::WeightInfo::export_terminal()))
2014 } else {
2015 Ok(Some(T::WeightInfo::export_non_terminal()))
2016 }
2017 },
2018 }
2019 }
2020
2021 #[cfg(feature = "runtime-benchmarks")]
2022 fn asap() {
2023 Self::create_targets_snapshot();
2025 for p in (Self::lsp()..=Self::msp()).rev() {
2026 Self::create_voters_snapshot_paged(p)
2027 }
2028 }
2029}
2030
2031#[cfg(test)]
2032mod phase_rotation {
2033 use super::{Event, *};
2034 use crate::{mock::*, verifier::Status, Phase};
2035 use frame_election_provider_support::ElectionProvider;
2036 use frame_support::assert_ok;
2037
2038 #[test]
2039 fn single_page() {
2040 ExtBuilder::full()
2041 .pages(1)
2042 .election_start(13)
2043 .fallback_mode(FallbackModes::Onchain)
2044 .build_and_execute(|| {
2045 assert_eq!(System::block_number(), 0);
2050 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2051 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 1));
2052 assert_eq!(MultiBlock::round(), 0);
2053
2054 roll_to(4);
2055 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2056 assert_eq!(MultiBlock::round(), 0);
2057
2058 roll_to(13);
2059 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2060 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
2061
2062 roll_to(14);
2063 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2064 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
2065
2066 roll_to(15);
2067 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2068 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2069 assert_eq!(MultiBlock::round(), 0);
2070
2071 assert_eq!(
2072 multi_block_events_since_last_call(),
2073 vec![
2074 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) },
2075 Event::PhaseTransitioned {
2076 from: Phase::Snapshot(0),
2077 to: Phase::Signed(SignedPhase::get() - 1)
2078 }
2079 ]
2080 );
2081
2082 roll_to(19);
2083 assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
2084 assert_eq!(MultiBlock::round(), 0);
2085
2086 roll_to(20);
2087 assert_eq!(
2088 MultiBlock::current_phase(),
2089 Phase::SignedValidation(SignedValidationPhase::get())
2090 );
2091 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2092 assert_eq!(MultiBlock::round(), 0);
2093
2094 assert_eq!(
2095 multi_block_events_since_last_call(),
2096 vec![Event::PhaseTransitioned {
2097 from: Phase::Signed(0),
2098 to: Phase::SignedValidation(SignedValidationPhase::get())
2099 }],
2100 );
2101
2102 roll_to(26);
2103 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2104 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2105 assert_eq!(MultiBlock::round(), 0);
2106
2107 roll_to(27);
2108 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2109 assert_eq!(
2110 multi_block_events_since_last_call(),
2111 vec![Event::PhaseTransitioned {
2112 from: Phase::SignedValidation(0),
2113 to: Phase::Unsigned(UnsignedPhase::get() - 1)
2114 }],
2115 );
2116
2117 roll_to(31);
2118 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2119
2120 roll_to(32);
2122 assert!(MultiBlock::current_phase().is_done());
2123
2124 roll_to(33);
2126 assert!(MultiBlock::current_phase().is_done());
2127
2128 roll_to(34);
2130 assert!(MultiBlock::current_phase().is_done());
2131 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2132
2133 MultiBlock::elect(0).unwrap();
2134
2135 assert!(MultiBlock::current_phase().is_off());
2136 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 1));
2137 assert_eq!(MultiBlock::round(), 1);
2138
2139 roll_to(42);
2140 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2141 })
2142 }
2143
2144 #[test]
2145 fn multi_page_2() {
2146 ExtBuilder::full()
2147 .pages(2)
2148 .fallback_mode(FallbackModes::Onchain)
2149 .election_start(12)
2150 .build_and_execute(|| {
2151 assert_eq!(System::block_number(), 0);
2156 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2157 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
2158 assert_eq!(MultiBlock::round(), 0);
2159
2160 roll_to(4);
2161 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2162 assert_eq!(MultiBlock::round(), 0);
2163
2164 roll_to(11);
2165 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2166 assert_eq!(MultiBlock::round(), 0);
2167
2168 roll_to(12);
2169 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2170 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
2171
2172 roll_to(13);
2173 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2174 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
2175
2176 roll_to(14);
2177 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2178 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2179
2180 roll_to(15);
2181 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2182 assert_eq!(MultiBlock::round(), 0);
2183 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2184
2185 assert_eq!(
2186 multi_block_events_since_last_call(),
2187 vec![
2188 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) },
2189 Event::PhaseTransitioned {
2190 from: Phase::Snapshot(0),
2191 to: Phase::Signed(SignedPhase::get() - 1)
2192 }
2193 ]
2194 );
2195
2196 roll_to(19);
2197 assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
2198 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2199 assert_eq!(MultiBlock::round(), 0);
2200
2201 roll_to(20);
2202 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2203 assert_eq!(MultiBlock::round(), 0);
2204 assert_eq!(
2205 MultiBlock::current_phase(),
2206 Phase::SignedValidation(SignedValidationPhase::get())
2207 );
2208
2209 assert_eq!(
2210 multi_block_events_since_last_call(),
2211 vec![Event::PhaseTransitioned {
2212 from: Phase::Signed(0),
2213 to: Phase::SignedValidation(SignedValidationPhase::get())
2214 }],
2215 );
2216
2217 roll_to(26);
2218 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2219 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2220 assert_eq!(MultiBlock::round(), 0);
2221
2222 roll_to(27);
2223 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2224 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2225 assert_eq!(MultiBlock::round(), 0);
2226
2227 assert_eq!(
2228 multi_block_events_since_last_call(),
2229 vec![Event::PhaseTransitioned {
2230 from: Phase::SignedValidation(0),
2231 to: Phase::Unsigned(UnsignedPhase::get() - 1)
2232 }],
2233 );
2234
2235 roll_to(31);
2236 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2237 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2238
2239 roll_to(32);
2240 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2241 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2242
2243 roll_to(33);
2245 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2246
2247 MultiBlock::elect(0).unwrap();
2249 assert!(MultiBlock::current_phase().is_off());
2250
2251 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
2253 assert_eq!(MultiBlock::round(), 1);
2254 })
2255 }
2256
2257 #[test]
2258 fn multi_page_3() {
2259 ExtBuilder::full()
2260 .pages(3)
2261 .fallback_mode(FallbackModes::Onchain)
2262 .build_and_execute(|| {
2263 assert_eq!(System::block_number(), 0);
2268 assert!(MultiBlock::current_phase().is_off());
2269 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
2270 assert_eq!(MultiBlock::round(), 0);
2271
2272 roll_to(10);
2273 assert!(MultiBlock::current_phase().is_off());
2274 assert_eq!(MultiBlock::round(), 0);
2275
2276 roll_to(11);
2277 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2278 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
2280
2281 roll_to(12);
2282 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2283 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
2284
2285 roll_to(13);
2286 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2287 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2288
2289 roll_to(14);
2290 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2291 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2292
2293 roll_to(15);
2294 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, Pages::get()));
2295 assert_eq!(MultiBlock::current_phase(), Phase::Signed(4));
2296 assert_eq!(
2297 multi_block_events_since_last_call(),
2298 vec![
2299 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2300 Event::PhaseTransitioned {
2301 from: Phase::Snapshot(0),
2302 to: Phase::Signed(SignedPhase::get() - 1)
2303 }
2304 ]
2305 );
2306 assert_eq!(MultiBlock::round(), 0);
2307
2308 roll_to(19);
2309 assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
2310 assert_eq!(MultiBlock::round(), 0);
2311
2312 roll_to(20);
2313 assert_eq!(
2314 MultiBlock::current_phase(),
2315 Phase::SignedValidation(SignedValidationPhase::get())
2316 );
2317 assert_eq!(
2318 multi_block_events_since_last_call(),
2319 vec![Event::PhaseTransitioned {
2320 from: Phase::Signed(0),
2321 to: Phase::SignedValidation(SignedValidationPhase::get())
2322 }]
2323 );
2324
2325 roll_to(26);
2326 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2327 assert_eq!(MultiBlock::round(), 0);
2328
2329 roll_to(27);
2330 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2331 assert_eq!(
2332 multi_block_events_since_last_call(),
2333 vec![Event::PhaseTransitioned {
2334 from: Phase::SignedValidation(0),
2335 to: Phase::Unsigned(UnsignedPhase::get() - 1)
2336 }]
2337 );
2338
2339 roll_to(31);
2340 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2341
2342 roll_to(32);
2343 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2344
2345 roll_to(33);
2347 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2348
2349 MultiBlock::elect(0).unwrap();
2350 assert!(MultiBlock::current_phase().is_off());
2351
2352 assert_none_snapshot();
2354 assert_eq!(MultiBlock::round(), 1);
2355 })
2356 }
2357
2358 #[test]
2359 fn weights_registered() {
2360 ExtBuilder::full().build_and_execute(|| {
2368 roll_to(10);
2369 assert!(MultiBlock::current_phase().is_off());
2370 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2372
2373 roll_next_and_phase(Phase::Snapshot(3));
2375 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2376
2377 roll_next_and_phase(Phase::Snapshot(2));
2378 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2379
2380 roll_next_and_phase(Phase::Snapshot(1));
2381 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2382
2383 roll_next_and_phase(Phase::Snapshot(0));
2384 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2385
2386 roll_next_and_phase(Phase::Signed(SignedPhase::get() - 1));
2387 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2388
2389 roll_next_and_phase(Phase::Signed(SignedPhase::get() - 2));
2391 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2392
2393 {
2395 let paged = mine_full_solution().unwrap();
2396 load_signed_for_verification(999, paged.clone());
2397 }
2398
2399 roll_to_signed_validation_open();
2401 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 3));
2402
2403 roll_next_and_phase_verifier(
2404 Phase::SignedValidation(SignedValidationPhase::get() - 1),
2405 Status::Ongoing(1),
2406 );
2407 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(1, 7));
2408
2409 roll_next_and_phase_verifier(
2410 Phase::SignedValidation(SignedValidationPhase::get() - 2),
2411 Status::Ongoing(0),
2412 );
2413 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(1, 7));
2414
2415 roll_next_and_phase_verifier(
2416 Phase::SignedValidation(SignedValidationPhase::get() - 3),
2417 Status::Nothing,
2418 );
2419 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(1, 7));
2420
2421 roll_to_unsigned_open();
2423 assert!(MultiBlock::current_phase().is_unsigned());
2424 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2425
2426 roll_next_and_phase(Phase::Unsigned(UnsignedPhase::get() - 2));
2427 assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2428
2429 });
2432 }
2433
2434 #[test]
2435 fn no_unsigned_phase() {
2436 ExtBuilder::full()
2437 .pages(3)
2438 .unsigned_phase(0)
2439 .election_start(16)
2440 .fallback_mode(FallbackModes::Onchain)
2441 .build_and_execute(|| {
2442 assert_eq!(System::block_number(), 0);
2447 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2448 assert_none_snapshot();
2449 assert_eq!(MultiBlock::round(), 0);
2450
2451 roll_to(4);
2452 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2453 assert_eq!(MultiBlock::round(), 0);
2454
2455 roll_to(16);
2456 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2457
2458 roll_to(17);
2459 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2460
2461 roll_to(18);
2462 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2463
2464 roll_to(19);
2465 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2466
2467 roll_to(20);
2468 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2469
2470 assert_full_snapshot();
2471 assert_eq!(MultiBlock::round(), 0);
2472
2473 roll_to(25);
2474 assert_eq!(
2475 MultiBlock::current_phase(),
2476 Phase::SignedValidation(SignedValidationPhase::get())
2477 );
2478
2479 assert_eq!(
2480 multi_block_events_since_last_call(),
2481 vec![
2482 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2483 Event::PhaseTransitioned {
2484 from: Phase::Snapshot(0),
2485 to: Phase::Signed(SignedPhase::get() - 1)
2486 },
2487 Event::PhaseTransitioned {
2488 from: Phase::Signed(0),
2489 to: Phase::SignedValidation(SignedValidationPhase::get())
2490 },
2491 ]
2492 );
2493
2494 roll_to(31);
2496 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2497
2498 roll_to(32);
2500 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2501
2502 roll_to(33);
2503 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2504
2505 MultiBlock::elect(0).unwrap();
2506 assert!(MultiBlock::current_phase().is_off());
2507
2508 assert_none_snapshot();
2510 assert_eq!(MultiBlock::round(), 1);
2511 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2512 verifier::QueuedSolution::<Runtime>::assert_killed();
2513 })
2514 }
2515
2516 #[test]
2517 fn no_signed_phase() {
2518 ExtBuilder::full()
2519 .pages(3)
2520 .signed_phase(0, 0)
2521 .election_start(21)
2522 .fallback_mode(FallbackModes::Onchain)
2523 .build_and_execute(|| {
2524 assert_eq!(System::block_number(), 0);
2529 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2530 assert_none_snapshot();
2531 assert_eq!(MultiBlock::round(), 0);
2532
2533 roll_to(20);
2534 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2535 assert_eq!(MultiBlock::round(), 0);
2536
2537 roll_to(21);
2538 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2539 roll_to(22);
2540 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2541 roll_to(23);
2542 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2543 roll_to(24);
2544 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2545
2546 roll_to(25);
2547 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2548 assert_full_snapshot();
2549 assert_eq!(MultiBlock::round(), 0);
2550
2551 assert_eq!(
2552 multi_block_events(),
2553 vec![
2554 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2555 Event::PhaseTransitioned {
2556 from: Phase::Snapshot(0),
2557 to: Phase::Unsigned(UnsignedPhase::get() - 1)
2558 },
2559 ]
2560 );
2561
2562 roll_to(29);
2563 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2564
2565 roll_to(30);
2566 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2567 roll_to(31);
2568 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2569
2570 MultiBlock::elect(0).unwrap();
2572 assert!(MultiBlock::current_phase().is_off());
2573
2574 assert_none_snapshot();
2576 assert_eq!(MultiBlock::round(), 1);
2577 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2578 verifier::QueuedSolution::<Runtime>::assert_killed();
2579 })
2580 }
2581
2582 #[test]
2583 #[should_panic(expected = "either signed or unsigned phase must be set")]
2584 fn no_signed_and_unsigned_phase() {
2585 ExtBuilder::full()
2586 .pages(3)
2587 .signed_phase(0, 0)
2588 .unsigned_phase(0)
2589 .election_start(10)
2590 .fallback_mode(FallbackModes::Onchain)
2591 .build_and_execute(|| {
2592 });
2594 }
2595
2596 #[test]
2597 #[should_panic(
2598 expected = "signed validation phase should be a multiple of the number of pages."
2599 )]
2600 fn incorrect_signed_validation_phase_shorter_than_number_of_pages() {
2601 ExtBuilder::full().pages(3).signed_validation_phase(2).build_and_execute(|| {})
2602 }
2603
2604 #[test]
2605 #[should_panic(
2606 expected = "signed validation phase should be a multiple of the number of pages."
2607 )]
2608 fn incorret_signed_validation_phase_not_a_multiple_of_the_number_of_pages() {
2609 ExtBuilder::full().pages(3).signed_validation_phase(7).build_and_execute(|| {})
2610 }
2611
2612 #[test]
2613 fn are_we_done_back_to_signed() {
2614 ExtBuilder::full()
2615 .are_we_done(AreWeDoneModes::BackToSigned)
2616 .build_and_execute(|| {
2617 roll_to_last_unsigned();
2619
2620 assert_eq!(MultiBlock::round(), 0);
2621 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2622 assert_eq!(
2623 multi_block_events_since_last_call(),
2624 vec![
2625 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2626 Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed(4) },
2627 Event::PhaseTransitioned {
2628 from: Phase::Signed(0),
2629 to: Phase::SignedValidation(SignedValidationPhase::get())
2630 },
2631 Event::PhaseTransitioned {
2632 from: Phase::SignedValidation(0),
2633 to: Phase::Unsigned(4)
2634 }
2635 ]
2636 );
2637
2638 roll_next_and_phase(Phase::Signed(SignedPhase::get() - 1));
2640 assert_eq!(MultiBlock::round(), 0);
2642
2643 roll_next_and_phase(Phase::Signed(SignedPhase::get() - 2));
2645 roll_next_and_phase(Phase::Signed(SignedPhase::get() - 3));
2646 });
2647 }
2648
2649 #[test]
2650 fn export_phase_only_transitions_on_elect() {
2651 ExtBuilder::full()
2652 .pages(3)
2653 .election_start(13)
2654 .fallback_mode(FallbackModes::Onchain)
2655 .build_and_execute(|| {
2656 roll_to_done();
2657
2658 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2659
2660 roll_next_and_phase(Phase::Done);
2662
2663 assert_ok!(MultiBlock::elect(2)); assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2666
2667 roll_next_and_phase(Phase::Export(1));
2669
2670 assert_ok!(MultiBlock::elect(1));
2672 assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2673
2674 roll_next_and_phase(Phase::Export(0));
2676
2677 assert_ok!(MultiBlock::elect(0));
2679 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2680 });
2681 }
2682
2683 #[test]
2684 fn export_phase_out_of_order_elect_fails() {
2685 ExtBuilder::full()
2686 .pages(3)
2687 .election_start(13)
2688 .fallback_mode(FallbackModes::Onchain)
2689 .build_and_execute(|| {
2690 roll_to_done();
2691
2692 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2693
2694 assert_ok!(MultiBlock::elect(2)); assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2697
2698 assert_eq!(MultiBlock::elect(2), Err(ElectionError::OutOfOrder));
2700
2701 assert_eq!(MultiBlock::elect(0), Err(ElectionError::OutOfOrder));
2703
2704 assert_ok!(MultiBlock::elect(1));
2706 assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2707 });
2708 }
2709
2710 #[test]
2711 #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
2712 fn target_snapshot_failed_event_emitted() {
2713 ExtBuilder::full()
2714 .pages(2)
2715 .election_start(13)
2716 .build_and_execute(|| {
2717 let too_many_targets: Vec<AccountId> = (1..=100).collect();
2720 Targets::set(too_many_targets);
2721
2722 roll_to(13);
2723 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2724
2725 let _ = multi_block_events_since_last_call();
2727
2728 roll_to(14);
2731
2732 let events = multi_block_events_since_last_call();
2734 assert!(
2735 events.contains(&Event::UnexpectedTargetSnapshotFailed),
2736 "UnexpectedTargetSnapshotFailed event should have been emitted when target snapshot creation fails. Events: {:?}",
2737 events
2738 );
2739
2740 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2742 });
2743 }
2744}
2745
2746#[cfg(test)]
2747mod election_provider {
2748 use super::*;
2749 use crate::{
2750 mock::*,
2751 unsigned::miner::OffchainWorkerMiner,
2752 verifier::{AsynchronousVerifier, Status, Verifier},
2753 Phase,
2754 };
2755 use frame_election_provider_support::{BoundedSupport, BoundedSupports, ElectionProvider};
2756 use frame_support::{
2757 assert_storage_noop, testing_prelude::bounded_vec, unsigned::ValidateUnsigned,
2758 };
2759
2760 #[test]
2764 fn multi_page_elect_simple_works() {
2765 ExtBuilder::full().build_and_execute(|| {
2766 roll_to_signed_open();
2767 assert!(MultiBlock::current_phase().is_signed());
2768
2769 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2771 let score = paged.score;
2772
2773 load_signed_for_verification(99, paged);
2775
2776 roll_to_signed_validation_open();
2778
2779 assert_eq!(
2780 multi_block_events(),
2781 vec![
2782 Event::PhaseTransitioned {
2783 from: Phase::Off,
2784 to: Phase::Snapshot(Pages::get())
2785 },
2786 Event::PhaseTransitioned {
2787 from: Phase::Snapshot(0),
2788 to: Phase::Signed(SignedPhase::get() - 1)
2789 },
2790 Event::PhaseTransitioned {
2791 from: Phase::Signed(0),
2792 to: Phase::SignedValidation(SignedValidationPhase::get())
2793 }
2794 ]
2795 );
2796 assert_eq!(verifier_events_since_last_call(), vec![]);
2797
2798 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2800 assert_eq!(
2801 <Runtime as crate::Config>::Verifier::status(),
2802 verifier::Status::Ongoing(2)
2803 );
2804
2805 roll_next_and_phase_verifier(Phase::SignedValidation(5), Status::Ongoing(1));
2807 assert_eq!(verifier_events_since_last_call(), vec![verifier::Event::Verified(2, 2)]);
2808
2809 roll_next_and_phase_verifier(Phase::SignedValidation(4), Status::Ongoing(0));
2810 assert_eq!(verifier_events_since_last_call(), vec![verifier::Event::Verified(1, 2)]);
2811
2812 roll_next_and_phase_verifier(Phase::SignedValidation(3), Status::Nothing);
2813 assert_eq!(
2814 verifier_events_since_last_call(),
2815 vec![verifier::Event::Verified(0, 2), verifier::Event::Queued(score, None)]
2816 );
2817
2818 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2820
2821 roll_to_unsigned_open();
2824
2825 assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2827 assert_eq!(MultiBlock::round(), 0);
2828 assert_full_snapshot();
2829
2830 let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp())
2832 .rev() .map(|page| {
2834 MultiBlock::elect(page as PageIndex).unwrap();
2835 if page == 0 {
2836 assert!(MultiBlock::current_phase().is_off())
2837 } else {
2838 assert_eq!(MultiBlock::current_phase(), Phase::Export(page - 1))
2839 }
2840 })
2841 .collect::<Vec<_>>();
2842
2843 verifier::QueuedSolution::<Runtime>::assert_killed();
2845 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2847 assert_eq!(Round::<Runtime>::get(), 1);
2849 assert_storage_noop!(Snapshot::<Runtime>::kill());
2851 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2855 });
2856 }
2857
2858 #[test]
2859 fn multi_page_elect_fast_track() {
2860 ExtBuilder::full().build_and_execute(|| {
2861 roll_to_signed_open();
2862 let round = MultiBlock::round();
2863 assert!(MultiBlock::current_phase().is_signed());
2864
2865 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2867 let score = paged.score;
2868 load_signed_for_verification_and_start(99, paged, 0);
2869
2870 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2872
2873 roll_next_and_phase_verifier(Phase::SignedValidation(5), Status::Ongoing(1));
2875 roll_next_and_phase_verifier(Phase::SignedValidation(4), Status::Ongoing(0));
2876 roll_next_and_phase_verifier(Phase::SignedValidation(3), Status::Nothing);
2877
2878 assert_eq!(
2879 verifier_events_since_last_call(),
2880 vec![
2881 verifier::Event::Verified(2, 2),
2882 verifier::Event::Verified(1, 2),
2883 verifier::Event::Verified(0, 2),
2884 verifier::Event::Queued(score, None),
2885 ]
2886 );
2887
2888 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2890
2891 roll_to_unsigned_open();
2893
2894 assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2896 assert_eq!(Round::<Runtime>::get(), 0);
2897 assert_full_snapshot();
2898
2899 let _supports = crate::Pallet::<Runtime>::elect(0).unwrap();
2901
2902 assert_eq!(MultiBlock::round(), round + 1);
2904 verifier::QueuedSolution::<Runtime>::assert_killed();
2906 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2908 assert_eq!(Round::<Runtime>::get(), 1);
2910 assert_none_snapshot();
2912 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(round));
2914 });
2915 }
2916
2917 #[test]
2918 fn elect_does_not_finish_without_call_of_page_0() {
2919 ExtBuilder::full().build_and_execute(|| {
2920 roll_to_signed_open();
2921 assert!(MultiBlock::current_phase().is_signed());
2922
2923 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2925 let score = paged.score;
2926 load_signed_for_verification_and_start(99, paged, 0);
2927
2928 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2930
2931 roll_next_and_phase_verifier(Phase::SignedValidation(5), Status::Ongoing(1));
2933 roll_next_and_phase_verifier(Phase::SignedValidation(4), Status::Ongoing(0));
2934 roll_next_and_phase_verifier(Phase::SignedValidation(3), Status::Nothing);
2935
2936 assert_eq!(
2937 verifier_events_since_last_call(),
2938 vec![
2939 verifier::Event::Verified(2, 2),
2940 verifier::Event::Verified(1, 2),
2941 verifier::Event::Verified(0, 2),
2942 verifier::Event::Queued(score, None),
2943 ]
2944 );
2945
2946 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2948
2949 roll_to_unsigned_open();
2951
2952 assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2954 assert_eq!(Round::<Runtime>::get(), 0);
2955 assert_full_snapshot();
2956
2957 let solutions = (1..=MultiBlock::msp())
2959 .rev() .map(|page| {
2961 crate::Pallet::<Runtime>::elect(page as PageIndex).unwrap();
2962 assert!(MultiBlock::current_phase().is_export());
2963 })
2964 .collect::<Vec<_>>();
2965 assert_eq!(solutions.len(), 2);
2966
2967 assert!(MultiBlock::current_phase().is_export());
2969 assert_eq!(Round::<Runtime>::get(), 0);
2970 assert_full_snapshot();
2971 });
2972 }
2973
2974 #[test]
2975 fn continue_fallback_works() {
2976 ExtBuilder::full().fallback_mode(FallbackModes::Continue).build_and_execute(|| {
2978 roll_to_unsigned_open();
2980
2981 let miner_pages = <Runtime as unsigned::Config>::MinerPages::get();
2983 let unsigned_solution =
2985 OffchainWorkerMiner::<Runtime>::mine_solution(miner_pages, true).unwrap();
2986
2987 assert_ok!(UnsignedPallet::submit_unsigned(
2989 RuntimeOrigin::none(),
2990 Box::new(unsigned_solution)
2991 ));
2992
2993 roll_to_done();
2995
2996 let result1 = MultiBlock::elect(2);
3000 assert!(result1.is_ok());
3001 assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
3002
3003 let result2 = MultiBlock::elect(1);
3007 assert!(result2.is_err());
3008 assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
3009
3010 let result3 = MultiBlock::elect(0);
3012 assert!(result3.is_err());
3013 assert!(matches!(MultiBlock::current_phase(), Phase::Off));
3014 });
3015 }
3016
3017 #[test]
3018 fn skip_unsigned_phase() {
3019 ExtBuilder::full().build_and_execute(|| {
3020 roll_to_signed_open();
3021 assert!(MultiBlock::current_phase().is_signed());
3022 let round = MultiBlock::round();
3023
3024 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
3026
3027 load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
3028
3029 assert!(matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)));
3032 assert_eq!(Round::<Runtime>::get(), 0);
3033 assert_full_snapshot();
3034
3035 let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp())
3037 .rev() .map(|page| {
3039 MultiBlock::elect(page as PageIndex).unwrap();
3040 if page == 0 {
3041 assert!(MultiBlock::current_phase().is_off())
3042 } else {
3043 assert!(MultiBlock::current_phase().is_export())
3044 }
3045 })
3046 .collect::<Vec<_>>();
3047
3048 assert_eq!(MultiBlock::round(), round + 1);
3050 verifier::QueuedSolution::<Runtime>::assert_killed();
3052 assert_eq!(MultiBlock::current_phase(), Phase::Off);
3054 assert_none_snapshot();
3056 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(round));
3058 });
3059 }
3060
3061 #[test]
3062 fn call_to_elect_should_prevent_any_submission() {
3063 ExtBuilder::full().build_and_execute(|| {
3064 roll_to_signed_open();
3065 assert!(MultiBlock::current_phase().is_signed());
3066
3067 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
3069 load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
3070
3071 assert!(matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)));
3072
3073 assert!(MultiBlock::elect(MultiBlock::msp()).is_ok());
3075
3076 assert_noop!(
3078 SignedPallet::submit_page(RuntimeOrigin::signed(999), 0, Default::default()),
3079 crate::signed::Error::<Runtime>::PhaseNotSigned,
3080 );
3081 assert_noop!(
3082 SignedPallet::register(RuntimeOrigin::signed(999), Default::default()),
3083 crate::signed::Error::<Runtime>::PhaseNotSigned,
3084 );
3085 assert_storage_noop!(assert!(<UnsignedPallet as ValidateUnsigned>::pre_dispatch(
3086 &unsigned::Call::submit_unsigned { paged_solution: Default::default() }
3087 )
3088 .is_err()));
3089 });
3090 }
3091
3092 #[test]
3093 fn multi_page_onchain_elect_fallback_works() {
3094 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3095 roll_to_signed_open();
3096
3097 assert_eq!(
3099 MultiBlock::elect(2).unwrap(),
3100 BoundedSupports(bounded_vec![
3101 (10, BoundedSupport { total: 15, voters: bounded_vec![(1, 10), (4, 5)] }),
3102 (
3103 40,
3104 BoundedSupport {
3105 total: 25,
3106 voters: bounded_vec![(2, 10), (3, 10), (4, 5)]
3107 }
3108 )
3109 ])
3110 );
3111 assert_eq!(
3113 MultiBlock::elect(1).unwrap(),
3114 BoundedSupports(bounded_vec![
3115 (10, BoundedSupport { total: 15, voters: bounded_vec![(5, 5), (8, 10)] }),
3116 (
3117 30,
3118 BoundedSupport {
3119 total: 25,
3120 voters: bounded_vec![(5, 5), (6, 10), (7, 10)]
3121 }
3122 )
3123 ])
3124 );
3125 assert_eq!(
3127 MultiBlock::elect(0).unwrap(),
3128 BoundedSupports(bounded_vec![
3129 (30, BoundedSupport { total: 30, voters: bounded_vec![(30, 30)] }),
3130 (40, BoundedSupport { total: 40, voters: bounded_vec![(40, 40)] })
3131 ])
3132 );
3133
3134 assert_eq!(
3135 multi_block_events(),
3136 vec![
3137 Event::PhaseTransitioned {
3138 from: Phase::Off,
3139 to: Phase::Snapshot(Pages::get())
3140 },
3141 Event::PhaseTransitioned {
3142 from: Phase::Snapshot(0),
3143 to: Phase::Signed(SignedPhase::get() - 1)
3144 },
3145 Event::PhaseTransitioned {
3146 from: Phase::Signed(SignedPhase::get() - 1),
3147 to: Phase::Export(1)
3148 },
3149 Event::PhaseTransitioned { from: Phase::Export(0), to: Phase::Off }
3150 ]
3151 );
3152 assert_eq!(verifier_events(), vec![]);
3153
3154 assert_eq!(MultiBlock::current_phase(), Phase::Off);
3156 });
3157 }
3158
3159 #[test]
3160 fn multi_page_fallback_shortcut_to_msp_works() {
3161 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3162 roll_to_signed_open();
3163
3164 assert!(MultiBlock::elect(0).is_ok());
3166
3167 assert_eq!(
3168 multi_block_events(),
3169 vec![
3170 Event::PhaseTransitioned {
3171 from: Phase::Off,
3172 to: Phase::Snapshot(Pages::get())
3173 },
3174 Event::PhaseTransitioned {
3175 from: Phase::Snapshot(0),
3176 to: Phase::Signed(SignedPhase::get() - 1)
3177 },
3178 Event::PhaseTransitioned {
3179 from: Phase::Signed(SignedPhase::get() - 1),
3180 to: Phase::Off
3181 }
3182 ]
3183 );
3184
3185 assert_eq!(MultiBlock::current_phase(), Phase::Off);
3187 });
3188 }
3189
3190 #[test]
3191 fn elect_call_when_not_ongoing() {
3192 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3193 roll_to_snapshot_created();
3194 assert_eq!(MultiBlock::status(), Ok(None));
3195 assert!(MultiBlock::elect(0).is_ok());
3196 });
3197 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3198 roll_to(10);
3199 assert_eq!(MultiBlock::status(), Err(()));
3200 assert_eq!(MultiBlock::elect(0), Err(ElectionError::NotOngoing));
3201 });
3202 }
3203}
3204
3205#[cfg(test)]
3206mod manage_ops {
3207 use super::*;
3208 use crate::mock::*;
3209
3210 #[test]
3211 fn trigger_fallback_works() {
3212 ExtBuilder::full()
3213 .fallback_mode(FallbackModes::Emergency)
3214 .build_and_execute(|| {
3215 roll_to_signed_open();
3216
3217 assert_noop!(
3219 MultiBlock::manage(
3220 RuntimeOrigin::signed(Manager::get() + 1),
3221 ManagerOperation::EmergencyFallback
3222 ),
3223 DispatchError::BadOrigin
3224 );
3225
3226 assert_eq!(
3229 MultiBlock::elect(0),
3230 Err(ElectionError::Fallback("Emergency phase started.".to_string()))
3231 );
3232 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3233
3234 FallbackMode::set(FallbackModes::Onchain);
3236 assert_ok!(MultiBlock::manage(
3237 RuntimeOrigin::signed(Manager::get()),
3238 ManagerOperation::EmergencyFallback
3239 ));
3240
3241 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3242 assert_ok!(MultiBlock::elect(0));
3243 assert_eq!(MultiBlock::current_phase(), Phase::Off);
3244
3245 assert_eq!(
3246 multi_block_events(),
3247 vec![
3248 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
3249 Event::PhaseTransitioned {
3250 from: Phase::Snapshot(0),
3251 to: Phase::Signed(SignedPhase::get() - 1)
3252 },
3253 Event::PhaseTransitioned {
3254 from: Phase::Signed(SignedPhase::get() - 1),
3255 to: Phase::Emergency
3256 },
3257 Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off }
3258 ]
3259 );
3260 assert_eq!(
3261 verifier_events(),
3262 vec![verifier::Event::Queued(
3263 ElectionScore { minimal_stake: 15, sum_stake: 40, sum_stake_squared: 850 },
3264 None
3265 )]
3266 );
3267 })
3268 }
3269
3270 #[test]
3281 fn force_rotate_round() {
3282 ExtBuilder::full().build_and_execute(|| {
3283 roll_to_signed_open();
3284 let round = MultiBlock::round();
3285 let paged =
3286 unsigned::miner::OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false)
3287 .unwrap();
3288 load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
3289
3290 assert_full_snapshot();
3292 assert!(verifier::QueuedSolution::<T>::queued_score().is_some());
3294 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(2));
3296
3297 assert_noop!(
3301 MultiBlock::manage(
3302 RuntimeOrigin::signed(Manager::get() + 1),
3303 ManagerOperation::ForceRotateRound
3304 ),
3305 DispatchError::BadOrigin
3306 );
3307 assert_ok!(MultiBlock::manage(
3309 RuntimeOrigin::signed(Manager::get()),
3310 ManagerOperation::ForceRotateRound
3311 ));
3312
3313 assert_eq!(MultiBlock::current_phase(), Phase::Off);
3315 assert_eq!(MultiBlock::round(), round + 1);
3317 assert_none_snapshot();
3319 verifier::QueuedSolution::<T>::assert_killed();
3321 });
3322 }
3323
3324 #[test]
3325 fn force_set_phase() {
3326 ExtBuilder::full().build_and_execute(|| {
3327 roll_to_signed_open();
3328 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
3329
3330 assert_noop!(
3332 MultiBlock::manage(
3333 RuntimeOrigin::signed(Manager::get() + 1),
3334 ManagerOperation::ForceSetPhase(Phase::Done)
3335 ),
3336 DispatchError::BadOrigin
3337 );
3338
3339 assert_ok!(MultiBlock::manage(
3341 RuntimeOrigin::signed(Manager::get()),
3342 ManagerOperation::ForceSetPhase(Phase::Unsigned(UnsignedPhase::get() - 1))
3343 ));
3344
3345 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
3346
3347 assert_ok!(MultiBlock::manage(
3349 RuntimeOrigin::root(),
3350 ManagerOperation::ForceSetPhase(Phase::Unsigned(UnsignedPhase::get() - 2))
3351 ));
3352 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 2));
3353 });
3354 }
3355}
3356#[cfg(test)]
3357mod admin_ops {
3358 use super::*;
3359 use crate::mock::*;
3360
3361 #[test]
3362 fn set_solution_emergency_works() {
3363 ExtBuilder::full().build_and_execute(|| {
3364 roll_to_signed_open();
3365
3366 assert_noop!(
3368 MultiBlock::admin(
3369 RuntimeOrigin::signed(Manager::get() + 1),
3370 AdminOperation::EmergencySetSolution(Default::default(), Default::default())
3371 ),
3372 DispatchError::BadOrigin
3373 );
3374
3375 assert_noop!(
3377 MultiBlock::admin(
3378 RuntimeOrigin::signed(Manager::get()),
3379 AdminOperation::EmergencySetSolution(Default::default(), Default::default())
3380 ),
3381 DispatchError::BadOrigin
3382 );
3383
3384 assert_eq!(
3386 MultiBlock::elect(0),
3387 Err(ElectionError::Fallback("Emergency phase started.".to_string()))
3388 );
3389 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3390
3391 let (emergency, score) = emergency_solution();
3393 assert_ok!(MultiBlock::admin(
3394 RuntimeOrigin::root(),
3395 AdminOperation::EmergencySetSolution(Box::new(emergency), score)
3396 ));
3397
3398 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3399 assert_ok!(MultiBlock::elect(0));
3400 assert_eq!(MultiBlock::current_phase(), Phase::Off);
3401
3402 assert_eq!(
3403 multi_block_events(),
3404 vec![
3405 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
3406 Event::PhaseTransitioned {
3407 from: Phase::Snapshot(0),
3408 to: Phase::Signed(SignedPhase::get() - 1)
3409 },
3410 Event::PhaseTransitioned {
3411 from: Phase::Signed(SignedPhase::get() - 1),
3412 to: Phase::Emergency
3413 },
3414 Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off }
3415 ]
3416 );
3417 assert_eq!(
3418 verifier_events(),
3419 vec![verifier::Event::Queued(
3420 ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 },
3421 None
3422 )]
3423 );
3424 })
3425 }
3426
3427 #[test]
3428 fn set_minimum_solution_score() {
3429 ExtBuilder::full().build_and_execute(|| {
3430 assert_noop!(
3432 MultiBlock::admin(
3433 RuntimeOrigin::signed(Manager::get() + 1),
3434 AdminOperation::SetMinUntrustedScore(ElectionScore {
3435 minimal_stake: 100,
3436 ..Default::default()
3437 })
3438 ),
3439 DispatchError::BadOrigin
3440 );
3441
3442 assert_noop!(
3444 MultiBlock::admin(
3445 RuntimeOrigin::signed(Manager::get()),
3446 AdminOperation::SetMinUntrustedScore(ElectionScore {
3447 minimal_stake: 100,
3448 ..Default::default()
3449 })
3450 ),
3451 DispatchError::BadOrigin
3452 );
3453
3454 assert_eq!(VerifierPallet::minimum_score(), None);
3455 assert_ok!(MultiBlock::admin(
3456 RuntimeOrigin::root(),
3457 AdminOperation::SetMinUntrustedScore(ElectionScore {
3458 minimal_stake: 100,
3459 ..Default::default()
3460 })
3461 ));
3462 assert_eq!(
3463 VerifierPallet::minimum_score().unwrap(),
3464 ElectionScore { minimal_stake: 100, ..Default::default() }
3465 );
3466 });
3467 }
3468}