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