1#![cfg_attr(not(feature = "std"), no_std)]
243
244extern crate alloc;
245
246use alloc::{boxed::Box, vec::Vec};
247use codec::{Decode, DecodeWithMemTracking, Encode};
248use frame_election_provider_support::{
249 bounds::{CountBound, ElectionBounds, SizeBound},
250 BoundedSupports, BoundedSupportsOf, ElectionDataProvider, ElectionProvider,
251 InstantElectionProvider, NposSolution, PageIndex,
252};
253use frame_support::{
254 dispatch::DispatchClass,
255 ensure,
256 traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
257 weights::Weight,
258 DefaultNoBound, EqNoBound, PartialEqNoBound,
259};
260use frame_system::{ensure_none, offchain::CreateBare, pallet_prelude::BlockNumberFor};
261use scale_info::TypeInfo;
262use sp_arithmetic::{
263 traits::{CheckedAdd, Zero},
264 UpperOf,
265};
266use sp_npos_elections::{ElectionScore, IdentifierT, Supports, VoteWeight};
267use sp_runtime::{
268 transaction_validity::{
269 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
270 TransactionValidityError, ValidTransaction,
271 },
272 DispatchError, ModuleError, PerThing, Perbill, RuntimeDebug, SaturatedConversion,
273};
274
275#[cfg(feature = "try-runtime")]
276use sp_runtime::TryRuntimeError;
277
278#[cfg(feature = "runtime-benchmarks")]
279mod benchmarking;
280#[cfg(test)]
281mod mock;
282#[cfg(all(test, feature = "remote-mining"))]
283mod remote_mining;
284#[macro_use]
285pub mod helpers;
286
287pub(crate) const SINGLE_PAGE: u32 = 0;
289const LOG_TARGET: &str = "runtime::election-provider";
290
291pub mod migrations;
292pub mod signed;
293pub mod unsigned;
294pub mod weights;
295
296pub use signed::{
297 BalanceOf, GeometricDepositBase, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission,
298 SignedSubmissionOf, SignedSubmissions, SubmissionIndicesOf,
299};
300use unsigned::VoterOf;
301pub use unsigned::{Miner, MinerConfig};
302pub use weights::WeightInfo;
303
304pub type SolutionOf<T> = <T as MinerConfig>::Solution;
306pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
308pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
310pub type SolutionAccuracyOf<T> =
312 <SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
313pub type ReadySolutionOf<T> = ReadySolution<
315 <T as MinerConfig>::AccountId,
316 <T as MinerConfig>::MaxWinners,
317 <T as MinerConfig>::MaxBackersPerWinner,
318>;
319pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;
321
322pub trait BenchmarkingConfig {
324 const VOTERS: [u32; 2];
326 const TARGETS: [u32; 2];
328 const ACTIVE_VOTERS: [u32; 2];
330 const DESIRED_TARGETS: [u32; 2];
332 const SNAPSHOT_MAXIMUM_VOTERS: u32;
334 const MINER_MAXIMUM_VOTERS: u32;
336 const MAXIMUM_TARGETS: u32;
338}
339
340#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
342pub enum Phase<Bn> {
343 Off,
345 Signed,
347 Unsigned((bool, Bn)),
358 Emergency,
362}
363
364impl<Bn> Default for Phase<Bn> {
365 fn default() -> Self {
366 Phase::Off
367 }
368}
369
370impl<Bn: PartialEq + Eq> Phase<Bn> {
371 pub fn is_emergency(&self) -> bool {
373 matches!(self, Phase::Emergency)
374 }
375
376 pub fn is_signed(&self) -> bool {
378 matches!(self, Phase::Signed)
379 }
380
381 pub fn is_unsigned(&self) -> bool {
383 matches!(self, Phase::Unsigned(_))
384 }
385
386 pub fn is_unsigned_open_at(&self, at: Bn) -> bool {
388 matches!(self, Phase::Unsigned((true, real)) if *real == at)
389 }
390
391 pub fn is_unsigned_open(&self) -> bool {
393 matches!(self, Phase::Unsigned((true, _)))
394 }
395
396 pub fn is_off(&self) -> bool {
398 matches!(self, Phase::Off)
399 }
400}
401
402#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
404pub enum ElectionCompute {
405 OnChain,
407 Signed,
409 Unsigned,
411 Fallback,
413 Emergency,
415}
416
417impl Default for ElectionCompute {
418 fn default() -> Self {
419 ElectionCompute::OnChain
420 }
421}
422
423#[derive(
430 PartialEq,
431 Eq,
432 Clone,
433 Encode,
434 Decode,
435 DecodeWithMemTracking,
436 RuntimeDebug,
437 PartialOrd,
438 Ord,
439 TypeInfo,
440)]
441pub struct RawSolution<S> {
442 pub solution: S,
444 pub score: ElectionScore,
446 pub round: u32,
448}
449
450impl<C: Default> Default for RawSolution<C> {
451 fn default() -> Self {
452 Self { round: 1, solution: Default::default(), score: Default::default() }
454 }
455}
456
457#[derive(
459 PartialEqNoBound,
460 EqNoBound,
461 Clone,
462 Encode,
463 Decode,
464 RuntimeDebug,
465 DefaultNoBound,
466 scale_info::TypeInfo,
467)]
468#[scale_info(skip_type_params(AccountId, MaxWinners, MaxBackersPerWinner))]
469pub struct ReadySolution<AccountId, MaxWinners, MaxBackersPerWinner>
470where
471 AccountId: IdentifierT,
472 MaxWinners: Get<u32>,
473 MaxBackersPerWinner: Get<u32>,
474{
475 pub supports: BoundedSupports<AccountId, MaxWinners, MaxBackersPerWinner>,
480 pub score: ElectionScore,
484 pub compute: ElectionCompute,
486}
487
488#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
493#[scale_info(skip_type_params(T))]
494pub struct RoundSnapshot<AccountId, VoterType> {
495 pub voters: Vec<VoterType>,
497 pub targets: Vec<AccountId>,
499}
500
501#[derive(
507 PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, Default, TypeInfo,
508)]
509pub struct SolutionOrSnapshotSize {
510 #[codec(compact)]
512 pub voters: u32,
513 #[codec(compact)]
515 pub targets: u32,
516}
517
518#[derive(frame_support::DebugNoBound)]
522#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))]
523pub enum ElectionError<T: Config> {
524 Feasibility(FeasibilityError),
526 Miner(unsigned::MinerError),
528 DataProvider(&'static str),
530 Fallback(FallbackErrorOf<T>),
532 MultiPageNotSupported,
535 NothingQueued,
537}
538
539impl<T: Config> PartialEq for ElectionError<T>
542where
543 FallbackErrorOf<T>: PartialEq,
544{
545 fn eq(&self, other: &Self) -> bool {
546 use ElectionError::*;
547 match (self, other) {
548 (Feasibility(x), Feasibility(y)) if x == y => true,
549 (Miner(x), Miner(y)) if x == y => true,
550 (DataProvider(x), DataProvider(y)) if x == y => true,
551 (Fallback(x), Fallback(y)) if x == y => true,
552 (MultiPageNotSupported, MultiPageNotSupported) => true,
553 (NothingQueued, NothingQueued) => true,
554 _ => false,
555 }
556 }
557}
558
559impl<T: Config> From<FeasibilityError> for ElectionError<T> {
560 fn from(e: FeasibilityError) -> Self {
561 ElectionError::Feasibility(e)
562 }
563}
564
565impl<T: Config> From<unsigned::MinerError> for ElectionError<T> {
566 fn from(e: unsigned::MinerError) -> Self {
567 ElectionError::Miner(e)
568 }
569}
570
571#[derive(Debug, Eq, PartialEq)]
573#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))]
574pub enum FeasibilityError {
575 WrongWinnerCount,
577 SnapshotUnavailable,
582 NposElection(sp_npos_elections::Error),
584 InvalidVote,
586 InvalidVoter,
588 InvalidScore,
590 InvalidRound,
592 UntrustedScoreTooLow,
594 TooManyDesiredTargets,
596 BoundedConversionFailed,
600}
601
602impl From<sp_npos_elections::Error> for FeasibilityError {
603 fn from(e: sp_npos_elections::Error) -> Self {
604 FeasibilityError::NposElection(e)
605 }
606}
607
608pub use pallet::*;
609#[frame_support::pallet]
610pub mod pallet {
611 use super::*;
612 use frame_election_provider_support::{InstantElectionProvider, NposSolver};
613 use frame_support::{pallet_prelude::*, traits::EstimateCallFee};
614 use frame_system::pallet_prelude::*;
615 use sp_runtime::traits::Convert;
616
617 #[pallet::config]
618 pub trait Config: frame_system::Config + CreateBare<Call<Self>> {
619 #[allow(deprecated)]
620 type RuntimeEvent: From<Event<Self>>
621 + IsType<<Self as frame_system::Config>::RuntimeEvent>
622 + TryInto<Event<Self>>;
623
624 type Currency: ReservableCurrency<Self::AccountId> + Currency<Self::AccountId>;
626
627 type EstimateCallFee: EstimateCallFee<Call<Self>, BalanceOf<Self>>;
629
630 type UnsignedPhase: Get<BlockNumberFor<Self>>;
632 type SignedPhase: Get<BlockNumberFor<Self>>;
634
635 #[pallet::constant]
638 type BetterSignedThreshold: Get<Perbill>;
639
640 #[pallet::constant]
645 type OffchainRepeat: Get<BlockNumberFor<Self>>;
646
647 #[pallet::constant]
649 type MinerTxPriority: Get<TransactionPriority>;
650
651 type MinerConfig: crate::unsigned::MinerConfig<
656 AccountId = Self::AccountId,
657 MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
658 MaxWinners = Self::MaxWinners,
659 MaxBackersPerWinner = Self::MaxBackersPerWinner,
660 >;
661
662 #[pallet::constant]
670 type SignedMaxSubmissions: Get<u32>;
671
672 #[pallet::constant]
678 type SignedMaxWeight: Get<Weight>;
679
680 #[pallet::constant]
682 type SignedMaxRefunds: Get<u32>;
683
684 #[pallet::constant]
686 type SignedRewardBase: Get<BalanceOf<Self>>;
687
688 #[pallet::constant]
690 type SignedDepositByte: Get<BalanceOf<Self>>;
691
692 #[pallet::constant]
694 type SignedDepositWeight: Get<BalanceOf<Self>>;
695
696 #[pallet::constant]
700 type MaxWinners: Get<u32>;
701
702 #[pallet::constant]
706 type MaxBackersPerWinner: Get<u32>;
707
708 type SignedDepositBase: Convert<usize, BalanceOf<Self>>;
711
712 type ElectionBounds: Get<ElectionBounds>;
714
715 type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
717
718 type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>;
720
721 type DataProvider: ElectionDataProvider<
723 AccountId = Self::AccountId,
724 BlockNumber = BlockNumberFor<Self>,
725 >;
726
727 type Fallback: InstantElectionProvider<
729 AccountId = Self::AccountId,
730 BlockNumber = BlockNumberFor<Self>,
731 DataProvider = Self::DataProvider,
732 MaxBackersPerWinner = Self::MaxBackersPerWinner,
733 MaxWinnersPerPage = Self::MaxWinners,
734 >;
735
736 type GovernanceFallback: InstantElectionProvider<
741 AccountId = Self::AccountId,
742 BlockNumber = BlockNumberFor<Self>,
743 DataProvider = Self::DataProvider,
744 MaxWinnersPerPage = Self::MaxWinners,
745 MaxBackersPerWinner = Self::MaxBackersPerWinner,
746 >;
747
748 type Solver: NposSolver<AccountId = Self::AccountId>;
750
751 type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
754
755 type BenchmarkingConfig: BenchmarkingConfig;
757
758 type WeightInfo: WeightInfo;
760 }
761
762 #[pallet::extra_constants]
764 impl<T: Config> Pallet<T> {
765 #[pallet::constant_name(MinerMaxLength)]
766 fn max_length() -> u32 {
767 <T::MinerConfig as MinerConfig>::MaxLength::get()
768 }
769
770 #[pallet::constant_name(MinerMaxWeight)]
771 fn max_weight() -> Weight {
772 <T::MinerConfig as MinerConfig>::MaxWeight::get()
773 }
774
775 #[pallet::constant_name(MinerMaxVotesPerVoter)]
776 fn max_votes_per_voter() -> u32 {
777 <T::MinerConfig as MinerConfig>::MaxVotesPerVoter::get()
778 }
779
780 #[pallet::constant_name(MinerMaxWinners)]
781 fn max_winners() -> u32 {
782 <T::MinerConfig as MinerConfig>::MaxWinners::get()
783 }
784 }
785
786 #[pallet::hooks]
787 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
788 fn on_initialize(now: BlockNumberFor<T>) -> Weight {
789 let next_election = T::DataProvider::next_election_prediction(now).max(now);
790
791 let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get();
792 let unsigned_deadline = T::UnsignedPhase::get();
793
794 let remaining = next_election - now;
795 let current_phase = CurrentPhase::<T>::get();
796
797 log!(
798 trace,
799 "current phase {:?}, next election {:?}, queued? {:?}, metadata: {:?}",
800 current_phase,
801 next_election,
802 QueuedSolution::<T>::get().map(|rs| (rs.supports.len(), rs.compute, rs.score)),
803 SnapshotMetadata::<T>::get()
804 );
805 match current_phase {
806 Phase::Off if remaining <= signed_deadline && remaining > unsigned_deadline => {
807 match Self::create_snapshot() {
809 Ok(_) => {
810 Self::phase_transition(Phase::Signed);
811 T::WeightInfo::on_initialize_open_signed()
812 },
813 Err(why) => {
814 log!(warn, "failed to open signed phase due to {:?}", why);
816 T::WeightInfo::on_initialize_nothing()
817 },
818 }
819 },
820 Phase::Signed | Phase::Off
821 if remaining <= unsigned_deadline && remaining > Zero::zero() =>
822 {
823 let (need_snapshot, enabled) = if current_phase == Phase::Signed {
826 let _ = Self::finalize_signed_phase();
836 (false, true)
840 } else {
841 (true, true)
844 };
845
846 if need_snapshot {
847 match Self::create_snapshot() {
848 Ok(_) => {
849 Self::phase_transition(Phase::Unsigned((enabled, now)));
850 T::WeightInfo::on_initialize_open_unsigned()
851 },
852 Err(why) => {
853 log!(warn, "failed to open unsigned phase due to {:?}", why);
854 T::WeightInfo::on_initialize_nothing()
855 },
856 }
857 } else {
858 Self::phase_transition(Phase::Unsigned((enabled, now)));
859 T::WeightInfo::on_initialize_open_unsigned()
860 }
861 },
862 _ => T::WeightInfo::on_initialize_nothing(),
863 }
864 }
865
866 fn offchain_worker(now: BlockNumberFor<T>) {
867 use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock};
868
869 let mut lock =
873 StorageLock::<BlockAndTime<frame_system::Pallet<T>>>::with_block_deadline(
874 unsigned::OFFCHAIN_LOCK,
875 T::UnsignedPhase::get().saturated_into(),
876 );
877
878 match lock.try_lock() {
879 Ok(_guard) => {
880 Self::do_synchronized_offchain_worker(now);
881 },
882 Err(deadline) => {
883 log!(debug, "offchain worker lock not released, deadline is {:?}", deadline);
884 },
885 };
886 }
887
888 fn integrity_test() {
889 use core::mem::size_of;
890 assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
893 assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
894
895 let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
898
899 let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote)
901 .map(|_| {
902 <UpperOf<SolutionAccuracyOf<T>>>::from(
903 SolutionAccuracyOf::<T>::one().deconstruct(),
904 )
905 })
906 .collect();
907 let _: UpperOf<SolutionAccuracyOf<T>> = maximum_chain_accuracy
908 .iter()
909 .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap());
910
911 assert_eq!(
917 <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
918 <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
919 );
920
921 assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get());
925 }
926
927 #[cfg(feature = "try-runtime")]
928 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
929 Self::do_try_state()
930 }
931 }
932
933 #[pallet::call]
934 impl<T: Config> Pallet<T> {
935 #[pallet::call_index(0)]
950 #[pallet::weight((
951 T::WeightInfo::submit_unsigned(
952 witness.voters,
953 witness.targets,
954 raw_solution.solution.voter_count() as u32,
955 raw_solution.solution.unique_targets().len() as u32
956 ),
957 DispatchClass::Operational,
958 ))]
959 pub fn submit_unsigned(
960 origin: OriginFor<T>,
961 raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
962 witness: SolutionOrSnapshotSize,
963 ) -> DispatchResult {
964 ensure_none(origin)?;
965 let error_message = "Invalid unsigned submission must produce invalid block and \
966 deprive validator from their authoring reward.";
967
968 Self::unsigned_pre_dispatch_checks(&raw_solution).expect(error_message);
970
971 let SolutionOrSnapshotSize { voters, targets } =
973 SnapshotMetadata::<T>::get().expect(error_message);
974
975 assert!(voters as u32 == witness.voters, "{}", error_message);
977 assert!(targets as u32 == witness.targets, "{}", error_message);
978
979 let ready = Self::feasibility_check(*raw_solution, ElectionCompute::Unsigned)
980 .expect(error_message);
981
982 log!(debug, "queued unsigned solution with score {:?}", ready.score);
984 let ejected_a_solution = QueuedSolution::<T>::exists();
985 QueuedSolution::<T>::put(ready);
986 Self::deposit_event(Event::SolutionStored {
987 compute: ElectionCompute::Unsigned,
988 origin: None,
989 prev_ejected: ejected_a_solution,
990 });
991
992 Ok(())
993 }
994
995 #[pallet::call_index(1)]
1001 #[pallet::weight(T::DbWeight::get().writes(1))]
1002 pub fn set_minimum_untrusted_score(
1003 origin: OriginFor<T>,
1004 maybe_next_score: Option<ElectionScore>,
1005 ) -> DispatchResult {
1006 T::ForceOrigin::ensure_origin(origin)?;
1007 MinimumUntrustedScore::<T>::set(maybe_next_score);
1008 Ok(())
1009 }
1010
1011 #[pallet::call_index(2)]
1020 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
1021 pub fn set_emergency_election_result(
1022 origin: OriginFor<T>,
1023 supports: Supports<T::AccountId>,
1024 ) -> DispatchResult {
1025 T::ForceOrigin::ensure_origin(origin)?;
1026 ensure!(CurrentPhase::<T>::get().is_emergency(), Error::<T>::CallNotAllowed);
1027
1028 let supports: BoundedSupportsOf<Self> =
1030 supports.try_into().map_err(|_| Error::<T>::TooManyWinners)?;
1031
1032 let solution = ReadySolution {
1035 supports,
1036 score: Default::default(),
1037 compute: ElectionCompute::Emergency,
1038 };
1039
1040 Self::deposit_event(Event::SolutionStored {
1041 compute: ElectionCompute::Emergency,
1042 origin: None,
1043 prev_ejected: QueuedSolution::<T>::exists(),
1044 });
1045
1046 QueuedSolution::<T>::put(solution);
1047 Ok(())
1048 }
1049
1050 #[pallet::call_index(3)]
1060 #[pallet::weight(T::WeightInfo::submit())]
1061 pub fn submit(
1062 origin: OriginFor<T>,
1063 raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
1064 ) -> DispatchResult {
1065 let who = ensure_signed(origin)?;
1066
1067 ensure!(CurrentPhase::<T>::get().is_signed(), Error::<T>::PreDispatchEarlySubmission);
1069 ensure!(raw_solution.round == Round::<T>::get(), Error::<T>::PreDispatchDifferentRound);
1070
1071 let size = SnapshotMetadata::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1077
1078 ensure!(
1079 Self::solution_weight_of(&raw_solution, size).all_lt(T::SignedMaxWeight::get()),
1080 Error::<T>::SignedTooMuchWeight,
1081 );
1082
1083 let deposit = Self::deposit_for(&raw_solution, size);
1085 let call_fee = {
1086 let call = Call::submit { raw_solution: raw_solution.clone() };
1087 T::EstimateCallFee::estimate_call_fee(&call, None::<Weight>.into())
1088 };
1089
1090 let submission = SignedSubmission {
1091 who: who.clone(),
1092 deposit,
1093 raw_solution: *raw_solution,
1094 call_fee,
1095 };
1096
1097 let mut signed_submissions = Self::signed_submissions();
1100 let maybe_removed = match signed_submissions.insert(submission) {
1101 signed::InsertResult::NotInserted => return Err(Error::<T>::SignedQueueFull.into()),
1104 signed::InsertResult::Inserted => None,
1105 signed::InsertResult::InsertedEjecting(weakest) => Some(weakest),
1106 };
1107
1108 T::Currency::reserve(&who, deposit).map_err(|_| Error::<T>::SignedCannotPayDeposit)?;
1110
1111 let ejected_a_solution = maybe_removed.is_some();
1112 if let Some(removed) = maybe_removed {
1114 let _remainder = T::Currency::unreserve(&removed.who, removed.deposit);
1115 debug_assert!(_remainder.is_zero());
1116 }
1117
1118 signed_submissions.put();
1119 Self::deposit_event(Event::SolutionStored {
1120 compute: ElectionCompute::Signed,
1121 origin: Some(who),
1122 prev_ejected: ejected_a_solution,
1123 });
1124 Ok(())
1125 }
1126
1127 #[pallet::call_index(4)]
1132 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
1133 pub fn governance_fallback(origin: OriginFor<T>) -> DispatchResult {
1134 T::ForceOrigin::ensure_origin(origin)?;
1135 ensure!(CurrentPhase::<T>::get().is_emergency(), Error::<T>::CallNotAllowed);
1136
1137 let RoundSnapshot { voters, targets } =
1138 Snapshot::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1139 let desired_targets =
1140 DesiredTargets::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1141
1142 let supports = T::GovernanceFallback::instant_elect(voters, targets, desired_targets)
1143 .map_err(|e| {
1144 log!(error, "GovernanceFallback failed: {:?}", e);
1145 Error::<T>::FallbackFailed
1146 })?;
1147
1148 let solution = ReadySolution {
1149 supports,
1150 score: Default::default(),
1151 compute: ElectionCompute::Fallback,
1152 };
1153
1154 Self::deposit_event(Event::SolutionStored {
1155 compute: ElectionCompute::Fallback,
1156 origin: None,
1157 prev_ejected: QueuedSolution::<T>::exists(),
1158 });
1159
1160 QueuedSolution::<T>::put(solution);
1161 Ok(())
1162 }
1163 }
1164
1165 #[pallet::event]
1166 #[pallet::generate_deposit(pub(super) fn deposit_event)]
1167 pub enum Event<T: Config> {
1168 SolutionStored {
1176 compute: ElectionCompute,
1177 origin: Option<T::AccountId>,
1178 prev_ejected: bool,
1179 },
1180 ElectionFinalized { compute: ElectionCompute, score: ElectionScore },
1182 ElectionFailed,
1186 Rewarded { account: <T as frame_system::Config>::AccountId, value: BalanceOf<T> },
1188 Slashed { account: <T as frame_system::Config>::AccountId, value: BalanceOf<T> },
1190 PhaseTransitioned {
1192 from: Phase<BlockNumberFor<T>>,
1193 to: Phase<BlockNumberFor<T>>,
1194 round: u32,
1195 },
1196 }
1197
1198 #[pallet::error]
1200 pub enum Error<T> {
1201 PreDispatchEarlySubmission,
1203 PreDispatchWrongWinnerCount,
1205 PreDispatchWeakSubmission,
1207 SignedQueueFull,
1209 SignedCannotPayDeposit,
1211 SignedInvalidWitness,
1213 SignedTooMuchWeight,
1215 OcwCallWrongEra,
1217 MissingSnapshotMetadata,
1219 InvalidSubmissionIndex,
1221 CallNotAllowed,
1223 FallbackFailed,
1225 BoundNotMet,
1227 TooManyWinners,
1229 PreDispatchDifferentRound,
1231 }
1232
1233 #[pallet::validate_unsigned]
1234 impl<T: Config> ValidateUnsigned for Pallet<T> {
1235 type Call = Call<T>;
1236 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1237 if let Call::submit_unsigned { raw_solution, .. } = call {
1238 match source {
1240 TransactionSource::Local | TransactionSource::InBlock => { },
1241 _ => return InvalidTransaction::Call.into(),
1242 }
1243
1244 Self::unsigned_pre_dispatch_checks(raw_solution)
1245 .inspect_err(|err| {
1246 log!(debug, "unsigned transaction validation failed due to {:?}", err);
1247 })
1248 .map_err(dispatch_error_to_invalid)?;
1249
1250 ValidTransaction::with_tag_prefix("OffchainElection")
1251 .priority(
1253 T::MinerTxPriority::get()
1254 .saturating_add(raw_solution.score.minimal_stake.saturated_into()),
1255 )
1256 .and_provides(raw_solution.round)
1259 .longevity(T::UnsignedPhase::get().saturated_into::<u64>())
1261 .propagate(false)
1263 .build()
1264 } else {
1265 InvalidTransaction::Call.into()
1266 }
1267 }
1268
1269 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
1270 if let Call::submit_unsigned { raw_solution, .. } = call {
1271 Self::unsigned_pre_dispatch_checks(raw_solution)
1272 .map_err(dispatch_error_to_invalid)
1273 .map_err(Into::into)
1274 } else {
1275 Err(InvalidTransaction::Call.into())
1276 }
1277 }
1278 }
1279
1280 #[pallet::type_value]
1281 pub fn DefaultForRound() -> u32 {
1282 1
1283 }
1284
1285 #[pallet::storage]
1292 pub type Round<T: Config> = StorageValue<_, u32, ValueQuery, DefaultForRound>;
1293
1294 #[pallet::storage]
1296 pub type CurrentPhase<T: Config> = StorageValue<_, Phase<BlockNumberFor<T>>, ValueQuery>;
1297
1298 #[pallet::storage]
1302 pub type QueuedSolution<T: Config> = StorageValue<_, ReadySolutionOf<T::MinerConfig>>;
1303
1304 #[pallet::storage]
1309 pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T::AccountId, VoterOf<T>>>;
1310
1311 #[pallet::storage]
1316 pub type DesiredTargets<T> = StorageValue<_, u32>;
1317
1318 #[pallet::storage]
1323 pub type SnapshotMetadata<T: Config> = StorageValue<_, SolutionOrSnapshotSize>;
1324
1325 #[pallet::storage]
1339 pub type SignedSubmissionNextIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
1340
1341 #[pallet::storage]
1348 pub type SignedSubmissionIndices<T: Config> =
1349 StorageValue<_, SubmissionIndicesOf<T>, ValueQuery>;
1350
1351 #[pallet::storage]
1359 pub type SignedSubmissionsMap<T: Config> =
1360 StorageMap<_, Twox64Concat, u32, SignedSubmissionOf<T>, OptionQuery>;
1361
1362 #[pallet::storage]
1369 pub type MinimumUntrustedScore<T: Config> = StorageValue<_, ElectionScore>;
1370
1371 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
1375
1376 #[pallet::pallet]
1377 #[pallet::without_storage_info]
1378 #[pallet::storage_version(STORAGE_VERSION)]
1379 pub struct Pallet<T>(_);
1380}
1381
1382pub struct SnapshotWrapper<T>(core::marker::PhantomData<T>);
1385
1386impl<T: Config> SnapshotWrapper<T> {
1387 pub fn kill() {
1389 Snapshot::<T>::kill();
1390 SnapshotMetadata::<T>::kill();
1391 DesiredTargets::<T>::kill();
1392 }
1393 pub fn set(metadata: SolutionOrSnapshotSize, desired_targets: u32, buffer: &[u8]) {
1395 SnapshotMetadata::<T>::put(metadata);
1396 DesiredTargets::<T>::put(desired_targets);
1397 sp_io::storage::set(&Snapshot::<T>::hashed_key(), &buffer);
1398 }
1399
1400 #[cfg(feature = "try-runtime")]
1403 pub fn is_consistent() -> bool {
1404 let snapshots = [
1405 Snapshot::<T>::exists(),
1406 SnapshotMetadata::<T>::exists(),
1407 DesiredTargets::<T>::exists(),
1408 ];
1409
1410 snapshots.iter().skip(1).all(|v| snapshots[0] == *v)
1412 }
1413}
1414
1415impl<T: Config> Pallet<T> {
1416 pub fn round() -> u32 {
1423 Round::<T>::get()
1424 }
1425
1426 pub fn current_phase() -> Phase<BlockNumberFor<T>> {
1428 CurrentPhase::<T>::get()
1429 }
1430
1431 pub fn queued_solution() -> Option<ReadySolutionOf<T::MinerConfig>> {
1435 QueuedSolution::<T>::get()
1436 }
1437
1438 pub fn snapshot() -> Option<RoundSnapshot<T::AccountId, VoterOf<T>>> {
1443 Snapshot::<T>::get()
1444 }
1445
1446 pub fn desired_targets() -> Option<u32> {
1451 DesiredTargets::<T>::get()
1452 }
1453
1454 pub fn snapshot_metadata() -> Option<SolutionOrSnapshotSize> {
1459 SnapshotMetadata::<T>::get()
1460 }
1461
1462 pub fn minimum_untrusted_score() -> Option<ElectionScore> {
1467 MinimumUntrustedScore::<T>::get()
1468 }
1469
1470 fn do_synchronized_offchain_worker(now: BlockNumberFor<T>) {
1473 let current_phase = CurrentPhase::<T>::get();
1474 log!(trace, "lock for offchain worker acquired. Phase = {:?}", current_phase);
1475 match current_phase {
1476 Phase::Unsigned((true, opened)) if opened == now => {
1477 let initial_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
1479 unsigned::kill_ocw_solution::<T>();
1482 Self::mine_check_save_submit()
1483 });
1484 log!(debug, "initial offchain thread output: {:?}", initial_output);
1485 },
1486 Phase::Unsigned((true, opened)) if opened < now => {
1487 let resubmit_output = Self::ensure_offchain_repeat_frequency(now)
1490 .and_then(|_| Self::restore_or_compute_then_maybe_submit());
1491 log!(debug, "resubmit offchain thread output: {:?}", resubmit_output);
1492 },
1493 _ => {},
1494 }
1495 }
1496
1497 pub(crate) fn phase_transition(to: Phase<BlockNumberFor<T>>) {
1499 log!(info, "Starting phase {:?}, round {}.", to, Round::<T>::get());
1500 Self::deposit_event(Event::PhaseTransitioned {
1501 from: CurrentPhase::<T>::get(),
1502 to,
1503 round: Round::<T>::get(),
1504 });
1505 CurrentPhase::<T>::put(to);
1506 }
1507
1508 fn create_snapshot_internal(
1512 targets: Vec<T::AccountId>,
1513 voters: Vec<VoterOf<T>>,
1514 desired_targets: u32,
1515 ) {
1516 let metadata =
1517 SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 };
1518 log!(info, "creating a snapshot with metadata {:?}", metadata);
1519
1520 let snapshot = RoundSnapshot::<T::AccountId, VoterOf<T>> { voters, targets };
1524 let size = snapshot.encoded_size();
1525 log!(debug, "snapshot pre-calculated size {:?}", size);
1526 let mut buffer = Vec::with_capacity(size);
1527 snapshot.encode_to(&mut buffer);
1528
1529 debug_assert_eq!(buffer, snapshot.encode());
1531 debug_assert!(buffer.len() == size && size == buffer.capacity());
1533
1534 SnapshotWrapper::<T>::set(metadata, desired_targets, &buffer);
1535 }
1536
1537 fn create_snapshot_external(
1543 ) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
1544 let election_bounds = T::ElectionBounds::get();
1545 let targets = T::DataProvider::electable_targets_stateless(election_bounds.targets)
1546 .and_then(|t| {
1547 election_bounds.ensure_targets_limits(
1548 CountBound(t.len() as u32),
1549 SizeBound(t.encoded_size() as u32),
1550 )?;
1551 Ok(t)
1552 })
1553 .map_err(ElectionError::DataProvider)?;
1554
1555 let voters = T::DataProvider::electing_voters_stateless(election_bounds.voters)
1556 .and_then(|v| {
1557 election_bounds.ensure_voters_limits(
1558 CountBound(v.len() as u32),
1559 SizeBound(v.encoded_size() as u32),
1560 )?;
1561 Ok(v)
1562 })
1563 .map_err(ElectionError::DataProvider)?;
1564
1565 let mut desired_targets = <Pallet<T> as ElectionProvider>::desired_targets_checked()
1566 .map_err(|e| ElectionError::DataProvider(e))?;
1567
1568 let max_desired_targets: u32 = targets.len() as u32;
1571 if desired_targets > max_desired_targets {
1572 log!(
1573 warn,
1574 "desired_targets: {} > targets.len(): {}, capping desired_targets",
1575 desired_targets,
1576 max_desired_targets
1577 );
1578 desired_targets = max_desired_targets;
1579 }
1580
1581 Ok((targets, voters, desired_targets))
1582 }
1583
1584 pub fn create_snapshot() -> Result<(), ElectionError<T>> {
1595 let (targets, voters, desired_targets) = Self::create_snapshot_external()?;
1597
1598 let internal_weight =
1600 T::WeightInfo::create_snapshot_internal(voters.len() as u32, targets.len() as u32);
1601 Self::create_snapshot_internal(targets, voters, desired_targets);
1602 Self::register_weight(internal_weight);
1603 Ok(())
1604 }
1605
1606 fn register_weight(weight: Weight) {
1610 frame_system::Pallet::<T>::register_extra_weight_unchecked(
1611 weight,
1612 DispatchClass::Mandatory,
1613 );
1614 }
1615
1616 pub fn feasibility_check(
1618 raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
1619 compute: ElectionCompute,
1620 ) -> Result<ReadySolutionOf<T::MinerConfig>, FeasibilityError> {
1621 let desired_targets =
1622 DesiredTargets::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;
1623
1624 let snapshot = Snapshot::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;
1625 let round = Round::<T>::get();
1626 let minimum_untrusted_score = MinimumUntrustedScore::<T>::get();
1627
1628 Miner::<T::MinerConfig>::feasibility_check(
1629 raw_solution,
1630 compute,
1631 desired_targets,
1632 snapshot,
1633 round,
1634 minimum_untrusted_score,
1635 )
1636 }
1637
1638 fn rotate_round() {
1644 Round::<T>::mutate(|r| *r += 1);
1646
1647 Self::phase_transition(Phase::Off);
1649
1650 SnapshotWrapper::<T>::kill();
1652 }
1653
1654 fn do_elect() -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
1655 let _ = Self::finalize_signed_phase();
1663
1664 QueuedSolution::<T>::take()
1665 .ok_or(ElectionError::<T>::NothingQueued)
1666 .or_else(|_| {
1667 log!(warn, "No solution queued, falling back to instant fallback.",);
1668
1669 #[cfg(feature = "runtime-benchmarks")]
1670 Self::asap();
1671
1672 let (voters, targets, desired_targets) = if T::Fallback::bother() {
1673 let RoundSnapshot { voters, targets } = Snapshot::<T>::get().ok_or(
1674 ElectionError::<T>::Feasibility(FeasibilityError::SnapshotUnavailable),
1675 )?;
1676 let desired_targets = DesiredTargets::<T>::get().ok_or(
1677 ElectionError::<T>::Feasibility(FeasibilityError::SnapshotUnavailable),
1678 )?;
1679 (voters, targets, desired_targets)
1680 } else {
1681 (Default::default(), Default::default(), Default::default())
1682 };
1683 T::Fallback::instant_elect(voters, targets, desired_targets)
1684 .map_err(|fe| ElectionError::Fallback(fe))
1685 .and_then(|supports| {
1686 Ok(ReadySolution {
1687 supports,
1688 score: Default::default(),
1689 compute: ElectionCompute::Fallback,
1690 })
1691 })
1692 })
1693 .map(|ReadySolution { compute, score, supports }| {
1694 Self::deposit_event(Event::ElectionFinalized { compute, score });
1695 log!(info, "Finalized election round with compute {:?}.", compute);
1696 supports
1697 })
1698 .map_err(|err| {
1699 Self::deposit_event(Event::ElectionFailed);
1700 log!(warn, "Failed to finalize election round. reason {:?}", err);
1701 err
1702 })
1703 }
1704
1705 fn weigh_supports(supports: &BoundedSupportsOf<Self>) {
1707 let active_voters = supports
1708 .iter()
1709 .map(|(_, x)| x)
1710 .fold(Zero::zero(), |acc, next| acc + next.voters.len() as u32);
1711 let desired_targets = supports.len() as u32;
1712 Self::register_weight(T::WeightInfo::elect_queued(active_voters, desired_targets));
1713 }
1714}
1715
1716#[cfg(feature = "try-runtime")]
1717impl<T: Config> Pallet<T> {
1718 fn do_try_state() -> Result<(), TryRuntimeError> {
1719 Self::try_state_snapshot()?;
1720 Self::try_state_signed_submissions_map()?;
1721 Self::try_state_phase_off()
1722 }
1723
1724 fn try_state_snapshot() -> Result<(), TryRuntimeError> {
1728 if SnapshotWrapper::<T>::is_consistent() {
1729 Ok(())
1730 } else {
1731 Err("If snapshot exists, metadata and desired targets should be set too. Otherwise, none should be set.".into())
1732 }
1733 }
1734
1735 fn try_state_signed_submissions_map() -> Result<(), TryRuntimeError> {
1740 let mut last_score: ElectionScore = Default::default();
1741 let indices = SignedSubmissionIndices::<T>::get();
1742
1743 for (i, indice) in indices.iter().enumerate() {
1744 let submission = SignedSubmissionsMap::<T>::get(indice.2);
1745 if submission.is_none() {
1746 return Err(
1747 "All signed submissions indices must be part of the submissions map".into()
1748 )
1749 }
1750
1751 if i == 0 {
1752 last_score = indice.0
1753 } else {
1754 if last_score.strict_threshold_better(indice.0, Perbill::zero()) {
1755 return Err(
1756 "Signed submission indices vector must be ordered by election score".into()
1757 )
1758 }
1759 last_score = indice.0;
1760 }
1761 }
1762
1763 if SignedSubmissionsMap::<T>::iter().nth(indices.len()).is_some() {
1764 return Err(
1765 "Signed submissions map length should be the same as the indices vec length".into()
1766 )
1767 }
1768
1769 match SignedSubmissionNextIndex::<T>::get() {
1770 0 => Ok(()),
1771 next =>
1772 if SignedSubmissionsMap::<T>::get(next).is_some() {
1773 return Err(
1774 "The next submissions index should not be in the submissions maps already"
1775 .into(),
1776 )
1777 } else {
1778 Ok(())
1779 },
1780 }
1781 }
1782
1783 fn try_state_phase_off() -> Result<(), TryRuntimeError> {
1786 match CurrentPhase::<T>::get().is_off() {
1787 false => Ok(()),
1788 true =>
1789 if Snapshot::<T>::get().is_some() {
1790 Err("Snapshot must be none when in Phase::Off".into())
1791 } else {
1792 Ok(())
1793 },
1794 }
1795 }
1796}
1797
1798impl<T: Config> ElectionProvider for Pallet<T> {
1799 type AccountId = T::AccountId;
1800 type BlockNumber = BlockNumberFor<T>;
1801 type Error = ElectionError<T>;
1802 type MaxWinnersPerPage = T::MaxWinners;
1803 type MaxBackersPerWinner = T::MaxBackersPerWinner;
1804 type MaxBackersPerWinnerFinal = T::MaxBackersPerWinner;
1805 type Pages = sp_core::ConstU32<1>;
1806 type DataProvider = T::DataProvider;
1807
1808 fn elect(page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
1809 ensure!(page == SINGLE_PAGE, ElectionError::<T>::MultiPageNotSupported);
1811
1812 let res = match Self::do_elect() {
1813 Ok(bounded_supports) => {
1814 Self::weigh_supports(&bounded_supports);
1816 Self::rotate_round();
1817 Ok(bounded_supports)
1818 },
1819 Err(why) => {
1820 log!(error, "Entering emergency mode: {:?}", why);
1821 Self::phase_transition(Phase::Emergency);
1822 Err(why)
1823 },
1824 };
1825
1826 log!(info, "ElectionProvider::elect({}) => {:?}", page, res.as_ref().map(|s| s.len()));
1827 res
1828 }
1829
1830 fn duration() -> Self::BlockNumber {
1831 let signed: BlockNumberFor<T> = T::SignedPhase::get().saturated_into();
1832 let unsigned: BlockNumberFor<T> = T::UnsignedPhase::get().saturated_into();
1833 signed + unsigned
1834 }
1835
1836 fn start() -> Result<(), Self::Error> {
1837 log!(
1838 warn,
1839 "we received signal, but this pallet works in the basis of legacy pull based election"
1840 );
1841 Ok(())
1842 }
1843
1844 fn status() -> Result<bool, ()> {
1845 let has_queued = QueuedSolution::<T>::exists();
1846 let phase = CurrentPhase::<T>::get();
1847 match (phase, has_queued) {
1848 (Phase::Unsigned(_), true) => Ok(true),
1849 (Phase::Off, _) => Err(()),
1850 _ => Ok(false),
1851 }
1852 }
1853
1854 #[cfg(feature = "runtime-benchmarks")]
1855 fn asap() {
1856 if !Snapshot::<T>::exists() {
1858 Self::create_snapshot()
1859 .inspect_err(|e| {
1860 crate::log!(error, "failed to create snapshot while asap-preparing: {:?}", e)
1861 })
1862 .unwrap()
1863 }
1864 }
1865}
1866
1867pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction {
1870 let error_number = match error {
1871 DispatchError::Module(ModuleError { error, .. }) => error[0],
1872 _ => 0,
1873 };
1874 InvalidTransaction::Custom(error_number)
1875}
1876
1877#[cfg(test)]
1878mod feasibility_check {
1879 use super::*;
1884 use crate::mock::{
1885 raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase,
1886 TargetIndex, UnsignedPhase, VoterIndex,
1887 };
1888 use frame_support::{assert_noop, assert_ok};
1889
1890 const COMPUTE: ElectionCompute = ElectionCompute::OnChain;
1891
1892 #[test]
1893 fn snapshot_is_there() {
1894 ExtBuilder::default().build_and_execute(|| {
1895 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1896 assert!(CurrentPhase::<Runtime>::get().is_signed());
1897 let solution = raw_solution();
1898
1899 SnapshotWrapper::<Runtime>::kill();
1902
1903 assert_noop!(
1904 MultiPhase::feasibility_check(solution, COMPUTE),
1905 FeasibilityError::SnapshotUnavailable
1906 );
1907 })
1908 }
1909
1910 #[test]
1911 fn round() {
1912 ExtBuilder::default().build_and_execute(|| {
1913 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1914 assert!(CurrentPhase::<Runtime>::get().is_signed());
1915
1916 let mut solution = raw_solution();
1917 solution.round += 1;
1918 assert_noop!(
1919 MultiPhase::feasibility_check(solution, COMPUTE),
1920 FeasibilityError::InvalidRound
1921 );
1922 })
1923 }
1924
1925 #[test]
1926 fn desired_targets_gets_capped() {
1927 ExtBuilder::default().desired_targets(8).build_and_execute(|| {
1928 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1929 assert!(CurrentPhase::<Runtime>::get().is_signed());
1930
1931 let raw = raw_solution();
1932
1933 assert_eq!(raw.solution.unique_targets().len(), 4);
1934 assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 4);
1936
1937 assert_ok!(MultiPhase::feasibility_check(raw, COMPUTE));
1939 })
1940 }
1941
1942 #[test]
1943 fn less_than_desired_targets_fails() {
1944 ExtBuilder::default().desired_targets(8).build_and_execute(|| {
1945 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1946 assert!(CurrentPhase::<Runtime>::get().is_signed());
1947
1948 let mut raw = raw_solution();
1949
1950 assert_eq!(raw.solution.unique_targets().len(), 4);
1951 assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 4);
1953
1954 raw.solution.votes1[0].1 = 4;
1956
1957 assert_noop!(
1959 MultiPhase::feasibility_check(raw, COMPUTE),
1960 FeasibilityError::WrongWinnerCount,
1961 );
1962 })
1963 }
1964
1965 #[test]
1966 fn winner_indices() {
1967 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
1968 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1969 assert!(CurrentPhase::<Runtime>::get().is_signed());
1970
1971 let mut raw = raw_solution();
1972 assert_eq!(Snapshot::<Runtime>::get().unwrap().targets.len(), 4);
1973 raw.solution
1979 .votes1
1980 .iter_mut()
1981 .filter(|(_, t)| *t == TargetIndex::from(3u16))
1982 .for_each(|(_, t)| *t += 1);
1983 raw.solution.votes2.iter_mut().for_each(|(_, [(t0, _)], t1)| {
1984 if *t0 == TargetIndex::from(3u16) {
1985 *t0 += 1
1986 };
1987 if *t1 == TargetIndex::from(3u16) {
1988 *t1 += 1
1989 };
1990 });
1991 assert_noop!(
1992 MultiPhase::feasibility_check(raw, COMPUTE),
1993 FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex)
1994 );
1995 })
1996 }
1997
1998 #[test]
1999 fn voter_indices() {
2000 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
2002 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
2003 assert!(CurrentPhase::<Runtime>::get().is_signed());
2004
2005 let mut solution = raw_solution();
2006 assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2007 assert!(
2011 solution
2012 .solution
2013 .votes1
2014 .iter_mut()
2015 .filter(|(v, _)| *v == VoterIndex::from(7u32))
2016 .map(|(v, _)| *v = 8)
2017 .count() > 0
2018 );
2019 assert_noop!(
2020 MultiPhase::feasibility_check(solution, COMPUTE),
2021 FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex),
2022 );
2023 })
2024 }
2025
2026 #[test]
2027 fn voter_votes() {
2028 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
2029 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
2030 assert!(CurrentPhase::<Runtime>::get().is_signed());
2031
2032 let mut solution = raw_solution();
2033 assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2034 assert_eq!(
2039 solution
2040 .solution
2041 .votes1
2042 .iter_mut()
2043 .filter(|(v, t)| *v == 7 && *t == 3)
2044 .map(|(_, t)| *t = 2)
2045 .count(),
2046 1,
2047 );
2048 assert_noop!(
2049 MultiPhase::feasibility_check(solution, COMPUTE),
2050 FeasibilityError::InvalidVote,
2051 );
2052 })
2053 }
2054
2055 #[test]
2056 fn score() {
2057 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
2058 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
2059 assert!(CurrentPhase::<Runtime>::get().is_signed());
2060
2061 let mut solution = raw_solution();
2062 assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2063
2064 solution.score.minimal_stake += 1;
2066
2067 assert_noop!(
2068 MultiPhase::feasibility_check(solution, COMPUTE),
2069 FeasibilityError::InvalidScore,
2070 );
2071 })
2072 }
2073}
2074
2075#[cfg(test)]
2076mod tests {
2077 use super::*;
2078 use crate::{
2079 mock::{
2080 multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned,
2081 ElectionsBounds, ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime,
2082 RuntimeOrigin, SignedMaxSubmissions, System, Voters,
2083 },
2084 Phase,
2085 };
2086 use frame_election_provider_support::bounds::ElectionBoundsBuilder;
2087 use frame_support::{assert_noop, assert_ok};
2088 use sp_npos_elections::{BalancingConfig, Support};
2089
2090 #[test]
2091 fn phase_rotation_works() {
2092 ExtBuilder::default().build_and_execute(|| {
2093 assert_eq!(System::block_number(), 0);
2098 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2099 assert_eq!(Round::<Runtime>::get(), 1);
2100
2101 roll_to(4);
2102 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2103 assert!(Snapshot::<Runtime>::get().is_none());
2104 assert_eq!(Round::<Runtime>::get(), 1);
2105
2106 roll_to_signed();
2107 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2108 assert_eq!(
2109 multi_phase_events(),
2110 vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2111 );
2112 assert!(Snapshot::<Runtime>::get().is_some());
2113 assert_eq!(Round::<Runtime>::get(), 1);
2114
2115 roll_to(24);
2116 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2117 assert!(Snapshot::<Runtime>::get().is_some());
2118 assert_eq!(Round::<Runtime>::get(), 1);
2119
2120 roll_to_unsigned();
2121 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2122 assert_eq!(
2123 multi_phase_events(),
2124 vec![
2125 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2126 Event::PhaseTransitioned {
2127 from: Phase::Signed,
2128 to: Phase::Unsigned((true, 25)),
2129 round: 1
2130 },
2131 ],
2132 );
2133 assert!(Snapshot::<Runtime>::get().is_some());
2134
2135 roll_to(29);
2136 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2137 assert!(Snapshot::<Runtime>::get().is_some());
2138
2139 roll_to(30);
2140 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2141 assert!(Snapshot::<Runtime>::get().is_some());
2142
2143 roll_to(32);
2145 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2146 assert!(Snapshot::<Runtime>::get().is_some());
2147
2148 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2149
2150 assert!(CurrentPhase::<Runtime>::get().is_off());
2151 assert!(Snapshot::<Runtime>::get().is_none());
2152 assert_eq!(Round::<Runtime>::get(), 2);
2153
2154 roll_to(44);
2155 assert!(CurrentPhase::<Runtime>::get().is_off());
2156
2157 roll_to_signed();
2158 assert!(CurrentPhase::<Runtime>::get().is_signed());
2159
2160 roll_to(55);
2161 assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(55));
2162
2163 assert_eq!(
2164 multi_phase_events(),
2165 vec![
2166 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2167 Event::PhaseTransitioned {
2168 from: Phase::Signed,
2169 to: Phase::Unsigned((true, 25)),
2170 round: 1
2171 },
2172 Event::ElectionFinalized {
2173 compute: ElectionCompute::Fallback,
2174 score: ElectionScore {
2175 minimal_stake: 0,
2176 sum_stake: 0,
2177 sum_stake_squared: 0
2178 }
2179 },
2180 Event::PhaseTransitioned {
2181 from: Phase::Unsigned((true, 25)),
2182 to: Phase::Off,
2183 round: 2
2184 },
2185 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 2 },
2186 Event::PhaseTransitioned {
2187 from: Phase::Signed,
2188 to: Phase::Unsigned((true, 55)),
2189 round: 2
2190 },
2191 ]
2192 );
2193 })
2194 }
2195
2196 #[test]
2197 fn signed_phase_void() {
2198 ExtBuilder::default().phases(0, 10).build_and_execute(|| {
2199 roll_to(15);
2200 assert!(CurrentPhase::<Runtime>::get().is_off());
2201
2202 roll_to(19);
2203 assert!(CurrentPhase::<Runtime>::get().is_off());
2204
2205 roll_to(20);
2206 assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(20));
2207 assert!(Snapshot::<Runtime>::get().is_some());
2208
2209 roll_to(30);
2210 assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(20));
2211
2212 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2213
2214 assert!(CurrentPhase::<Runtime>::get().is_off());
2215 assert!(Snapshot::<Runtime>::get().is_none());
2216
2217 assert_eq!(
2218 multi_phase_events(),
2219 vec![
2220 Event::PhaseTransitioned {
2221 from: Phase::Off,
2222 to: Phase::Unsigned((true, 20)),
2223 round: 1
2224 },
2225 Event::ElectionFinalized {
2226 compute: ElectionCompute::Fallback,
2227 score: ElectionScore {
2228 minimal_stake: 0,
2229 sum_stake: 0,
2230 sum_stake_squared: 0
2231 }
2232 },
2233 Event::PhaseTransitioned {
2234 from: Phase::Unsigned((true, 20)),
2235 to: Phase::Off,
2236 round: 2
2237 },
2238 ]
2239 );
2240 });
2241 }
2242
2243 #[test]
2244 fn unsigned_phase_void() {
2245 ExtBuilder::default().phases(10, 0).build_and_execute(|| {
2246 roll_to(15);
2247 assert!(CurrentPhase::<Runtime>::get().is_off());
2248
2249 roll_to(19);
2250 assert!(CurrentPhase::<Runtime>::get().is_off());
2251
2252 roll_to_signed();
2253 assert!(CurrentPhase::<Runtime>::get().is_signed());
2254 assert!(Snapshot::<Runtime>::get().is_some());
2255
2256 roll_to(30);
2257 assert!(CurrentPhase::<Runtime>::get().is_signed());
2258
2259 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2260
2261 assert!(CurrentPhase::<Runtime>::get().is_off());
2262 assert!(Snapshot::<Runtime>::get().is_none());
2263
2264 assert_eq!(
2265 multi_phase_events(),
2266 vec![
2267 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2268 Event::ElectionFinalized {
2269 compute: ElectionCompute::Fallback,
2270 score: ElectionScore {
2271 minimal_stake: 0,
2272 sum_stake: 0,
2273 sum_stake_squared: 0
2274 }
2275 },
2276 Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2277 ]
2278 )
2279 });
2280 }
2281
2282 #[test]
2283 fn early_termination() {
2284 ExtBuilder::default().build_and_execute(|| {
2286 roll_to_signed();
2289 assert_eq!(
2290 multi_phase_events(),
2291 vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2292 );
2293 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2294 assert_eq!(Round::<Runtime>::get(), 1);
2295
2296 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2298
2299 assert_eq!(
2301 multi_phase_events(),
2302 vec![
2303 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2304 Event::ElectionFinalized {
2305 compute: ElectionCompute::Fallback,
2306 score: Default::default()
2307 },
2308 Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2309 ],
2310 );
2311 assert_eq!(Round::<Runtime>::get(), 2);
2313 assert!(Snapshot::<Runtime>::get().is_none());
2314 assert!(SnapshotMetadata::<Runtime>::get().is_none());
2315 assert!(DesiredTargets::<Runtime>::get().is_none());
2316 assert!(QueuedSolution::<Runtime>::get().is_none());
2317 assert!(MultiPhase::signed_submissions().is_empty());
2318 })
2319 }
2320
2321 #[test]
2322 fn early_termination_with_submissions() {
2323 ExtBuilder::default().build_and_execute(|| {
2325 roll_to_signed();
2328 assert_eq!(
2329 multi_phase_events(),
2330 vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2331 );
2332 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2333 assert_eq!(Round::<Runtime>::get(), 1);
2334
2335 for s in 0..SignedMaxSubmissions::get() {
2337 let solution = RawSolution {
2338 score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
2339 ..Default::default()
2340 };
2341 assert_ok!(MultiPhase::submit(
2342 crate::mock::RuntimeOrigin::signed(99),
2343 Box::new(solution)
2344 ));
2345 }
2346
2347 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2349
2350 assert_eq!(Round::<Runtime>::get(), 2);
2352 assert!(Snapshot::<Runtime>::get().is_none());
2353 assert!(SnapshotMetadata::<Runtime>::get().is_none());
2354 assert!(DesiredTargets::<Runtime>::get().is_none());
2355 assert!(QueuedSolution::<Runtime>::get().is_none());
2356 assert!(MultiPhase::signed_submissions().is_empty());
2357
2358 assert_eq!(
2359 multi_phase_events(),
2360 vec![
2361 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2362 Event::SolutionStored {
2363 compute: ElectionCompute::Signed,
2364 origin: Some(99),
2365 prev_ejected: false
2366 },
2367 Event::SolutionStored {
2368 compute: ElectionCompute::Signed,
2369 origin: Some(99),
2370 prev_ejected: false
2371 },
2372 Event::SolutionStored {
2373 compute: ElectionCompute::Signed,
2374 origin: Some(99),
2375 prev_ejected: false
2376 },
2377 Event::SolutionStored {
2378 compute: ElectionCompute::Signed,
2379 origin: Some(99),
2380 prev_ejected: false
2381 },
2382 Event::SolutionStored {
2383 compute: ElectionCompute::Signed,
2384 origin: Some(99),
2385 prev_ejected: false
2386 },
2387 Event::Slashed { account: 99, value: 5 },
2388 Event::Slashed { account: 99, value: 5 },
2389 Event::Slashed { account: 99, value: 5 },
2390 Event::Slashed { account: 99, value: 5 },
2391 Event::Slashed { account: 99, value: 5 },
2392 Event::ElectionFinalized {
2393 compute: ElectionCompute::Fallback,
2394 score: ElectionScore {
2395 minimal_stake: 0,
2396 sum_stake: 0,
2397 sum_stake_squared: 0
2398 }
2399 },
2400 Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2401 ]
2402 );
2403 })
2404 }
2405
2406 #[test]
2407 fn check_events_with_compute_signed() {
2408 ExtBuilder::default().build_and_execute(|| {
2409 roll_to_signed();
2410 assert!(CurrentPhase::<Runtime>::get().is_signed());
2411
2412 let solution = raw_solution();
2413 assert_ok!(MultiPhase::submit(
2414 crate::mock::RuntimeOrigin::signed(99),
2415 Box::new(solution)
2416 ));
2417
2418 roll_to(30);
2419 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2420
2421 assert_eq!(
2422 multi_phase_events(),
2423 vec![
2424 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2425 Event::SolutionStored {
2426 compute: ElectionCompute::Signed,
2427 origin: Some(99),
2428 prev_ejected: false
2429 },
2430 Event::Rewarded { account: 99, value: 7 },
2431 Event::PhaseTransitioned {
2432 from: Phase::Signed,
2433 to: Phase::Unsigned((true, 25)),
2434 round: 1
2435 },
2436 Event::ElectionFinalized {
2437 compute: ElectionCompute::Signed,
2438 score: ElectionScore {
2439 minimal_stake: 40,
2440 sum_stake: 100,
2441 sum_stake_squared: 5200
2442 }
2443 },
2444 Event::PhaseTransitioned {
2445 from: Phase::Unsigned((true, 25)),
2446 to: Phase::Off,
2447 round: 2
2448 },
2449 ],
2450 );
2451 })
2452 }
2453
2454 #[test]
2455 fn check_events_with_compute_unsigned() {
2456 ExtBuilder::default().build_and_execute(|| {
2457 roll_to_unsigned();
2458 assert!(CurrentPhase::<Runtime>::get().is_unsigned());
2459
2460 assert!(Snapshot::<Runtime>::get().is_some());
2462 assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 2);
2463
2464 let (solution, witness, _) = MultiPhase::mine_solution().unwrap();
2466
2467 assert!(QueuedSolution::<Runtime>::get().is_none());
2469 assert_ok!(MultiPhase::submit_unsigned(
2470 crate::mock::RuntimeOrigin::none(),
2471 Box::new(solution),
2472 witness
2473 ));
2474 assert!(QueuedSolution::<Runtime>::get().is_some());
2475
2476 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2477
2478 assert_eq!(
2479 multi_phase_events(),
2480 vec![
2481 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2482 Event::PhaseTransitioned {
2483 from: Phase::Signed,
2484 to: Phase::Unsigned((true, 25)),
2485 round: 1
2486 },
2487 Event::SolutionStored {
2488 compute: ElectionCompute::Unsigned,
2489 origin: None,
2490 prev_ejected: false
2491 },
2492 Event::ElectionFinalized {
2493 compute: ElectionCompute::Unsigned,
2494 score: ElectionScore {
2495 minimal_stake: 40,
2496 sum_stake: 100,
2497 sum_stake_squared: 5200
2498 }
2499 },
2500 Event::PhaseTransitioned {
2501 from: Phase::Unsigned((true, 25)),
2502 to: Phase::Off,
2503 round: 2
2504 },
2505 ],
2506 );
2507 })
2508 }
2509
2510 #[test]
2511 fn try_elect_multi_page_fails() {
2512 let prepare_election = || {
2513 roll_to_signed();
2514 assert!(Snapshot::<Runtime>::get().is_some());
2515
2516 let (solution, _, _) = MultiPhase::mine_solution().unwrap();
2518 assert_ok!(MultiPhase::submit(
2519 crate::mock::RuntimeOrigin::signed(99),
2520 Box::new(solution),
2521 ));
2522 roll_to(30);
2523 assert!(QueuedSolution::<Runtime>::get().is_some());
2524 };
2525
2526 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2527 prepare_election();
2528 assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2530 });
2531
2532 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2533 prepare_election();
2534 assert_noop!(MultiPhase::elect(SINGLE_PAGE + 1), ElectionError::MultiPageNotSupported);
2536 })
2537 }
2538
2539 #[test]
2540 fn fallback_strategy_works() {
2541 ExtBuilder::default().onchain_fallback(true).build_and_execute(|| {
2542 roll_to_unsigned();
2543 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2544
2545 assert!(QueuedSolution::<Runtime>::get().is_none());
2547 let supports = MultiPhase::elect(SINGLE_PAGE).unwrap();
2548
2549 let expected_supports = vec![
2550 (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }),
2551 (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }),
2552 ]
2553 .try_into()
2554 .unwrap();
2555
2556 assert_eq!(supports, expected_supports);
2557
2558 assert_eq!(
2559 multi_phase_events(),
2560 vec![
2561 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2562 Event::PhaseTransitioned {
2563 from: Phase::Signed,
2564 to: Phase::Unsigned((true, 25)),
2565 round: 1
2566 },
2567 Event::ElectionFinalized {
2568 compute: ElectionCompute::Fallback,
2569 score: ElectionScore {
2570 minimal_stake: 0,
2571 sum_stake: 0,
2572 sum_stake_squared: 0
2573 }
2574 },
2575 Event::PhaseTransitioned {
2576 from: Phase::Unsigned((true, 25)),
2577 to: Phase::Off,
2578 round: 2
2579 },
2580 ]
2581 );
2582 });
2583
2584 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2585 roll_to_unsigned();
2586 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2587
2588 assert!(QueuedSolution::<Runtime>::get().is_none());
2590 assert_eq!(
2591 MultiPhase::elect(SINGLE_PAGE).unwrap_err(),
2592 ElectionError::Fallback("NoFallback.")
2593 );
2594 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2596 assert!(Snapshot::<Runtime>::get().is_some());
2598
2599 assert_eq!(
2600 multi_phase_events(),
2601 vec![
2602 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2603 Event::PhaseTransitioned {
2604 from: Phase::Signed,
2605 to: Phase::Unsigned((true, 25)),
2606 round: 1
2607 },
2608 Event::ElectionFailed,
2609 Event::PhaseTransitioned {
2610 from: Phase::Unsigned((true, 25)),
2611 to: Phase::Emergency,
2612 round: 1
2613 },
2614 ]
2615 );
2616 })
2617 }
2618
2619 #[test]
2620 fn governance_fallback_works() {
2621 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2622 roll_to_unsigned();
2623 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2624
2625 assert!(QueuedSolution::<Runtime>::get().is_none());
2627 assert_eq!(
2628 MultiPhase::elect(SINGLE_PAGE).unwrap_err(),
2629 ElectionError::Fallback("NoFallback.")
2630 );
2631
2632 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2634 assert!(QueuedSolution::<Runtime>::get().is_none());
2635 assert!(Snapshot::<Runtime>::get().is_some());
2636
2637 assert_noop!(
2639 MultiPhase::governance_fallback(RuntimeOrigin::signed(99)),
2640 DispatchError::BadOrigin
2641 );
2642
2643 assert_ok!(MultiPhase::governance_fallback(RuntimeOrigin::root()));
2645 assert!(QueuedSolution::<Runtime>::get().is_some());
2647 assert!(MultiPhase::elect(SINGLE_PAGE).is_ok());
2649 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2650
2651 assert_eq!(
2652 multi_phase_events(),
2653 vec![
2654 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2655 Event::PhaseTransitioned {
2656 from: Phase::Signed,
2657 to: Phase::Unsigned((true, 25)),
2658 round: 1
2659 },
2660 Event::ElectionFailed,
2661 Event::PhaseTransitioned {
2662 from: Phase::Unsigned((true, 25)),
2663 to: Phase::Emergency,
2664 round: 1
2665 },
2666 Event::SolutionStored {
2667 compute: ElectionCompute::Fallback,
2668 origin: None,
2669 prev_ejected: false
2670 },
2671 Event::ElectionFinalized {
2672 compute: ElectionCompute::Fallback,
2673 score: Default::default()
2674 },
2675 Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off, round: 2 },
2676 ]
2677 );
2678 })
2679 }
2680
2681 #[test]
2682 fn snapshot_too_big_truncate() {
2683 ExtBuilder::default().build_and_execute(|| {
2685 assert_eq!(Voters::get().len(), 8);
2687 let new_bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build();
2689 ElectionsBounds::set(new_bounds);
2690
2691 roll_to_signed();
2693 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2694
2695 assert_eq!(
2696 SnapshotMetadata::<Runtime>::get().unwrap(),
2697 SolutionOrSnapshotSize { voters: 2, targets: 4 }
2698 );
2699 })
2700 }
2701
2702 #[test]
2703 fn untrusted_score_verification_is_respected() {
2704 ExtBuilder::default().build_and_execute(|| {
2705 roll_to_signed();
2706 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2707
2708 crate::mock::Balancing::set(Some(BalancingConfig { iterations: 2, tolerance: 0 }));
2710
2711 let (solution, _, _) = MultiPhase::mine_solution().unwrap();
2712 assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));
2714
2715 MinimumUntrustedScore::<Runtime>::put(ElectionScore {
2716 minimal_stake: 49,
2717 ..Default::default()
2718 });
2719 assert_ok!(MultiPhase::feasibility_check(solution.clone(), ElectionCompute::Signed));
2720
2721 MinimumUntrustedScore::<Runtime>::put(ElectionScore {
2722 minimal_stake: 51,
2723 ..Default::default()
2724 });
2725 assert_noop!(
2726 MultiPhase::feasibility_check(solution, ElectionCompute::Signed),
2727 FeasibilityError::UntrustedScoreTooLow,
2728 );
2729 })
2730 }
2731
2732 #[test]
2733 fn number_of_voters_allowed_2sec_block() {
2734 assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real);
2736
2737 let all_voters: u32 = 10_000;
2738 let all_targets: u32 = 5_000;
2739 let desired: u32 = 1_000;
2740 let weight_with = |active| {
2741 <Runtime as Config>::WeightInfo::submit_unsigned(
2742 all_voters,
2743 all_targets,
2744 active,
2745 desired,
2746 )
2747 };
2748
2749 let mut active = 1;
2750 while weight_with(active)
2751 .all_lte(<Runtime as frame_system::Config>::BlockWeights::get().max_block) ||
2752 active == all_voters
2753 {
2754 active += 1;
2755 }
2756
2757 println!("can support {} voters to yield a weight of {}", active, weight_with(active));
2758 }
2759}