diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index fee288cb2b7f8e1d685816dc2ef2a20cbcfc5038..019c14b2c7dfdfa9a66dc8fd6ffc46bb2654a576 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -23,7 +23,9 @@ #![recursion_limit = "256"] use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight}; +use frame_election_provider_support::{ + onchain, ElectionDataProvider, ExtendedBalance, SequentialPhragmen, VoteWeight, +}; use frame_support::{ construct_runtime, pallet_prelude::Get, @@ -660,6 +662,25 @@ impl onchain::BoundedConfig for OnChainSeqPhragmen { type TargetsBound = ConstU32<2_000>; } +impl pallet_election_provider_multi_phase::MinerConfig for Runtime { + type AccountId = AccountId; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type Solution = NposSolution16; + type MaxVotesPerVoter = + <<Self as pallet_election_provider_multi_phase::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + + // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their + // weight estimate function is wired to this call's weight. + fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { + < + <Self as pallet_election_provider_multi_phase::Config>::WeightInfo + as + pallet_election_provider_multi_phase::WeightInfo + >::submit_unsigned(v, t, a, d) + } +} + impl pallet_election_provider_multi_phase::Config for Runtime { type Event = Event; type Currency = Balances; @@ -669,9 +690,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MultiPhaseUnsignedPriority; + type MinerConfig = Self; type SignedMaxSubmissions = ConstU32<10>; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; @@ -682,7 +702,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); // burn slashes type RewardHandler = (); // nothing to do upon rewards type DataProvider = Staking; - type Solution = NposSolution16; type Fallback = onchain::BoundedExecution<OnChainSeqPhragmen>; type GovernanceFallback = onchain::BoundedExecution<OnChainSeqPhragmen>; type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>; @@ -2017,7 +2036,7 @@ mod tests { #[test] fn perbill_as_onchain_accuracy() { type OnChainAccuracy = - <<Runtime as pallet_election_provider_multi_phase::Config>::Solution as NposSolution>::Accuracy; + <<Runtime as pallet_election_provider_multi_phase::MinerConfig>::Solution as NposSolution>::Accuracy; let maximum_chain_accuracy: Vec<UpperOf<OnChainAccuracy>> = (0..MaxNominations::get()) .map(|_| <UpperOf<OnChainAccuracy>>::from(OnChainAccuracy::one().deconstruct())) .collect(); diff --git a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs index a1c643754158b352404cd66eccea6a29311e402a..a8195df7305ffa1073f5afbc60d8f8c0cfcfff5e 100644 --- a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs @@ -39,15 +39,15 @@ fn solution_with_size<T: Config>( size: SolutionOrSnapshotSize, active_voters_count: u32, desired_targets: u32, -) -> Result<RawSolution<SolutionOf<T>>, &'static str> { +) -> Result<RawSolution<SolutionOf<T::MinerConfig>>, &'static str> { ensure!(size.targets >= desired_targets, "must have enough targets"); ensure!( - size.targets >= (<SolutionOf<T>>::LIMIT * 2) as u32, + size.targets >= (<SolutionOf<T::MinerConfig>>::LIMIT * 2) as u32, "must have enough targets for unique votes." ); ensure!(size.voters >= active_voters_count, "must have enough voters"); ensure!( - (<SolutionOf<T>>::LIMIT as u32) < desired_targets, + (<SolutionOf<T::MinerConfig>>::LIMIT as u32) < desired_targets, "must have enough winners to give them votes." ); @@ -74,10 +74,10 @@ fn solution_with_size<T: Config>( // chose a random subset of winners. let winner_votes: BoundedVec<_, _> = winners .as_slice() - .choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT) + .choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT) .cloned() .try_collect() - .expect("<SolutionOf<T>>::LIMIT is the correct bound; qed."); + .expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed."); let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED); (voter, stake, winner_votes) }) @@ -92,10 +92,10 @@ fn solution_with_size<T: Config>( let rest_voters = (active_voters_count..size.voters) .map(|i| { let votes: BoundedVec<_, _> = (&non_winners) - .choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT) + .choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT) .cloned() .try_collect() - .expect("<SolutionOf<T>>::LIMIT is the correct bound; qed."); + .expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed."); let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED); (voter, stake, votes) }) @@ -120,12 +120,12 @@ fn solution_with_size<T: Config>( // down the road. T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake)); - let cache = helpers::generate_voter_cache::<T>(&all_voters); - let stake_of = helpers::stake_of_fn::<T>(&all_voters, &cache); - let voter_index = helpers::voter_index_fn::<T>(&cache); - let target_index = helpers::target_index_fn::<T>(&targets); - let voter_at = helpers::voter_at_fn::<T>(&all_voters); - let target_at = helpers::target_at_fn::<T>(&targets); + let cache = helpers::generate_voter_cache::<T::MinerConfig>(&all_voters); + let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&all_voters, &cache); + let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache); + let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets); + let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&all_voters); + let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets); let assignments = active_voters .iter() @@ -143,7 +143,8 @@ fn solution_with_size<T: Config>( .collect::<Vec<_>>(); let solution = - <SolutionOf<T>>::from_assignment(&assignments, &voter_index, &target_index).unwrap(); + <SolutionOf<T::MinerConfig>>::from_assignment(&assignments, &voter_index, &target_index) + .unwrap(); let score = solution.clone().score(stake_of, voter_at, target_at).unwrap(); let round = <MultiPhase<T>>::round(); @@ -480,14 +481,14 @@ frame_benchmarking::benchmarks! { let witness = SolutionOrSnapshotSize { voters: v, targets: t }; let RawSolution { solution, .. } = solution_with_size::<T>(witness, a, d)?; let RoundSnapshot { voters, targets } = MultiPhase::<T>::snapshot().ok_or("snapshot missing")?; - let voter_at = helpers::voter_at_fn::<T>(&voters); - let target_at = helpers::target_at_fn::<T>(&targets); + let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&voters); + let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets); let mut assignments = solution.into_assignment(voter_at, target_at).expect("solution generated by `solution_with_size` must be valid."); // make a voter cache and some helper functions for access - let cache = helpers::generate_voter_cache::<T>(&voters); - let voter_index = helpers::voter_index_fn::<T>(&cache); - let target_index = helpers::target_index_fn::<T>(&targets); + let cache = helpers::generate_voter_cache::<T::MinerConfig>(&voters); + let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache); + let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets); // sort assignments by decreasing voter stake assignments.sort_by_key(|crate::unsigned::Assignment::<T> { who, .. }| { @@ -504,21 +505,21 @@ frame_benchmarking::benchmarks! { .collect::<Result<Vec<_>, _>>() .unwrap(); - let encoded_size_of = |assignments: &[IndexAssignmentOf<T>]| { - SolutionOf::<T>::try_from(assignments).map(|solution| solution.encoded_size()) + let encoded_size_of = |assignments: &[IndexAssignmentOf<T::MinerConfig>]| { + SolutionOf::<T::MinerConfig>::try_from(assignments).map(|solution| solution.encoded_size()) }; let desired_size = Percent::from_percent(100 - f.saturated_into::<u8>()) .mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap()); log!(trace, "desired_size = {}", desired_size); }: { - MultiPhase::<T>::trim_assignments_length( + crate::Miner::<T::MinerConfig>::trim_assignments_length( desired_size.saturated_into(), &mut index_assignments, &encoded_size_of, ).unwrap(); } verify { - let solution = SolutionOf::<T>::try_from(index_assignments.as_slice()).unwrap(); + let solution = SolutionOf::<T::MinerConfig>::try_from(index_assignments.as_slice()).unwrap(); let encoding = solution.encode(); log!( trace, diff --git a/substrate/frame/election-provider-multi-phase/src/helpers.rs b/substrate/frame/election-provider-multi-phase/src/helpers.rs index 48da194cc65d9a1fcbb4475711a815827496d7c4..0a7240c5d27af326ed88694b4c1925bfbd4519cc 100644 --- a/substrate/frame/election-provider-multi-phase/src/helpers.rs +++ b/substrate/frame/election-provider-multi-phase/src/helpers.rs @@ -17,7 +17,10 @@ //! Some helper functions/macros for this crate. -use crate::{unsigned::VoterOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight}; +use crate::{ + unsigned::{MinerConfig, MinerVoterOf}, + SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight, +}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; #[macro_export] @@ -30,11 +33,22 @@ macro_rules! log { }; } +// This is only useful for a context where a `<T: Config>` is not in scope. +#[macro_export] +macro_rules! log_no_system { + ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("🗳 ", $pattern) $(, $values)* + ) + }; +} + /// Generate a btree-map cache of the voters and their indices. /// /// This can be used to efficiently build index getter closures. -pub fn generate_voter_cache<T: Config>( - snapshot: &Vec<VoterOf<T>>, +pub fn generate_voter_cache<T: MinerConfig>( + snapshot: &Vec<MinerVoterOf<T>>, ) -> BTreeMap<T::AccountId, usize> { let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new(); snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { @@ -54,7 +68,7 @@ pub fn generate_voter_cache<T: Config>( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn<T: Config>( +pub fn voter_index_fn<T: MinerConfig>( cache: &BTreeMap<T::AccountId, usize>, ) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ { move |who| { @@ -68,7 +82,7 @@ pub fn voter_index_fn<T: Config>( /// /// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is /// borrowed. -pub fn voter_index_fn_owned<T: Config>( +pub fn voter_index_fn_owned<T: MinerConfig>( cache: BTreeMap<T::AccountId, usize>, ) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> { move |who| { @@ -83,7 +97,7 @@ pub fn voter_index_fn_owned<T: Config>( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn_usize<T: Config>( +pub fn voter_index_fn_usize<T: MinerConfig>( cache: &BTreeMap<T::AccountId, usize>, ) -> impl Fn(&T::AccountId) -> Option<usize> + '_ { move |who| cache.get(who).cloned() @@ -96,8 +110,8 @@ pub fn voter_index_fn_usize<T: Config>( /// /// Not meant to be used in production. #[cfg(test)] -pub fn voter_index_fn_linear<T: Config>( - snapshot: &Vec<VoterOf<T>>, +pub fn voter_index_fn_linear<T: MinerConfig>( + snapshot: &Vec<MinerVoterOf<T>>, ) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ { move |who| { snapshot @@ -114,7 +128,7 @@ pub fn voter_index_fn_linear<T: Config>( /// Note: to the extent possible, the returned function should be cached and reused. Producing that /// function requires a `O(n log n)` data transform. Each invocation of that function completes /// in `O(log n)`. -pub fn target_index_fn<T: Config>( +pub fn target_index_fn<T: MinerConfig>( snapshot: &Vec<T::AccountId>, ) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ { let cache: BTreeMap<_, _> = @@ -134,7 +148,7 @@ pub fn target_index_fn<T: Config>( /// /// Not meant to be used in production. #[cfg(test)] -pub fn target_index_fn_linear<T: Config>( +pub fn target_index_fn_linear<T: MinerConfig>( snapshot: &Vec<T::AccountId>, ) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ { move |who| { @@ -147,8 +161,8 @@ pub fn target_index_fn_linear<T: Config>( /// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter /// account using a linearly indexible snapshot. -pub fn voter_at_fn<T: Config>( - snapshot: &Vec<VoterOf<T>>, +pub fn voter_at_fn<T: MinerConfig>( + snapshot: &Vec<MinerVoterOf<T>>, ) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ { move |i| { <SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i) @@ -159,7 +173,7 @@ pub fn voter_at_fn<T: Config>( /// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target /// account using a linearly indexible snapshot. -pub fn target_at_fn<T: Config>( +pub fn target_at_fn<T: MinerConfig>( snapshot: &Vec<T::AccountId>, ) -> impl Fn(SolutionTargetIndexOf<T>) -> Option<T::AccountId> + '_ { move |i| { @@ -173,8 +187,8 @@ pub fn target_at_fn<T: Config>( /// /// This is not optimized and uses a linear search. #[cfg(test)] -pub fn stake_of_fn_linear<T: Config>( - snapshot: &Vec<VoterOf<T>>, +pub fn stake_of_fn_linear<T: MinerConfig>( + snapshot: &Vec<MinerVoterOf<T>>, ) -> impl Fn(&T::AccountId) -> VoteWeight + '_ { move |who| { snapshot @@ -191,8 +205,8 @@ pub fn stake_of_fn_linear<T: Config>( /// /// The cache need must be derived from the same snapshot. Zero is returned if a voter is /// non-existent. -pub fn stake_of_fn<'a, T: Config>( - snapshot: &'a Vec<VoterOf<T>>, +pub fn stake_of_fn<'a, T: MinerConfig>( + snapshot: &'a Vec<MinerVoterOf<T>>, cache: &'a BTreeMap<T::AccountId, usize>, ) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { move |who| { diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 4aea5b9da4794486a431f3bd878b178dbd9f5049..36d2373c9f623a76a5e935ffee4a546bc408b20e 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -103,7 +103,7 @@ //! //! Validators will only submit solutions if the one that they have computed is sufficiently better //! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the -//! weight of the solution to [`pallet::Config::MinerMaxWeight`]. +//! weight of the solution to [`MinerConfig::MaxWeight`]. //! //! The unsigned phase can be made passive depending on how the previous signed phase went, by //! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always @@ -276,16 +276,18 @@ pub use signed::{ BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf, SignedSubmissions, SubmissionIndicesOf, }; +pub use unsigned::{Miner, MinerConfig}; /// The solution type used by this crate. -pub type SolutionOf<T> = <T as Config>::Solution; +pub type SolutionOf<T> = <T as MinerConfig>::Solution; /// The voter index. Derived from [`SolutionOf`]. pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex; /// The target index. Derived from [`SolutionOf`]. pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex; /// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`]. -pub type SolutionAccuracyOf<T> = <SolutionOf<T> as NposSolution>::Accuracy; +pub type SolutionAccuracyOf<T> = + <SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy; /// The fallback election type. pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error; @@ -488,7 +490,7 @@ pub enum ElectionError<T: Config> { /// An error happened in the feasibility check sub-system. Feasibility(FeasibilityError), /// An error in the miner (offchain) sub-system. - Miner(unsigned::MinerError<T>), + Miner(unsigned::MinerError), /// An error happened in the data provider. DataProvider(&'static str), /// An error nested in the fallback. @@ -520,8 +522,8 @@ impl<T: Config> From<FeasibilityError> for ElectionError<T> { } } -impl<T: Config> From<unsigned::MinerError<T>> for ElectionError<T> { - fn from(e: unsigned::MinerError<T>) -> Self { +impl<T: Config> From<unsigned::MinerError> for ElectionError<T> { + fn from(e: unsigned::MinerError) -> Self { ElectionError::Miner(e) } } @@ -605,12 +607,14 @@ pub mod pallet { #[pallet::constant] type MinerTxPriority: Get<TransactionPriority>; - /// Maximum weight that the miner should consume. + /// Configurations of the embedded miner. /// - /// The miner will ensure that the total weight of the unsigned solution will not exceed - /// this value, based on [`WeightInfo::submit_unsigned`]. - #[pallet::constant] - type MinerMaxWeight: Get<Weight>; + /// Any external software implementing this can use the [`unsigned::Miner`] type provided, + /// which can mine new solutions and trim them accordingly. + type MinerConfig: crate::unsigned::MinerConfig< + AccountId = Self::AccountId, + MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter, + >; /// Maximum number of signed submissions that can be queued. /// @@ -624,7 +628,9 @@ pub mod pallet { /// Maximum weight of a signed solution. /// - /// This should probably be similar to [`Config::MinerMaxWeight`]. + /// If [`Config::MinerConfig`] is being implemented to submit signed solutions (outside of + /// this pallet), then [`MinerConfig::solution_weight`] is used to compare against + /// this value. #[pallet::constant] type SignedMaxWeight: Get<Weight>; @@ -652,11 +658,11 @@ pub mod pallet { /// are only over a single block, but once multi-block elections are introduced they will /// take place over multiple blocks. #[pallet::constant] - type MaxElectingVoters: Get<SolutionVoterIndexOf<Self>>; + type MaxElectingVoters: Get<SolutionVoterIndexOf<Self::MinerConfig>>; /// The maximum number of electable targets to put in the snapshot. #[pallet::constant] - type MaxElectableTargets: Get<SolutionTargetIndexOf<Self>>; + type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>>; /// Handler for the slashed deposits. type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>; @@ -664,30 +670,12 @@ pub mod pallet { /// Handler for the rewards. type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>; - /// Maximum length (bytes) that the mined solution should consume. - /// - /// The miner will ensure that the total length of the unsigned solution will not exceed - /// this value. - #[pallet::constant] - type MinerMaxLength: Get<u32>; - /// Something that will provide the election data. type DataProvider: ElectionDataProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, >; - /// The solution type. - type Solution: codec::Codec - + Default - + PartialEq - + Eq - + Clone - + sp_std::fmt::Debug - + Ord - + NposSolution - + TypeInfo; - /// Configuration for the fallback. type Fallback: InstantElectionProvider< AccountId = Self::AccountId, @@ -824,12 +812,12 @@ pub mod pallet { use sp_std::mem::size_of; // The index type of both voters and targets need to be smaller than that of usize (very // unlikely to be the case, but anyhow).. - assert!(size_of::<SolutionVoterIndexOf<T>>() <= size_of::<usize>()); - assert!(size_of::<SolutionTargetIndexOf<T>>() <= size_of::<usize>()); + assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>()); + assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>()); // ---------------------------- // Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`]. - let max_vote: usize = <SolutionOf<T> as NposSolution>::LIMIT; + let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT; // 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf<OffchainAccuracy>`. let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote) @@ -850,7 +838,7 @@ pub mod pallet { // solution cannot represent any voters more than `LIMIT` anyhow. assert_eq!( <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(), - <SolutionOf<T> as NposSolution>::LIMIT as u32, + <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32, ); // While it won't cause any failures, setting `SignedMaxRefunds` gt @@ -887,7 +875,7 @@ pub mod pallet { ))] pub fn submit_unsigned( origin: OriginFor<T>, - raw_solution: Box<RawSolution<SolutionOf<T>>>, + raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>, witness: SolutionOrSnapshotSize, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; @@ -976,7 +964,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor<T>, - raw_solution: Box<RawSolution<SolutionOf<T>>>, + raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -991,7 +979,7 @@ pub mod pallet { let size = Self::snapshot_metadata().ok_or(Error::<T>::MissingSnapshotMetadata)?; ensure!( - Self::feasibility_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), + Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), Error::<T>::SignedTooMuchWeight, ); @@ -1429,7 +1417,7 @@ impl<T: Config> Pallet<T> { /// Checks the feasibility of a solution. pub fn feasibility_check( - raw_solution: RawSolution<SolutionOf<T>>, + raw_solution: RawSolution<SolutionOf<T::MinerConfig>>, compute: ElectionCompute, ) -> Result<ReadySolution<T::AccountId>, FeasibilityError> { let RawSolution { solution, score, round } = raw_solution; @@ -1459,10 +1447,10 @@ impl<T: Config> Pallet<T> { Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?; // ----- Start building. First, we need some closures. - let cache = helpers::generate_voter_cache::<T>(&snapshot_voters); - let voter_at = helpers::voter_at_fn::<T>(&snapshot_voters); - let target_at = helpers::target_at_fn::<T>(&snapshot_targets); - let voter_index = helpers::voter_index_fn_usize::<T>(&cache); + let cache = helpers::generate_voter_cache::<T::MinerConfig>(&snapshot_voters); + let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&snapshot_voters); + let target_at = helpers::target_at_fn::<T::MinerConfig>(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::<T::MinerConfig>(&cache); // Then convert solution -> assignment. This will fail if any of the indices are gibberish, // namely any of the voters or targets. @@ -1493,7 +1481,7 @@ impl<T: Config> Pallet<T> { })?; // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::<T>(&snapshot_voters, &cache); + let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&snapshot_voters, &cache); // This might fail if the normalization fails. Very unlikely. See `integrity_test`. let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) @@ -1803,8 +1791,8 @@ mod tests { use super::*; use crate::{ mock::{ - multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MultiPhase, - Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, + multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MockedWeightInfo, + MultiPhase, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, }, Phase, }; @@ -2123,7 +2111,7 @@ mod tests { // set the solution balancing to get the desired score. crate::mock::Balancing::set(Some((2, 0))); - let (solution, _) = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); + let (solution, _) = MultiPhase::mine_solution().unwrap(); // Default solution's score. assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. })); @@ -2147,7 +2135,7 @@ mod tests { #[test] fn number_of_voters_allowed_2sec_block() { // Just a rough estimate with the substrate weights. - assert!(!MockWeightInfo::get()); + assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real); let all_voters: u32 = 10_000; let all_targets: u32 = 5_000; diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 38d9c8dfc1b7e10262349dc93916140d180f0d93..afce24ff6e3f073c95ceb895e9593c3dfcfee852 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -16,7 +16,7 @@ // limitations under the License. use super::*; -use crate as multi_phase; +use crate::{self as multi_phase, unsigned::MinerConfig}; use frame_election_provider_support::{ data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, }; @@ -239,6 +239,13 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum MockedWeightInfo { + Basic, + Complex, + Real, +} + parameter_types! { pub static Targets: Vec<AccountId> = vec![10, 20, 30, 40]; pub static Voters: Vec<VoterOf<Runtime>> = vec![ @@ -269,7 +276,7 @@ parameter_types! { pub static OffchainRepeat: BlockNumber = 5; pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; - pub static MockWeightInfo: bool = false; + pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real; pub static MaxElectingVoters: VoterIndex = u32::max_value(); pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); @@ -314,85 +321,6 @@ impl InstantElectionProvider for MockFallback { } } -// Hopefully this won't be too much of a hassle to maintain. -pub struct DualMockWeightInfo; -impl multi_phase::weights::WeightInfo for DualMockWeightInfo { - fn on_initialize_nothing() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_nothing() - } - } - fn create_snapshot_internal(v: u32, t: u32) -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::create_snapshot_internal(v, t) - } - } - fn on_initialize_open_signed() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_open_signed() - } - } - fn on_initialize_open_unsigned() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned() - } - } - fn elect_queued(a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::elect_queued(a, d) - } - } - fn finalize_signed_phase_accept_solution() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::finalize_signed_phase_accept_solution() - } - } - fn finalize_signed_phase_reject_solution() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::finalize_signed_phase_reject_solution() - } - } - fn submit() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::submit() - } - } - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - // 10 base - // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) - } else { - <() as multi_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d) - } - } - fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - // 10 base - // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) - } else { - <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d) - } - } -} - parameter_types! { pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0)); } @@ -410,6 +338,24 @@ impl BenchmarkingConfig for TestBenchmarkingConfig { const MAXIMUM_TARGETS: u32 = 200; } +impl MinerConfig for Runtime { + type AccountId = AccountId; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter; + type Solution = TestNposSolution; + + fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { + match MockWeightInfo::get() { + MockedWeightInfo::Basic => + (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)), + MockedWeightInfo::Complex => (0 * v + 0 * t + 1000 * a + 0 * d) as Weight, + MockedWeightInfo::Real => + <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d), + } + } +} + impl crate::Config for Runtime { type Event = Event; type Currency = Balances; @@ -419,8 +365,6 @@ impl crate::Config for Runtime { type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = BetterSignedThreshold; type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MinerTxPriority; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; @@ -432,14 +376,14 @@ impl crate::Config for Runtime { type SlashHandler = (); type RewardHandler = (); type DataProvider = StakingMock; - type WeightInfo = DualMockWeightInfo; + type WeightInfo = (); type BenchmarkingConfig = TestBenchmarkingConfig; type Fallback = MockFallback; type GovernanceFallback = NoFallback<Self>; type ForceOrigin = frame_system::EnsureRoot<AccountId>; - type Solution = TestNposSolution; type MaxElectingVoters = MaxElectingVoters; type MaxElectableTargets = MaxElectableTargets; + type MinerConfig = Self; type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>; } @@ -562,7 +506,7 @@ impl ExtBuilder { <MinerMaxWeight>::set(weight); self } - pub fn mock_weight_info(self, mock: bool) -> Self { + pub fn mock_weight_info(self, mock: MockedWeightInfo) -> Self { <MockWeightInfo>::set(mock); self } diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs index 5f61eb7575da4103a60edc3476f73d0d11be9eaa..eca75139f925a873807ba346c0294ff964d1603f 100644 --- a/substrate/frame/election-provider-multi-phase/src/signed.rs +++ b/substrate/frame/election-provider-multi-phase/src/signed.rs @@ -18,9 +18,9 @@ //! The signed phase implementation. use crate::{ - Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, ReadySolution, - SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, SolutionOf, - SolutionOrSnapshotSize, Weight, WeightInfo, + unsigned::MinerConfig, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, + ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, + SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo, }; use codec::{Decode, Encode, HasCompact}; use frame_election_provider_support::NposSolution; @@ -93,8 +93,11 @@ pub type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency< pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency< <T as frame_system::Config>::AccountId, >>::NegativeImbalance; -pub type SignedSubmissionOf<T> = - SignedSubmission<<T as frame_system::Config>::AccountId, BalanceOf<T>, SolutionOf<T>>; +pub type SignedSubmissionOf<T> = SignedSubmission< + <T as frame_system::Config>::AccountId, + BalanceOf<T>, + <<T as crate::Config>::MinerConfig as MinerConfig>::Solution, +>; pub type SubmissionIndicesOf<T> = BoundedBTreeMap<ElectionScore, u32, <T as Config>::SignedMaxSubmissions>; @@ -482,12 +485,12 @@ impl<T: Config> Pallet<T> { T::SlashHandler::on_unbalanced(negative_imbalance); } - /// The feasibility weight of the given raw solution. - pub fn feasibility_weight_of( - raw_solution: &RawSolution<SolutionOf<T>>, + /// The weight of the given raw solution. + pub fn solution_weight_of( + raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>, size: SolutionOrSnapshotSize, ) -> Weight { - T::WeightInfo::feasibility_check( + T::MinerConfig::solution_weight( size.voters, size.targets, raw_solution.solution.voter_count() as u32, @@ -503,12 +506,12 @@ impl<T: Config> Pallet<T> { /// 2. a per-byte deposit, for renting the state usage. /// 3. a per-weight deposit, for the potential weight usage in an upcoming on_initialize pub fn deposit_for( - raw_solution: &RawSolution<SolutionOf<T>>, + raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>, size: SolutionOrSnapshotSize, ) -> BalanceOf<T> { let encoded_len: u32 = raw_solution.encoded_size().saturated_into(); let encoded_len: BalanceOf<T> = encoded_len.into(); - let feasibility_weight = Self::feasibility_weight_of(raw_solution, size); + let feasibility_weight = Self::solution_weight_of(raw_solution, size); let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len); let weight_deposit = @@ -525,8 +528,8 @@ mod tests { use super::*; use crate::{ mock::{ - balances, raw_solution, roll_to, Balances, ExtBuilder, MultiPhase, Origin, Runtime, - SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight, + balances, raw_solution, roll_to, Balances, ExtBuilder, MockedWeightInfo, MultiPhase, + Origin, Runtime, SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight, }, Error, Perbill, Phase, }; @@ -955,14 +958,13 @@ mod tests { fn cannot_consume_too_much_future_weight() { ExtBuilder::default() .signed_weight(40) - .mock_weight_info(true) + .mock_weight_info(MockedWeightInfo::Basic) .build_and_execute(|| { roll_to(15); assert!(MultiPhase::current_phase().is_signed()); - let (raw, witness) = - MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); - let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = <Runtime as MinerConfig>::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 126dcb10416cc36bd88b1340628559d1480f81b2..9a1b52d3545694e88e15db898f5409aa46fddc65 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -20,14 +20,15 @@ use crate::{ helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution, ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, - WeightInfo, }; use codec::Encode; -use frame_election_provider_support::{NposSolution, NposSolver, PerThing128}; -use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; +use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight}; +use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec}; use frame_system::offchain::SubmitTransaction; +use scale_info::TypeInfo; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult, + ElectionScore, }; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, @@ -47,6 +48,12 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele /// voted. pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::DataProvider>; +/// Same as [`VoterOf`], but parameterized by the `MinerConfig`. +pub type MinerVoterOf<T> = frame_election_provider_support::Voter< + <T as MinerConfig>::AccountId, + <T as MinerConfig>::MaxVotesPerVoter, +>; + /// The relative distribution of a voter's stake among the winning targets. pub type Assignment<T> = sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>; @@ -59,7 +66,7 @@ pub type IndexAssignmentOf<T> = frame_election_provider_support::IndexAssignment pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error; /// Error type for operations related to the OCW npos solution miner. #[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)] -pub enum MinerError<T: Config> { +pub enum MinerError { /// An internal error in the NPoS elections crate. NposElections(sp_npos_elections::Error), /// Snapshot data was unavailable unexpectedly. @@ -81,23 +88,23 @@ pub enum MinerError<T: Config> { /// There are no more voters to remove to trim the solution. NoMoreVoters, /// An error from the solver. - Solver(SolverErrorOf<T>), + Solver, } -impl<T: Config> From<sp_npos_elections::Error> for MinerError<T> { +impl From<sp_npos_elections::Error> for MinerError { fn from(e: sp_npos_elections::Error) -> Self { MinerError::NposElections(e) } } -impl<T: Config> From<FeasibilityError> for MinerError<T> { +impl From<FeasibilityError> for MinerError { fn from(e: FeasibilityError) -> Self { MinerError::Feasibility(e) } } /// Save a given call into OCW storage. -fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> { +fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> { log!(debug, "saving a call to the offchain storage."); let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL); match storage.mutate::<_, (), _>(|_| Ok(call.clone())) { @@ -115,7 +122,7 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> { } /// Get a saved solution from OCW storage if it exists. -fn restore_solution<T: Config>() -> Result<Call<T>, MinerError<T>> { +fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> { StorageValueRef::persistent(OFFCHAIN_CACHED_CALL) .get() .ok() @@ -146,9 +153,46 @@ fn ocw_solution_exists<T: Config>() -> bool { } impl<T: Config> Pallet<T> { + /// Mine a new npos solution. + /// + /// The Npos Solver type, `S`, must have the same AccountId and Error type as the + /// [`crate::Config::Solver`] in order to create a unified return type. + pub fn mine_solution( + ) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> { + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + let (solution, score, size) = Miner::<T::MinerConfig>::mine_solution_with_snapshot::< + T::Solver, + >(voters, targets, desired_targets)?; + let round = Self::round(); + Ok((RawSolution { solution, score, round }, size)) + } + + /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which + /// is ready to be submitted to the chain. + /// + /// Will always reduce the solution as well. + pub fn prepare_election_result<Accuracy: PerThing128>( + election_result: ElectionResult<T::AccountId, Accuracy>, + ) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> { + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + let (solution, score, size) = + Miner::<T::MinerConfig>::prepare_election_result_with_snapshot( + election_result, + voters, + targets, + desired_targets, + )?; + let round = Self::round(); + Ok((RawSolution { solution, score, round }, size)) + } + /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit /// if our call's score is greater than that of the cached solution. - pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError<T>> { + pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { log!(debug, "miner attempting to restore or compute an unsigned solution."); let call = restore_solution::<T>() @@ -162,7 +206,7 @@ impl<T: Config> Pallet<T> { Err(MinerError::SolutionCallInvalid) } }) - .or_else::<MinerError<T>, _>(|error| { + .or_else::<MinerError, _>(|error| { log!(debug, "restoring solution failed due to {:?}", error); match error { MinerError::NoStoredSolution => { @@ -193,7 +237,7 @@ impl<T: Config> Pallet<T> { } /// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction. - pub fn mine_check_save_submit() -> Result<(), MinerError<T>> { + pub fn mine_check_save_submit() -> Result<(), MinerError> { log!(debug, "miner attempting to compute an unsigned solution."); let call = Self::mine_checked_call()?; @@ -202,7 +246,7 @@ impl<T: Config> Pallet<T> { } /// Mine a new solution as a call. Performs all checks. - pub fn mine_checked_call() -> Result<Call<T>, MinerError<T>> { + pub fn mine_checked_call() -> Result<Call<T>, MinerError> { // get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. let (raw_solution, witness) = Self::mine_and_check()?; @@ -219,7 +263,7 @@ impl<T: Config> Pallet<T> { Ok(call) } - fn submit_call(call: Call<T>) -> Result<(), MinerError<T>> { + fn submit_call(call: Call<T>) -> Result<(), MinerError> { log!(debug, "miner submitting a solution as an unsigned transaction"); SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) @@ -230,9 +274,9 @@ impl<T: Config> Pallet<T> { // // Performance: note that it internally clones the provided solution. pub fn basic_checks( - raw_solution: &RawSolution<SolutionOf<T>>, + raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>, solution_type: &str, - ) -> Result<(), MinerError<T>> { + ) -> Result<(), MinerError> { Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| { log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err); MinerError::PreDispatchChecksFailed(err) @@ -255,45 +299,155 @@ impl<T: Config> Pallet<T> { /// If you want a checked solution and submit it at the same time, use /// [`Pallet::mine_check_save_submit`]. pub fn mine_and_check( - ) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> { - let (raw_solution, witness) = Self::mine_solution::<T::Solver>()?; + ) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> { + let (raw_solution, witness) = Self::mine_solution()?; Self::basic_checks(&raw_solution, "mined")?; Ok((raw_solution, witness)) } - /// Mine a new npos solution. + /// Checks if an execution of the offchain worker is permitted at the given block number, or + /// not. /// - /// The Npos Solver type, `S`, must have the same AccountId and Error type as the - /// [`crate::Config::Solver`] in order to create a unified return type. - pub fn mine_solution<S>( - ) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> - where - S: NposSolver<AccountId = T::AccountId, Error = SolverErrorOf<T>>, - { - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + /// This makes sure that + /// 1. we don't run on previous blocks in case of a re-org + /// 2. we don't run twice within a window of length `T::OffchainRepeat`. + /// + /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` + /// is returned, `now` is written in storage and will be used in further calls as the baseline. + pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { + let threshold = T::OffchainRepeat::get(); + let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); - S::solve(desired_targets as usize, targets, voters) - .map_err(|e| MinerError::Solver::<T>(e)) - .and_then(|e| Self::prepare_election_result::<S::Accuracy>(e)) + let mutate_stat = last_block.mutate::<_, &'static str, _>( + |maybe_head: Result<Option<T::BlockNumber>, _>| { + match maybe_head { + Ok(Some(head)) if now < head => Err("fork."), + Ok(Some(head)) if now >= head && now <= head + threshold => + Err("recently executed."), + Ok(Some(head)) if now > head + threshold => { + // we can run again now. Write the new head. + Ok(now) + }, + _ => { + // value doesn't exists. Probably this node just booted up. Write, and run + Ok(now) + }, + } + }, + ); + + match mutate_stat { + // all good + Ok(_) => Ok(()), + // failed to write. + Err(MutateStorageError::ConcurrentModification(_)) => + Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")), + // fork etc. + Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)), + } } - /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which - /// is ready to be submitted to the chain. + /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned + /// transaction. /// - /// Will always reduce the solution as well. - pub fn prepare_election_result<Accuracy: PerThing128>( - election_result: ElectionResult<T::AccountId, Accuracy>, - ) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> { - // NOTE: This code path is generally not optimized as it is run offchain. Could use some at - // some point though. + /// Can optionally also be called during dispatch, if needed. + /// + /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's + /// code, so that we do less and less storage reads here. + pub fn unsigned_pre_dispatch_checks( + raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>, + ) -> DispatchResult { + // ensure solution is timely. Don't panic yet. This is a cheap check. + ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission); - // storage items. Note: we have already read this from storage, they must be in cache. - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + // ensure round is current + ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra); + // ensure correct number of winners. + ensure!( + Self::desired_targets().unwrap_or_default() == + raw_solution.solution.unique_targets().len() as u32, + Error::<T>::PreDispatchWrongWinnerCount, + ); + + // ensure score is being improved. Panic henceforth. + ensure!( + Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution + .score + .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), + Error::<T>::PreDispatchWeakSubmission, + ); + + Ok(()) + } +} + +/// Configurations for a miner that comes with this pallet. +pub trait MinerConfig { + /// The account id type. + type AccountId: Ord + Clone + codec::Codec + sp_std::fmt::Debug; + /// The solution that the miner is mining. + type Solution: codec::Codec + + Default + + PartialEq + + Eq + + Clone + + sp_std::fmt::Debug + + Ord + + NposSolution + + TypeInfo; + /// Maximum number of votes per voter in the snapshots. + type MaxVotesPerVoter; + /// Maximum length of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + type MaxLength: Get<u32>; + /// Maximum weight of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + /// + /// The weight is computed using `solution_weight`. + type MaxWeight: Get<Weight>; + /// Something that can compute the weight of a solution. + /// + /// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`]. + fn solution_weight(voters: u32, targets: u32, active_voters: u32, degree: u32) -> Weight; +} + +/// A base miner, suitable to be used for both signed and unsigned submissions. +pub struct Miner<T: MinerConfig>(sp_std::marker::PhantomData<T>); +impl<T: MinerConfig> Miner<T> { + /// Same as [`Pallet::mine_solution`], but the input snapshot data must be given. + pub fn mine_solution_with_snapshot<S>( + voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>, + targets: Vec<T::AccountId>, + desired_targets: u32, + ) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize), MinerError> + where + S: NposSolver<AccountId = T::AccountId>, + { + S::solve(desired_targets as usize, targets.clone(), voters.clone()) + .map_err(|e| { + log_no_system!(error, "solver error: {:?}", e); + MinerError::Solver + }) + .and_then(|e| { + Self::prepare_election_result_with_snapshot::<S::Accuracy>( + e, + voters, + targets, + desired_targets, + ) + }) + } + + /// Same as [`Pallet::prepare_election_result`], but the input snapshot mut be given as inputs. + pub fn prepare_election_result_with_snapshot<Accuracy: PerThing128>( + election_result: ElectionResult<T::AccountId, Accuracy>, + voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>, + targets: Vec<T::AccountId>, + desired_targets: u32, + ) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize), MinerError> { // now make some helper closures. let cache = helpers::generate_voter_cache::<T>(&voters); let voter_index = helpers::voter_index_fn::<T>(&cache); @@ -355,11 +509,11 @@ impl<T: Config> Pallet<T> { Self::trim_assignments_weight( desired_targets, size, - T::MinerMaxWeight::get(), + T::MaxWeight::get(), &mut index_assignments, ); Self::trim_assignments_length( - T::MinerMaxLength::get(), + T::MaxLength::get(), &mut index_assignments, &encoded_size_of, )?; @@ -370,43 +524,7 @@ impl<T: Config> Pallet<T> { // re-calc score. let score = solution.clone().score(stake_of, voter_at, target_at)?; - let round = Self::round(); - Ok((RawSolution { solution, score, round }, size)) - } - - /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. - /// - /// The weight of the solution is foremost a function of the number of voters (i.e. - /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The - /// number of winners shall not be changed (otherwise the solution is invalid) and the - /// `ElectionSize` is merely a representation of the total number of stakers. - /// - /// Thus, we reside to stripping away some voters from the `assignments`. - /// - /// Note that the solution is already computed, and the winners are elected based on the merit - /// of the entire stake in the system. Nonetheless, some of the voters will be removed further - /// down the line. - /// - /// Indeed, the score must be computed **after** this step. If this step reduces the score too - /// much or remove a winner, then the solution must be discarded **after** this step. - pub fn trim_assignments_weight( - desired_targets: u32, - size: SolutionOrSnapshotSize, - max_weight: Weight, - assignments: &mut Vec<IndexAssignmentOf<T>>, - ) { - let maximum_allowed_voters = - Self::maximum_voter_for_weight::<T::WeightInfo>(desired_targets, size, max_weight); - let removing: usize = - assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); - log!( - debug, - "from {} assignments, truncating to {} for weight, removing {}", - assignments.len(), - maximum_allowed_voters, - removing, - ); - assignments.truncate(maximum_allowed_voters as usize); + Ok((solution, score, size)) } /// Greedily reduce the size of the solution to fit into the block w.r.t length. @@ -427,7 +545,7 @@ impl<T: Config> Pallet<T> { max_allowed_length: u32, assignments: &mut Vec<IndexAssignmentOf<T>>, encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>, - ) -> Result<(), MinerError<T>> { + ) -> Result<(), MinerError> { // Perform a binary search for the max subset of which can fit into the allowed // length. Having discovered that, we can truncate efficiently. let max_allowed_length: usize = max_allowed_length.saturated_into(); @@ -470,7 +588,7 @@ impl<T: Config> Pallet<T> { // after this point, we never error. // check before edit. - log!( + log_no_system!( debug, "from {} assignments, truncating to {} for length, removing {}", assignments.len(), @@ -482,10 +600,45 @@ impl<T: Config> Pallet<T> { Ok(()) } + /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. + /// + /// The weight of the solution is foremost a function of the number of voters (i.e. + /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The + /// number of winners shall not be changed (otherwise the solution is invalid) and the + /// `ElectionSize` is merely a representation of the total number of stakers. + /// + /// Thus, we reside to stripping away some voters from the `assignments`. + /// + /// Note that the solution is already computed, and the winners are elected based on the merit + /// of the entire stake in the system. Nonetheless, some of the voters will be removed further + /// down the line. + /// + /// Indeed, the score must be computed **after** this step. If this step reduces the score too + /// much or remove a winner, then the solution must be discarded **after** this step. + pub fn trim_assignments_weight( + desired_targets: u32, + size: SolutionOrSnapshotSize, + max_weight: Weight, + assignments: &mut Vec<IndexAssignmentOf<T>>, + ) { + let maximum_allowed_voters = + Self::maximum_voter_for_weight(desired_targets, size, max_weight); + let removing: usize = + assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); + log_no_system!( + debug, + "from {} assignments, truncating to {} for weight, removing {}", + assignments.len(), + maximum_allowed_voters, + removing, + ); + assignments.truncate(maximum_allowed_voters as usize); + } + /// Find the maximum `len` that a solution can have in order to fit into the block weight. /// /// This only returns a value between zero and `size.nominators`. - pub fn maximum_voter_for_weight<W: WeightInfo>( + pub fn maximum_voter_for_weight( desired_winners: u32, size: SolutionOrSnapshotSize, max_weight: Weight, @@ -499,7 +652,7 @@ impl<T: Config> Pallet<T> { // helper closures. let weight_with = |active_voters: u32| -> Weight { - W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners) + T::solution_weight(size.voters, size.targets, active_voters, desired_winners) }; let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result<u32, ()> { @@ -553,176 +706,65 @@ impl<T: Config> Pallet<T> { ); final_decision } - - /// Checks if an execution of the offchain worker is permitted at the given block number, or - /// not. - /// - /// This makes sure that - /// 1. we don't run on previous blocks in case of a re-org - /// 2. we don't run twice within a window of length `T::OffchainRepeat`. - /// - /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` - /// is returned, `now` is written in storage and will be used in further calls as the baseline. - pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError<T>> { - let threshold = T::OffchainRepeat::get(); - let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); - - let mutate_stat = last_block.mutate::<_, &'static str, _>( - |maybe_head: Result<Option<T::BlockNumber>, _>| { - match maybe_head { - Ok(Some(head)) if now < head => Err("fork."), - Ok(Some(head)) if now >= head && now <= head + threshold => - Err("recently executed."), - Ok(Some(head)) if now > head + threshold => { - // we can run again now. Write the new head. - Ok(now) - }, - _ => { - // value doesn't exists. Probably this node just booted up. Write, and run - Ok(now) - }, - } - }, - ); - - match mutate_stat { - // all good - Ok(_) => Ok(()), - // failed to write. - Err(MutateStorageError::ConcurrentModification(_)) => - Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")), - // fork etc. - Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)), - } - } - - /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned - /// transaction. - /// - /// Can optionally also be called during dispatch, if needed. - /// - /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's - /// code, so that we do less and less storage reads here. - pub fn unsigned_pre_dispatch_checks( - raw_solution: &RawSolution<SolutionOf<T>>, - ) -> DispatchResult { - // ensure solution is timely. Don't panic yet. This is a cheap check. - ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission); - - // ensure round is current - ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra); - - // ensure correct number of winners. - ensure!( - Self::desired_targets().unwrap_or_default() == - raw_solution.solution.unique_targets().len() as u32, - Error::<T>::PreDispatchWrongWinnerCount, - ); - - // ensure score is being improved. Panic henceforth. - ensure!( - Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution - .score - .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), - Error::<T>::PreDispatchWeakSubmission, - ); - - Ok(()) - } } #[cfg(test)] mod max_weight { #![allow(unused_variables)] use super::*; - use crate::mock::MultiPhase; - - struct TestWeight; - impl crate::weights::WeightInfo for TestWeight { - fn elect_queued(a: u32, d: u32) -> Weight { - unreachable!() - } - fn create_snapshot_internal(v: u32, t: u32) -> Weight { - unreachable!() - } - fn on_initialize_nothing() -> Weight { - unreachable!() - } - fn on_initialize_open_signed() -> Weight { - unreachable!() - } - fn on_initialize_open_unsigned() -> Weight { - unreachable!() - } - fn finalize_signed_phase_accept_solution() -> Weight { - unreachable!() - } - fn finalize_signed_phase_reject_solution() -> Weight { - unreachable!() - } - fn submit() -> Weight { - unreachable!() - } - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { - (0 * v + 0 * t + 1000 * a + 0 * d) as Weight - } - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight { - unreachable!() - } - } - + use crate::mock::{MockWeightInfo, Runtime}; #[test] fn find_max_voter_binary_search_works() { let w = SolutionOrSnapshotSize { voters: 10, targets: 0 }; - - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2990), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2999), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3000), 3); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 3); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 5500), 5); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 7777), 7); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 9999), 9); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_000), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_999), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 11_000), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 22_000), 10); + MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1990), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2990), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2999), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3000), 3); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 3); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 5500), 5); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 7777), 7); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 9999), 9); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 10_000), 10); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 10_999), 10); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 11_000), 10); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 22_000), 10); let w = SolutionOrSnapshotSize { voters: 1, targets: 0 }; - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1990), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 1); let w = SolutionOrSnapshotSize { voters: 2, targets: 0 }; - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 2); + assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 2); } } @@ -988,8 +1030,7 @@ mod tests { assert_eq!(MultiPhase::desired_targets().unwrap(), 2); // mine seq_phragmen solution with 2 iters. - let (solution, witness) = - MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); + let (solution, witness) = MultiPhase::mine_solution().unwrap(); // ensure this solution is valid. assert!(MultiPhase::queued_solution().is_none()); @@ -1002,14 +1043,13 @@ mod tests { fn miner_trims_weight() { ExtBuilder::default() .miner_weight(100) - .mock_weight_info(true) + .mock_weight_info(crate::mock::MockedWeightInfo::Basic) .build_and_execute(|| { roll_to(25); assert!(MultiPhase::current_phase().is_unsigned()); - let (raw, witness) = - MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); - let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = <Runtime as MinerConfig>::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, @@ -1022,9 +1062,8 @@ mod tests { // now reduce the max weight <MinerMaxWeight>::set(25); - let (raw, witness) = - MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); - let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = <Runtime as MinerConfig>::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, @@ -1044,8 +1083,7 @@ mod tests { assert!(MultiPhase::current_phase().is_unsigned()); // Force the number of winners to be bigger to fail - let (mut solution, _) = - MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); + let (mut solution, _) = MultiPhase::mine_solution().unwrap(); solution.solution.votes1[0].1 = 4; assert_eq!( @@ -1460,8 +1498,12 @@ mod tests { let solution_clone = solution.clone(); // when - MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of) - .unwrap(); + Miner::<Runtime>::trim_assignments_length( + encoded_len, + &mut assignments, + encoded_size_of, + ) + .unwrap(); // then let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap(); @@ -1481,7 +1523,7 @@ mod tests { let solution_clone = solution.clone(); // when - MultiPhase::trim_assignments_length( + Miner::<Runtime>::trim_assignments_length( encoded_len as u32 - 1, &mut assignments, encoded_size_of, @@ -1514,8 +1556,12 @@ mod tests { .unwrap(); // when - MultiPhase::trim_assignments_length(encoded_len - 1, &mut assignments, encoded_size_of) - .unwrap(); + Miner::<Runtime>::trim_assignments_length( + encoded_len - 1, + &mut assignments, + encoded_size_of, + ) + .unwrap(); // then assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment"); @@ -1542,11 +1588,11 @@ mod tests { assert_eq!(min_solution_size, SolutionOf::<Runtime>::LIMIT); // all of this should not panic. - MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone()) + Miner::<Runtime>::trim_assignments_length(0, &mut assignments, encoded_size_of.clone()) .unwrap(); - MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone()) + Miner::<Runtime>::trim_assignments_length(1, &mut assignments, encoded_size_of.clone()) .unwrap(); - MultiPhase::trim_assignments_length( + Miner::<Runtime>::trim_assignments_length( min_solution_size as u32, &mut assignments, encoded_size_of, @@ -1563,7 +1609,7 @@ mod tests { // trim to min solution size. let min_solution_size = SolutionOf::<Runtime>::LIMIT as u32; - MultiPhase::trim_assignments_length( + Miner::<Runtime>::trim_assignments_length( min_solution_size, &mut assignments, encoded_size_of, @@ -1582,15 +1628,15 @@ mod tests { roll_to(25); // how long would the default solution be? - let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); - let max_length = <Runtime as Config>::MinerMaxLength::get(); + let solution = MultiPhase::mine_solution().unwrap(); + let max_length = <Runtime as MinerConfig>::MaxLength::get(); let solution_size = solution.0.solution.encoded_size(); assert!(solution_size <= max_length as usize); // now set the max size to less than the actual size and regenerate - <Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1); - let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap(); - let max_length = <Runtime as Config>::MinerMaxLength::get(); + <Runtime as MinerConfig>::MaxLength::set(solution_size as u32 - 1); + let solution = MultiPhase::mine_solution().unwrap(); + let max_length = <Runtime as MinerConfig>::MaxLength::get(); let solution_size = solution.0.solution.encoded_size(); assert!(solution_size <= max_length as usize); });