1#![cfg_attr(not(feature = "std"), no_std)]
231
232extern crate alloc;
233
234use alloc::{boxed::Box, vec::Vec};
235use codec::{Decode, DecodeWithMemTracking, Encode};
236use frame_election_provider_support::{
237 bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder, SizeBound},
238 BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
239 ElectionProviderBase, InstantElectionProvider, NposSolution,
240};
241use frame_support::{
242 dispatch::DispatchClass,
243 ensure,
244 traits::{Currency, DefensiveResult, Get, OnUnbalanced, ReservableCurrency},
245 weights::Weight,
246 DefaultNoBound, EqNoBound, PartialEqNoBound,
247};
248use frame_system::{ensure_none, offchain::CreateInherent, pallet_prelude::BlockNumberFor};
249use scale_info::TypeInfo;
250use sp_arithmetic::{
251 traits::{CheckedAdd, Zero},
252 UpperOf,
253};
254use sp_npos_elections::{BoundedSupports, ElectionScore, IdentifierT, Supports, VoteWeight};
255use sp_runtime::{
256 transaction_validity::{
257 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
258 TransactionValidityError, ValidTransaction,
259 },
260 DispatchError, ModuleError, PerThing, Perbill, RuntimeDebug, SaturatedConversion,
261};
262
263#[cfg(feature = "try-runtime")]
264use sp_runtime::TryRuntimeError;
265
266#[cfg(feature = "runtime-benchmarks")]
267mod benchmarking;
268#[cfg(test)]
269mod mock;
270#[macro_use]
271pub mod helpers;
272
273const LOG_TARGET: &str = "runtime::election-provider";
274
275pub mod migrations;
276pub mod signed;
277pub mod unsigned;
278pub mod weights;
279
280pub use signed::{
281 BalanceOf, GeometricDepositBase, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission,
282 SignedSubmissionOf, SignedSubmissions, SubmissionIndicesOf,
283};
284use unsigned::VoterOf;
285pub use unsigned::{Miner, MinerConfig};
286pub use weights::WeightInfo;
287
288pub type SolutionOf<T> = <T as MinerConfig>::Solution;
290
291pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
293pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
295pub type SolutionAccuracyOf<T> =
297 <SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
298pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProviderBase>::Error;
300
301pub trait BenchmarkingConfig {
303 const VOTERS: [u32; 2];
305 const TARGETS: [u32; 2];
307 const ACTIVE_VOTERS: [u32; 2];
309 const DESIRED_TARGETS: [u32; 2];
311 const SNAPSHOT_MAXIMUM_VOTERS: u32;
313 const MINER_MAXIMUM_VOTERS: u32;
315 const MAXIMUM_TARGETS: u32;
317}
318
319#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
321pub enum Phase<Bn> {
322 Off,
324 Signed,
326 Unsigned((bool, Bn)),
337 Emergency,
341}
342
343impl<Bn> Default for Phase<Bn> {
344 fn default() -> Self {
345 Phase::Off
346 }
347}
348
349impl<Bn: PartialEq + Eq> Phase<Bn> {
350 pub fn is_emergency(&self) -> bool {
352 matches!(self, Phase::Emergency)
353 }
354
355 pub fn is_signed(&self) -> bool {
357 matches!(self, Phase::Signed)
358 }
359
360 pub fn is_unsigned(&self) -> bool {
362 matches!(self, Phase::Unsigned(_))
363 }
364
365 pub fn is_unsigned_open_at(&self, at: Bn) -> bool {
367 matches!(self, Phase::Unsigned((true, real)) if *real == at)
368 }
369
370 pub fn is_unsigned_open(&self) -> bool {
372 matches!(self, Phase::Unsigned((true, _)))
373 }
374
375 pub fn is_off(&self) -> bool {
377 matches!(self, Phase::Off)
378 }
379}
380
381#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
383pub enum ElectionCompute {
384 OnChain,
386 Signed,
388 Unsigned,
390 Fallback,
392 Emergency,
394}
395
396impl Default for ElectionCompute {
397 fn default() -> Self {
398 ElectionCompute::OnChain
399 }
400}
401
402#[derive(
409 PartialEq,
410 Eq,
411 Clone,
412 Encode,
413 Decode,
414 DecodeWithMemTracking,
415 RuntimeDebug,
416 PartialOrd,
417 Ord,
418 TypeInfo,
419)]
420pub struct RawSolution<S> {
421 pub solution: S,
423 pub score: ElectionScore,
425 pub round: u32,
427}
428
429impl<C: Default> Default for RawSolution<C> {
430 fn default() -> Self {
431 Self { round: 1, solution: Default::default(), score: Default::default() }
433 }
434}
435
436#[derive(
438 PartialEqNoBound,
439 EqNoBound,
440 Clone,
441 Encode,
442 Decode,
443 RuntimeDebug,
444 DefaultNoBound,
445 scale_info::TypeInfo,
446)]
447#[scale_info(skip_type_params(AccountId, MaxWinners))]
448pub struct ReadySolution<AccountId, MaxWinners>
449where
450 AccountId: IdentifierT,
451 MaxWinners: Get<u32>,
452{
453 pub supports: BoundedSupports<AccountId, MaxWinners>,
458 pub score: ElectionScore,
462 pub compute: ElectionCompute,
464}
465
466#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
471#[scale_info(skip_type_params(T))]
472pub struct RoundSnapshot<AccountId, DataProvider> {
473 pub voters: Vec<DataProvider>,
475 pub targets: Vec<AccountId>,
477}
478
479#[derive(
485 PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, Default, TypeInfo,
486)]
487pub struct SolutionOrSnapshotSize {
488 #[codec(compact)]
490 pub voters: u32,
491 #[codec(compact)]
493 pub targets: u32,
494}
495
496#[derive(frame_support::DebugNoBound)]
500#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))]
501pub enum ElectionError<T: Config> {
502 Feasibility(FeasibilityError),
504 Miner(unsigned::MinerError),
506 DataProvider(&'static str),
508 Fallback(FallbackErrorOf<T>),
510 NothingQueued,
512}
513
514#[cfg(test)]
517impl<T: Config> PartialEq for ElectionError<T>
518where
519 FallbackErrorOf<T>: PartialEq,
520{
521 fn eq(&self, other: &Self) -> bool {
522 use ElectionError::*;
523 match (self, other) {
524 (Feasibility(x), Feasibility(y)) if x == y => true,
525 (Miner(x), Miner(y)) if x == y => true,
526 (DataProvider(x), DataProvider(y)) if x == y => true,
527 (Fallback(x), Fallback(y)) if x == y => true,
528 _ => false,
529 }
530 }
531}
532
533impl<T: Config> From<FeasibilityError> for ElectionError<T> {
534 fn from(e: FeasibilityError) -> Self {
535 ElectionError::Feasibility(e)
536 }
537}
538
539impl<T: Config> From<unsigned::MinerError> for ElectionError<T> {
540 fn from(e: unsigned::MinerError) -> Self {
541 ElectionError::Miner(e)
542 }
543}
544
545#[derive(Debug, Eq, PartialEq)]
547#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))]
548pub enum FeasibilityError {
549 WrongWinnerCount,
551 SnapshotUnavailable,
556 NposElection(sp_npos_elections::Error),
558 InvalidVote,
560 InvalidVoter,
562 InvalidScore,
564 InvalidRound,
566 UntrustedScoreTooLow,
568 TooManyDesiredTargets,
570 BoundedConversionFailed,
574}
575
576impl From<sp_npos_elections::Error> for FeasibilityError {
577 fn from(e: sp_npos_elections::Error) -> Self {
578 FeasibilityError::NposElection(e)
579 }
580}
581
582pub use pallet::*;
583#[frame_support::pallet]
584pub mod pallet {
585 use super::*;
586 use frame_election_provider_support::{InstantElectionProvider, NposSolver};
587 use frame_support::{pallet_prelude::*, traits::EstimateCallFee};
588 use frame_system::pallet_prelude::*;
589 use sp_runtime::traits::Convert;
590
591 #[pallet::config]
592 pub trait Config: frame_system::Config + CreateInherent<Call<Self>> {
593 type RuntimeEvent: From<Event<Self>>
594 + IsType<<Self as frame_system::Config>::RuntimeEvent>
595 + TryInto<Event<Self>>;
596
597 type Currency: ReservableCurrency<Self::AccountId> + Currency<Self::AccountId>;
599
600 type EstimateCallFee: EstimateCallFee<Call<Self>, BalanceOf<Self>>;
602
603 type UnsignedPhase: Get<BlockNumberFor<Self>>;
605 type SignedPhase: Get<BlockNumberFor<Self>>;
607
608 #[pallet::constant]
611 type BetterSignedThreshold: Get<Perbill>;
612
613 #[pallet::constant]
618 type OffchainRepeat: Get<BlockNumberFor<Self>>;
619
620 #[pallet::constant]
622 type MinerTxPriority: Get<TransactionPriority>;
623
624 type MinerConfig: crate::unsigned::MinerConfig<
629 AccountId = Self::AccountId,
630 MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
631 MaxWinners = Self::MaxWinners,
632 >;
633
634 #[pallet::constant]
642 type SignedMaxSubmissions: Get<u32>;
643
644 #[pallet::constant]
650 type SignedMaxWeight: Get<Weight>;
651
652 #[pallet::constant]
654 type SignedMaxRefunds: Get<u32>;
655
656 #[pallet::constant]
658 type SignedRewardBase: Get<BalanceOf<Self>>;
659
660 #[pallet::constant]
662 type SignedDepositByte: Get<BalanceOf<Self>>;
663
664 #[pallet::constant]
666 type SignedDepositWeight: Get<BalanceOf<Self>>;
667
668 #[pallet::constant]
673 type MaxWinners: Get<u32>;
674
675 type SignedDepositBase: Convert<usize, BalanceOf<Self>>;
678
679 type ElectionBounds: Get<ElectionBounds>;
683
684 type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
686
687 type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>;
689
690 type DataProvider: ElectionDataProvider<
692 AccountId = Self::AccountId,
693 BlockNumber = BlockNumberFor<Self>,
694 >;
695
696 type Fallback: InstantElectionProvider<
698 AccountId = Self::AccountId,
699 BlockNumber = BlockNumberFor<Self>,
700 DataProvider = Self::DataProvider,
701 MaxWinners = Self::MaxWinners,
702 >;
703
704 type GovernanceFallback: InstantElectionProvider<
709 AccountId = Self::AccountId,
710 BlockNumber = BlockNumberFor<Self>,
711 DataProvider = Self::DataProvider,
712 MaxWinners = Self::MaxWinners,
713 >;
714
715 type Solver: NposSolver<AccountId = Self::AccountId>;
717
718 type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
721
722 type BenchmarkingConfig: BenchmarkingConfig;
724
725 type WeightInfo: WeightInfo;
727 }
728
729 #[pallet::extra_constants]
731 impl<T: Config> Pallet<T> {
732 #[pallet::constant_name(MinerMaxLength)]
733 fn max_length() -> u32 {
734 <T::MinerConfig as MinerConfig>::MaxLength::get()
735 }
736
737 #[pallet::constant_name(MinerMaxWeight)]
738 fn max_weight() -> Weight {
739 <T::MinerConfig as MinerConfig>::MaxWeight::get()
740 }
741
742 #[pallet::constant_name(MinerMaxVotesPerVoter)]
743 fn max_votes_per_voter() -> u32 {
744 <T::MinerConfig as MinerConfig>::MaxVotesPerVoter::get()
745 }
746
747 #[pallet::constant_name(MinerMaxWinners)]
748 fn max_winners() -> u32 {
749 <T::MinerConfig as MinerConfig>::MaxWinners::get()
750 }
751 }
752
753 #[pallet::hooks]
754 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
755 fn on_initialize(now: BlockNumberFor<T>) -> Weight {
756 let next_election = T::DataProvider::next_election_prediction(now).max(now);
757
758 let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get();
759 let unsigned_deadline = T::UnsignedPhase::get();
760
761 let remaining = next_election - now;
762 let current_phase = CurrentPhase::<T>::get();
763
764 log!(
765 trace,
766 "current phase {:?}, next election {:?}, metadata: {:?}",
767 current_phase,
768 next_election,
769 SnapshotMetadata::<T>::get()
770 );
771 match current_phase {
772 Phase::Off if remaining <= signed_deadline && remaining > unsigned_deadline => {
773 match Self::create_snapshot() {
775 Ok(_) => {
776 Self::phase_transition(Phase::Signed);
777 T::WeightInfo::on_initialize_open_signed()
778 },
779 Err(why) => {
780 log!(warn, "failed to open signed phase due to {:?}", why);
782 T::WeightInfo::on_initialize_nothing()
783 },
784 }
785 },
786 Phase::Signed | Phase::Off
787 if remaining <= unsigned_deadline && remaining > Zero::zero() =>
788 {
789 let (need_snapshot, enabled) = if current_phase == Phase::Signed {
792 let _ = Self::finalize_signed_phase();
802 (false, true)
806 } else {
807 (true, true)
810 };
811
812 if need_snapshot {
813 match Self::create_snapshot() {
814 Ok(_) => {
815 Self::phase_transition(Phase::Unsigned((enabled, now)));
816 T::WeightInfo::on_initialize_open_unsigned()
817 },
818 Err(why) => {
819 log!(warn, "failed to open unsigned phase due to {:?}", why);
820 T::WeightInfo::on_initialize_nothing()
821 },
822 }
823 } else {
824 Self::phase_transition(Phase::Unsigned((enabled, now)));
825 T::WeightInfo::on_initialize_open_unsigned()
826 }
827 },
828 _ => T::WeightInfo::on_initialize_nothing(),
829 }
830 }
831
832 fn offchain_worker(now: BlockNumberFor<T>) {
833 use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock};
834
835 let mut lock =
839 StorageLock::<BlockAndTime<frame_system::Pallet<T>>>::with_block_deadline(
840 unsigned::OFFCHAIN_LOCK,
841 T::UnsignedPhase::get().saturated_into(),
842 );
843
844 match lock.try_lock() {
845 Ok(_guard) => {
846 Self::do_synchronized_offchain_worker(now);
847 },
848 Err(deadline) => {
849 log!(debug, "offchain worker lock not released, deadline is {:?}", deadline);
850 },
851 };
852 }
853
854 fn integrity_test() {
855 use core::mem::size_of;
856 assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
859 assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
860
861 let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
864
865 let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote)
867 .map(|_| {
868 <UpperOf<SolutionAccuracyOf<T>>>::from(
869 SolutionAccuracyOf::<T>::one().deconstruct(),
870 )
871 })
872 .collect();
873 let _: UpperOf<SolutionAccuracyOf<T>> = maximum_chain_accuracy
874 .iter()
875 .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap());
876
877 assert_eq!(
883 <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
884 <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
885 );
886
887 assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get());
891 }
892
893 #[cfg(feature = "try-runtime")]
894 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
895 Self::do_try_state()
896 }
897 }
898
899 #[pallet::call]
900 impl<T: Config> Pallet<T> {
901 #[pallet::call_index(0)]
916 #[pallet::weight((
917 T::WeightInfo::submit_unsigned(
918 witness.voters,
919 witness.targets,
920 raw_solution.solution.voter_count() as u32,
921 raw_solution.solution.unique_targets().len() as u32
922 ),
923 DispatchClass::Operational,
924 ))]
925 pub fn submit_unsigned(
926 origin: OriginFor<T>,
927 raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
928 witness: SolutionOrSnapshotSize,
929 ) -> DispatchResult {
930 ensure_none(origin)?;
931 let error_message = "Invalid unsigned submission must produce invalid block and \
932 deprive validator from their authoring reward.";
933
934 Self::unsigned_pre_dispatch_checks(&raw_solution).expect(error_message);
936
937 let SolutionOrSnapshotSize { voters, targets } =
939 SnapshotMetadata::<T>::get().expect(error_message);
940
941 assert!(voters as u32 == witness.voters, "{}", error_message);
943 assert!(targets as u32 == witness.targets, "{}", error_message);
944
945 let ready = Self::feasibility_check(*raw_solution, ElectionCompute::Unsigned)
946 .expect(error_message);
947
948 log!(debug, "queued unsigned solution with score {:?}", ready.score);
950 let ejected_a_solution = QueuedSolution::<T>::exists();
951 QueuedSolution::<T>::put(ready);
952 Self::deposit_event(Event::SolutionStored {
953 compute: ElectionCompute::Unsigned,
954 origin: None,
955 prev_ejected: ejected_a_solution,
956 });
957
958 Ok(())
959 }
960
961 #[pallet::call_index(1)]
967 #[pallet::weight(T::DbWeight::get().writes(1))]
968 pub fn set_minimum_untrusted_score(
969 origin: OriginFor<T>,
970 maybe_next_score: Option<ElectionScore>,
971 ) -> DispatchResult {
972 T::ForceOrigin::ensure_origin(origin)?;
973 MinimumUntrustedScore::<T>::set(maybe_next_score);
974 Ok(())
975 }
976
977 #[pallet::call_index(2)]
986 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
987 pub fn set_emergency_election_result(
988 origin: OriginFor<T>,
989 supports: Supports<T::AccountId>,
990 ) -> DispatchResult {
991 T::ForceOrigin::ensure_origin(origin)?;
992 ensure!(CurrentPhase::<T>::get().is_emergency(), Error::<T>::CallNotAllowed);
993
994 let supports = supports.try_into().map_err(|_| Error::<T>::TooManyWinners)?;
996
997 let solution = ReadySolution {
1000 supports,
1001 score: Default::default(),
1002 compute: ElectionCompute::Emergency,
1003 };
1004
1005 Self::deposit_event(Event::SolutionStored {
1006 compute: ElectionCompute::Emergency,
1007 origin: None,
1008 prev_ejected: QueuedSolution::<T>::exists(),
1009 });
1010
1011 QueuedSolution::<T>::put(solution);
1012 Ok(())
1013 }
1014
1015 #[pallet::call_index(3)]
1025 #[pallet::weight(T::WeightInfo::submit())]
1026 pub fn submit(
1027 origin: OriginFor<T>,
1028 raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
1029 ) -> DispatchResult {
1030 let who = ensure_signed(origin)?;
1031
1032 ensure!(CurrentPhase::<T>::get().is_signed(), Error::<T>::PreDispatchEarlySubmission);
1034 ensure!(raw_solution.round == Round::<T>::get(), Error::<T>::PreDispatchDifferentRound);
1035
1036 let size = SnapshotMetadata::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1042
1043 ensure!(
1044 Self::solution_weight_of(&raw_solution, size).all_lt(T::SignedMaxWeight::get()),
1045 Error::<T>::SignedTooMuchWeight,
1046 );
1047
1048 let deposit = Self::deposit_for(&raw_solution, size);
1050 let call_fee = {
1051 let call = Call::submit { raw_solution: raw_solution.clone() };
1052 T::EstimateCallFee::estimate_call_fee(&call, None::<Weight>.into())
1053 };
1054
1055 let submission = SignedSubmission {
1056 who: who.clone(),
1057 deposit,
1058 raw_solution: *raw_solution,
1059 call_fee,
1060 };
1061
1062 let mut signed_submissions = Self::signed_submissions();
1065 let maybe_removed = match signed_submissions.insert(submission) {
1066 signed::InsertResult::NotInserted => return Err(Error::<T>::SignedQueueFull.into()),
1069 signed::InsertResult::Inserted => None,
1070 signed::InsertResult::InsertedEjecting(weakest) => Some(weakest),
1071 };
1072
1073 T::Currency::reserve(&who, deposit).map_err(|_| Error::<T>::SignedCannotPayDeposit)?;
1075
1076 let ejected_a_solution = maybe_removed.is_some();
1077 if let Some(removed) = maybe_removed {
1079 let _remainder = T::Currency::unreserve(&removed.who, removed.deposit);
1080 debug_assert!(_remainder.is_zero());
1081 }
1082
1083 signed_submissions.put();
1084 Self::deposit_event(Event::SolutionStored {
1085 compute: ElectionCompute::Signed,
1086 origin: Some(who),
1087 prev_ejected: ejected_a_solution,
1088 });
1089 Ok(())
1090 }
1091
1092 #[pallet::call_index(4)]
1097 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
1098 pub fn governance_fallback(
1099 origin: OriginFor<T>,
1100 maybe_max_voters: Option<u32>,
1101 maybe_max_targets: Option<u32>,
1102 ) -> DispatchResult {
1103 T::ForceOrigin::ensure_origin(origin)?;
1104 ensure!(CurrentPhase::<T>::get().is_emergency(), Error::<T>::CallNotAllowed);
1105
1106 let election_bounds = ElectionBoundsBuilder::default()
1107 .voters_count(maybe_max_voters.unwrap_or(u32::MAX).into())
1108 .targets_count(maybe_max_targets.unwrap_or(u32::MAX).into())
1109 .build();
1110
1111 let supports = T::GovernanceFallback::instant_elect(
1112 election_bounds.voters,
1113 election_bounds.targets,
1114 )
1115 .map_err(|e| {
1116 log!(error, "GovernanceFallback failed: {:?}", e);
1117 Error::<T>::FallbackFailed
1118 })?;
1119
1120 let supports: BoundedVec<_, T::MaxWinners> = supports
1123 .into_inner()
1124 .try_into()
1125 .defensive_map_err(|_| Error::<T>::BoundNotMet)?;
1126
1127 let solution = ReadySolution {
1128 supports,
1129 score: Default::default(),
1130 compute: ElectionCompute::Fallback,
1131 };
1132
1133 Self::deposit_event(Event::SolutionStored {
1134 compute: ElectionCompute::Fallback,
1135 origin: None,
1136 prev_ejected: QueuedSolution::<T>::exists(),
1137 });
1138
1139 QueuedSolution::<T>::put(solution);
1140 Ok(())
1141 }
1142 }
1143
1144 #[pallet::event]
1145 #[pallet::generate_deposit(pub(super) fn deposit_event)]
1146 pub enum Event<T: Config> {
1147 SolutionStored {
1155 compute: ElectionCompute,
1156 origin: Option<T::AccountId>,
1157 prev_ejected: bool,
1158 },
1159 ElectionFinalized { compute: ElectionCompute, score: ElectionScore },
1161 ElectionFailed,
1165 Rewarded { account: <T as frame_system::Config>::AccountId, value: BalanceOf<T> },
1167 Slashed { account: <T as frame_system::Config>::AccountId, value: BalanceOf<T> },
1169 PhaseTransitioned {
1171 from: Phase<BlockNumberFor<T>>,
1172 to: Phase<BlockNumberFor<T>>,
1173 round: u32,
1174 },
1175 }
1176
1177 #[pallet::error]
1179 pub enum Error<T> {
1180 PreDispatchEarlySubmission,
1182 PreDispatchWrongWinnerCount,
1184 PreDispatchWeakSubmission,
1186 SignedQueueFull,
1188 SignedCannotPayDeposit,
1190 SignedInvalidWitness,
1192 SignedTooMuchWeight,
1194 OcwCallWrongEra,
1196 MissingSnapshotMetadata,
1198 InvalidSubmissionIndex,
1200 CallNotAllowed,
1202 FallbackFailed,
1204 BoundNotMet,
1206 TooManyWinners,
1208 PreDispatchDifferentRound,
1210 }
1211
1212 #[pallet::validate_unsigned]
1213 impl<T: Config> ValidateUnsigned for Pallet<T> {
1214 type Call = Call<T>;
1215 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1216 if let Call::submit_unsigned { raw_solution, .. } = call {
1217 match source {
1219 TransactionSource::Local | TransactionSource::InBlock => { },
1220 _ => return InvalidTransaction::Call.into(),
1221 }
1222
1223 let _ = Self::unsigned_pre_dispatch_checks(raw_solution)
1224 .inspect_err(|err| {
1225 log!(debug, "unsigned transaction validation failed due to {:?}", err);
1226 })
1227 .map_err(dispatch_error_to_invalid)?;
1228
1229 ValidTransaction::with_tag_prefix("OffchainElection")
1230 .priority(
1232 T::MinerTxPriority::get()
1233 .saturating_add(raw_solution.score.minimal_stake.saturated_into()),
1234 )
1235 .and_provides(raw_solution.round)
1238 .longevity(T::UnsignedPhase::get().saturated_into::<u64>())
1240 .propagate(false)
1242 .build()
1243 } else {
1244 InvalidTransaction::Call.into()
1245 }
1246 }
1247
1248 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
1249 if let Call::submit_unsigned { raw_solution, .. } = call {
1250 Self::unsigned_pre_dispatch_checks(raw_solution)
1251 .map_err(dispatch_error_to_invalid)
1252 .map_err(Into::into)
1253 } else {
1254 Err(InvalidTransaction::Call.into())
1255 }
1256 }
1257 }
1258
1259 #[pallet::type_value]
1260 pub fn DefaultForRound() -> u32 {
1261 1
1262 }
1263
1264 #[pallet::storage]
1271 pub type Round<T: Config> = StorageValue<_, u32, ValueQuery, DefaultForRound>;
1272
1273 #[pallet::storage]
1275 pub type CurrentPhase<T: Config> = StorageValue<_, Phase<BlockNumberFor<T>>, ValueQuery>;
1276
1277 #[pallet::storage]
1281 pub type QueuedSolution<T: Config> =
1282 StorageValue<_, ReadySolution<T::AccountId, T::MaxWinners>>;
1283
1284 #[pallet::storage]
1289 pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T::AccountId, VoterOf<T>>>;
1290
1291 #[pallet::storage]
1296 pub type DesiredTargets<T> = StorageValue<_, u32>;
1297
1298 #[pallet::storage]
1303 pub type SnapshotMetadata<T: Config> = StorageValue<_, SolutionOrSnapshotSize>;
1304
1305 #[pallet::storage]
1319 pub type SignedSubmissionNextIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
1320
1321 #[pallet::storage]
1328 pub type SignedSubmissionIndices<T: Config> =
1329 StorageValue<_, SubmissionIndicesOf<T>, ValueQuery>;
1330
1331 #[pallet::storage]
1339 pub type SignedSubmissionsMap<T: Config> =
1340 StorageMap<_, Twox64Concat, u32, SignedSubmissionOf<T>, OptionQuery>;
1341
1342 #[pallet::storage]
1349 pub type MinimumUntrustedScore<T: Config> = StorageValue<_, ElectionScore>;
1350
1351 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
1355
1356 #[pallet::pallet]
1357 #[pallet::without_storage_info]
1358 #[pallet::storage_version(STORAGE_VERSION)]
1359 pub struct Pallet<T>(_);
1360}
1361
1362pub struct SnapshotWrapper<T>(core::marker::PhantomData<T>);
1365
1366impl<T: Config> SnapshotWrapper<T> {
1367 pub fn kill() {
1369 Snapshot::<T>::kill();
1370 SnapshotMetadata::<T>::kill();
1371 DesiredTargets::<T>::kill();
1372 }
1373 pub fn set(metadata: SolutionOrSnapshotSize, desired_targets: u32, buffer: &[u8]) {
1375 SnapshotMetadata::<T>::put(metadata);
1376 DesiredTargets::<T>::put(desired_targets);
1377 sp_io::storage::set(&Snapshot::<T>::hashed_key(), &buffer);
1378 }
1379
1380 #[cfg(feature = "try-runtime")]
1383 pub fn is_consistent() -> bool {
1384 let snapshots = [
1385 Snapshot::<T>::exists(),
1386 SnapshotMetadata::<T>::exists(),
1387 DesiredTargets::<T>::exists(),
1388 ];
1389
1390 snapshots.iter().skip(1).all(|v| snapshots[0] == *v)
1392 }
1393}
1394
1395impl<T: Config> Pallet<T> {
1396 pub fn round() -> u32 {
1403 Round::<T>::get()
1404 }
1405
1406 pub fn current_phase() -> Phase<BlockNumberFor<T>> {
1408 CurrentPhase::<T>::get()
1409 }
1410
1411 pub fn queued_solution() -> Option<ReadySolution<T::AccountId, T::MaxWinners>> {
1415 QueuedSolution::<T>::get()
1416 }
1417
1418 pub fn snapshot() -> Option<RoundSnapshot<T::AccountId, VoterOf<T>>> {
1423 Snapshot::<T>::get()
1424 }
1425
1426 pub fn desired_targets() -> Option<u32> {
1431 DesiredTargets::<T>::get()
1432 }
1433
1434 pub fn snapshot_metadata() -> Option<SolutionOrSnapshotSize> {
1439 SnapshotMetadata::<T>::get()
1440 }
1441
1442 pub fn minimum_untrusted_score() -> Option<ElectionScore> {
1447 MinimumUntrustedScore::<T>::get()
1448 }
1449
1450 fn do_synchronized_offchain_worker(now: BlockNumberFor<T>) {
1453 let current_phase = CurrentPhase::<T>::get();
1454 log!(trace, "lock for offchain worker acquired. Phase = {:?}", current_phase);
1455 match current_phase {
1456 Phase::Unsigned((true, opened)) if opened == now => {
1457 let initial_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
1459 unsigned::kill_ocw_solution::<T>();
1462 Self::mine_check_save_submit()
1463 });
1464 log!(debug, "initial offchain thread output: {:?}", initial_output);
1465 },
1466 Phase::Unsigned((true, opened)) if opened < now => {
1467 let resubmit_output = Self::ensure_offchain_repeat_frequency(now)
1470 .and_then(|_| Self::restore_or_compute_then_maybe_submit());
1471 log!(debug, "resubmit offchain thread output: {:?}", resubmit_output);
1472 },
1473 _ => {},
1474 }
1475 }
1476
1477 pub(crate) fn phase_transition(to: Phase<BlockNumberFor<T>>) {
1479 log!(info, "Starting phase {:?}, round {}.", to, Round::<T>::get());
1480 Self::deposit_event(Event::PhaseTransitioned {
1481 from: CurrentPhase::<T>::get(),
1482 to,
1483 round: Round::<T>::get(),
1484 });
1485 CurrentPhase::<T>::put(to);
1486 }
1487
1488 fn create_snapshot_internal(
1492 targets: Vec<T::AccountId>,
1493 voters: Vec<VoterOf<T>>,
1494 desired_targets: u32,
1495 ) {
1496 let metadata =
1497 SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 };
1498 log!(info, "creating a snapshot with metadata {:?}", metadata);
1499
1500 let snapshot = RoundSnapshot::<T::AccountId, VoterOf<T>> { voters, targets };
1504 let size = snapshot.encoded_size();
1505 log!(debug, "snapshot pre-calculated size {:?}", size);
1506 let mut buffer = Vec::with_capacity(size);
1507 snapshot.encode_to(&mut buffer);
1508
1509 debug_assert_eq!(buffer, snapshot.encode());
1511 debug_assert!(buffer.len() == size && size == buffer.capacity());
1513
1514 SnapshotWrapper::<T>::set(metadata, desired_targets, &buffer);
1515 }
1516
1517 fn create_snapshot_external(
1521 ) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
1522 let election_bounds = T::ElectionBounds::get();
1523
1524 let targets = T::DataProvider::electable_targets(election_bounds.targets)
1525 .and_then(|t| {
1526 election_bounds.ensure_targets_limits(
1527 CountBound(t.len() as u32),
1528 SizeBound(t.encoded_size() as u32),
1529 )?;
1530 Ok(t)
1531 })
1532 .map_err(ElectionError::DataProvider)?;
1533
1534 let voters = T::DataProvider::electing_voters(election_bounds.voters)
1535 .and_then(|v| {
1536 election_bounds.ensure_voters_limits(
1537 CountBound(v.len() as u32),
1538 SizeBound(v.encoded_size() as u32),
1539 )?;
1540 Ok(v)
1541 })
1542 .map_err(ElectionError::DataProvider)?;
1543
1544 let mut desired_targets = <Pallet<T> as ElectionProviderBase>::desired_targets_checked()
1545 .map_err(|e| ElectionError::DataProvider(e))?;
1546
1547 let max_desired_targets: u32 = targets.len() as u32;
1550 if desired_targets > max_desired_targets {
1551 log!(
1552 warn,
1553 "desired_targets: {} > targets.len(): {}, capping desired_targets",
1554 desired_targets,
1555 max_desired_targets
1556 );
1557 desired_targets = max_desired_targets;
1558 }
1559
1560 Ok((targets, voters, desired_targets))
1561 }
1562
1563 pub fn create_snapshot() -> Result<(), ElectionError<T>> {
1574 let (targets, voters, desired_targets) = Self::create_snapshot_external()?;
1576
1577 let internal_weight =
1579 T::WeightInfo::create_snapshot_internal(voters.len() as u32, targets.len() as u32);
1580 Self::create_snapshot_internal(targets, voters, desired_targets);
1581 Self::register_weight(internal_weight);
1582 Ok(())
1583 }
1584
1585 fn register_weight(weight: Weight) {
1589 frame_system::Pallet::<T>::register_extra_weight_unchecked(
1590 weight,
1591 DispatchClass::Mandatory,
1592 );
1593 }
1594
1595 pub fn feasibility_check(
1597 raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
1598 compute: ElectionCompute,
1599 ) -> Result<ReadySolution<T::AccountId, T::MaxWinners>, FeasibilityError> {
1600 let desired_targets =
1601 DesiredTargets::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;
1602
1603 let snapshot = Snapshot::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;
1604 let round = Round::<T>::get();
1605 let minimum_untrusted_score = MinimumUntrustedScore::<T>::get();
1606
1607 Miner::<T::MinerConfig>::feasibility_check(
1608 raw_solution,
1609 compute,
1610 desired_targets,
1611 snapshot,
1612 round,
1613 minimum_untrusted_score,
1614 )
1615 }
1616
1617 fn rotate_round() {
1623 Round::<T>::mutate(|r| *r += 1);
1625
1626 Self::phase_transition(Phase::Off);
1628
1629 SnapshotWrapper::<T>::kill();
1631 }
1632
1633 fn do_elect() -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
1634 let _ = Self::finalize_signed_phase();
1642
1643 QueuedSolution::<T>::take()
1644 .ok_or(ElectionError::<T>::NothingQueued)
1645 .or_else(|_| {
1646 T::Fallback::instant_elect(
1650 DataProviderBounds::default(),
1651 DataProviderBounds::default(),
1652 )
1653 .map_err(|fe| ElectionError::Fallback(fe))
1654 .and_then(|supports| {
1655 Ok(ReadySolution {
1656 supports,
1657 score: Default::default(),
1658 compute: ElectionCompute::Fallback,
1659 })
1660 })
1661 })
1662 .map(|ReadySolution { compute, score, supports }| {
1663 Self::deposit_event(Event::ElectionFinalized { compute, score });
1664 if Round::<T>::get() != 1 {
1665 log!(info, "Finalized election round with compute {:?}.", compute);
1666 }
1667 supports
1668 })
1669 .map_err(|err| {
1670 Self::deposit_event(Event::ElectionFailed);
1671 if Round::<T>::get() != 1 {
1672 log!(warn, "Failed to finalize election round. reason {:?}", err);
1673 }
1674 err
1675 })
1676 }
1677
1678 fn weigh_supports(supports: &Supports<T::AccountId>) {
1680 let active_voters = supports
1681 .iter()
1682 .map(|(_, x)| x)
1683 .fold(Zero::zero(), |acc, next| acc + next.voters.len() as u32);
1684 let desired_targets = supports.len() as u32;
1685 Self::register_weight(T::WeightInfo::elect_queued(active_voters, desired_targets));
1686 }
1687}
1688
1689#[cfg(feature = "try-runtime")]
1690impl<T: Config> Pallet<T> {
1691 fn do_try_state() -> Result<(), TryRuntimeError> {
1692 Self::try_state_snapshot()?;
1693 Self::try_state_signed_submissions_map()?;
1694 Self::try_state_phase_off()
1695 }
1696
1697 fn try_state_snapshot() -> Result<(), TryRuntimeError> {
1701 if SnapshotWrapper::<T>::is_consistent() {
1702 Ok(())
1703 } else {
1704 Err("If snapshot exists, metadata and desired targets should be set too. Otherwise, none should be set.".into())
1705 }
1706 }
1707
1708 fn try_state_signed_submissions_map() -> Result<(), TryRuntimeError> {
1713 let mut last_score: ElectionScore = Default::default();
1714 let indices = SignedSubmissionIndices::<T>::get();
1715
1716 for (i, indice) in indices.iter().enumerate() {
1717 let submission = SignedSubmissionsMap::<T>::get(indice.2);
1718 if submission.is_none() {
1719 return Err(
1720 "All signed submissions indices must be part of the submissions map".into()
1721 )
1722 }
1723
1724 if i == 0 {
1725 last_score = indice.0
1726 } else {
1727 if last_score.strict_threshold_better(indice.0, Perbill::zero()) {
1728 return Err(
1729 "Signed submission indices vector must be ordered by election score".into()
1730 )
1731 }
1732 last_score = indice.0;
1733 }
1734 }
1735
1736 if SignedSubmissionsMap::<T>::iter().nth(indices.len()).is_some() {
1737 return Err(
1738 "Signed submissions map length should be the same as the indices vec length".into()
1739 )
1740 }
1741
1742 match SignedSubmissionNextIndex::<T>::get() {
1743 0 => Ok(()),
1744 next =>
1745 if SignedSubmissionsMap::<T>::get(next).is_some() {
1746 return Err(
1747 "The next submissions index should not be in the submissions maps already"
1748 .into(),
1749 )
1750 } else {
1751 Ok(())
1752 },
1753 }
1754 }
1755
1756 fn try_state_phase_off() -> Result<(), TryRuntimeError> {
1759 match CurrentPhase::<T>::get().is_off() {
1760 false => Ok(()),
1761 true =>
1762 if Snapshot::<T>::get().is_some() {
1763 Err("Snapshot must be none when in Phase::Off".into())
1764 } else {
1765 Ok(())
1766 },
1767 }
1768 }
1769}
1770
1771impl<T: Config> ElectionProviderBase for Pallet<T> {
1772 type AccountId = T::AccountId;
1773 type BlockNumber = BlockNumberFor<T>;
1774 type Error = ElectionError<T>;
1775 type MaxWinners = T::MaxWinners;
1776 type DataProvider = T::DataProvider;
1777}
1778
1779impl<T: Config> ElectionProvider for Pallet<T> {
1780 fn ongoing() -> bool {
1781 match CurrentPhase::<T>::get() {
1782 Phase::Off => false,
1783 _ => true,
1784 }
1785 }
1786
1787 fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
1788 match Self::do_elect() {
1789 Ok(supports) => {
1790 Self::weigh_supports(&supports);
1792 Self::rotate_round();
1793 Ok(supports)
1794 },
1795 Err(why) => {
1796 log!(error, "Entering emergency mode: {:?}", why);
1797 Self::phase_transition(Phase::Emergency);
1798 Err(why)
1799 },
1800 }
1801 }
1802}
1803
1804pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction {
1807 let error_number = match error {
1808 DispatchError::Module(ModuleError { error, .. }) => error[0],
1809 _ => 0,
1810 };
1811 InvalidTransaction::Custom(error_number)
1812}
1813
1814#[cfg(test)]
1815mod feasibility_check {
1816 use super::*;
1821 use crate::mock::{
1822 raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase,
1823 TargetIndex, UnsignedPhase, VoterIndex,
1824 };
1825 use frame_support::{assert_noop, assert_ok};
1826
1827 const COMPUTE: ElectionCompute = ElectionCompute::OnChain;
1828
1829 #[test]
1830 fn snapshot_is_there() {
1831 ExtBuilder::default().build_and_execute(|| {
1832 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1833 assert!(CurrentPhase::<Runtime>::get().is_signed());
1834 let solution = raw_solution();
1835
1836 SnapshotWrapper::<Runtime>::kill();
1839
1840 assert_noop!(
1841 MultiPhase::feasibility_check(solution, COMPUTE),
1842 FeasibilityError::SnapshotUnavailable
1843 );
1844 })
1845 }
1846
1847 #[test]
1848 fn round() {
1849 ExtBuilder::default().build_and_execute(|| {
1850 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1851 assert!(CurrentPhase::<Runtime>::get().is_signed());
1852
1853 let mut solution = raw_solution();
1854 solution.round += 1;
1855 assert_noop!(
1856 MultiPhase::feasibility_check(solution, COMPUTE),
1857 FeasibilityError::InvalidRound
1858 );
1859 })
1860 }
1861
1862 #[test]
1863 fn desired_targets_gets_capped() {
1864 ExtBuilder::default().desired_targets(8).build_and_execute(|| {
1865 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1866 assert!(CurrentPhase::<Runtime>::get().is_signed());
1867
1868 let raw = raw_solution();
1869
1870 assert_eq!(raw.solution.unique_targets().len(), 4);
1871 assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 4);
1873
1874 assert_ok!(MultiPhase::feasibility_check(raw, COMPUTE));
1876 })
1877 }
1878
1879 #[test]
1880 fn less_than_desired_targets_fails() {
1881 ExtBuilder::default().desired_targets(8).build_and_execute(|| {
1882 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1883 assert!(CurrentPhase::<Runtime>::get().is_signed());
1884
1885 let mut raw = raw_solution();
1886
1887 assert_eq!(raw.solution.unique_targets().len(), 4);
1888 assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 4);
1890
1891 raw.solution.votes1[0].1 = 4;
1893
1894 assert_noop!(
1896 MultiPhase::feasibility_check(raw, COMPUTE),
1897 FeasibilityError::WrongWinnerCount,
1898 );
1899 })
1900 }
1901
1902 #[test]
1903 fn winner_indices() {
1904 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
1905 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1906 assert!(CurrentPhase::<Runtime>::get().is_signed());
1907
1908 let mut raw = raw_solution();
1909 assert_eq!(Snapshot::<Runtime>::get().unwrap().targets.len(), 4);
1910 raw.solution
1916 .votes1
1917 .iter_mut()
1918 .filter(|(_, t)| *t == TargetIndex::from(3u16))
1919 .for_each(|(_, t)| *t += 1);
1920 raw.solution.votes2.iter_mut().for_each(|(_, [(t0, _)], t1)| {
1921 if *t0 == TargetIndex::from(3u16) {
1922 *t0 += 1
1923 };
1924 if *t1 == TargetIndex::from(3u16) {
1925 *t1 += 1
1926 };
1927 });
1928 assert_noop!(
1929 MultiPhase::feasibility_check(raw, COMPUTE),
1930 FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex)
1931 );
1932 })
1933 }
1934
1935 #[test]
1936 fn voter_indices() {
1937 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
1939 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1940 assert!(CurrentPhase::<Runtime>::get().is_signed());
1941
1942 let mut solution = raw_solution();
1943 assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
1944 assert!(
1948 solution
1949 .solution
1950 .votes1
1951 .iter_mut()
1952 .filter(|(v, _)| *v == VoterIndex::from(7u32))
1953 .map(|(v, _)| *v = 8)
1954 .count() > 0
1955 );
1956 assert_noop!(
1957 MultiPhase::feasibility_check(solution, COMPUTE),
1958 FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex),
1959 );
1960 })
1961 }
1962
1963 #[test]
1964 fn voter_votes() {
1965 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
1966 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1967 assert!(CurrentPhase::<Runtime>::get().is_signed());
1968
1969 let mut solution = raw_solution();
1970 assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
1971 assert_eq!(
1976 solution
1977 .solution
1978 .votes1
1979 .iter_mut()
1980 .filter(|(v, t)| *v == 7 && *t == 3)
1981 .map(|(_, t)| *t = 2)
1982 .count(),
1983 1,
1984 );
1985 assert_noop!(
1986 MultiPhase::feasibility_check(solution, COMPUTE),
1987 FeasibilityError::InvalidVote,
1988 );
1989 })
1990 }
1991
1992 #[test]
1993 fn score() {
1994 ExtBuilder::default().desired_targets(2).build_and_execute(|| {
1995 roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1996 assert!(CurrentPhase::<Runtime>::get().is_signed());
1997
1998 let mut solution = raw_solution();
1999 assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2000
2001 solution.score.minimal_stake += 1;
2003
2004 assert_noop!(
2005 MultiPhase::feasibility_check(solution, COMPUTE),
2006 FeasibilityError::InvalidScore,
2007 );
2008 })
2009 }
2010}
2011
2012#[cfg(test)]
2013mod tests {
2014 use super::*;
2015 use crate::{
2016 mock::{
2017 multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned, AccountId,
2018 ElectionsBounds, ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime,
2019 RuntimeOrigin, SignedMaxSubmissions, System, TargetIndex, Targets, Voters,
2020 },
2021 Phase,
2022 };
2023 use frame_support::{assert_noop, assert_ok};
2024 use sp_npos_elections::{BalancingConfig, Support};
2025
2026 #[test]
2027 fn phase_rotation_works() {
2028 ExtBuilder::default().build_and_execute(|| {
2029 assert_eq!(System::block_number(), 0);
2034 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2035 assert_eq!(Round::<Runtime>::get(), 1);
2036
2037 roll_to(4);
2038 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2039 assert!(Snapshot::<Runtime>::get().is_none());
2040 assert_eq!(Round::<Runtime>::get(), 1);
2041
2042 roll_to_signed();
2043 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2044 assert_eq!(
2045 multi_phase_events(),
2046 vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2047 );
2048 assert!(Snapshot::<Runtime>::get().is_some());
2049 assert_eq!(Round::<Runtime>::get(), 1);
2050
2051 roll_to(24);
2052 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2053 assert!(Snapshot::<Runtime>::get().is_some());
2054 assert_eq!(Round::<Runtime>::get(), 1);
2055
2056 roll_to_unsigned();
2057 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2058 assert_eq!(
2059 multi_phase_events(),
2060 vec![
2061 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2062 Event::PhaseTransitioned {
2063 from: Phase::Signed,
2064 to: Phase::Unsigned((true, 25)),
2065 round: 1
2066 },
2067 ],
2068 );
2069 assert!(Snapshot::<Runtime>::get().is_some());
2070
2071 roll_to(29);
2072 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2073 assert!(Snapshot::<Runtime>::get().is_some());
2074
2075 roll_to(30);
2076 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2077 assert!(Snapshot::<Runtime>::get().is_some());
2078
2079 roll_to(32);
2081 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2082 assert!(Snapshot::<Runtime>::get().is_some());
2083
2084 assert_ok!(MultiPhase::elect());
2085
2086 assert!(CurrentPhase::<Runtime>::get().is_off());
2087 assert!(Snapshot::<Runtime>::get().is_none());
2088 assert_eq!(Round::<Runtime>::get(), 2);
2089
2090 roll_to(44);
2091 assert!(CurrentPhase::<Runtime>::get().is_off());
2092
2093 roll_to_signed();
2094 assert!(CurrentPhase::<Runtime>::get().is_signed());
2095
2096 roll_to(55);
2097 assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(55));
2098
2099 assert_eq!(
2100 multi_phase_events(),
2101 vec![
2102 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2103 Event::PhaseTransitioned {
2104 from: Phase::Signed,
2105 to: Phase::Unsigned((true, 25)),
2106 round: 1
2107 },
2108 Event::ElectionFinalized {
2109 compute: ElectionCompute::Fallback,
2110 score: ElectionScore {
2111 minimal_stake: 0,
2112 sum_stake: 0,
2113 sum_stake_squared: 0
2114 }
2115 },
2116 Event::PhaseTransitioned {
2117 from: Phase::Unsigned((true, 25)),
2118 to: Phase::Off,
2119 round: 2
2120 },
2121 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 2 },
2122 Event::PhaseTransitioned {
2123 from: Phase::Signed,
2124 to: Phase::Unsigned((true, 55)),
2125 round: 2
2126 },
2127 ]
2128 );
2129 })
2130 }
2131
2132 #[test]
2133 fn signed_phase_void() {
2134 ExtBuilder::default().phases(0, 10).build_and_execute(|| {
2135 roll_to(15);
2136 assert!(CurrentPhase::<Runtime>::get().is_off());
2137
2138 roll_to(19);
2139 assert!(CurrentPhase::<Runtime>::get().is_off());
2140
2141 roll_to(20);
2142 assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(20));
2143 assert!(Snapshot::<Runtime>::get().is_some());
2144
2145 roll_to(30);
2146 assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(20));
2147
2148 assert_ok!(MultiPhase::elect());
2149
2150 assert!(CurrentPhase::<Runtime>::get().is_off());
2151 assert!(Snapshot::<Runtime>::get().is_none());
2152
2153 assert_eq!(
2154 multi_phase_events(),
2155 vec![
2156 Event::PhaseTransitioned {
2157 from: Phase::Off,
2158 to: Phase::Unsigned((true, 20)),
2159 round: 1
2160 },
2161 Event::ElectionFinalized {
2162 compute: ElectionCompute::Fallback,
2163 score: ElectionScore {
2164 minimal_stake: 0,
2165 sum_stake: 0,
2166 sum_stake_squared: 0
2167 }
2168 },
2169 Event::PhaseTransitioned {
2170 from: Phase::Unsigned((true, 20)),
2171 to: Phase::Off,
2172 round: 2
2173 },
2174 ]
2175 );
2176 });
2177 }
2178
2179 #[test]
2180 fn unsigned_phase_void() {
2181 ExtBuilder::default().phases(10, 0).build_and_execute(|| {
2182 roll_to(15);
2183 assert!(CurrentPhase::<Runtime>::get().is_off());
2184
2185 roll_to(19);
2186 assert!(CurrentPhase::<Runtime>::get().is_off());
2187
2188 roll_to_signed();
2189 assert!(CurrentPhase::<Runtime>::get().is_signed());
2190 assert!(Snapshot::<Runtime>::get().is_some());
2191
2192 roll_to(30);
2193 assert!(CurrentPhase::<Runtime>::get().is_signed());
2194
2195 assert_ok!(MultiPhase::elect());
2196
2197 assert!(CurrentPhase::<Runtime>::get().is_off());
2198 assert!(Snapshot::<Runtime>::get().is_none());
2199
2200 assert_eq!(
2201 multi_phase_events(),
2202 vec![
2203 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2204 Event::ElectionFinalized {
2205 compute: ElectionCompute::Fallback,
2206 score: ElectionScore {
2207 minimal_stake: 0,
2208 sum_stake: 0,
2209 sum_stake_squared: 0
2210 }
2211 },
2212 Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2213 ]
2214 )
2215 });
2216 }
2217
2218 #[test]
2219 fn both_phases_void() {
2220 ExtBuilder::default().phases(0, 0).build_and_execute(|| {
2221 roll_to(15);
2222 assert!(CurrentPhase::<Runtime>::get().is_off());
2223
2224 roll_to(19);
2225 assert!(CurrentPhase::<Runtime>::get().is_off());
2226
2227 roll_to(20);
2228 assert!(CurrentPhase::<Runtime>::get().is_off());
2229
2230 roll_to(30);
2231 assert!(CurrentPhase::<Runtime>::get().is_off());
2232
2233 assert_ok!(MultiPhase::elect());
2235
2236 assert!(CurrentPhase::<Runtime>::get().is_off());
2237
2238 assert_eq!(
2239 multi_phase_events(),
2240 vec![
2241 Event::ElectionFinalized {
2242 compute: ElectionCompute::Fallback,
2243 score: ElectionScore {
2244 minimal_stake: 0,
2245 sum_stake: 0,
2246 sum_stake_squared: 0
2247 }
2248 },
2249 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Off, round: 2 },
2250 ]
2251 );
2252 });
2253 }
2254
2255 #[test]
2256 fn early_termination() {
2257 ExtBuilder::default().build_and_execute(|| {
2259 roll_to_signed();
2262 assert_eq!(
2263 multi_phase_events(),
2264 vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2265 );
2266 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2267 assert_eq!(Round::<Runtime>::get(), 1);
2268
2269 assert_ok!(MultiPhase::elect());
2271
2272 assert_eq!(
2274 multi_phase_events(),
2275 vec![
2276 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2277 Event::ElectionFinalized {
2278 compute: ElectionCompute::Fallback,
2279 score: Default::default()
2280 },
2281 Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2282 ],
2283 );
2284 assert_eq!(Round::<Runtime>::get(), 2);
2286 assert!(Snapshot::<Runtime>::get().is_none());
2287 assert!(SnapshotMetadata::<Runtime>::get().is_none());
2288 assert!(DesiredTargets::<Runtime>::get().is_none());
2289 assert!(QueuedSolution::<Runtime>::get().is_none());
2290 assert!(MultiPhase::signed_submissions().is_empty());
2291 })
2292 }
2293
2294 #[test]
2295 fn early_termination_with_submissions() {
2296 ExtBuilder::default().build_and_execute(|| {
2298 roll_to_signed();
2301 assert_eq!(
2302 multi_phase_events(),
2303 vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2304 );
2305 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2306 assert_eq!(Round::<Runtime>::get(), 1);
2307
2308 for s in 0..SignedMaxSubmissions::get() {
2310 let solution = RawSolution {
2311 score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
2312 ..Default::default()
2313 };
2314 assert_ok!(MultiPhase::submit(
2315 crate::mock::RuntimeOrigin::signed(99),
2316 Box::new(solution)
2317 ));
2318 }
2319
2320 assert_ok!(MultiPhase::elect());
2322
2323 assert_eq!(Round::<Runtime>::get(), 2);
2325 assert!(Snapshot::<Runtime>::get().is_none());
2326 assert!(SnapshotMetadata::<Runtime>::get().is_none());
2327 assert!(DesiredTargets::<Runtime>::get().is_none());
2328 assert!(QueuedSolution::<Runtime>::get().is_none());
2329 assert!(MultiPhase::signed_submissions().is_empty());
2330
2331 assert_eq!(
2332 multi_phase_events(),
2333 vec![
2334 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2335 Event::SolutionStored {
2336 compute: ElectionCompute::Signed,
2337 origin: Some(99),
2338 prev_ejected: false
2339 },
2340 Event::SolutionStored {
2341 compute: ElectionCompute::Signed,
2342 origin: Some(99),
2343 prev_ejected: false
2344 },
2345 Event::SolutionStored {
2346 compute: ElectionCompute::Signed,
2347 origin: Some(99),
2348 prev_ejected: false
2349 },
2350 Event::SolutionStored {
2351 compute: ElectionCompute::Signed,
2352 origin: Some(99),
2353 prev_ejected: false
2354 },
2355 Event::SolutionStored {
2356 compute: ElectionCompute::Signed,
2357 origin: Some(99),
2358 prev_ejected: false
2359 },
2360 Event::Slashed { account: 99, value: 5 },
2361 Event::Slashed { account: 99, value: 5 },
2362 Event::Slashed { account: 99, value: 5 },
2363 Event::Slashed { account: 99, value: 5 },
2364 Event::Slashed { account: 99, value: 5 },
2365 Event::ElectionFinalized {
2366 compute: ElectionCompute::Fallback,
2367 score: ElectionScore {
2368 minimal_stake: 0,
2369 sum_stake: 0,
2370 sum_stake_squared: 0
2371 }
2372 },
2373 Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2374 ]
2375 );
2376 })
2377 }
2378
2379 #[test]
2380 fn check_events_with_compute_signed() {
2381 ExtBuilder::default().build_and_execute(|| {
2382 roll_to_signed();
2383 assert!(CurrentPhase::<Runtime>::get().is_signed());
2384
2385 let solution = raw_solution();
2386 assert_ok!(MultiPhase::submit(
2387 crate::mock::RuntimeOrigin::signed(99),
2388 Box::new(solution)
2389 ));
2390
2391 roll_to(30);
2392 assert_ok!(MultiPhase::elect());
2393
2394 assert_eq!(
2395 multi_phase_events(),
2396 vec![
2397 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2398 Event::SolutionStored {
2399 compute: ElectionCompute::Signed,
2400 origin: Some(99),
2401 prev_ejected: false
2402 },
2403 Event::Rewarded { account: 99, value: 7 },
2404 Event::PhaseTransitioned {
2405 from: Phase::Signed,
2406 to: Phase::Unsigned((true, 25)),
2407 round: 1
2408 },
2409 Event::ElectionFinalized {
2410 compute: ElectionCompute::Signed,
2411 score: ElectionScore {
2412 minimal_stake: 40,
2413 sum_stake: 100,
2414 sum_stake_squared: 5200
2415 }
2416 },
2417 Event::PhaseTransitioned {
2418 from: Phase::Unsigned((true, 25)),
2419 to: Phase::Off,
2420 round: 2
2421 },
2422 ],
2423 );
2424 })
2425 }
2426
2427 #[test]
2428 fn check_events_with_compute_unsigned() {
2429 ExtBuilder::default().build_and_execute(|| {
2430 roll_to_unsigned();
2431 assert!(CurrentPhase::<Runtime>::get().is_unsigned());
2432
2433 assert!(Snapshot::<Runtime>::get().is_some());
2435 assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 2);
2436
2437 let (solution, witness, _) = MultiPhase::mine_solution().unwrap();
2439
2440 assert!(QueuedSolution::<Runtime>::get().is_none());
2442 assert_ok!(MultiPhase::submit_unsigned(
2443 crate::mock::RuntimeOrigin::none(),
2444 Box::new(solution),
2445 witness
2446 ));
2447 assert!(QueuedSolution::<Runtime>::get().is_some());
2448
2449 assert_ok!(MultiPhase::elect());
2450
2451 assert_eq!(
2452 multi_phase_events(),
2453 vec![
2454 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2455 Event::PhaseTransitioned {
2456 from: Phase::Signed,
2457 to: Phase::Unsigned((true, 25)),
2458 round: 1
2459 },
2460 Event::SolutionStored {
2461 compute: ElectionCompute::Unsigned,
2462 origin: None,
2463 prev_ejected: false
2464 },
2465 Event::ElectionFinalized {
2466 compute: ElectionCompute::Unsigned,
2467 score: ElectionScore {
2468 minimal_stake: 40,
2469 sum_stake: 100,
2470 sum_stake_squared: 5200
2471 }
2472 },
2473 Event::PhaseTransitioned {
2474 from: Phase::Unsigned((true, 25)),
2475 to: Phase::Off,
2476 round: 2
2477 },
2478 ],
2479 );
2480 })
2481 }
2482
2483 #[test]
2484 fn fallback_strategy_works() {
2485 ExtBuilder::default().onchain_fallback(true).build_and_execute(|| {
2486 roll_to_unsigned();
2487 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2488
2489 assert!(QueuedSolution::<Runtime>::get().is_none());
2491 let supports = MultiPhase::elect().unwrap();
2492
2493 assert_eq!(
2494 supports,
2495 vec![
2496 (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }),
2497 (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] })
2498 ]
2499 );
2500
2501 assert_eq!(
2502 multi_phase_events(),
2503 vec![
2504 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2505 Event::PhaseTransitioned {
2506 from: Phase::Signed,
2507 to: Phase::Unsigned((true, 25)),
2508 round: 1
2509 },
2510 Event::ElectionFinalized {
2511 compute: ElectionCompute::Fallback,
2512 score: ElectionScore {
2513 minimal_stake: 0,
2514 sum_stake: 0,
2515 sum_stake_squared: 0
2516 }
2517 },
2518 Event::PhaseTransitioned {
2519 from: Phase::Unsigned((true, 25)),
2520 to: Phase::Off,
2521 round: 2
2522 },
2523 ]
2524 );
2525 });
2526
2527 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2528 roll_to_unsigned();
2529 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2530
2531 assert!(QueuedSolution::<Runtime>::get().is_none());
2533 assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback."));
2534 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2536 assert!(Snapshot::<Runtime>::get().is_some());
2538
2539 assert_eq!(
2540 multi_phase_events(),
2541 vec![
2542 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2543 Event::PhaseTransitioned {
2544 from: Phase::Signed,
2545 to: Phase::Unsigned((true, 25)),
2546 round: 1
2547 },
2548 Event::ElectionFailed,
2549 Event::PhaseTransitioned {
2550 from: Phase::Unsigned((true, 25)),
2551 to: Phase::Emergency,
2552 round: 1
2553 },
2554 ]
2555 );
2556 })
2557 }
2558
2559 #[test]
2560 fn governance_fallback_works() {
2561 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2562 roll_to_unsigned();
2563 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2564
2565 assert!(QueuedSolution::<Runtime>::get().is_none());
2567 assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback."));
2568
2569 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2571 assert!(QueuedSolution::<Runtime>::get().is_none());
2572 assert!(Snapshot::<Runtime>::get().is_some());
2573
2574 assert_noop!(
2576 MultiPhase::governance_fallback(RuntimeOrigin::signed(99), None, None),
2577 DispatchError::BadOrigin
2578 );
2579
2580 assert_ok!(MultiPhase::governance_fallback(RuntimeOrigin::root(), None, None));
2582 assert!(QueuedSolution::<Runtime>::get().is_some());
2584 assert!(MultiPhase::elect().is_ok());
2586 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2587
2588 assert_eq!(
2589 multi_phase_events(),
2590 vec![
2591 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2592 Event::PhaseTransitioned {
2593 from: Phase::Signed,
2594 to: Phase::Unsigned((true, 25)),
2595 round: 1
2596 },
2597 Event::ElectionFailed,
2598 Event::PhaseTransitioned {
2599 from: Phase::Unsigned((true, 25)),
2600 to: Phase::Emergency,
2601 round: 1
2602 },
2603 Event::SolutionStored {
2604 compute: ElectionCompute::Fallback,
2605 origin: None,
2606 prev_ejected: false
2607 },
2608 Event::ElectionFinalized {
2609 compute: ElectionCompute::Fallback,
2610 score: Default::default()
2611 },
2612 Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off, round: 2 },
2613 ]
2614 );
2615 })
2616 }
2617
2618 #[test]
2619 fn snapshot_too_big_failure_onchain_fallback() {
2620 ExtBuilder::default().build_and_execute(|| {
2622 let new_bounds = ElectionBoundsBuilder::default().targets_count(1_000.into()).build();
2624 ElectionsBounds::set(new_bounds);
2625
2626 Targets::set((0..(1_000 as AccountId) + 1).collect::<Vec<_>>());
2627
2628 roll_to(15);
2630 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2631
2632 roll_to(25);
2634 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2635
2636 let supports = MultiPhase::elect().unwrap();
2638 assert!(supports.len() > 0);
2639
2640 assert_eq!(
2641 multi_phase_events(),
2642 vec![
2643 Event::ElectionFinalized {
2644 compute: ElectionCompute::Fallback,
2645 score: ElectionScore {
2646 minimal_stake: 0,
2647 sum_stake: 0,
2648 sum_stake_squared: 0
2649 }
2650 },
2651 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Off, round: 2 },
2652 ]
2653 );
2654 });
2655 }
2656
2657 #[test]
2658 fn snapshot_too_big_failure_no_fallback() {
2659 ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2661 let new_bounds = ElectionBoundsBuilder::default().targets_count(1_000.into()).build();
2663 ElectionsBounds::set(new_bounds);
2664
2665 Targets::set((0..(TargetIndex::max_value() as AccountId) + 1).collect::<Vec<_>>());
2666
2667 roll_to(15);
2669 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2670
2671 roll_to(25);
2673 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2674
2675 roll_to(29);
2676 let err = MultiPhase::elect().unwrap_err();
2677 assert_eq!(err, ElectionError::Fallback("NoFallback."));
2678 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2679
2680 assert_eq!(
2681 multi_phase_events(),
2682 vec![
2683 Event::ElectionFailed,
2684 Event::PhaseTransitioned { from: Phase::Off, to: Phase::Emergency, round: 1 }
2685 ]
2686 );
2687 });
2688 }
2689
2690 #[test]
2691 fn snapshot_too_big_truncate() {
2692 ExtBuilder::default().build_and_execute(|| {
2694 assert_eq!(Voters::get().len(), 8);
2696 let new_bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build();
2698 ElectionsBounds::set(new_bounds);
2699
2700 roll_to_signed();
2702 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2703
2704 assert_eq!(
2705 SnapshotMetadata::<Runtime>::get().unwrap(),
2706 SolutionOrSnapshotSize { voters: 2, targets: 4 }
2707 );
2708 })
2709 }
2710
2711 #[test]
2712 fn untrusted_score_verification_is_respected() {
2713 ExtBuilder::default().build_and_execute(|| {
2714 roll_to_signed();
2715 assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2716
2717 crate::mock::Balancing::set(Some(BalancingConfig { iterations: 2, tolerance: 0 }));
2719
2720 let (solution, _, _) = MultiPhase::mine_solution().unwrap();
2721 assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));
2723
2724 MinimumUntrustedScore::<Runtime>::put(ElectionScore {
2725 minimal_stake: 49,
2726 ..Default::default()
2727 });
2728 assert_ok!(MultiPhase::feasibility_check(solution.clone(), ElectionCompute::Signed));
2729
2730 MinimumUntrustedScore::<Runtime>::put(ElectionScore {
2731 minimal_stake: 51,
2732 ..Default::default()
2733 });
2734 assert_noop!(
2735 MultiPhase::feasibility_check(solution, ElectionCompute::Signed),
2736 FeasibilityError::UntrustedScoreTooLow,
2737 );
2738 })
2739 }
2740
2741 #[test]
2742 fn number_of_voters_allowed_2sec_block() {
2743 assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real);
2745
2746 let all_voters: u32 = 10_000;
2747 let all_targets: u32 = 5_000;
2748 let desired: u32 = 1_000;
2749 let weight_with = |active| {
2750 <Runtime as Config>::WeightInfo::submit_unsigned(
2751 all_voters,
2752 all_targets,
2753 active,
2754 desired,
2755 )
2756 };
2757
2758 let mut active = 1;
2759 while weight_with(active)
2760 .all_lte(<Runtime as frame_system::Config>::BlockWeights::get().max_block) ||
2761 active == all_voters
2762 {
2763 active += 1;
2764 }
2765
2766 println!("can support {} voters to yield a weight of {}", active, weight_with(active));
2767 }
2768}