1#![cfg_attr(not(feature = "std"), no_std)]
197
198#[cfg(any(feature = "runtime-benchmarks", test))]
199use crate::signed::{CalculateBaseDeposit, CalculatePageDeposit};
200use codec::{Decode, Encode, MaxEncodedLen};
201use frame_election_provider_support::{
202 onchain, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
203 InstantElectionProvider,
204};
205use frame_support::{
206 pallet_prelude::*,
207 traits::{Defensive, EnsureOrigin},
208 DebugNoBound, Twox64Concat,
209};
210use frame_system::pallet_prelude::*;
211use scale_info::TypeInfo;
212use sp_arithmetic::{
213 traits::{CheckedAdd, Zero},
214 PerThing, UpperOf,
215};
216use sp_npos_elections::VoteWeight;
217use sp_runtime::{
218 traits::{Hash, Saturating},
219 SaturatedConversion,
220};
221use sp_std::{borrow::ToOwned, boxed::Box, prelude::*};
222use verifier::Verifier;
223
224#[cfg(test)]
225mod mock;
226#[macro_use]
227pub mod helpers;
228#[cfg(feature = "runtime-benchmarks")]
229pub mod benchmarking;
230
231pub const LOG_PREFIX: &'static str = "runtime::multiblock-election";
233
234macro_rules! clear_round_based_map {
235 ($map: ty, $round: expr) => {{
236 let __r = <$map>::clear_prefix($round, u32::MAX, None);
237 debug_assert!(__r.unique <= T::Pages::get(), "clearing map caused too many removals")
238 }};
239}
240
241pub mod signed;
243pub mod types;
245pub mod unsigned;
247pub mod verifier;
249pub mod weights;
251
252pub use pallet::*;
253pub use types::*;
254pub use weights::traits::pallet_election_provider_multi_block::WeightInfo;
255
256pub struct InitiateEmergencyPhase<T>(sp_std::marker::PhantomData<T>);
258impl<T: Config> ElectionProvider for InitiateEmergencyPhase<T> {
259 type AccountId = T::AccountId;
260 type BlockNumber = BlockNumberFor<T>;
261 type DataProvider = T::DataProvider;
262 type Error = &'static str;
263 type Pages = T::Pages;
264 type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
265 type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
266 type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
267
268 fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
269 Pallet::<T>::phase_transition(Phase::Emergency);
270 Err("Emergency phase started.")
271 }
272
273 fn status() -> Result<bool, ()> {
274 Ok(true)
275 }
276
277 fn start() -> Result<(), Self::Error> {
278 Ok(())
279 }
280
281 fn duration() -> Self::BlockNumber {
282 Zero::zero()
283 }
284}
285
286impl<T: Config> InstantElectionProvider for InitiateEmergencyPhase<T> {
287 fn instant_elect(
288 _voters: Vec<VoterOf<T::MinerConfig>>,
289 _targets: Vec<Self::AccountId>,
290 _desired_targets: u32,
291 ) -> Result<BoundedSupportsOf<Self>, Self::Error> {
292 Self::elect(0)
293 }
294
295 fn bother() -> bool {
296 false
297 }
298}
299
300pub struct Continue<T>(sp_std::marker::PhantomData<T>);
304impl<T: Config> ElectionProvider for Continue<T> {
305 type AccountId = T::AccountId;
306 type BlockNumber = BlockNumberFor<T>;
307 type DataProvider = T::DataProvider;
308 type Error = &'static str;
309 type Pages = T::Pages;
310 type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
311 type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
312 type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
313
314 fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
315 Err("'Continue' fallback will do nothing")
316 }
317
318 fn start() -> Result<(), Self::Error> {
319 Ok(())
320 }
321
322 fn duration() -> Self::BlockNumber {
323 Zero::zero()
324 }
325
326 fn status() -> Result<bool, ()> {
327 Ok(true)
328 }
329}
330
331impl<T: Config> InstantElectionProvider for Continue<T> {
332 fn instant_elect(
333 _voters: Vec<VoterOf<T::MinerConfig>>,
334 _targets: Vec<Self::AccountId>,
335 _desired_targets: u32,
336 ) -> Result<BoundedSupportsOf<Self>, Self::Error> {
337 Self::elect(0)
338 }
339
340 fn bother() -> bool {
341 false
342 }
343}
344
345pub struct IfSolutionQueuedElse<T, Queued, NotQueued>(
353 sp_std::marker::PhantomData<(T, Queued, NotQueued)>,
354);
355
356pub struct GetDone<T>(sp_std::marker::PhantomData<T>);
358impl<T: Config> Get<Phase<T>> for GetDone<T> {
359 fn get() -> Phase<T> {
360 Phase::Done
361 }
362}
363
364pub struct GetSigned<T>(sp_std::marker::PhantomData<T>);
366impl<T: Config> Get<Phase<T>> for GetSigned<T> {
367 fn get() -> Phase<T> {
368 Phase::Signed(T::SignedPhase::get().saturating_sub(1u32.into()))
369 }
370}
371
372pub type ProceedRegardlessOf<T> = IfSolutionQueuedElse<T, GetDone<T>, GetDone<T>>;
374
375pub type RevertToSignedIfNotQueuedOf<T> = IfSolutionQueuedElse<T, GetDone<T>, GetSigned<T>>;
378
379impl<T: Config, Queued, NotQueued> IfSolutionQueuedElse<T, Queued, NotQueued> {
380 fn something_queued() -> bool {
381 let queued_score = <T::Verifier as verifier::Verifier>::queued_score().is_some();
382 #[cfg(debug_assertions)]
383 {
384 let any_pages_queued = (Pallet::<T>::lsp()..=Pallet::<T>::msp()).any(|p| {
385 <T::Verifier as verifier::Verifier>::get_queued_solution_page(p).is_some()
386 });
387 assert_eq!(
388 queued_score, any_pages_queued,
389 "queued score ({}) and queued pages ({}) must match",
390 queued_score, any_pages_queued
391 );
392 }
393 queued_score
394 }
395}
396
397impl<T: Config, Queued: Get<Phase<T>>, NotQueued: Get<Phase<T>>> Get<Phase<T>>
398 for IfSolutionQueuedElse<T, Queued, NotQueued>
399{
400 fn get() -> Phase<T> {
401 if Self::something_queued() {
402 Queued::get()
403 } else {
404 NotQueued::get()
405 }
406 }
407}
408
409#[derive(
413 frame_support::DebugNoBound, frame_support::PartialEqNoBound, frame_support::EqNoBound,
414)]
415pub enum ElectionError<T: Config> {
416 Feasibility(verifier::FeasibilityError),
418 Fallback(FallbackErrorOf<T>),
420 OnChain(onchain::Error),
422 DataProvider(&'static str),
424 SupportPageNotAvailable,
426 NotOngoing,
428 Ongoing,
430 OutOfOrder,
432 Other(&'static str),
434}
435
436impl<T: Config> From<onchain::Error> for ElectionError<T> {
437 fn from(e: onchain::Error) -> Self {
438 ElectionError::OnChain(e)
439 }
440}
441
442impl<T: Config> From<verifier::FeasibilityError> for ElectionError<T> {
443 fn from(e: verifier::FeasibilityError) -> Self {
444 ElectionError::Feasibility(e)
445 }
446}
447
448#[derive(
450 Encode,
451 Decode,
452 DecodeWithMemTracking,
453 MaxEncodedLen,
454 TypeInfo,
455 DebugNoBound,
456 CloneNoBound,
457 PartialEqNoBound,
458 EqNoBound,
459)]
460#[codec(mel_bound(T: Config))]
461#[scale_info(skip_type_params(T))]
462pub enum AdminOperation<T: Config> {
463 ForceRotateRound,
465 ForceSetPhase(Phase<T>),
469 EmergencySetSolution(Box<BoundedSupportsOf<Pallet<T>>>, ElectionScore),
473 EmergencyFallback,
478 SetMinUntrustedScore(ElectionScore),
484}
485
486pub trait OnRoundRotation {
488 fn on_round_rotation(ending: u32);
490}
491
492impl OnRoundRotation for () {
493 fn on_round_rotation(_: u32) {}
494}
495
496pub struct CleanRound<T>(core::marker::PhantomData<T>);
502impl<T: Config> OnRoundRotation for CleanRound<T> {
503 fn on_round_rotation(_ending: u32) {
504 T::Verifier::kill();
506
507 pallet::Snapshot::<T>::kill();
509
510 }
512}
513
514#[frame_support::pallet]
515pub mod pallet {
516 use super::*;
517
518 #[pallet::config]
519 pub trait Config: frame_system::Config {
520 #[pallet::constant]
522 type UnsignedPhase: Get<BlockNumberFor<Self>>;
523 #[pallet::constant]
525 type SignedPhase: Get<BlockNumberFor<Self>>;
526 #[pallet::constant]
532 type SignedValidationPhase: Get<BlockNumberFor<Self>>;
533
534 #[pallet::constant]
536 type VoterSnapshotPerBlock: Get<u32>;
537
538 #[pallet::constant]
540 type TargetSnapshotPerBlock: Get<u32>;
541
542 #[pallet::constant]
549 type Pages: Get<PageIndex>;
550
551 type DataProvider: ElectionDataProvider<
553 AccountId = Self::AccountId,
554 BlockNumber = BlockNumberFor<Self>,
555 >;
556
557 type MinerConfig: crate::unsigned::miner::MinerConfig<
562 Pages = Self::Pages,
563 AccountId = <Self as frame_system::Config>::AccountId,
564 MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
565 VoterSnapshotPerBlock = Self::VoterSnapshotPerBlock,
566 TargetSnapshotPerBlock = Self::TargetSnapshotPerBlock,
567 MaxBackersPerWinner = <Self::Verifier as verifier::Verifier>::MaxBackersPerWinner,
568 MaxWinnersPerPage = <Self::Verifier as verifier::Verifier>::MaxWinnersPerPage,
569 >;
570
571 type Fallback: InstantElectionProvider<
573 AccountId = Self::AccountId,
574 BlockNumber = BlockNumberFor<Self>,
575 DataProvider = Self::DataProvider,
576 MaxBackersPerWinner = <Self::Verifier as verifier::Verifier>::MaxBackersPerWinner,
577 MaxWinnersPerPage = <Self::Verifier as verifier::Verifier>::MaxWinnersPerPage,
578 >;
579
580 type Verifier: verifier::Verifier<
582 Solution = SolutionOf<Self::MinerConfig>,
583 AccountId = Self::AccountId,
584 > + verifier::AsynchronousVerifier;
585
586 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
588
589 type AreWeDone: Get<Phase<Self>>;
594
595 type WeightInfo: WeightInfo;
597
598 type OnRoundRotation: super::OnRoundRotation;
601 }
602
603 #[pallet::call]
604 impl<T: Config> Pallet<T> {
605 #[pallet::weight(T::WeightInfo::manage())]
611 #[pallet::call_index(0)]
612 pub fn manage(origin: OriginFor<T>, op: AdminOperation<T>) -> DispatchResultWithPostInfo {
613 use crate::verifier::Verifier;
614 use sp_npos_elections::EvaluateSupport;
615
616 let _ = T::AdminOrigin::ensure_origin(origin);
617 match op {
618 AdminOperation::EmergencyFallback => {
619 ensure!(Self::current_phase() == Phase::Emergency, Error::<T>::UnexpectedPhase);
620 let voters = Snapshot::<T>::voters(Self::msp()).ok_or(Error::<T>::Snapshot)?;
623 let targets = Snapshot::<T>::targets().ok_or(Error::<T>::Snapshot)?;
624 let desired_targets =
625 Snapshot::<T>::desired_targets().ok_or(Error::<T>::Snapshot)?;
626 let fallback = T::Fallback::instant_elect(
627 voters.into_inner(),
628 targets.into_inner(),
629 desired_targets,
630 )
631 .map_err(|e| {
632 log!(warn, "Fallback failed: {:?}", e);
633 Error::<T>::Fallback
634 })?;
635 let score = fallback.evaluate();
636 T::Verifier::force_set_single_page_valid(fallback, 0, score);
637 Ok(().into())
638 },
639 AdminOperation::EmergencySetSolution(supports, score) => {
640 ensure!(Self::current_phase() == Phase::Emergency, Error::<T>::UnexpectedPhase);
641 T::Verifier::force_set_single_page_valid(*supports, 0, score);
643 Ok(().into())
644 },
645 AdminOperation::ForceSetPhase(phase) => {
646 Self::phase_transition(phase);
647 Ok(().into())
648 },
649 AdminOperation::ForceRotateRound => {
650 Self::rotate_round();
651 Ok(().into())
652 },
653 AdminOperation::SetMinUntrustedScore(score) => {
654 T::Verifier::set_minimum_score(score);
655 Ok(().into())
656 },
657 }
658 }
659 }
660
661 #[pallet::hooks]
662 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
663 fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
664 let current_phase = CurrentPhase::<T>::get();
665 let weight1 = match current_phase {
666 Phase::Snapshot(x) if x == T::Pages::get() => {
667 Self::create_targets_snapshot();
669 T::WeightInfo::on_initialize_into_snapshot_msp()
670 },
671 Phase::Snapshot(x) => {
672 Self::create_voters_snapshot_paged(x);
674 T::WeightInfo::on_initialize_into_snapshot_rest()
675 },
676 _ => T::WeightInfo::on_initialize_nothing(),
677 };
678
679 if !matches!(current_phase, Phase::Export(_)) {
681 let next_phase = current_phase.next();
682
683 let weight2 = match next_phase {
684 Phase::Signed(_) => T::WeightInfo::on_initialize_into_signed(),
685 Phase::SignedValidation(_) =>
686 T::WeightInfo::on_initialize_into_signed_validation(),
687 Phase::Unsigned(_) => T::WeightInfo::on_initialize_into_unsigned(),
688 _ => T::WeightInfo::on_initialize_nothing(),
689 };
690
691 Self::phase_transition(next_phase);
692
693 #[cfg(test)]
695 {
696 let test_election_start: BlockNumberFor<T> =
697 (crate::mock::ElectionStart::get() as u32).into();
698 if _now == test_election_start {
699 crate::log!(info, "TESTING: Starting election at block {}", _now);
700 crate::mock::MultiBlock::start().unwrap();
701 }
702 }
703
704 weight1 + weight2
705 } else {
706 weight1
708 }
709 }
710
711 fn integrity_test() {
712 use sp_std::mem::size_of;
713 assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
716 assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
717
718 assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<u32>());
721 assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<u32>());
722
723 assert!(T::Pages::get() > 0);
725
726 let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
728
729 let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T::MinerConfig>>> = (0..
731 max_vote)
732 .map(|_| {
733 <UpperOf<SolutionAccuracyOf<T::MinerConfig>>>::from(
734 <SolutionAccuracyOf<T::MinerConfig>>::one().deconstruct(),
735 )
736 })
737 .collect();
738 let _: UpperOf<SolutionAccuracyOf<T::MinerConfig>> = maximum_chain_accuracy
739 .iter()
740 .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap());
741
742 assert_eq!(
748 <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
749 <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
750 );
751
752 let has_signed = !T::SignedPhase::get().is_zero();
754 let signed_validation = T::SignedValidationPhase::get();
755 let has_signed_validation = !signed_validation.is_zero();
756 let has_unsigned = !T::UnsignedPhase::get().is_zero();
757 assert!(
758 has_signed == has_signed_validation,
759 "Signed phase not set correct -- both should be set or unset"
760 );
761 assert!(
762 signed_validation.is_zero() ||
763 signed_validation % T::Pages::get().into() == Zero::zero(),
764 "signed validation phase should be a multiple of the number of pages."
765 );
766
767 assert!(has_signed || has_unsigned, "either signed or unsigned phase must be set");
768 }
769
770 #[cfg(feature = "try-runtime")]
771 fn try_state(now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
772 Self::do_try_state(now).map_err(Into::into)
773 }
774 }
775
776 #[pallet::event]
777 #[pallet::generate_deposit(pub(super) fn deposit_event)]
778 pub enum Event<T: Config> {
779 PhaseTransitioned {
782 from: Phase<T>,
784 to: Phase<T>,
786 },
787 UnexpectedTargetSnapshotFailed,
789 UnexpectedVoterSnapshotFailed,
791 }
792
793 #[pallet::error]
795 pub enum Error<T> {
796 Fallback,
798 UnexpectedPhase,
800 Snapshot,
802 }
803
804 #[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)]
806 pub enum CommonError {
807 EarlySubmission,
809 WrongRound,
811 WeakSubmission,
813 WrongPageCount,
815 WrongWinnerCount,
817 WrongFingerprint,
819 Snapshot,
821 }
822
823 #[pallet::storage]
830 #[pallet::getter(fn round)]
831 pub type Round<T: Config> = StorageValue<_, u32, ValueQuery>;
832
833 #[pallet::storage]
835 #[pallet::getter(fn current_phase)]
836 pub type CurrentPhase<T: Config> = StorageValue<_, Phase<T>, ValueQuery>;
837
838 pub(crate) struct Snapshot<T>(sp_std::marker::PhantomData<T>);
875 impl<T: Config> Snapshot<T> {
876 pub(crate) fn set_desired_targets(d: u32) {
878 DesiredTargets::<T>::insert(Self::round(), d);
879 }
880
881 pub(crate) fn set_targets(targets: BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>) {
882 let hash = Self::write_storage_with_pre_allocate(
883 &PagedTargetSnapshot::<T>::hashed_key_for(Self::round(), Pallet::<T>::msp()),
884 targets,
885 );
886 PagedTargetSnapshotHash::<T>::insert(Self::round(), Pallet::<T>::msp(), hash);
887 }
888
889 pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf<T::MinerConfig>) {
890 let hash = Self::write_storage_with_pre_allocate(
891 &PagedVoterSnapshot::<T>::hashed_key_for(Self::round(), page),
892 voters,
893 );
894 PagedVoterSnapshotHash::<T>::insert(Self::round(), page, hash);
895 }
896
897 pub(crate) fn kill() {
901 DesiredTargets::<T>::remove(Self::round());
902 clear_round_based_map!(PagedVoterSnapshot::<T>, Self::round());
903 clear_round_based_map!(PagedVoterSnapshotHash::<T>, Self::round());
904 clear_round_based_map!(PagedTargetSnapshot::<T>, Self::round());
905 clear_round_based_map!(PagedTargetSnapshotHash::<T>, Self::round());
906 }
907
908 pub(crate) fn desired_targets() -> Option<u32> {
910 DesiredTargets::<T>::get(Self::round())
911 }
912
913 pub(crate) fn voters(page: PageIndex) -> Option<VoterPageOf<T::MinerConfig>> {
914 PagedVoterSnapshot::<T>::get(Self::round(), page)
915 }
916
917 pub(crate) fn targets() -> Option<BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>> {
918 PagedTargetSnapshot::<T>::get(Self::round(), Pallet::<T>::msp())
920 }
921
922 pub fn fingerprint() -> T::Hash {
929 let mut hashed_target_and_voters =
930 Self::targets_hash().unwrap_or_default().as_ref().to_vec();
931 let hashed_voters = (Pallet::<T>::msp()..=Pallet::<T>::lsp())
932 .map(|i| PagedVoterSnapshotHash::<T>::get(Self::round(), i).unwrap_or_default())
933 .flat_map(|hash| <T::Hash as AsRef<[u8]>>::as_ref(&hash).to_owned())
934 .collect::<Vec<u8>>();
935 hashed_target_and_voters.extend(hashed_voters);
936 T::Hashing::hash(&hashed_target_and_voters)
937 }
938
939 fn write_storage_with_pre_allocate<E: Encode>(key: &[u8], data: E) -> T::Hash {
940 let size = data.encoded_size();
941 let mut buffer = Vec::with_capacity(size);
942 data.encode_to(&mut buffer);
943
944 let hash = T::Hashing::hash(&buffer);
945
946 debug_assert_eq!(buffer, data.encode());
948 debug_assert!(buffer.len() == size && size == buffer.capacity());
950 sp_io::storage::set(key, &buffer);
951
952 hash
953 }
954
955 pub(crate) fn targets_hash() -> Option<T::Hash> {
956 PagedTargetSnapshotHash::<T>::get(Self::round(), Pallet::<T>::msp())
957 }
958
959 fn round() -> u32 {
960 Pallet::<T>::round()
961 }
962 }
963
964 #[allow(unused)]
965 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
966 impl<T: Config> Snapshot<T> {
967 pub(crate) fn ensure_target_snapshot(exists: bool) -> Result<(), &'static str> {
969 ensure!(exists ^ Self::desired_targets().is_none(), "desired target mismatch");
970 ensure!(exists ^ Self::targets().is_none(), "targets mismatch");
971 ensure!(exists ^ Self::targets_hash().is_none(), "targets hash mismatch");
972
973 if let Some(targets) = Self::targets() {
975 let hash = Self::targets_hash().expect("must exist; qed");
976 ensure!(hash == T::Hashing::hash(&targets.encode()), "targets hash mismatch");
977 }
978 Ok(())
979 }
980
981 pub(crate) fn ensure_voter_snapshot(
983 exists: bool,
984 mut up_to_page: PageIndex,
985 ) -> Result<(), &'static str> {
986 up_to_page = up_to_page.min(T::Pages::get());
987 let mut sum_existing_voters: usize = 0;
989 for p in (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp())
990 .rev()
991 .take(up_to_page as usize)
992 {
993 ensure!(
994 (exists ^ Self::voters(p).is_none()) &&
995 (exists ^ Self::voters_hash(p).is_none()),
996 "voter page existence mismatch"
997 );
998
999 if let Some(voters_page) = Self::voters(p) {
1000 sum_existing_voters = sum_existing_voters.saturating_add(voters_page.len());
1001 let hash = Self::voters_hash(p).expect("must exist; qed");
1002 ensure!(hash == T::Hashing::hash(&voters_page.encode()), "voter hash mismatch");
1003 }
1004 }
1005
1006 for p in (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp())
1008 .take((T::Pages::get() - up_to_page) as usize)
1009 {
1010 ensure!(
1011 (exists ^ Self::voters(p).is_some()) &&
1012 (exists ^ Self::voters_hash(p).is_some()),
1013 "voter page non-existence mismatch"
1014 );
1015 }
1016 Ok(())
1017 }
1018
1019 pub(crate) fn ensure_snapshot(
1020 exists: bool,
1021 mut up_to_page: PageIndex,
1022 ) -> Result<(), &'static str> {
1023 Self::ensure_target_snapshot(exists)
1024 .and_then(|_| Self::ensure_voter_snapshot(exists, up_to_page))
1025 }
1026
1027 pub(crate) fn ensure_full_snapshot() -> Result<(), &'static str> {
1028 ensure!(Self::desired_targets().is_some(), "desired target mismatch");
1030 ensure!(Self::targets_hash().is_some(), "targets hash mismatch");
1031 ensure!(
1032 Self::targets_decode_len().unwrap_or_default() as u32 ==
1033 T::TargetSnapshotPerBlock::get(),
1034 "targets decode length mismatch"
1035 );
1036
1037 for p in crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp() {
1039 ensure!(
1040 Self::voters_hash(p).is_some() &&
1041 Self::voters_decode_len(p).unwrap_or_default() as u32 ==
1042 T::VoterSnapshotPerBlock::get(),
1043 "voter page existence mismatch"
1044 );
1045 }
1046
1047 Ok(())
1048 }
1049
1050 pub(crate) fn voters_decode_len(page: PageIndex) -> Option<usize> {
1051 PagedVoterSnapshot::<T>::decode_len(Self::round(), page)
1052 }
1053
1054 pub(crate) fn targets_decode_len() -> Option<usize> {
1055 PagedTargetSnapshot::<T>::decode_len(Self::round(), Pallet::<T>::msp())
1056 }
1057
1058 pub(crate) fn voters_hash(page: PageIndex) -> Option<T::Hash> {
1059 PagedVoterSnapshotHash::<T>::get(Self::round(), page)
1060 }
1061
1062 pub(crate) fn sanity_check() -> Result<(), &'static str> {
1063 let phase = Pallet::<T>::current_phase();
1066 let _ = match phase {
1067 Phase::Off => Self::ensure_snapshot(false, T::Pages::get()),
1069
1070 Phase::Snapshot(p) if p == T::Pages::get() =>
1072 Self::ensure_snapshot(false, T::Pages::get()),
1073 Phase::Snapshot(p) if p < T::Pages::get() && p > 0 =>
1075 Self::ensure_snapshot(true, T::Pages::get() - p - 1),
1076 Phase::Snapshot(_) => Ok(()),
1078
1079 Phase::Emergency |
1081 Phase::Signed(_) |
1082 Phase::SignedValidation(_) |
1083 Phase::Export(_) |
1084 Phase::Done |
1085 Phase::Unsigned(_) => Self::ensure_snapshot(true, T::Pages::get()),
1086 }?;
1087
1088 Ok(())
1089 }
1090 }
1091
1092 #[cfg(test)]
1093 impl<T: Config> Snapshot<T> {
1094 pub(crate) fn voter_pages() -> PageIndex {
1095 use sp_runtime::SaturatedConversion;
1096 PagedVoterSnapshot::<T>::iter().count().saturated_into::<PageIndex>()
1097 }
1098
1099 pub(crate) fn target_pages() -> PageIndex {
1100 use sp_runtime::SaturatedConversion;
1101 PagedTargetSnapshot::<T>::iter().count().saturated_into::<PageIndex>()
1102 }
1103
1104 pub(crate) fn voters_iter_flattened() -> impl Iterator<Item = VoterOf<T::MinerConfig>> {
1105 let key_range =
1106 (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp()).collect::<Vec<_>>();
1107 key_range
1108 .into_iter()
1109 .flat_map(|k| PagedVoterSnapshot::<T>::get(Self::round(), k).unwrap_or_default())
1110 }
1111
1112 pub(crate) fn remove_voter_page(page: PageIndex) {
1113 PagedVoterSnapshot::<T>::remove(Self::round(), page);
1114 }
1115
1116 pub(crate) fn kill_desired_targets() {
1117 DesiredTargets::<T>::remove(Self::round());
1118 }
1119
1120 pub(crate) fn remove_target_page() {
1121 PagedTargetSnapshot::<T>::remove(Self::round(), Pallet::<T>::msp());
1122 }
1123
1124 pub(crate) fn remove_target(at: usize) {
1125 PagedTargetSnapshot::<T>::mutate(
1126 Self::round(),
1127 crate::Pallet::<T>::msp(),
1128 |maybe_targets| {
1129 if let Some(targets) = maybe_targets {
1130 targets.remove(at);
1131 PagedTargetSnapshotHash::<T>::insert(
1133 Self::round(),
1134 crate::Pallet::<T>::msp(),
1135 T::Hashing::hash(&targets.encode()),
1136 )
1137 } else {
1138 unreachable!();
1139 }
1140 },
1141 )
1142 }
1143 }
1144
1145 #[pallet::storage]
1147 pub type DesiredTargets<T> = StorageMap<_, Twox64Concat, u32, u32>;
1148 #[pallet::storage]
1150 pub type PagedVoterSnapshot<T: Config> = StorageDoubleMap<
1151 _,
1152 Twox64Concat,
1153 u32,
1154 Twox64Concat,
1155 PageIndex,
1156 VoterPageOf<T::MinerConfig>,
1157 >;
1158 #[pallet::storage]
1162 pub type PagedVoterSnapshotHash<T: Config> =
1163 StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, PageIndex, T::Hash>;
1164 #[pallet::storage]
1168 pub type PagedTargetSnapshot<T: Config> = StorageDoubleMap<
1169 _,
1170 Twox64Concat,
1171 u32,
1172 Twox64Concat,
1173 PageIndex,
1174 BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
1175 >;
1176 #[pallet::storage]
1180 pub type PagedTargetSnapshotHash<T: Config> =
1181 StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, PageIndex, T::Hash>;
1182
1183 #[pallet::pallet]
1184 pub struct Pallet<T>(PhantomData<T>);
1185}
1186
1187impl<T: Config> Pallet<T> {
1188 fn msp() -> PageIndex {
1192 T::Pages::get().checked_sub(1).defensive_unwrap_or_default()
1193 }
1194
1195 fn lsp() -> PageIndex {
1199 Zero::zero()
1200 }
1201
1202 pub fn msp_range_for(length: usize) -> Vec<PageIndex> {
1208 (Self::lsp()..Self::msp() + 1).rev().take(length).rev().collect::<Vec<_>>()
1209 }
1210
1211 pub(crate) fn phase_transition(to: Phase<T>) {
1212 let from = Self::current_phase();
1213 use sp_std::mem::discriminant;
1214 if discriminant(&from) != discriminant(&to) {
1215 log!(debug, "transitioning phase from {:?} to {:?}", from, to);
1216 Self::deposit_event(Event::PhaseTransitioned { from, to });
1217 } else {
1218 log!(trace, "transitioning phase from {:?} to {:?}", from, to);
1219 }
1220 <CurrentPhase<T>>::put(to);
1221 }
1222
1223 pub(crate) fn snapshot_independent_checks(
1235 paged_solution: &PagedRawSolution<T::MinerConfig>,
1236 maybe_snapshot_fingerprint: Option<T::Hash>,
1237 ) -> Result<(), CommonError> {
1238 ensure!(Self::round() == paged_solution.round, CommonError::WrongRound);
1245
1246 ensure!(
1248 <T::Verifier as Verifier>::ensure_claimed_score_improves(paged_solution.score),
1249 CommonError::WeakSubmission,
1250 );
1251
1252 ensure!(
1254 paged_solution.solution_pages.len().saturated_into::<PageIndex>() <= T::Pages::get(),
1255 CommonError::WrongPageCount
1256 );
1257
1258 if let Some(desired_targets) = Snapshot::<T>::desired_targets() {
1260 ensure!(
1261 desired_targets == paged_solution.winner_count_single_page_target_snapshot() as u32,
1262 CommonError::WrongWinnerCount
1263 )
1264 }
1265
1266 ensure!(
1268 maybe_snapshot_fingerprint
1269 .map_or(true, |snapshot_fingerprint| Snapshot::<T>::fingerprint() ==
1270 snapshot_fingerprint),
1271 CommonError::WrongFingerprint
1272 );
1273
1274 Ok(())
1275 }
1276
1277 pub(crate) fn create_targets_snapshot() {
1282 let desired_targets = match T::DataProvider::desired_targets() {
1284 Ok(targets) => targets,
1285 Err(e) => {
1286 Self::deposit_event(Event::UnexpectedTargetSnapshotFailed);
1287 defensive!("Failed to get desired targets: {:?}", e);
1288 return;
1289 },
1290 };
1291 Snapshot::<T>::set_desired_targets(desired_targets);
1292
1293 let count = T::TargetSnapshotPerBlock::get();
1294 let bounds = DataProviderBounds { count: Some(count.into()), size: None };
1295 let targets: BoundedVec<_, T::TargetSnapshotPerBlock> =
1296 match T::DataProvider::electable_targets(bounds, 0)
1297 .and_then(|v| v.try_into().map_err(|_| "try-into failed"))
1298 {
1299 Ok(targets) => targets,
1300 Err(e) => {
1301 Self::deposit_event(Event::UnexpectedTargetSnapshotFailed);
1302 defensive!("Failed to create target snapshot: {:?}", e);
1303 return;
1304 },
1305 };
1306
1307 let count = targets.len() as u32;
1308 log!(debug, "created target snapshot with {} targets.", count);
1309 Snapshot::<T>::set_targets(targets);
1310 }
1311
1312 pub(crate) fn create_voters_snapshot_paged(remaining: PageIndex) {
1317 let count = T::VoterSnapshotPerBlock::get();
1318 let bounds = DataProviderBounds { count: Some(count.into()), size: None };
1319 let voters: BoundedVec<_, T::VoterSnapshotPerBlock> =
1320 match T::DataProvider::electing_voters(bounds, remaining)
1321 .and_then(|v| v.try_into().map_err(|_| "try-into failed"))
1322 {
1323 Ok(voters) => voters,
1324 Err(e) => {
1325 Self::deposit_event(Event::UnexpectedVoterSnapshotFailed);
1326 defensive!("Failed to create voter snapshot: {:?}", e);
1327 return;
1328 },
1329 };
1330
1331 let count = voters.len() as u32;
1332 Snapshot::<T>::set_voters(remaining, voters);
1333 log!(debug, "created voter snapshot with {} voters, {} remaining.", count, remaining);
1334 }
1335
1336 pub(crate) fn rotate_round() {
1342 <Round<T>>::mutate(|r| {
1344 T::OnRoundRotation::on_round_rotation(*r);
1346 *r += 1
1347 });
1348
1349 Self::phase_transition(Phase::Off);
1351 }
1352
1353 fn fallback_for_page(page: PageIndex) -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
1359 use frame_election_provider_support::InstantElectionProvider;
1360 let (voters, targets, desired_targets) = if T::Fallback::bother() {
1361 (
1362 Snapshot::<T>::voters(page).ok_or(ElectionError::Other("snapshot!"))?,
1363 Snapshot::<T>::targets().ok_or(ElectionError::Other("snapshot!"))?,
1364 Snapshot::<T>::desired_targets().ok_or(ElectionError::Other("snapshot!"))?,
1365 )
1366 } else {
1367 (Default::default(), Default::default(), Default::default())
1368 };
1369 T::Fallback::instant_elect(voters.into_inner(), targets.into_inner(), desired_targets)
1370 .map_err(|fe| ElectionError::Fallback(fe))
1371 }
1372
1373 pub fn average_election_duration() -> u32 {
1375 let signed: u32 = T::SignedPhase::get().saturated_into();
1376 let unsigned: u32 = T::UnsignedPhase::get().saturated_into();
1377 let signed_validation: u32 = T::SignedValidationPhase::get().saturated_into();
1378 let snapshot = T::Pages::get();
1379
1380 let _export = T::Pages::get();
1382
1383 snapshot + signed + signed_validation + unsigned
1384 }
1385
1386 #[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
1387 pub(crate) fn do_try_state(_: BlockNumberFor<T>) -> Result<(), &'static str> {
1388 Snapshot::<T>::sanity_check()
1389 }
1390}
1391
1392#[allow(unused)]
1393#[cfg(any(feature = "runtime-benchmarks", test))]
1394impl<T> Pallet<T>
1396where
1397 T: Config + crate::signed::Config + crate::unsigned::Config + crate::verifier::Config,
1398 BlockNumberFor<T>: From<u32>,
1399{
1400 pub(crate) fn roll_until_matches(criteria: impl FnOnce() -> bool + Copy) {
1402 loop {
1403 Self::roll_next(true, false);
1404 if criteria() {
1405 break;
1406 }
1407 }
1408 }
1409
1410 pub(crate) fn roll_until_before_matches(criteria: impl FnOnce() -> bool + Copy) {
1412 use frame_support::storage::TransactionOutcome;
1413 loop {
1414 let should_break = frame_support::storage::with_transaction(
1415 || -> TransactionOutcome<Result<_, DispatchError>> {
1416 Pallet::<T>::roll_next(true, false);
1417 if criteria() {
1418 TransactionOutcome::Rollback(Ok(true))
1419 } else {
1420 TransactionOutcome::Commit(Ok(false))
1421 }
1422 },
1423 )
1424 .unwrap();
1425
1426 if should_break {
1427 break;
1428 }
1429 }
1430 }
1431
1432 pub(crate) fn roll_to_signed_and_mine_full_solution() -> PagedRawSolution<T::MinerConfig> {
1433 use unsigned::miner::OffchainWorkerMiner;
1434 Self::roll_until_matches(|| Self::current_phase().is_signed());
1435 crate::Snapshot::<T>::ensure_full_snapshot().expect("Snapshot is not full");
1437 OffchainWorkerMiner::<T>::mine_solution(T::Pages::get(), false)
1438 .expect("mine_solution failed")
1439 }
1440
1441 pub(crate) fn submit_full_solution(
1442 PagedRawSolution { score, solution_pages, .. }: PagedRawSolution<T::MinerConfig>,
1443 ) -> DispatchResultWithPostInfo {
1444 use frame_system::RawOrigin;
1445 use sp_std::boxed::Box;
1446 use types::Pagify;
1447
1448 let alice = crate::Pallet::<T>::funded_account("alice", 0);
1450 signed::Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
1451
1452 for (index, page) in solution_pages.pagify(T::Pages::get()) {
1454 signed::Pallet::<T>::submit_page(
1455 RawOrigin::Signed(alice.clone()).into(),
1456 index,
1457 Some(Box::new(page.clone())),
1458 )
1459 .inspect_err(|&e| {
1460 log!(error, "submit_page {:?} failed: {:?}", page, e);
1461 })?;
1462 }
1463
1464 Ok(().into())
1465 }
1466
1467 pub(crate) fn roll_to_signed_and_submit_full_solution() -> DispatchResultWithPostInfo {
1468 Self::submit_full_solution(Self::roll_to_signed_and_mine_full_solution())
1469 }
1470
1471 fn funded_account(seed: &'static str, index: u32) -> T::AccountId {
1472 use frame_benchmarking::whitelist;
1473 use frame_support::traits::fungible::{Inspect, Mutate};
1474 let who: T::AccountId = frame_benchmarking::account(seed, index, 777);
1475 whitelist!(who);
1476
1477 let worst_case_deposit = {
1483 let max_queue_size = T::MaxSubmissions::get() as usize;
1484 let base = T::DepositBase::calculate_base_deposit(max_queue_size);
1485 let pages =
1486 T::DepositPerPage::calculate_page_deposit(max_queue_size, T::Pages::get() as usize);
1487 base.saturating_add(pages)
1488 };
1489
1490 let min_balance = T::Currency::minimum_balance();
1493 let num_operations = 1u32.saturating_add(T::Pages::get()); let tx_fee_buffer = (min_balance / 100u32.into()).saturating_mul(num_operations.into());
1495
1496 let total_needed = worst_case_deposit
1497 .saturating_add(tx_fee_buffer)
1498 .saturating_add(T::Currency::minimum_balance());
1499
1500 T::Currency::mint_into(&who, total_needed).unwrap();
1501 who
1502 }
1503
1504 pub(crate) fn roll_to(n: BlockNumberFor<T>, with_signed: bool, try_state: bool) {
1506 let now = frame_system::Pallet::<T>::block_number();
1507 assert!(n > now, "cannot roll to current or past block");
1508 let one: BlockNumberFor<T> = 1u32.into();
1509 let mut i = now + one;
1510 while i <= n {
1511 frame_system::Pallet::<T>::set_block_number(i);
1512
1513 Pallet::<T>::on_initialize(i);
1514 verifier::Pallet::<T>::on_initialize(i);
1515 unsigned::Pallet::<T>::on_initialize(i);
1516
1517 if with_signed {
1518 signed::Pallet::<T>::on_initialize(i);
1519 }
1520
1521 if try_state {
1523 Pallet::<T>::do_try_state(i).unwrap();
1524 verifier::Pallet::<T>::do_try_state(i).unwrap();
1525 unsigned::Pallet::<T>::do_try_state(i).unwrap();
1526 signed::Pallet::<T>::do_try_state(i).unwrap();
1527 }
1528
1529 i += one;
1530 }
1531 }
1532
1533 pub(crate) fn roll_next(with_signed: bool, try_state: bool) {
1535 Self::roll_to(
1536 frame_system::Pallet::<T>::block_number() + 1u32.into(),
1537 with_signed,
1538 try_state,
1539 );
1540 }
1541}
1542
1543impl<T: Config> ElectionProvider for Pallet<T> {
1544 type AccountId = T::AccountId;
1545 type BlockNumber = BlockNumberFor<T>;
1546 type Error = ElectionError<T>;
1547 type DataProvider = T::DataProvider;
1548 type Pages = T::Pages;
1549 type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
1550 type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
1551 type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
1552
1553 fn elect(remaining: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
1554 match Self::status() {
1555 Ok(_) => (),
1557 Err(_) => return Err(ElectionError::NotOngoing),
1558 }
1559
1560 let current_phase = CurrentPhase::<T>::get();
1561 if let Phase::Export(expected) = current_phase {
1562 ensure!(expected == remaining, ElectionError::OutOfOrder);
1563 }
1564
1565 let result = T::Verifier::get_queued_solution_page(remaining)
1566 .ok_or(ElectionError::SupportPageNotAvailable)
1567 .or_else(|err: ElectionError<T>| {
1568 log!(
1569 debug,
1570 "primary election for page {} failed due to: {:?}, trying fallback",
1571 remaining,
1572 err,
1573 );
1574 Self::fallback_for_page(remaining)
1575 })
1576 .map_err(|err| {
1577 log!(debug, "fallback also ({:?}) failed for page {:?}", err, remaining);
1582 err
1583 })
1584 .map(|supports| {
1585 supports.into()
1587 });
1588
1589 if CurrentPhase::<T>::get().is_emergency() && result.is_err() {
1591 log!(error, "Emergency phase triggered, halting the election.");
1592 } else {
1593 if remaining.is_zero() {
1594 Self::rotate_round()
1595 } else {
1596 Self::phase_transition(Phase::Export(remaining - 1))
1597 }
1598 }
1599
1600 result
1601 }
1602
1603 fn start() -> Result<(), Self::Error> {
1604 match Self::status() {
1605 Err(()) => (),
1606 Ok(_) => return Err(ElectionError::Ongoing),
1607 }
1608
1609 Self::phase_transition(Phase::<T>::start_phase());
1610 Ok(())
1611 }
1612
1613 fn duration() -> Self::BlockNumber {
1614 Self::average_election_duration().into()
1615 }
1616
1617 fn status() -> Result<bool, ()> {
1618 match <CurrentPhase<T>>::get() {
1619 Phase::Off => Err(()),
1621
1622 Phase::Signed(_) |
1624 Phase::SignedValidation(_) |
1625 Phase::Unsigned(_) |
1626 Phase::Snapshot(_) |
1627 Phase::Emergency => Ok(false),
1628
1629 Phase::Done | Phase::Export(_) => Ok(true),
1631 }
1632 }
1633
1634 #[cfg(feature = "runtime-benchmarks")]
1635 fn asap() {
1636 Self::create_targets_snapshot();
1638 for p in (Self::lsp()..=Self::msp()).rev() {
1639 Self::create_voters_snapshot_paged(p)
1640 }
1641 }
1642}
1643
1644#[cfg(test)]
1645mod phase_rotation {
1646 use super::{Event, *};
1647 use crate::{mock::*, Phase};
1648 use frame_election_provider_support::ElectionProvider;
1649
1650 #[test]
1651 fn single_page() {
1652 ExtBuilder::full()
1653 .pages(1)
1654 .election_start(13)
1655 .fallback_mode(FallbackModes::Onchain)
1656 .build_and_execute(|| {
1657 assert_eq!(System::block_number(), 0);
1662 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1663 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 1));
1664 assert_eq!(MultiBlock::round(), 0);
1665
1666 roll_to(4);
1667 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1668 assert_eq!(MultiBlock::round(), 0);
1669
1670 roll_to(13);
1671 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
1672 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
1673
1674 roll_to(14);
1675 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
1676 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
1677
1678 roll_to(15);
1679 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
1680 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
1681 assert_eq!(MultiBlock::round(), 0);
1682
1683 assert_eq!(
1684 multi_block_events_since_last_call(),
1685 vec![
1686 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) },
1687 Event::PhaseTransitioned {
1688 from: Phase::Snapshot(0),
1689 to: Phase::Signed(SignedPhase::get() - 1)
1690 }
1691 ]
1692 );
1693
1694 roll_to(19);
1695 assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
1696 assert_eq!(MultiBlock::round(), 0);
1697
1698 roll_to(20);
1699 assert_eq!(
1700 MultiBlock::current_phase(),
1701 Phase::SignedValidation(SignedValidationPhase::get())
1702 );
1703 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
1704 assert_eq!(MultiBlock::round(), 0);
1705
1706 assert_eq!(
1707 multi_block_events_since_last_call(),
1708 vec![Event::PhaseTransitioned {
1709 from: Phase::Signed(0),
1710 to: Phase::SignedValidation(SignedValidationPhase::get())
1711 }],
1712 );
1713
1714 roll_to(26);
1715 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
1716 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
1717 assert_eq!(MultiBlock::round(), 0);
1718
1719 roll_to(27);
1720 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
1721 assert_eq!(
1722 multi_block_events_since_last_call(),
1723 vec![Event::PhaseTransitioned {
1724 from: Phase::SignedValidation(0),
1725 to: Phase::Unsigned(UnsignedPhase::get() - 1)
1726 }],
1727 );
1728
1729 roll_to(31);
1730 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
1731
1732 roll_to(32);
1734 assert!(MultiBlock::current_phase().is_done());
1735
1736 roll_to(33);
1738 assert!(MultiBlock::current_phase().is_done());
1739
1740 roll_to(34);
1742 assert!(MultiBlock::current_phase().is_done());
1743 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
1744
1745 MultiBlock::elect(0).unwrap();
1746
1747 assert!(MultiBlock::current_phase().is_off());
1748 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 1));
1749 assert_eq!(MultiBlock::round(), 1);
1750
1751 roll_to(42);
1752 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1753 })
1754 }
1755
1756 #[test]
1757 fn multi_page_2() {
1758 ExtBuilder::full()
1759 .pages(2)
1760 .fallback_mode(FallbackModes::Onchain)
1761 .election_start(12)
1762 .build_and_execute(|| {
1763 assert_eq!(System::block_number(), 0);
1768 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1769 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
1770 assert_eq!(MultiBlock::round(), 0);
1771
1772 roll_to(4);
1773 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1774 assert_eq!(MultiBlock::round(), 0);
1775
1776 roll_to(11);
1777 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1778 assert_eq!(MultiBlock::round(), 0);
1779
1780 roll_to(12);
1781 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
1782 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
1783
1784 roll_to(13);
1785 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
1786 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
1787
1788 roll_to(14);
1789 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
1790 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
1791
1792 roll_to(15);
1793 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1794 assert_eq!(MultiBlock::round(), 0);
1795 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
1796
1797 assert_eq!(
1798 multi_block_events_since_last_call(),
1799 vec![
1800 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) },
1801 Event::PhaseTransitioned {
1802 from: Phase::Snapshot(0),
1803 to: Phase::Signed(SignedPhase::get() - 1)
1804 }
1805 ]
1806 );
1807
1808 roll_to(19);
1809 assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
1810 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1811 assert_eq!(MultiBlock::round(), 0);
1812
1813 roll_to(20);
1814 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1815 assert_eq!(MultiBlock::round(), 0);
1816 assert_eq!(
1817 MultiBlock::current_phase(),
1818 Phase::SignedValidation(SignedValidationPhase::get())
1819 );
1820
1821 assert_eq!(
1822 multi_block_events_since_last_call(),
1823 vec![Event::PhaseTransitioned {
1824 from: Phase::Signed(0),
1825 to: Phase::SignedValidation(SignedValidationPhase::get())
1826 }],
1827 );
1828
1829 roll_to(26);
1830 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
1831 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1832 assert_eq!(MultiBlock::round(), 0);
1833
1834 roll_to(27);
1835 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
1836 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1837 assert_eq!(MultiBlock::round(), 0);
1838
1839 assert_eq!(
1840 multi_block_events_since_last_call(),
1841 vec![Event::PhaseTransitioned {
1842 from: Phase::SignedValidation(0),
1843 to: Phase::Unsigned(UnsignedPhase::get() - 1)
1844 }],
1845 );
1846
1847 roll_to(31);
1848 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
1849 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1850
1851 roll_to(32);
1852 assert_eq!(MultiBlock::current_phase(), Phase::Done);
1853 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1854
1855 roll_to(33);
1857 assert_eq!(MultiBlock::current_phase(), Phase::Done);
1858
1859 MultiBlock::elect(0).unwrap();
1861 assert!(MultiBlock::current_phase().is_off());
1862
1863 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
1865 assert_eq!(MultiBlock::round(), 1);
1866 })
1867 }
1868
1869 #[test]
1870 fn multi_page_3() {
1871 ExtBuilder::full()
1872 .pages(3)
1873 .fallback_mode(FallbackModes::Onchain)
1874 .build_and_execute(|| {
1875 assert_eq!(System::block_number(), 0);
1880 assert!(MultiBlock::current_phase().is_off());
1881 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
1882 assert_eq!(MultiBlock::round(), 0);
1883
1884 roll_to(10);
1885 assert!(MultiBlock::current_phase().is_off());
1886 assert_eq!(MultiBlock::round(), 0);
1887
1888 roll_to(11);
1889 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
1890 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
1892
1893 roll_to(12);
1894 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
1895 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
1896
1897 roll_to(13);
1898 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
1899 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
1900
1901 roll_to(14);
1902 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
1903 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
1904
1905 roll_to(15);
1906 assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, Pages::get()));
1907 assert_eq!(MultiBlock::current_phase(), Phase::Signed(4));
1908 assert_eq!(
1909 multi_block_events_since_last_call(),
1910 vec![
1911 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
1912 Event::PhaseTransitioned {
1913 from: Phase::Snapshot(0),
1914 to: Phase::Signed(SignedPhase::get() - 1)
1915 }
1916 ]
1917 );
1918 assert_eq!(MultiBlock::round(), 0);
1919
1920 roll_to(19);
1921 assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
1922 assert_eq!(MultiBlock::round(), 0);
1923
1924 roll_to(20);
1925 assert_eq!(
1926 MultiBlock::current_phase(),
1927 Phase::SignedValidation(SignedValidationPhase::get())
1928 );
1929 assert_eq!(
1930 multi_block_events_since_last_call(),
1931 vec![Event::PhaseTransitioned {
1932 from: Phase::Signed(0),
1933 to: Phase::SignedValidation(SignedValidationPhase::get())
1934 }]
1935 );
1936
1937 roll_to(26);
1938 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
1939 assert_eq!(MultiBlock::round(), 0);
1940
1941 roll_to(27);
1942 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
1943 assert_eq!(
1944 multi_block_events_since_last_call(),
1945 vec![Event::PhaseTransitioned {
1946 from: Phase::SignedValidation(0),
1947 to: Phase::Unsigned(UnsignedPhase::get() - 1)
1948 }]
1949 );
1950
1951 roll_to(31);
1952 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
1953
1954 roll_to(32);
1955 assert_eq!(MultiBlock::current_phase(), Phase::Done);
1956
1957 roll_to(33);
1959 assert_eq!(MultiBlock::current_phase(), Phase::Done);
1960
1961 MultiBlock::elect(0).unwrap();
1962 assert!(MultiBlock::current_phase().is_off());
1963
1964 assert_none_snapshot();
1966 assert_eq!(MultiBlock::round(), 1);
1967 })
1968 }
1969
1970 #[test]
1971 fn no_unsigned_phase() {
1972 ExtBuilder::full()
1973 .pages(3)
1974 .unsigned_phase(0)
1975 .election_start(16)
1976 .fallback_mode(FallbackModes::Onchain)
1977 .build_and_execute(|| {
1978 assert_eq!(System::block_number(), 0);
1983 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1984 assert_none_snapshot();
1985 assert_eq!(MultiBlock::round(), 0);
1986
1987 roll_to(4);
1988 assert_eq!(MultiBlock::current_phase(), Phase::Off);
1989 assert_eq!(MultiBlock::round(), 0);
1990
1991 roll_to(16);
1992 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
1993
1994 roll_to(17);
1995 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
1996
1997 roll_to(18);
1998 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
1999
2000 roll_to(19);
2001 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2002
2003 roll_to(20);
2004 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2005
2006 assert_full_snapshot();
2007 assert_eq!(MultiBlock::round(), 0);
2008
2009 roll_to(25);
2010 assert_eq!(
2011 MultiBlock::current_phase(),
2012 Phase::SignedValidation(SignedValidationPhase::get())
2013 );
2014
2015 assert_eq!(
2016 multi_block_events_since_last_call(),
2017 vec![
2018 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2019 Event::PhaseTransitioned {
2020 from: Phase::Snapshot(0),
2021 to: Phase::Signed(SignedPhase::get() - 1)
2022 },
2023 Event::PhaseTransitioned {
2024 from: Phase::Signed(0),
2025 to: Phase::SignedValidation(SignedValidationPhase::get())
2026 },
2027 ]
2028 );
2029
2030 roll_to(31);
2032 assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2033
2034 roll_to(32);
2036 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2037
2038 roll_to(33);
2039 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2040
2041 MultiBlock::elect(0).unwrap();
2042 assert!(MultiBlock::current_phase().is_off());
2043
2044 assert_none_snapshot();
2046 assert_eq!(MultiBlock::round(), 1);
2047 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2048 verifier::QueuedSolution::<Runtime>::assert_killed();
2049 })
2050 }
2051
2052 #[test]
2053 fn no_signed_phase() {
2054 ExtBuilder::full()
2055 .pages(3)
2056 .signed_phase(0, 0)
2057 .election_start(21)
2058 .fallback_mode(FallbackModes::Onchain)
2059 .build_and_execute(|| {
2060 assert_eq!(System::block_number(), 0);
2065 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2066 assert_none_snapshot();
2067 assert_eq!(MultiBlock::round(), 0);
2068
2069 roll_to(20);
2070 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2071 assert_eq!(MultiBlock::round(), 0);
2072
2073 roll_to(21);
2074 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2075 roll_to(22);
2076 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2077 roll_to(23);
2078 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2079 roll_to(24);
2080 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2081
2082 roll_to(25);
2083 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2084 assert_full_snapshot();
2085 assert_eq!(MultiBlock::round(), 0);
2086
2087 assert_eq!(
2088 multi_block_events(),
2089 vec![
2090 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2091 Event::PhaseTransitioned {
2092 from: Phase::Snapshot(0),
2093 to: Phase::Unsigned(UnsignedPhase::get() - 1)
2094 },
2095 ]
2096 );
2097
2098 roll_to(29);
2099 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2100
2101 roll_to(30);
2102 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2103 roll_to(31);
2104 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2105
2106 MultiBlock::elect(0).unwrap();
2108 assert!(MultiBlock::current_phase().is_off());
2109
2110 assert_none_snapshot();
2112 assert_eq!(MultiBlock::round(), 1);
2113 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2114 verifier::QueuedSolution::<Runtime>::assert_killed();
2115 })
2116 }
2117
2118 #[test]
2119 #[should_panic(expected = "either signed or unsigned phase must be set")]
2120 fn no_signed_and_unsigned_phase() {
2121 ExtBuilder::full()
2122 .pages(3)
2123 .signed_phase(0, 0)
2124 .unsigned_phase(0)
2125 .election_start(10)
2126 .fallback_mode(FallbackModes::Onchain)
2127 .build_and_execute(|| {
2128 });
2130 }
2131
2132 #[test]
2133 #[should_panic(
2134 expected = "signed validation phase should be a multiple of the number of pages."
2135 )]
2136 fn incorrect_signed_validation_phase_shorter_than_number_of_pages() {
2137 ExtBuilder::full().pages(3).signed_validation_phase(2).build_and_execute(|| {})
2138 }
2139
2140 #[test]
2141 #[should_panic(
2142 expected = "signed validation phase should be a multiple of the number of pages."
2143 )]
2144 fn incorret_signed_validation_phase_not_a_multiple_of_the_number_of_pages() {
2145 ExtBuilder::full().pages(3).signed_validation_phase(7).build_and_execute(|| {})
2146 }
2147
2148 #[test]
2149 fn are_we_done_back_to_signed() {
2150 ExtBuilder::full()
2151 .are_we_done(AreWeDoneModes::BackToSigned)
2152 .build_and_execute(|| {
2153 roll_to_last_unsigned();
2155
2156 assert_eq!(MultiBlock::round(), 0);
2157 assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2158 assert_eq!(
2159 multi_block_events_since_last_call(),
2160 vec![
2161 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2162 Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed(4) },
2163 Event::PhaseTransitioned {
2164 from: Phase::Signed(0),
2165 to: Phase::SignedValidation(SignedValidationPhase::get())
2166 },
2167 Event::PhaseTransitioned {
2168 from: Phase::SignedValidation(0),
2169 to: Phase::Unsigned(4)
2170 }
2171 ]
2172 );
2173
2174 roll_next();
2175 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2177 assert_eq!(MultiBlock::round(), 0);
2179
2180 roll_next();
2182 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 2));
2183
2184 roll_next();
2185 assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 3));
2186 });
2187 }
2188
2189 #[test]
2190 fn export_phase_only_transitions_on_elect() {
2191 ExtBuilder::full()
2192 .pages(3)
2193 .election_start(13)
2194 .fallback_mode(FallbackModes::Onchain)
2195 .build_and_execute(|| {
2196 roll_to_done();
2197
2198 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2199
2200 roll_next();
2202 assert_eq!(
2203 MultiBlock::current_phase(),
2204 Phase::Done,
2205 "Done phase should not auto-transition"
2206 );
2207
2208 assert_ok!(MultiBlock::elect(2)); assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2211
2212 roll_next();
2214 assert_eq!(
2215 MultiBlock::current_phase(),
2216 Phase::Export(1),
2217 "Export phase should not auto-transition"
2218 );
2219
2220 assert_ok!(MultiBlock::elect(1));
2222 assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2223
2224 roll_next();
2226 assert_eq!(
2227 MultiBlock::current_phase(),
2228 Phase::Export(0),
2229 "Export(0) should not auto-transition"
2230 );
2231
2232 assert_ok!(MultiBlock::elect(0));
2234 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2235 });
2236 }
2237
2238 #[test]
2239 fn export_phase_out_of_order_elect_fails() {
2240 ExtBuilder::full()
2241 .pages(3)
2242 .election_start(13)
2243 .fallback_mode(FallbackModes::Onchain)
2244 .build_and_execute(|| {
2245 roll_to_done();
2246
2247 assert_eq!(MultiBlock::current_phase(), Phase::Done);
2248
2249 assert_ok!(MultiBlock::elect(2)); assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2252
2253 assert_eq!(MultiBlock::elect(2), Err(ElectionError::OutOfOrder));
2255
2256 assert_eq!(MultiBlock::elect(0), Err(ElectionError::OutOfOrder));
2258
2259 assert_ok!(MultiBlock::elect(1));
2261 assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2262 });
2263 }
2264
2265 #[test]
2266 #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
2267 fn target_snapshot_failed_event_emitted() {
2268 ExtBuilder::full()
2269 .pages(2)
2270 .election_start(13)
2271 .build_and_execute(|| {
2272 let too_many_targets: Vec<AccountId> = (1..=100).collect();
2275 Targets::set(too_many_targets);
2276
2277 roll_to(13);
2278 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2279
2280 let _ = multi_block_events_since_last_call();
2282
2283 roll_to(14);
2286
2287 let events = multi_block_events_since_last_call();
2289 assert!(
2290 events.contains(&Event::UnexpectedTargetSnapshotFailed),
2291 "UnexpectedTargetSnapshotFailed event should have been emitted when target snapshot creation fails. Events: {:?}",
2292 events
2293 );
2294
2295 assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2297 });
2298 }
2299}
2300
2301#[cfg(test)]
2302mod election_provider {
2303 use super::*;
2304 use crate::{
2305 mock::*,
2306 unsigned::miner::OffchainWorkerMiner,
2307 verifier::{AsynchronousVerifier, Verifier},
2308 Phase,
2309 };
2310 use frame_election_provider_support::{BoundedSupport, BoundedSupports, ElectionProvider};
2311 use frame_support::{
2312 assert_storage_noop, testing_prelude::bounded_vec, unsigned::ValidateUnsigned,
2313 };
2314
2315 #[test]
2319 fn multi_page_elect_simple_works() {
2320 ExtBuilder::full().build_and_execute(|| {
2321 roll_to_signed_open();
2322 assert!(MultiBlock::current_phase().is_signed());
2323
2324 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2326 let score = paged.score;
2327
2328 load_signed_for_verification(99, paged);
2330
2331 roll_to_signed_validation_open();
2333
2334 assert_eq!(
2335 multi_block_events(),
2336 vec![
2337 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2338 Event::PhaseTransitioned {
2339 from: Phase::Snapshot(0),
2340 to: Phase::Signed(SignedPhase::get() - 1)
2341 },
2342 Event::PhaseTransitioned {
2343 from: Phase::Signed(0),
2344 to: Phase::SignedValidation(SignedValidationPhase::get())
2345 }
2346 ]
2347 );
2348 assert_eq!(verifier_events(), vec![]);
2349
2350 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2352 assert_eq!(<Runtime as crate::Config>::Verifier::status(), verifier::Status::Nothing);
2353
2354 roll_next();
2356 assert_eq!(
2357 <Runtime as crate::Config>::Verifier::status(),
2358 verifier::Status::Ongoing(2)
2359 );
2360 assert_eq!(verifier_events(), vec![]);
2361
2362 roll_next();
2364 assert_eq!(verifier_events(), vec![verifier::Event::Verified(2, 2)]);
2365
2366 roll_next();
2367 assert_eq!(
2368 verifier_events(),
2369 vec![verifier::Event::Verified(2, 2), verifier::Event::Verified(1, 2)]
2370 );
2371
2372 roll_next();
2373 assert_eq!(
2374 verifier_events(),
2375 vec![
2376 verifier::Event::Verified(2, 2),
2377 verifier::Event::Verified(1, 2),
2378 verifier::Event::Verified(0, 2),
2379 verifier::Event::Queued(score, None),
2380 ]
2381 );
2382
2383 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2385
2386 roll_to_unsigned_open();
2389
2390 assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2392 assert_eq!(MultiBlock::round(), 0);
2393 assert_full_snapshot();
2394
2395 let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp())
2397 .rev() .map(|page| {
2399 MultiBlock::elect(page as PageIndex).unwrap();
2400 if page == 0 {
2401 assert!(MultiBlock::current_phase().is_off())
2402 } else {
2403 assert!(MultiBlock::current_phase().is_export())
2404 }
2405 })
2406 .collect::<Vec<_>>();
2407
2408 verifier::QueuedSolution::<Runtime>::assert_killed();
2410 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2412 assert_eq!(Round::<Runtime>::get(), 1);
2414 assert_storage_noop!(Snapshot::<Runtime>::kill());
2416 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2420 });
2421 }
2422
2423 #[test]
2424 fn multi_page_elect_fast_track() {
2425 ExtBuilder::full().build_and_execute(|| {
2426 roll_to_signed_open();
2427 let round = MultiBlock::round();
2428 assert!(MultiBlock::current_phase().is_signed());
2429
2430 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2432 let score = paged.score;
2433 load_signed_for_verification_and_start(99, paged, 0);
2434
2435 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2437
2438 roll_next();
2440 roll_next();
2441 roll_next();
2442 roll_next();
2443
2444 assert_eq!(
2445 verifier_events(),
2446 vec![
2447 verifier::Event::Verified(2, 2),
2448 verifier::Event::Verified(1, 2),
2449 verifier::Event::Verified(0, 2),
2450 verifier::Event::Queued(score, None),
2451 ]
2452 );
2453
2454 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2456
2457 roll_to_unsigned_open();
2459
2460 assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2462 assert_eq!(Round::<Runtime>::get(), 0);
2463 assert_full_snapshot();
2464
2465 let _solution = crate::Pallet::<Runtime>::elect(0).unwrap();
2467
2468 assert_eq!(MultiBlock::round(), round + 1);
2470 verifier::QueuedSolution::<Runtime>::assert_killed();
2472 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2474 assert_eq!(Round::<Runtime>::get(), 1);
2476 assert_none_snapshot();
2478 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(round));
2480 });
2481 }
2482
2483 #[test]
2484 fn elect_does_not_finish_without_call_of_page_0() {
2485 ExtBuilder::full().build_and_execute(|| {
2486 roll_to_signed_open();
2487 assert!(MultiBlock::current_phase().is_signed());
2488
2489 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2491 let score = paged.score;
2492 load_signed_for_verification_and_start(99, paged, 0);
2493
2494 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2496
2497 roll_next();
2499 roll_next();
2500 roll_next();
2501 roll_next();
2502
2503 assert_eq!(
2504 verifier_events(),
2505 vec![
2506 verifier::Event::Verified(2, 2),
2507 verifier::Event::Verified(1, 2),
2508 verifier::Event::Verified(0, 2),
2509 verifier::Event::Queued(score, None),
2510 ]
2511 );
2512
2513 assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2515
2516 roll_to_unsigned_open();
2518
2519 assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2521 assert_eq!(Round::<Runtime>::get(), 0);
2522 assert_full_snapshot();
2523
2524 let solutions = (1..=MultiBlock::msp())
2526 .rev() .map(|page| {
2528 crate::Pallet::<Runtime>::elect(page as PageIndex).unwrap();
2529 assert!(MultiBlock::current_phase().is_export());
2530 })
2531 .collect::<Vec<_>>();
2532 assert_eq!(solutions.len(), 2);
2533
2534 assert!(MultiBlock::current_phase().is_export());
2536 assert_eq!(Round::<Runtime>::get(), 0);
2537 assert_full_snapshot();
2538 });
2539 }
2540
2541 #[test]
2542 fn elect_advances_phase_even_on_error() {
2543 ExtBuilder::full().fallback_mode(FallbackModes::Continue).build_and_execute(|| {
2545 roll_to_unsigned_open();
2547
2548 let miner_pages = <Runtime as unsigned::Config>::MinerPages::get();
2550 let unsigned_solution =
2552 OffchainWorkerMiner::<Runtime>::mine_solution(miner_pages, true).unwrap();
2553
2554 assert_ok!(UnsignedPallet::submit_unsigned(
2556 RuntimeOrigin::none(),
2557 Box::new(unsigned_solution)
2558 ));
2559
2560 roll_to_done();
2562
2563 let result1 = MultiBlock::elect(2);
2567 assert!(result1.is_ok());
2568 assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2569
2570 let result2 = MultiBlock::elect(1);
2574 assert!(result2.is_err());
2575 assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2576
2577 let result3 = MultiBlock::elect(0);
2579 assert!(result3.is_err());
2580 assert!(matches!(MultiBlock::current_phase(), Phase::Off));
2581 });
2582 }
2583
2584 #[test]
2585 fn skip_unsigned_phase() {
2586 ExtBuilder::full().build_and_execute(|| {
2587 roll_to_signed_open();
2588 assert!(MultiBlock::current_phase().is_signed());
2589 let round = MultiBlock::round();
2590
2591 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2593
2594 load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
2595
2596 assert!(matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)));
2599 assert_eq!(Round::<Runtime>::get(), 0);
2600 assert_full_snapshot();
2601
2602 let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp())
2604 .rev() .map(|page| {
2606 MultiBlock::elect(page as PageIndex).unwrap();
2607 if page == 0 {
2608 assert!(MultiBlock::current_phase().is_off())
2609 } else {
2610 assert!(MultiBlock::current_phase().is_export())
2611 }
2612 })
2613 .collect::<Vec<_>>();
2614
2615 assert_eq!(MultiBlock::round(), round + 1);
2617 verifier::QueuedSolution::<Runtime>::assert_killed();
2619 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2621 assert_storage_noop!(Snapshot::<Runtime>::kill());
2623 assert_ok!(signed::Submissions::<Runtime>::ensure_killed(round));
2625 });
2626 }
2627
2628 #[test]
2629 fn call_to_elect_should_prevent_any_submission() {
2630 ExtBuilder::full().build_and_execute(|| {
2631 roll_to_signed_open();
2632 assert!(MultiBlock::current_phase().is_signed());
2633
2634 let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2636 load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
2637
2638 assert!(matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)));
2639
2640 assert!(MultiBlock::elect(MultiBlock::msp()).is_ok());
2642
2643 assert_noop!(
2645 SignedPallet::submit_page(RuntimeOrigin::signed(999), 0, Default::default()),
2646 crate::signed::Error::<Runtime>::PhaseNotSigned,
2647 );
2648 assert_noop!(
2649 SignedPallet::register(RuntimeOrigin::signed(999), Default::default()),
2650 crate::signed::Error::<Runtime>::PhaseNotSigned,
2651 );
2652 assert_storage_noop!(assert!(<UnsignedPallet as ValidateUnsigned>::pre_dispatch(
2653 &unsigned::Call::submit_unsigned { paged_solution: Default::default() }
2654 )
2655 .is_err()));
2656 });
2657 }
2658
2659 #[test]
2660 fn multi_page_onchain_elect_fallback_works() {
2661 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
2662 roll_to_signed_open();
2663
2664 assert_eq!(
2666 MultiBlock::elect(2).unwrap(),
2667 BoundedSupports(bounded_vec![
2668 (10, BoundedSupport { total: 15, voters: bounded_vec![(1, 10), (4, 5)] }),
2669 (
2670 40,
2671 BoundedSupport {
2672 total: 25,
2673 voters: bounded_vec![(2, 10), (3, 10), (4, 5)]
2674 }
2675 )
2676 ])
2677 );
2678 assert_eq!(
2680 MultiBlock::elect(1).unwrap(),
2681 BoundedSupports(bounded_vec![
2682 (10, BoundedSupport { total: 15, voters: bounded_vec![(5, 5), (8, 10)] }),
2683 (
2684 30,
2685 BoundedSupport {
2686 total: 25,
2687 voters: bounded_vec![(5, 5), (6, 10), (7, 10)]
2688 }
2689 )
2690 ])
2691 );
2692 assert_eq!(
2694 MultiBlock::elect(0).unwrap(),
2695 BoundedSupports(bounded_vec![
2696 (30, BoundedSupport { total: 30, voters: bounded_vec![(30, 30)] }),
2697 (40, BoundedSupport { total: 40, voters: bounded_vec![(40, 40)] })
2698 ])
2699 );
2700
2701 assert_eq!(
2702 multi_block_events(),
2703 vec![
2704 Event::PhaseTransitioned {
2705 from: Phase::Off,
2706 to: Phase::Snapshot(Pages::get())
2707 },
2708 Event::PhaseTransitioned {
2709 from: Phase::Snapshot(0),
2710 to: Phase::Signed(SignedPhase::get() - 1)
2711 },
2712 Event::PhaseTransitioned {
2713 from: Phase::Signed(SignedPhase::get() - 1),
2714 to: Phase::Export(1)
2715 },
2716 Event::PhaseTransitioned { from: Phase::Export(0), to: Phase::Off }
2717 ]
2718 );
2719 assert_eq!(verifier_events(), vec![]);
2720
2721 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2723 });
2724 }
2725
2726 #[test]
2727 fn multi_page_fallback_shortcut_to_msp_works() {
2728 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
2729 roll_to_signed_open();
2730
2731 assert!(MultiBlock::elect(0).is_ok());
2733
2734 assert_eq!(
2735 multi_block_events(),
2736 vec![
2737 Event::PhaseTransitioned {
2738 from: Phase::Off,
2739 to: Phase::Snapshot(Pages::get())
2740 },
2741 Event::PhaseTransitioned {
2742 from: Phase::Snapshot(0),
2743 to: Phase::Signed(SignedPhase::get() - 1)
2744 },
2745 Event::PhaseTransitioned {
2746 from: Phase::Signed(SignedPhase::get() - 1),
2747 to: Phase::Off
2748 }
2749 ]
2750 );
2751
2752 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2754 });
2755 }
2756
2757 #[test]
2758 #[should_panic]
2759 fn continue_fallback_works() {
2760 todo!()
2761 }
2762
2763 #[test]
2764 #[should_panic]
2765 fn emergency_fallback_works() {
2766 todo!();
2767 }
2768
2769 #[test]
2770 fn elect_call_when_not_ongoing() {
2771 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
2772 roll_to_snapshot_created();
2773 assert_eq!(MultiBlock::status(), Ok(false));
2774 assert!(MultiBlock::elect(0).is_ok());
2775 });
2776 ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
2777 roll_to(10);
2778 assert_eq!(MultiBlock::status(), Err(()));
2779 assert_eq!(MultiBlock::elect(0), Err(ElectionError::NotOngoing));
2780 });
2781 }
2782}
2783
2784#[cfg(test)]
2785mod admin_ops {
2786 use super::*;
2787 use crate::mock::*;
2788 use frame_support::assert_ok;
2789
2790 #[test]
2791 fn set_solution_emergency_works() {
2792 ExtBuilder::full().build_and_execute(|| {
2793 roll_to_signed_open();
2794
2795 assert_eq!(
2797 MultiBlock::elect(0),
2798 Err(ElectionError::Fallback("Emergency phase started.".to_string()))
2799 );
2800 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
2801
2802 let (emergency, score) = emergency_solution();
2804 assert_ok!(MultiBlock::manage(
2805 RuntimeOrigin::root(),
2806 AdminOperation::EmergencySetSolution(Box::new(emergency), score)
2807 ));
2808
2809 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
2810 assert_ok!(MultiBlock::elect(0));
2811 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2812
2813 assert_eq!(
2814 multi_block_events(),
2815 vec![
2816 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2817 Event::PhaseTransitioned {
2818 from: Phase::Snapshot(0),
2819 to: Phase::Signed(SignedPhase::get() - 1)
2820 },
2821 Event::PhaseTransitioned {
2822 from: Phase::Signed(SignedPhase::get() - 1),
2823 to: Phase::Emergency
2824 },
2825 Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off }
2826 ]
2827 );
2828 assert_eq!(
2829 verifier_events(),
2830 vec![verifier::Event::Queued(
2831 ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 },
2832 None
2833 )]
2834 );
2835 })
2836 }
2837
2838 #[test]
2839 fn trigger_fallback_works() {
2840 ExtBuilder::full()
2841 .fallback_mode(FallbackModes::Emergency)
2842 .build_and_execute(|| {
2843 roll_to_signed_open();
2844
2845 assert_eq!(
2848 MultiBlock::elect(0),
2849 Err(ElectionError::Fallback("Emergency phase started.".to_string()))
2850 );
2851 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
2852
2853 FallbackMode::set(FallbackModes::Onchain);
2855 assert_ok!(MultiBlock::manage(
2856 RuntimeOrigin::root(),
2857 AdminOperation::EmergencyFallback
2858 ));
2859
2860 assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
2861 assert_ok!(MultiBlock::elect(0));
2862 assert_eq!(MultiBlock::current_phase(), Phase::Off);
2863
2864 assert_eq!(
2865 multi_block_events(),
2866 vec![
2867 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2868 Event::PhaseTransitioned {
2869 from: Phase::Snapshot(0),
2870 to: Phase::Signed(SignedPhase::get() - 1)
2871 },
2872 Event::PhaseTransitioned {
2873 from: Phase::Signed(SignedPhase::get() - 1),
2874 to: Phase::Emergency
2875 },
2876 Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off }
2877 ]
2878 );
2879 assert_eq!(
2880 verifier_events(),
2881 vec![verifier::Event::Queued(
2882 ElectionScore { minimal_stake: 15, sum_stake: 40, sum_stake_squared: 850 },
2883 None
2884 )]
2885 );
2886 })
2887 }
2888
2889 #[test]
2890 #[should_panic]
2891 fn force_rotate_round() {
2892 todo!();
2895 }
2896
2897 #[test]
2898 fn set_minimum_solution_score() {
2899 ExtBuilder::full().build_and_execute(|| {
2900 assert_eq!(VerifierPallet::minimum_score(), None);
2901 assert_ok!(MultiBlock::manage(
2902 RuntimeOrigin::root(),
2903 AdminOperation::SetMinUntrustedScore(ElectionScore {
2904 minimal_stake: 100,
2905 ..Default::default()
2906 })
2907 ));
2908 assert_eq!(
2909 VerifierPallet::minimum_score().unwrap(),
2910 ElectionScore { minimal_stake: 100, ..Default::default() }
2911 );
2912 });
2913 }
2914}