referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_phase/
unsigned.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! The unsigned phase, and its miner.
19
20use crate::{
21	helpers, Call, Config, CurrentPhase, DesiredTargets, ElectionCompute, Error, FeasibilityError,
22	Pallet, QueuedSolution, RawSolution, ReadySolution, ReadySolutionOf, Round, RoundSnapshot,
23	Snapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight,
24};
25use alloc::{boxed::Box, vec::Vec};
26use codec::Encode;
27use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight};
28use frame_support::{
29	dispatch::DispatchResult,
30	ensure,
31	traits::{DefensiveResult, Get},
32	BoundedVec,
33};
34use frame_system::{
35	offchain::{CreateBare, SubmitTransaction},
36	pallet_prelude::BlockNumberFor,
37};
38use scale_info::TypeInfo;
39use sp_npos_elections::{
40	assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult,
41	ElectionScore, EvaluateSupport,
42};
43use sp_runtime::{
44	offchain::storage::{MutateStorageError, StorageValueRef},
45	DispatchError, SaturatedConversion,
46};
47
48/// Storage key used to store the last block number at which offchain worker ran.
49pub(crate) const OFFCHAIN_LAST_BLOCK: &[u8] = b"parity/multi-phase-unsigned-election";
50/// Storage key used to store the offchain worker running status.
51pub(crate) const OFFCHAIN_LOCK: &[u8] = b"parity/multi-phase-unsigned-election/lock";
52
53/// Storage key used to cache the solution `call`.
54pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-election/call";
55
56/// A voter's fundamental data: their ID, their stake, and the list of candidates for whom they
57/// voted.
58pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::DataProvider>;
59
60/// Same as [`VoterOf`], but parameterized by the `MinerConfig`.
61pub type MinerVoterOf<T> = frame_election_provider_support::Voter<
62	<T as MinerConfig>::AccountId,
63	<T as MinerConfig>::MaxVotesPerVoter,
64>;
65
66/// The relative distribution of a voter's stake among the winning targets.
67pub type Assignment<T> =
68	sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>;
69
70/// The [`IndexAssignment`][frame_election_provider_support::IndexAssignment] type specialized for a
71/// particular runtime `T`.
72pub type IndexAssignmentOf<T> = frame_election_provider_support::IndexAssignmentOf<SolutionOf<T>>;
73
74/// Error type of the pallet's [`crate::Config::Solver`].
75pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error;
76/// Error type for operations related to the OCW npos solution miner.
77#[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)]
78pub enum MinerError {
79	/// An internal error in the NPoS elections crate.
80	NposElections(sp_npos_elections::Error),
81	/// Snapshot data was unavailable unexpectedly.
82	SnapshotUnAvailable,
83	/// Submitting a transaction to the pool failed.
84	PoolSubmissionFailed,
85	/// The pre-dispatch checks failed for the mined solution.
86	PreDispatchChecksFailed(DispatchError),
87	/// The solution generated from the miner is not feasible.
88	Feasibility(FeasibilityError),
89	/// Something went wrong fetching the lock.
90	Lock(&'static str),
91	/// Cannot restore a solution that was not stored.
92	NoStoredSolution,
93	/// Cached solution is not a `submit_unsigned` call.
94	SolutionCallInvalid,
95	/// Failed to store a solution.
96	FailedToStoreSolution,
97	/// There are no more voters to remove to trim the solution.
98	NoMoreVoters,
99	/// An error from the solver.
100	Solver,
101	/// Desired targets are mire than the maximum allowed winners.
102	TooManyDesiredTargets,
103}
104
105impl From<sp_npos_elections::Error> for MinerError {
106	fn from(e: sp_npos_elections::Error) -> Self {
107		MinerError::NposElections(e)
108	}
109}
110
111impl From<FeasibilityError> for MinerError {
112	fn from(e: FeasibilityError) -> Self {
113		MinerError::Feasibility(e)
114	}
115}
116
117/// Reports the trimming result of a mined solution.
118#[derive(Debug, Clone)]
119pub struct TrimmingStatus {
120	/// Number of voters trimmed due to the solution weight limits.
121	weight: usize,
122	/// Number of voters trimmed due to the solution length limits.
123	length: usize,
124	/// Number of edges (voter -> target) trimmed due to the max backers per winner bound.
125	edges: usize,
126}
127
128impl TrimmingStatus {
129	pub fn is_trimmed(&self) -> bool {
130		self.weight > 0 || self.length > 0 || self.edges > 0
131	}
132
133	pub fn trimmed_weight(&self) -> usize {
134		self.weight
135	}
136
137	pub fn trimmed_length(&self) -> usize {
138		self.length
139	}
140
141	pub fn trimmed_edges(&self) -> usize {
142		self.edges
143	}
144}
145
146/// Save a given call into OCW storage.
147fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
148	log!(debug, "saving a call to the offchain storage.");
149	let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL);
150	match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
151		Ok(_) => Ok(()),
152		Err(MutateStorageError::ConcurrentModification(_)) => {
153			Err(MinerError::FailedToStoreSolution)
154		},
155		Err(MutateStorageError::ValueFunctionFailed(_)) => {
156			// this branch should be unreachable according to the definition of
157			// `StorageValueRef::mutate`: that function should only ever `Err` if the closure we
158			// pass it returns an error. however, for safety in case the definition changes, we do
159			// not optimize the branch away or panic.
160			Err(MinerError::FailedToStoreSolution)
161		},
162	}
163}
164
165/// Get a saved solution from OCW storage if it exists.
166fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> {
167	StorageValueRef::persistent(OFFCHAIN_CACHED_CALL)
168		.get()
169		.ok()
170		.flatten()
171		.ok_or(MinerError::NoStoredSolution)
172}
173
174/// Clear a saved solution from OCW storage.
175pub(super) fn kill_ocw_solution<T: Config>() {
176	log!(debug, "clearing offchain call cache storage.");
177	let mut storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL);
178	storage.clear();
179}
180
181/// Clear the offchain repeat storage.
182///
183/// After calling this, the next offchain worker is guaranteed to work, with respect to the
184/// frequency repeat.
185fn clear_offchain_repeat_frequency() {
186	let mut last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
187	last_block.clear();
188}
189
190/// `true` when OCW storage contains a solution
191#[cfg(test)]
192fn ocw_solution_exists<T: Config>() -> bool {
193	matches!(StorageValueRef::persistent(OFFCHAIN_CACHED_CALL).get::<Call<T>>(), Ok(Some(_)))
194}
195
196impl<T: Config + CreateBare<Call<T>>> Pallet<T> {
197	/// Mine a new npos solution.
198	///
199	/// The Npos Solver type, `S`, must have the same AccountId and Error type as the
200	/// [`crate::Config::Solver`] in order to create a unified return type.
201	pub fn mine_solution() -> Result<
202		(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize, TrimmingStatus),
203		MinerError,
204	> {
205		let RoundSnapshot { voters, targets } =
206			Snapshot::<T>::get().ok_or(MinerError::SnapshotUnAvailable)?;
207		let desired_targets = DesiredTargets::<T>::get().ok_or(MinerError::SnapshotUnAvailable)?;
208		ensure!(desired_targets <= T::MaxWinners::get(), MinerError::TooManyDesiredTargets);
209		let (solution, score, size, is_trimmed) =
210			Miner::<T::MinerConfig>::mine_solution_with_snapshot::<T::Solver>(
211				voters,
212				targets,
213				desired_targets,
214			)?;
215		let round = Round::<T>::get();
216		Ok((RawSolution { solution, score, round }, size, is_trimmed))
217	}
218
219	/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
220	/// if our call's score is greater than that of the cached solution.
221	pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> {
222		log!(debug, "miner attempting to restore or compute an unsigned solution.");
223
224		let call = restore_solution::<T>()
225			.and_then(|call| {
226				// ensure the cached call is still current before submitting
227				if let Call::submit_unsigned { raw_solution, .. } = &call {
228					// prevent errors arising from state changes in a forkful chain
229					Self::basic_checks(raw_solution, "restored")?;
230					Ok(call)
231				} else {
232					Err(MinerError::SolutionCallInvalid)
233				}
234			})
235			.or_else::<MinerError, _>(|error| {
236				log!(debug, "restoring solution failed due to {:?}", error);
237				match error {
238					MinerError::NoStoredSolution => {
239						log!(trace, "mining a new solution.");
240						// if not present or cache invalidated due to feasibility, regenerate.
241						// note that failing `Feasibility` can only mean that the solution was
242						// computed over a snapshot that has changed due to a fork.
243						let call = Self::mine_checked_call()?;
244						save_solution(&call)?;
245						Ok(call)
246					},
247					MinerError::Feasibility(_) => {
248						log!(trace, "wiping infeasible solution.");
249						// kill the infeasible solution, hopefully in the next runs (whenever they
250						// may be) we mine a new one.
251						kill_ocw_solution::<T>();
252						clear_offchain_repeat_frequency();
253						Err(error)
254					},
255					_ => {
256						// nothing to do. Return the error as-is.
257						Err(error)
258					},
259				}
260			})?;
261
262		Self::submit_call(call)
263	}
264
265	/// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction.
266	pub fn mine_check_save_submit() -> Result<(), MinerError> {
267		log!(debug, "miner attempting to compute an unsigned solution.");
268
269		let call = Self::mine_checked_call()?;
270		save_solution(&call)?;
271		Self::submit_call(call)
272	}
273
274	/// Mine a new solution as a call. Performs all checks.
275	pub fn mine_checked_call() -> Result<Call<T>, MinerError> {
276		// get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID.
277		let (raw_solution, witness, _trimming) = Self::mine_and_check()?;
278
279		let score = raw_solution.score;
280		let call: Call<T> = Call::submit_unsigned { raw_solution: Box::new(raw_solution), witness };
281
282		log!(
283			debug,
284			"mined a solution with score {:?} and size {} and trimming {:?}",
285			score,
286			call.using_encoded(|b| b.len()),
287			_trimming
288		);
289
290		Ok(call)
291	}
292
293	fn submit_call(call: Call<T>) -> Result<(), MinerError> {
294		log!(debug, "miner submitting a solution as an unsigned transaction");
295
296		let xt = T::create_bare(call.into());
297		SubmitTransaction::<T, Call<T>>::submit_transaction(xt)
298			.map_err(|_| MinerError::PoolSubmissionFailed)
299	}
300
301	// perform basic checks of a solution's validity
302	//
303	// Performance: note that it internally clones the provided solution.
304	pub fn basic_checks(
305		raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
306		solution_type: &str,
307	) -> Result<(), MinerError> {
308		Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
309			log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err);
310			MinerError::PreDispatchChecksFailed(err)
311		})?;
312
313		Self::feasibility_check(raw_solution.clone(), ElectionCompute::Unsigned).map_err(
314			|err| {
315				log!(debug, "feasibility check failed for {} solution: {:?}", solution_type, err);
316				err
317			},
318		)?;
319
320		Ok(())
321	}
322
323	/// Mine a new npos solution, with all the relevant checks to make sure that it will be accepted
324	/// to the chain.
325	///
326	/// If you want an unchecked solution, use [`Pallet::mine_solution`].
327	/// If you want a checked solution and submit it at the same time, use
328	/// [`Pallet::mine_check_save_submit`].
329	pub fn mine_and_check() -> Result<
330		(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize, TrimmingStatus),
331		MinerError,
332	> {
333		let (raw_solution, witness, is_trimmed) = Self::mine_solution()?;
334		Self::basic_checks(&raw_solution, "mined")?;
335		Ok((raw_solution, witness, is_trimmed))
336	}
337
338	/// Checks if an execution of the offchain worker is permitted at the given block number, or
339	/// not.
340	///
341	/// This makes sure that
342	/// 1. we don't run on previous blocks in case of a re-org
343	/// 2. we don't run twice within a window of length `T::OffchainRepeat`.
344	///
345	/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
346	/// is returned, `now` is written in storage and will be used in further calls as the baseline.
347	pub fn ensure_offchain_repeat_frequency(now: BlockNumberFor<T>) -> Result<(), MinerError> {
348		let threshold = T::OffchainRepeat::get();
349		let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
350
351		let mutate_stat = last_block.mutate::<_, &'static str, _>(
352			|maybe_head: Result<Option<BlockNumberFor<T>>, _>| {
353				match maybe_head {
354					Ok(Some(head)) if now < head => Err("fork."),
355					Ok(Some(head)) if now >= head && now <= head + threshold => {
356						Err("recently executed.")
357					},
358					Ok(Some(head)) if now > head + threshold => {
359						// we can run again now. Write the new head.
360						Ok(now)
361					},
362					_ => {
363						// value doesn't exists. Probably this node just booted up. Write, and run
364						Ok(now)
365					},
366				}
367			},
368		);
369
370		match mutate_stat {
371			// all good
372			Ok(_) => Ok(()),
373			// failed to write.
374			Err(MutateStorageError::ConcurrentModification(_)) => {
375				Err(MinerError::Lock("failed to write to offchain db (concurrent modification)."))
376			},
377			// fork etc.
378			Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
379		}
380	}
381
382	/// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned
383	/// transaction.
384	///
385	/// Can optionally also be called during dispatch, if needed.
386	///
387	/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
388	/// code, so that we do less and less storage reads here.
389	pub fn unsigned_pre_dispatch_checks(
390		raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
391	) -> DispatchResult {
392		// ensure solution is timely. Don't panic yet. This is a cheap check.
393		ensure!(
394			CurrentPhase::<T>::get().is_unsigned_open(),
395			Error::<T>::PreDispatchEarlySubmission
396		);
397
398		// ensure round is current
399		ensure!(Round::<T>::get() == raw_solution.round, Error::<T>::OcwCallWrongEra);
400
401		// ensure correct number of winners.
402		ensure!(
403			DesiredTargets::<T>::get().unwrap_or_default() ==
404				raw_solution.solution.unique_targets().len() as u32,
405			Error::<T>::PreDispatchWrongWinnerCount,
406		);
407
408		// ensure score is being improved. Panic henceforth.
409		ensure!(
410			QueuedSolution::<T>::get()
411				.map_or(true, |q: ReadySolution<_, _, _>| raw_solution.score > q.score),
412			Error::<T>::PreDispatchWeakSubmission,
413		);
414
415		Ok(())
416	}
417}
418
419/// Configurations for a miner that comes with this pallet.
420pub trait MinerConfig {
421	/// The account id type.
422	type AccountId: Ord + Clone + codec::Codec + core::fmt::Debug;
423	/// The solution that the miner is mining.
424	type Solution: codec::Codec
425		+ Default
426		+ PartialEq
427		+ Eq
428		+ Clone
429		+ core::fmt::Debug
430		+ Ord
431		+ NposSolution
432		+ TypeInfo;
433	/// Maximum number of votes per voter in the snapshots.
434	type MaxVotesPerVoter;
435	/// Maximum length of the solution that the miner is allowed to generate.
436	///
437	/// Solutions are trimmed to respect this.
438	type MaxLength: Get<u32>;
439	/// Maximum weight of the solution that the miner is allowed to generate.
440	///
441	/// Solutions are trimmed to respect this.
442	///
443	/// The weight is computed using `solution_weight`.
444	type MaxWeight: Get<Weight>;
445	/// The maximum number of winners that can be elected in the single page supported by this
446	/// pallet.
447	type MaxWinners: Get<u32>;
448	/// The maximum number of backers per winner in the last solution.
449	type MaxBackersPerWinner: Get<u32>;
450	/// Something that can compute the weight of a solution.
451	///
452	/// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`].
453	fn solution_weight(voters: u32, targets: u32, active_voters: u32, degree: u32) -> Weight;
454}
455
456/// A base miner, suitable to be used for both signed and unsigned submissions.
457pub struct Miner<T: MinerConfig>(core::marker::PhantomData<T>);
458impl<T: MinerConfig> Miner<T> {
459	/// Same as [`Pallet::mine_solution`], but the input snapshot data must be given.
460	pub fn mine_solution_with_snapshot<S>(
461		voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>,
462		targets: Vec<T::AccountId>,
463		desired_targets: u32,
464	) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize, TrimmingStatus), MinerError>
465	where
466		S: NposSolver<AccountId = T::AccountId>,
467	{
468		S::solve(desired_targets as usize, targets.clone(), voters.clone())
469			.map_err(|e| {
470				log_no_system!(error, "solver error: {:?}", e);
471				MinerError::Solver
472			})
473			.and_then(|e| {
474				Self::prepare_election_result_with_snapshot::<S::Accuracy>(
475					e,
476					voters,
477					targets,
478					desired_targets,
479				)
480			})
481	}
482
483	/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
484	/// is ready to be submitted to the chain.
485	///
486	/// Will always reduce the solution as well.
487	pub fn prepare_election_result_with_snapshot<Accuracy: PerThing128>(
488		election_result: ElectionResult<T::AccountId, Accuracy>,
489		voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>,
490		targets: Vec<T::AccountId>,
491		desired_targets: u32,
492	) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize, TrimmingStatus), MinerError>
493	{
494		// now make some helper closures.
495		let cache = helpers::generate_voter_cache::<T>(&voters);
496		let voter_index = helpers::voter_index_fn::<T>(&cache);
497		let target_index = helpers::target_index_fn::<T>(&targets);
498		let voter_at = helpers::voter_at_fn::<T>(&voters);
499		let target_at = helpers::target_at_fn::<T>(&targets);
500		let stake_of = helpers::stake_of_fn::<T>(&voters, &cache);
501
502		// Compute the size of a solution comprised of the selected arguments.
503		//
504		// This function completes in `O(edges)`; it's expensive, but linear.
505		let encoded_size_of = |assignments: &[IndexAssignmentOf<T>]| {
506			SolutionOf::<T>::try_from(assignments).map(|s| s.encoded_size())
507		};
508
509		let ElectionResult { assignments, winners: _ } = election_result;
510
511		// keeps track of how many edges were trimmed out.
512		let mut edges_trimmed = 0;
513
514		// Reduce (requires round-trip to staked form) and ensures the max backer per winner bound
515		// requirements are met.
516		let sorted_assignments = {
517			// convert to staked and reduce.
518			let mut staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?;
519
520			// we reduce before sorting in order to ensure that the reduction process doesn't
521			// accidentally change the sort order
522			sp_npos_elections::reduce(&mut staked);
523
524			// Sort the assignments by reversed voter stake. This ensures that we can efficiently
525			// truncate the list.
526			staked.sort_by_key(
527				|sp_npos_elections::StakedAssignment::<T::AccountId> { who, .. }| {
528					// though staked assignments are expressed in terms of absolute stake, we'd
529					// still need to iterate over all votes in order to actually compute the total
530					// stake. it should be faster to look it up from the cache.
531					let stake = cache
532						.get(who)
533						.map(|idx| {
534							let (_, stake, _) = voters[*idx];
535							stake
536						})
537						.unwrap_or_default();
538					core::cmp::Reverse(stake)
539				},
540			);
541
542			// ensures that the max backers per winner bounds are respected given the supports
543			// generated from the assignments. We achieve that by removing edges (voter ->
544			// target) in the assignments with lower stake until the total number of backers per
545			// winner fits within the expected bounded supports. This should be performed *after*
546			// applying reduce over the assignments to avoid over-trimming.
547			//
548			// a potential trimming does not affect the desired targets of the solution as the
549			// targets have *too many* edges by definition if trimmed.
550			let max_backers_per_winner = T::MaxBackersPerWinner::get().saturated_into::<usize>();
551
552			let _ = sp_npos_elections::to_supports(&staked)
553				.iter_mut()
554				.filter(|(_, support)| support.voters.len() > max_backers_per_winner)
555				.for_each(|(target, ref mut support)| {
556					// first sort by support stake, lowest at the tail.
557					support.voters.sort_by(|a, b| b.1.cmp(&a.1));
558
559					// filter out lowest stake edge in this support.
560					// optimization note: collects edge voters to remove from assignments into a
561					// btree set to optimize the search in the next loop.
562					let filtered: alloc::collections::BTreeSet<_> = support
563						.voters
564						.split_off(max_backers_per_winner)
565						.into_iter()
566						.map(|(who, _stake)| who)
567						.collect();
568
569					// remove lowest stake edges calculated above from assignments.
570					staked.iter_mut().for_each(|assignment| {
571						if filtered.contains(&assignment.who) {
572							assignment.distribution.retain(|(t, _)| t != target);
573						}
574					});
575
576					edges_trimmed += filtered.len();
577				});
578
579			debug_assert!({
580				// at this point we expect the supports generated from the assignments to fit within
581				// the expected bounded supports.
582				let expected_ok: Result<
583					crate::BoundedSupports<_, T::MaxWinners, T::MaxBackersPerWinner>,
584					_,
585				> = sp_npos_elections::to_supports(&staked).try_into();
586				expected_ok.is_ok()
587			});
588
589			// convert back.
590			assignment_staked_to_ratio_normalized(staked)?
591		};
592
593		// convert to `IndexAssignment`. This improves the runtime complexity of repeatedly
594		// converting to `Solution`.
595		let mut index_assignments = sorted_assignments
596			.into_iter()
597			.map(|assignment| IndexAssignmentOf::<T>::new(&assignment, &voter_index, &target_index))
598			.collect::<Result<Vec<_>, _>>()?;
599
600		// trim assignments list for weight and length.
601		let size =
602			SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 };
603		let weight_trimmed = Self::trim_assignments_weight(
604			desired_targets,
605			size,
606			T::MaxWeight::get(),
607			&mut index_assignments,
608		);
609		let length_trimmed = Self::trim_assignments_length(
610			T::MaxLength::get(),
611			&mut index_assignments,
612			&encoded_size_of,
613		)?;
614
615		// now make solution.
616		let solution = SolutionOf::<T>::try_from(&index_assignments)?;
617
618		// re-calc score.
619		let score = solution.clone().score(stake_of, voter_at, target_at)?;
620
621		let is_trimmed =
622			TrimmingStatus { weight: weight_trimmed, length: length_trimmed, edges: edges_trimmed };
623
624		log_no_system!(
625			debug,
626			"feasible solution mined: trimmed? {:?}, score: {:?}, encoded size: {:?}",
627			is_trimmed,
628			score,
629			solution.encoded_size()
630		);
631		Ok((solution, score, size, is_trimmed))
632	}
633
634	/// Greedily reduce the size of the solution to fit into the block w.r.t length.
635	///
636	/// The length of the solution is largely a function of the number of voters. The number of
637	/// winners cannot be changed. Thus, to reduce the solution size, we need to strip voters.
638	///
639	/// Note that this solution is already computed, and winners are elected based on the merit of
640	/// the total stake in the system. Nevertheless, some of the voters may be removed here.
641	///
642	/// Sometimes, removing a voter can cause a validator to also be implicitly removed, if
643	/// that voter was the only backer of that winner. In such cases, this solution is invalid,
644	/// which will be caught prior to submission.
645	///
646	/// The score must be computed **after** this step. If this step reduces the score too much,
647	/// then the solution must be discarded.
648	pub fn trim_assignments_length(
649		max_allowed_length: u32,
650		assignments: &mut Vec<IndexAssignmentOf<T>>,
651		encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>,
652	) -> Result<usize, MinerError> {
653		// Perform a binary search for the max subset of which can fit into the allowed
654		// length. Having discovered that, we can truncate efficiently.
655		let max_allowed_length: usize = max_allowed_length.saturated_into();
656		let mut high = assignments.len();
657		let mut low = 0;
658
659		// not much we can do if assignments are already empty.
660		if high == low {
661			return Ok(0);
662		}
663
664		while high - low > 1 {
665			let test = (high + low) / 2;
666			if encoded_size_of(&assignments[..test])? <= max_allowed_length {
667				low = test;
668			} else {
669				high = test;
670			}
671		}
672		let maximum_allowed_voters = if low < assignments.len() &&
673			encoded_size_of(&assignments[..low + 1])? <= max_allowed_length
674		{
675			low + 1
676		} else {
677			low
678		};
679
680		// ensure our post-conditions are correct
681		debug_assert!(
682			encoded_size_of(&assignments[..maximum_allowed_voters]).unwrap() <= max_allowed_length
683		);
684		debug_assert!(if maximum_allowed_voters < assignments.len() {
685			encoded_size_of(&assignments[..maximum_allowed_voters + 1]).unwrap() >
686				max_allowed_length
687		} else {
688			true
689		});
690
691		// NOTE: before this point, every access was immutable.
692		// after this point, we never error.
693		// check before edit.
694
695		let remove = assignments.len().saturating_sub(maximum_allowed_voters);
696
697		log_no_system!(
698			trace,
699			"from {} assignments, truncating to {} for length, removing {}",
700			assignments.len(),
701			maximum_allowed_voters,
702			remove
703		);
704		assignments.truncate(maximum_allowed_voters);
705
706		Ok(remove)
707	}
708
709	/// Greedily reduce the size of the solution to fit into the block w.r.t. weight.
710	///
711	/// The weight of the solution is foremost a function of the number of voters (i.e.
712	/// `assignments.len()`). Aside from this, the other components of the weight are invariant. The
713	/// number of winners shall not be changed (otherwise the solution is invalid) and the
714	/// `ElectionSize` is merely a representation of the total number of stakers.
715	///
716	/// Thus, we reside to stripping away some voters from the `assignments`.
717	///
718	/// Note that the solution is already computed, and the winners are elected based on the merit
719	/// of the entire stake in the system. Nonetheless, some of the voters will be removed further
720	/// down the line.
721	///
722	/// Indeed, the score must be computed **after** this step. If this step reduces the score too
723	/// much or remove a winner, then the solution must be discarded **after** this step.
724	pub fn trim_assignments_weight(
725		desired_targets: u32,
726		size: SolutionOrSnapshotSize,
727		max_weight: Weight,
728		assignments: &mut Vec<IndexAssignmentOf<T>>,
729	) -> usize {
730		let maximum_allowed_voters =
731			Self::maximum_voter_for_weight(desired_targets, size, max_weight);
732		let removing: usize =
733			assignments.len().saturating_sub(maximum_allowed_voters.saturated_into());
734		log_no_system!(
735			debug,
736			"from {} assignments, truncating to {} for weight, removing {}",
737			assignments.len(),
738			maximum_allowed_voters,
739			removing,
740		);
741		assignments.truncate(maximum_allowed_voters as usize);
742
743		removing
744	}
745
746	/// Find the maximum `len` that a solution can have in order to fit into the block weight.
747	///
748	/// This only returns a value between zero and `size.nominators`.
749	pub fn maximum_voter_for_weight(
750		desired_winners: u32,
751		size: SolutionOrSnapshotSize,
752		max_weight: Weight,
753	) -> u32 {
754		if size.voters < 1 {
755			return size.voters;
756		}
757
758		let max_voters = size.voters.max(1);
759		let mut voters = max_voters;
760
761		// helper closures.
762		let weight_with = |active_voters: u32| -> Weight {
763			T::solution_weight(size.voters, size.targets, active_voters, desired_winners)
764		};
765
766		let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result<u32, ()> {
767			if current_weight.all_lt(max_weight) {
768				let next_voters = voters.checked_add(step);
769				match next_voters {
770					Some(voters) if voters < max_voters => Ok(voters),
771					_ => Err(()),
772				}
773			} else if current_weight.any_gt(max_weight) {
774				voters.checked_sub(step).ok_or(())
775			} else {
776				// If any of the constituent weights is equal to the max weight, we're at max
777				Ok(voters)
778			}
779		};
780
781		// First binary-search the right amount of voters
782		let mut step = voters / 2;
783		let mut current_weight = weight_with(voters);
784
785		while step > 0 {
786			match next_voters(current_weight, voters, step) {
787				// proceed with the binary search
788				Ok(next) if next != voters => {
789					voters = next;
790				},
791				// we are out of bounds, break out of the loop.
792				Err(()) => break,
793				// we found the right value - early exit the function.
794				Ok(next) => return next,
795			}
796			step /= 2;
797			current_weight = weight_with(voters);
798		}
799
800		// Time to finish. We might have reduced less than expected due to rounding error. Increase
801		// one last time if we have any room left, the reduce until we are sure we are below limit.
802		while voters < max_voters && weight_with(voters + 1).all_lt(max_weight) {
803			voters += 1;
804		}
805		while voters.checked_sub(1).is_some() && weight_with(voters).any_gt(max_weight) {
806			voters -= 1;
807		}
808
809		let final_decision = voters.min(size.voters);
810		debug_assert!(
811			weight_with(final_decision).all_lte(max_weight),
812			"weight_with({}) <= {}",
813			final_decision,
814			max_weight,
815		);
816		final_decision
817	}
818
819	/// Checks the feasibility of a solution.
820	pub fn feasibility_check(
821		raw_solution: RawSolution<SolutionOf<T>>,
822		compute: ElectionCompute,
823		desired_targets: u32,
824		snapshot: RoundSnapshot<T::AccountId, MinerVoterOf<T>>,
825		current_round: u32,
826		minimum_untrusted_score: Option<ElectionScore>,
827	) -> Result<ReadySolutionOf<T>, FeasibilityError> {
828		let RawSolution { solution, score, round } = raw_solution;
829		let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot;
830
831		// First, check round.
832		ensure!(current_round == round, FeasibilityError::InvalidRound);
833
834		// Winners are not directly encoded in the solution.
835		let winners = solution.unique_targets();
836
837		ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount);
838		// Fail early if targets requested by data provider exceed maximum winners supported.
839		ensure!(desired_targets <= T::MaxWinners::get(), FeasibilityError::TooManyDesiredTargets);
840
841		// Ensure that the solution's score can pass absolute min-score.
842		let submitted_score = raw_solution.score;
843		ensure!(
844			minimum_untrusted_score
845				.map_or(true, |min_score| { submitted_score.strict_better(min_score) }),
846			FeasibilityError::UntrustedScoreTooLow
847		);
848
849		// ----- Start building. First, we need some closures.
850		let cache = helpers::generate_voter_cache::<T>(&snapshot_voters);
851		let voter_at = helpers::voter_at_fn::<T>(&snapshot_voters);
852		let target_at = helpers::target_at_fn::<T>(&snapshot_targets);
853		let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
854
855		// Then convert solution -> assignment. This will fail if any of the indices are gibberish,
856		// namely any of the voters or targets.
857		let assignments = solution
858			.into_assignment(voter_at, target_at)
859			.map_err::<FeasibilityError, _>(Into::into)?;
860
861		// Ensure that assignments is correct.
862		assignments.iter().try_for_each(|assignment| {
863			// Check that assignment.who is actually a voter (defensive-only).
864			// NOTE: while using the index map from `voter_index` is better than a blind linear
865			// search, this *still* has room for optimization. Note that we had the index when
866			// we did `solution -> assignment` and we lost it. Ideal is to keep the index
867			// around.
868
869			// Defensive-only: must exist in the snapshot.
870			let snapshot_index =
871				voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
872			// Defensive-only: index comes from the snapshot, must exist.
873			let (_voter, _stake, targets) =
874				snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
875
876			// Check that all of the targets are valid based on the snapshot.
877			if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) {
878				return Err(FeasibilityError::InvalidVote);
879			}
880			Ok(())
881		})?;
882
883		// ----- Start building support. First, we need one more closure.
884		let stake_of = helpers::stake_of_fn::<T>(&snapshot_voters, &cache);
885
886		// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
887		let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
888			.map_err::<FeasibilityError, _>(Into::into)?;
889		let supports = sp_npos_elections::to_supports(&staked_assignments);
890
891		// Finally, check that the claimed score was indeed correct.
892		let known_score = supports.evaluate();
893
894		ensure!(known_score == score, FeasibilityError::InvalidScore);
895
896		// Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. In
897		// addition, the miner should have ensured that the MaxBackerPerWinner bound in respected,
898		// thus this conversion should not fail.
899		let supports = supports
900			.try_into()
901			.defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?;
902
903		Ok(ReadySolution { supports, compute, score })
904	}
905}
906
907#[cfg(test)]
908mod max_weight {
909	#![allow(unused_variables)]
910	use super::*;
911	use crate::mock::{MockWeightInfo, Runtime};
912	#[test]
913	fn find_max_voter_binary_search_works() {
914		let w = SolutionOrSnapshotSize { voters: 10, targets: 0 };
915		MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex);
916		assert_eq!(
917			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(0, u64::MAX)),
918			0
919		);
920		assert_eq!(
921			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1, u64::MAX)),
922			0
923		);
924		assert_eq!(
925			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(999, u64::MAX)),
926			0
927		);
928		assert_eq!(
929			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1000, u64::MAX)),
930			1
931		);
932		assert_eq!(
933			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1001, u64::MAX)),
934			1
935		);
936		assert_eq!(
937			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1990, u64::MAX)),
938			1
939		);
940		assert_eq!(
941			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1999, u64::MAX)),
942			1
943		);
944		assert_eq!(
945			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2000, u64::MAX)),
946			2
947		);
948		assert_eq!(
949			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2001, u64::MAX)),
950			2
951		);
952		assert_eq!(
953			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2010, u64::MAX)),
954			2
955		);
956		assert_eq!(
957			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2990, u64::MAX)),
958			2
959		);
960		assert_eq!(
961			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2999, u64::MAX)),
962			2
963		);
964		assert_eq!(
965			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(3000, u64::MAX)),
966			3
967		);
968		assert_eq!(
969			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(3333, u64::MAX)),
970			3
971		);
972		assert_eq!(
973			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(5500, u64::MAX)),
974			5
975		);
976		assert_eq!(
977			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(7777, u64::MAX)),
978			7
979		);
980		assert_eq!(
981			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(9999, u64::MAX)),
982			9
983		);
984		assert_eq!(
985			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(10_000, u64::MAX)),
986			10
987		);
988		assert_eq!(
989			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(10_999, u64::MAX)),
990			10
991		);
992		assert_eq!(
993			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(11_000, u64::MAX)),
994			10
995		);
996		assert_eq!(
997			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(22_000, u64::MAX)),
998			10
999		);
1000
1001		let w = SolutionOrSnapshotSize { voters: 1, targets: 0 };
1002
1003		assert_eq!(
1004			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(0, u64::MAX)),
1005			0
1006		);
1007		assert_eq!(
1008			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1, u64::MAX)),
1009			0
1010		);
1011		assert_eq!(
1012			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(999, u64::MAX)),
1013			0
1014		);
1015		assert_eq!(
1016			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1000, u64::MAX)),
1017			1
1018		);
1019		assert_eq!(
1020			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1001, u64::MAX)),
1021			1
1022		);
1023		assert_eq!(
1024			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1990, u64::MAX)),
1025			1
1026		);
1027		assert_eq!(
1028			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1999, u64::MAX)),
1029			1
1030		);
1031		assert_eq!(
1032			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2000, u64::MAX)),
1033			1
1034		);
1035		assert_eq!(
1036			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2001, u64::MAX)),
1037			1
1038		);
1039		assert_eq!(
1040			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2010, u64::MAX)),
1041			1
1042		);
1043		assert_eq!(
1044			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(3333, u64::MAX)),
1045			1
1046		);
1047
1048		let w = SolutionOrSnapshotSize { voters: 2, targets: 0 };
1049
1050		assert_eq!(
1051			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(0, u64::MAX)),
1052			0
1053		);
1054		assert_eq!(
1055			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1, u64::MAX)),
1056			0
1057		);
1058		assert_eq!(
1059			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(999, u64::MAX)),
1060			0
1061		);
1062		assert_eq!(
1063			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1000, u64::MAX)),
1064			1
1065		);
1066		assert_eq!(
1067			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1001, u64::MAX)),
1068			1
1069		);
1070		assert_eq!(
1071			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(1999, u64::MAX)),
1072			1
1073		);
1074		assert_eq!(
1075			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2000, u64::MAX)),
1076			2
1077		);
1078		assert_eq!(
1079			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2001, u64::MAX)),
1080			2
1081		);
1082		assert_eq!(
1083			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(2010, u64::MAX)),
1084			2
1085		);
1086		assert_eq!(
1087			Miner::<Runtime>::maximum_voter_for_weight(0, w, Weight::from_parts(3333, u64::MAX)),
1088			2
1089		);
1090	}
1091}
1092
1093#[cfg(test)]
1094mod tests {
1095	use super::*;
1096	use crate::{
1097		mock::{
1098			multi_phase_events, roll_to, roll_to_signed, roll_to_unsigned, roll_to_with_ocw,
1099			trim_helpers, witness, BlockNumber, ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase,
1100			Runtime, RuntimeCall, RuntimeOrigin, System, TestNposSolution, TrimHelpers,
1101			UnsignedPhase,
1102		},
1103		Event, InvalidTransaction, Phase, QueuedSolution, TransactionSource,
1104		TransactionValidityError,
1105	};
1106	use alloc::vec;
1107	use codec::Decode;
1108	use frame_election_provider_support::IndexAssignment;
1109	use frame_support::{assert_noop, assert_ok, traits::OffchainWorker};
1110	use sp_npos_elections::ElectionScore;
1111	use sp_runtime::{
1112		bounded_vec,
1113		offchain::storage_lock::{BlockAndTime, StorageLock},
1114		traits::{Dispatchable, Zero},
1115		ModuleError, PerU16,
1116	};
1117
1118	#[allow(deprecated)]
1119	use sp_runtime::traits::ValidateUnsigned;
1120
1121	type Assignment = crate::unsigned::Assignment<Runtime>;
1122
1123	#[test]
1124	#[allow(deprecated)]
1125	fn validate_unsigned_retracts_wrong_phase() {
1126		ExtBuilder::default().desired_targets(0).build_and_execute(|| {
1127			let solution = RawSolution::<TestNposSolution> {
1128				score: ElectionScore { minimal_stake: 5, ..Default::default() },
1129				..Default::default()
1130			};
1131			let call = Call::submit_unsigned {
1132				raw_solution: Box::new(solution.clone()),
1133				witness: witness(),
1134			};
1135
1136			// initial
1137			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
1138			assert!(matches!(
1139				<MultiPhase as ValidateUnsigned>::validate_unsigned(
1140					TransactionSource::Local,
1141					&call
1142				)
1143				.unwrap_err(),
1144				TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
1145			));
1146			assert!(matches!(
1147				<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
1148				TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
1149			));
1150
1151			// signed
1152			roll_to_signed();
1153			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
1154			assert!(matches!(
1155				<MultiPhase as ValidateUnsigned>::validate_unsigned(
1156					TransactionSource::Local,
1157					&call
1158				)
1159				.unwrap_err(),
1160				TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
1161			));
1162			assert!(matches!(
1163				<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
1164				TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
1165			));
1166
1167			// unsigned
1168			roll_to_unsigned();
1169			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1170
1171			assert!(<MultiPhase as ValidateUnsigned>::validate_unsigned(
1172				TransactionSource::Local,
1173				&call
1174			)
1175			.is_ok());
1176			assert!(<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).is_ok());
1177
1178			// unsigned -- but not enabled.
1179			MultiPhase::phase_transition(Phase::Unsigned((false, 25)));
1180			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1181			assert!(matches!(
1182				<MultiPhase as ValidateUnsigned>::validate_unsigned(
1183					TransactionSource::Local,
1184					&call
1185				)
1186				.unwrap_err(),
1187				TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
1188			));
1189			assert!(matches!(
1190				<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
1191				TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
1192			));
1193		})
1194	}
1195
1196	#[test]
1197	#[allow(deprecated)]
1198	fn validate_unsigned_retracts_low_score() {
1199		ExtBuilder::default().desired_targets(0).build_and_execute(|| {
1200			roll_to_unsigned();
1201			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1202
1203			let solution = RawSolution::<TestNposSolution> {
1204				score: ElectionScore { minimal_stake: 5, ..Default::default() },
1205				..Default::default()
1206			};
1207			let call = Call::submit_unsigned {
1208				raw_solution: Box::new(solution.clone()),
1209				witness: witness(),
1210			};
1211
1212			// initial
1213			assert!(<MultiPhase as ValidateUnsigned>::validate_unsigned(
1214				TransactionSource::Local,
1215				&call
1216			)
1217			.is_ok());
1218			assert!(<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).is_ok());
1219
1220			// set a better score
1221			let ready = ReadySolution {
1222				score: ElectionScore { minimal_stake: 10, ..Default::default() },
1223				..Default::default()
1224			};
1225			QueuedSolution::<Runtime>::put(ready);
1226
1227			// won't work anymore.
1228			assert!(matches!(
1229				<MultiPhase as ValidateUnsigned>::validate_unsigned(
1230					TransactionSource::Local,
1231					&call
1232				)
1233				.unwrap_err(),
1234				TransactionValidityError::Invalid(InvalidTransaction::Custom(2))
1235			));
1236			assert!(matches!(
1237				<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
1238				TransactionValidityError::Invalid(InvalidTransaction::Custom(2))
1239			));
1240		})
1241	}
1242
1243	#[test]
1244	#[allow(deprecated)]
1245	fn validate_unsigned_retracts_incorrect_winner_count() {
1246		ExtBuilder::default().desired_targets(1).build_and_execute(|| {
1247			roll_to_unsigned();
1248			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1249
1250			let raw = RawSolution::<TestNposSolution> {
1251				score: ElectionScore { minimal_stake: 5, ..Default::default() },
1252				..Default::default()
1253			};
1254			let call =
1255				Call::submit_unsigned { raw_solution: Box::new(raw.clone()), witness: witness() };
1256			assert_eq!(raw.solution.unique_targets().len(), 0);
1257
1258			// won't work anymore.
1259			assert!(matches!(
1260				<MultiPhase as ValidateUnsigned>::validate_unsigned(
1261					TransactionSource::Local,
1262					&call
1263				)
1264				.unwrap_err(),
1265				TransactionValidityError::Invalid(InvalidTransaction::Custom(1))
1266			));
1267		})
1268	}
1269
1270	#[test]
1271	#[allow(deprecated)]
1272	fn priority_is_set() {
1273		ExtBuilder::default()
1274			.miner_tx_priority(20)
1275			.desired_targets(0)
1276			.build_and_execute(|| {
1277				roll_to_unsigned();
1278				assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1279
1280				let solution = RawSolution::<TestNposSolution> {
1281					score: ElectionScore { minimal_stake: 5, ..Default::default() },
1282					..Default::default()
1283				};
1284				let call = Call::submit_unsigned {
1285					raw_solution: Box::new(solution.clone()),
1286					witness: witness(),
1287				};
1288
1289				assert_eq!(
1290					<MultiPhase as ValidateUnsigned>::validate_unsigned(
1291						TransactionSource::Local,
1292						&call
1293					)
1294					.unwrap()
1295					.priority,
1296					25
1297				);
1298			})
1299	}
1300
1301	#[test]
1302	#[should_panic(expected = "Invalid unsigned submission must produce invalid block and \
1303	                           deprive validator from their authoring reward.: \
1304	                           Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: \
1305	                           Some(\"PreDispatchWrongWinnerCount\") })")]
1306	fn unfeasible_solution_panics() {
1307		ExtBuilder::default().build_and_execute(|| {
1308			roll_to_unsigned();
1309			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1310
1311			// This is in itself an invalid BS solution.
1312			let solution = RawSolution::<TestNposSolution> {
1313				score: ElectionScore { minimal_stake: 5, ..Default::default() },
1314				..Default::default()
1315			};
1316			let call = Call::submit_unsigned {
1317				raw_solution: Box::new(solution.clone()),
1318				witness: witness(),
1319			};
1320			let runtime_call: RuntimeCall = call.into();
1321			let _ = runtime_call.dispatch(RuntimeOrigin::none());
1322		})
1323	}
1324
1325	#[test]
1326	#[should_panic(expected = "Invalid unsigned submission must produce invalid block and \
1327	                           deprive validator from their authoring reward.")]
1328	fn wrong_witness_panics() {
1329		ExtBuilder::default().build_and_execute(|| {
1330			roll_to_unsigned();
1331			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1332
1333			// This solution is unfeasible as well, but we won't even get there.
1334			let solution = RawSolution::<TestNposSolution> {
1335				score: ElectionScore { minimal_stake: 5, ..Default::default() },
1336				..Default::default()
1337			};
1338
1339			let mut correct_witness = witness();
1340			correct_witness.voters += 1;
1341			correct_witness.targets -= 1;
1342			let call = Call::submit_unsigned {
1343				raw_solution: Box::new(solution.clone()),
1344				witness: correct_witness,
1345			};
1346			let runtime_call: RuntimeCall = call.into();
1347			let _ = runtime_call.dispatch(RuntimeOrigin::none());
1348		})
1349	}
1350
1351	#[test]
1352	fn miner_works() {
1353		ExtBuilder::default().build_and_execute(|| {
1354			roll_to_unsigned();
1355			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1356
1357			// ensure we have snapshots in place.
1358			assert!(Snapshot::<Runtime>::get().is_some());
1359			assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 2);
1360
1361			// mine seq_phragmen solution with 2 iters.
1362			let (solution, witness, _) = MultiPhase::mine_solution().unwrap();
1363
1364			// ensure this solution is valid.
1365			assert!(QueuedSolution::<Runtime>::get().is_none());
1366			assert_ok!(MultiPhase::submit_unsigned(
1367				RuntimeOrigin::none(),
1368				Box::new(solution),
1369				witness
1370			));
1371			assert!(QueuedSolution::<Runtime>::get().is_some());
1372			assert_eq!(
1373				multi_phase_events(),
1374				vec![
1375					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
1376					Event::PhaseTransitioned {
1377						from: Phase::Signed,
1378						to: Phase::Unsigned((true, 25)),
1379						round: 1
1380					},
1381					Event::SolutionStored {
1382						compute: ElectionCompute::Unsigned,
1383						origin: None,
1384						prev_ejected: false
1385					}
1386				]
1387			);
1388		})
1389	}
1390
1391	#[test]
1392	fn miner_trims_weight() {
1393		ExtBuilder::default()
1394			.miner_weight(Weight::from_parts(100, u64::MAX))
1395			.mock_weight_info(crate::mock::MockedWeightInfo::Basic)
1396			.build_and_execute(|| {
1397				roll_to_unsigned();
1398				assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1399
1400				let (raw, witness, t) = MultiPhase::mine_solution().unwrap();
1401				let solution_weight = <Runtime as MinerConfig>::solution_weight(
1402					witness.voters,
1403					witness.targets,
1404					raw.solution.voter_count() as u32,
1405					raw.solution.unique_targets().len() as u32,
1406				);
1407				// default solution will have 5 edges (5 * 5 + 10)
1408				assert_eq!(solution_weight, Weight::from_parts(35, 0));
1409				assert_eq!(raw.solution.voter_count(), 5);
1410				assert_eq!(t.trimmed_weight(), 0);
1411
1412				// now reduce the max weight
1413				<MinerMaxWeight>::set(Weight::from_parts(25, u64::MAX));
1414
1415				let (raw, witness, t) = MultiPhase::mine_solution().unwrap();
1416				let solution_weight = <Runtime as MinerConfig>::solution_weight(
1417					witness.voters,
1418					witness.targets,
1419					raw.solution.voter_count() as u32,
1420					raw.solution.unique_targets().len() as u32,
1421				);
1422				// default solution will have 5 edges (5 * 5 + 10)
1423				assert_eq!(solution_weight, Weight::from_parts(25, 0));
1424				assert_eq!(raw.solution.voter_count(), 3);
1425				assert_eq!(t.trimmed_weight(), 2);
1426			})
1427	}
1428
1429	#[test]
1430	fn miner_will_not_submit_if_not_enough_winners() {
1431		let (mut ext, _) = ExtBuilder::default().desired_targets(8).build_offchainify(0);
1432		ext.execute_with(|| {
1433			roll_to_unsigned();
1434			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1435
1436			// Force the number of winners to be bigger to fail
1437			let (mut solution, _, _) = MultiPhase::mine_solution().unwrap();
1438			solution.solution.votes1[0].1 = 4;
1439
1440			assert_eq!(
1441				MultiPhase::basic_checks(&solution, "mined").unwrap_err(),
1442				MinerError::PreDispatchChecksFailed(DispatchError::Module(ModuleError {
1443					index: 2,
1444					error: [1, 0, 0, 0],
1445					message: Some("PreDispatchWrongWinnerCount"),
1446				})),
1447			);
1448		})
1449	}
1450
1451	#[test]
1452	fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() {
1453		ExtBuilder::default()
1454			.desired_targets(1)
1455			.add_voter(7, 2, bounded_vec![10])
1456			.add_voter(8, 5, bounded_vec![10])
1457			.add_voter(9, 1, bounded_vec![10])
1458			.build_and_execute(|| {
1459				roll_to_unsigned();
1460				assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1461				assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 1);
1462
1463				// an initial solution
1464				let result = ElectionResult {
1465					winners: vec![(10, 12)],
1466					assignments: vec![
1467						Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
1468						Assignment {
1469							who: 7,
1470							// note: this percent doesn't even matter, in solution it is 100%.
1471							distribution: vec![(10, PerU16::one())],
1472						},
1473					],
1474				};
1475
1476				let RoundSnapshot { voters, targets } = Snapshot::<Runtime>::get().unwrap();
1477				let desired_targets = DesiredTargets::<Runtime>::get().unwrap();
1478
1479				let (raw, score, witness, _) =
1480					Miner::<Runtime>::prepare_election_result_with_snapshot(
1481						result,
1482						voters.clone(),
1483						targets.clone(),
1484						desired_targets,
1485					)
1486					.unwrap();
1487				let solution = RawSolution { solution: raw, score, round: Round::<Runtime>::get() };
1488				assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution));
1489				assert_ok!(MultiPhase::submit_unsigned(
1490					RuntimeOrigin::none(),
1491					Box::new(solution),
1492					witness
1493				));
1494				assert_eq!(QueuedSolution::<Runtime>::get().unwrap().score.minimal_stake, 12);
1495
1496				// trial 1: a solution who's minimal stake is 10, i.e. worse than the first solution
1497				// of 12.
1498				let result = ElectionResult {
1499					winners: vec![(10, 10)],
1500					assignments: vec![Assignment {
1501						who: 10,
1502						distribution: vec![(10, PerU16::one())],
1503					}],
1504				};
1505				let (raw, score, _, _) = Miner::<Runtime>::prepare_election_result_with_snapshot(
1506					result,
1507					voters.clone(),
1508					targets.clone(),
1509					desired_targets,
1510				)
1511				.unwrap();
1512				let solution = RawSolution { solution: raw, score, round: Round::<Runtime>::get() };
1513				// 10 is not better than 12
1514				assert_eq!(solution.score.minimal_stake, 10);
1515				// submitting this will actually panic.
1516				assert_noop!(
1517					MultiPhase::unsigned_pre_dispatch_checks(&solution),
1518					Error::<Runtime>::PreDispatchWeakSubmission,
1519				);
1520
1521				// trial 2: try resubmitting another solution with same score (12) as the queued
1522				// solution.
1523				let result = ElectionResult {
1524					winners: vec![(10, 12)],
1525					assignments: vec![
1526						Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
1527						Assignment {
1528							who: 7,
1529							// note: this percent doesn't even matter, in solution it is 100%.
1530							distribution: vec![(10, PerU16::one())],
1531						},
1532					],
1533				};
1534
1535				let (raw, score, _, _) = Miner::<Runtime>::prepare_election_result_with_snapshot(
1536					result,
1537					voters.clone(),
1538					targets.clone(),
1539					desired_targets,
1540				)
1541				.unwrap();
1542				let solution = RawSolution { solution: raw, score, round: Round::<Runtime>::get() };
1543				// 12 is not better than 12. We need score of at least 13 to be accepted.
1544				assert_eq!(solution.score.minimal_stake, 12);
1545				// submitting this will panic.
1546				assert_noop!(
1547					MultiPhase::unsigned_pre_dispatch_checks(&solution),
1548					Error::<Runtime>::PreDispatchWeakSubmission,
1549				);
1550
1551				// trial 3: a solution who's minimal stake is 13, i.e. 1 better than the queued
1552				// solution of 12.
1553				let result = ElectionResult {
1554					winners: vec![(10, 12)],
1555					assignments: vec![
1556						Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
1557						Assignment { who: 7, distribution: vec![(10, PerU16::one())] },
1558						Assignment { who: 9, distribution: vec![(10, PerU16::one())] },
1559					],
1560				};
1561				let (raw, score, witness, _) =
1562					Miner::<Runtime>::prepare_election_result_with_snapshot(
1563						result,
1564						voters.clone(),
1565						targets.clone(),
1566						desired_targets,
1567					)
1568					.unwrap();
1569				let solution = RawSolution { solution: raw, score, round: Round::<Runtime>::get() };
1570				assert_eq!(solution.score.minimal_stake, 13);
1571
1572				// this should work
1573				assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution));
1574				assert_ok!(MultiPhase::submit_unsigned(
1575					RuntimeOrigin::none(),
1576					Box::new(solution),
1577					witness
1578				));
1579
1580				// trial 4: a solution who's minimal stake is 17, i.e. 4 better than the last
1581				// solution.
1582				let result = ElectionResult {
1583					winners: vec![(10, 12)],
1584					assignments: vec![
1585						Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
1586						Assignment { who: 7, distribution: vec![(10, PerU16::one())] },
1587						Assignment {
1588							who: 8,
1589							// note: this percent doesn't even matter, in solution it is 100%.
1590							distribution: vec![(10, PerU16::one())],
1591						},
1592					],
1593				};
1594				let (raw, score, witness, _) =
1595					Miner::<Runtime>::prepare_election_result_with_snapshot(
1596						result,
1597						voters.clone(),
1598						targets.clone(),
1599						desired_targets,
1600					)
1601					.unwrap();
1602				let solution = RawSolution { solution: raw, score, round: Round::<Runtime>::get() };
1603				assert_eq!(solution.score.minimal_stake, 17);
1604
1605				// and it is fine
1606				assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution));
1607				assert_ok!(MultiPhase::submit_unsigned(
1608					RuntimeOrigin::none(),
1609					Box::new(solution),
1610					witness
1611				));
1612			})
1613	}
1614
1615	#[test]
1616	fn ocw_lock_prevents_frequent_execution() {
1617		let (mut ext, _) = ExtBuilder::default().build_offchainify(0);
1618		ext.execute_with(|| {
1619			let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
1620
1621			roll_to_unsigned();
1622			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1623
1624			// first execution -- okay.
1625			assert!(MultiPhase::ensure_offchain_repeat_frequency(25).is_ok());
1626
1627			// next block: rejected.
1628			assert_noop!(
1629				MultiPhase::ensure_offchain_repeat_frequency(26),
1630				MinerError::Lock("recently executed.")
1631			);
1632
1633			// allowed after `OFFCHAIN_REPEAT`
1634			assert!(
1635				MultiPhase::ensure_offchain_repeat_frequency((26 + offchain_repeat).into()).is_ok()
1636			);
1637
1638			// a fork like situation: re-execute last 3.
1639			assert!(MultiPhase::ensure_offchain_repeat_frequency(
1640				(26 + offchain_repeat - 3).into()
1641			)
1642			.is_err());
1643			assert!(MultiPhase::ensure_offchain_repeat_frequency(
1644				(26 + offchain_repeat - 2).into()
1645			)
1646			.is_err());
1647			assert!(MultiPhase::ensure_offchain_repeat_frequency(
1648				(26 + offchain_repeat - 1).into()
1649			)
1650			.is_err());
1651		})
1652	}
1653
1654	#[test]
1655	fn ocw_lock_released_after_successful_execution() {
1656		// first, ensure that a successful execution releases the lock
1657		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1658		ext.execute_with(|| {
1659			let guard = StorageValueRef::persistent(&OFFCHAIN_LOCK);
1660			let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
1661
1662			roll_to_unsigned();
1663			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1664
1665			// initially, the lock is not set.
1666			assert!(guard.get::<bool>().unwrap().is_none());
1667
1668			// a successful a-z execution.
1669			MultiPhase::offchain_worker(25);
1670			assert_eq!(pool.read().transactions.len(), 1);
1671
1672			// afterwards, the lock is not set either..
1673			assert!(guard.get::<bool>().unwrap().is_none());
1674			assert_eq!(last_block.get::<BlockNumber>().unwrap(), Some(25));
1675		});
1676	}
1677
1678	#[test]
1679	fn ocw_lock_prevents_overlapping_execution() {
1680		// ensure that if the guard is in hold, a new execution is not allowed.
1681		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1682		ext.execute_with(|| {
1683			roll_to_unsigned();
1684			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
1685
1686			// artificially set the value, as if another thread is mid-way.
1687			let mut lock = StorageLock::<BlockAndTime<System>>::with_block_deadline(
1688				OFFCHAIN_LOCK,
1689				UnsignedPhase::get().saturated_into(),
1690			);
1691			let guard = lock.lock();
1692
1693			// nothing submitted.
1694			MultiPhase::offchain_worker(25);
1695			assert_eq!(pool.read().transactions.len(), 0);
1696			MultiPhase::offchain_worker(26);
1697			assert_eq!(pool.read().transactions.len(), 0);
1698
1699			drop(guard);
1700
1701			// ๐ŸŽ‰ !
1702			MultiPhase::offchain_worker(25);
1703			assert_eq!(pool.read().transactions.len(), 1);
1704		});
1705	}
1706
1707	#[test]
1708	fn ocw_only_runs_when_unsigned_open_now() {
1709		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1710		ext.execute_with(|| {
1711			roll_to_unsigned();
1712			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
1713
1714			// we must clear the offchain storage to ensure the offchain execution check doesn't get
1715			// in the way.
1716			let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
1717
1718			MultiPhase::offchain_worker(24);
1719			assert!(pool.read().transactions.len().is_zero());
1720			storage.clear();
1721
1722			// creates, caches, submits without expecting previous cache value
1723			MultiPhase::offchain_worker(25);
1724			assert_eq!(pool.read().transactions.len(), 1);
1725			// assume that the tx has been processed
1726			pool.try_write().unwrap().transactions.clear();
1727
1728			// locked, but also, has previously cached.
1729			MultiPhase::offchain_worker(26);
1730			assert!(pool.read().transactions.len().is_zero());
1731		})
1732	}
1733
1734	#[test]
1735	fn ocw_clears_cache_on_unsigned_phase_open() {
1736		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1737		ext.execute_with(|| {
1738			const BLOCK: u64 = 25;
1739			let block_plus = |delta: u64| BLOCK + delta;
1740			let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
1741
1742			roll_to(BLOCK);
1743			// we are on the first block of the unsigned phase
1744			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, BLOCK)));
1745
1746			assert!(
1747				!ocw_solution_exists::<Runtime>(),
1748				"no solution should be present before we mine one",
1749			);
1750
1751			// create and cache a solution on the first block of the unsigned phase
1752			MultiPhase::offchain_worker(BLOCK);
1753			assert!(
1754				ocw_solution_exists::<Runtime>(),
1755				"a solution must be cached after running the worker",
1756			);
1757
1758			// record the submitted tx,
1759			let tx_cache_1 = pool.read().transactions[0].clone();
1760			// and assume it has been processed.
1761			pool.try_write().unwrap().transactions.clear();
1762
1763			// after an election, the solution is not cleared
1764			// we don't actually care about the result of the election
1765			let _ = MultiPhase::do_elect();
1766			MultiPhase::offchain_worker(block_plus(1));
1767			assert!(ocw_solution_exists::<Runtime>(), "elections does not clear the ocw cache");
1768
1769			// submit a solution with the offchain worker after the repeat interval
1770			MultiPhase::offchain_worker(block_plus(offchain_repeat + 1));
1771
1772			// record the submitted tx,
1773			let tx_cache_2 = pool.read().transactions[0].clone();
1774			// and assume it has been processed.
1775			pool.try_write().unwrap().transactions.clear();
1776
1777			// the OCW submitted the same solution twice since the cache was not cleared.
1778			assert_eq!(tx_cache_1, tx_cache_2);
1779
1780			let current_block = block_plus(offchain_repeat * 2 + 2);
1781			// force the unsigned phase to start on the current block.
1782			MultiPhase::phase_transition(Phase::Unsigned((true, current_block)));
1783
1784			// clear the cache and create a solution since we are on the first block of the unsigned
1785			// phase.
1786			MultiPhase::offchain_worker(current_block);
1787			let tx_cache_3 = pool.read().transactions[0].clone();
1788
1789			// the submitted solution changes because the cache was cleared.
1790			assert_eq!(tx_cache_1, tx_cache_3);
1791			assert_eq!(
1792				multi_phase_events(),
1793				vec![
1794					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
1795					Event::PhaseTransitioned {
1796						from: Phase::Signed,
1797						to: Phase::Unsigned((true, 25)),
1798						round: 1
1799					},
1800					Event::ElectionFinalized {
1801						compute: ElectionCompute::Fallback,
1802						score: ElectionScore {
1803							minimal_stake: 0,
1804							sum_stake: 0,
1805							sum_stake_squared: 0
1806						}
1807					},
1808					Event::PhaseTransitioned {
1809						from: Phase::Unsigned((true, 25)),
1810						to: Phase::Unsigned((true, 37)),
1811						round: 1
1812					},
1813				]
1814			);
1815		})
1816	}
1817
1818	#[test]
1819	fn ocw_resubmits_after_offchain_repeat() {
1820		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1821		ext.execute_with(|| {
1822			const BLOCK: u64 = 25;
1823			let block_plus = |delta: i32| ((BLOCK as i32) + delta) as u64;
1824			let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
1825
1826			roll_to(BLOCK);
1827			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, BLOCK)));
1828
1829			// we must clear the offchain storage to ensure the offchain execution check doesn't get
1830			// in the way.
1831			let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
1832
1833			MultiPhase::offchain_worker(block_plus(-1));
1834			assert!(pool.read().transactions.len().is_zero());
1835			storage.clear();
1836
1837			// creates, caches, submits without expecting previous cache value
1838			MultiPhase::offchain_worker(BLOCK);
1839			assert_eq!(pool.read().transactions.len(), 1);
1840			let tx_cache = pool.read().transactions[0].clone();
1841			// assume that the tx has been processed
1842			pool.try_write().unwrap().transactions.clear();
1843
1844			// attempts to resubmit the tx after the threshold has expired
1845			// note that we have to add 1: the semantics forbid resubmission at
1846			// BLOCK + offchain_repeat
1847			MultiPhase::offchain_worker(block_plus(1 + offchain_repeat as i32));
1848			assert_eq!(pool.read().transactions.len(), 1);
1849
1850			// resubmitted tx is identical to first submission
1851			let tx = &pool.read().transactions[0];
1852			assert_eq!(&tx_cache, tx);
1853		})
1854	}
1855
1856	#[test]
1857	fn ocw_regenerates_and_resubmits_after_offchain_repeat() {
1858		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1859		ext.execute_with(|| {
1860			const BLOCK: u64 = 25;
1861			let block_plus = |delta: i32| ((BLOCK as i32) + delta) as u64;
1862			let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
1863
1864			roll_to(BLOCK);
1865			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, BLOCK)));
1866
1867			// we must clear the offchain storage to ensure the offchain execution check doesn't get
1868			// in the way.
1869			let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
1870
1871			MultiPhase::offchain_worker(block_plus(-1));
1872			assert!(pool.read().transactions.len().is_zero());
1873			storage.clear();
1874
1875			// creates, caches, submits without expecting previous cache value
1876			MultiPhase::offchain_worker(BLOCK);
1877			assert_eq!(pool.read().transactions.len(), 1);
1878			let tx_cache = pool.read().transactions[0].clone();
1879			// assume that the tx has been processed
1880			pool.try_write().unwrap().transactions.clear();
1881
1882			// remove the cached submitted tx
1883			// this ensures that when the resubmit window rolls around, we're ready to regenerate
1884			// from scratch if necessary
1885			let mut call_cache = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
1886			assert!(matches!(call_cache.get::<Call<Runtime>>(), Ok(Some(_call))));
1887			call_cache.clear();
1888
1889			// attempts to resubmit the tx after the threshold has expired
1890			// note that we have to add 1: the semantics forbid resubmission at
1891			// BLOCK + offchain_repeat
1892			MultiPhase::offchain_worker(block_plus(1 + offchain_repeat as i32));
1893			assert_eq!(pool.read().transactions.len(), 1);
1894
1895			// resubmitted tx is identical to first submission
1896			let tx = &pool.read().transactions[0];
1897			assert_eq!(&tx_cache, tx);
1898		})
1899	}
1900
1901	#[test]
1902	fn ocw_can_submit_to_pool() {
1903		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1904		ext.execute_with(|| {
1905			roll_to_with_ocw(25);
1906			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
1907			// OCW must have submitted now
1908
1909			let encoded = pool.read().transactions[0].clone();
1910			let extrinsic: Extrinsic = codec::Decode::decode(&mut &*encoded).unwrap();
1911			let call = extrinsic.function;
1912			assert!(matches!(call, RuntimeCall::MultiPhase(Call::submit_unsigned { .. })));
1913		})
1914	}
1915
1916	#[test]
1917	#[allow(deprecated)]
1918	fn ocw_solution_must_have_correct_round() {
1919		let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
1920		ext.execute_with(|| {
1921			roll_to_with_ocw(25);
1922			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
1923			// OCW must have submitted now
1924			// now, before we check the call, update the round
1925			crate::Round::<Runtime>::mutate(|round| *round += 1);
1926
1927			let encoded = pool.read().transactions[0].clone();
1928			let extrinsic = Extrinsic::decode(&mut &*encoded).unwrap();
1929			let call = match extrinsic.function {
1930				RuntimeCall::MultiPhase(call @ Call::submit_unsigned { .. }) => call,
1931				_ => panic!("bad call: unexpected submission"),
1932			};
1933
1934			// Custom(7) maps to PreDispatchChecksFailed
1935			let pre_dispatch_check_error =
1936				TransactionValidityError::Invalid(InvalidTransaction::Custom(7));
1937			assert_eq!(
1938				<MultiPhase as ValidateUnsigned>::validate_unsigned(
1939					TransactionSource::Local,
1940					&call,
1941				)
1942				.unwrap_err(),
1943				pre_dispatch_check_error,
1944			);
1945			assert_eq!(
1946				<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
1947				pre_dispatch_check_error,
1948			);
1949		})
1950	}
1951
1952	#[test]
1953	fn mine_solution_always_respects_max_backers_per_winner() {
1954		use crate::mock::MaxBackersPerWinner;
1955		use frame_election_provider_support::BoundedSupport;
1956
1957		let targets = vec![10, 20, 30, 40];
1958		let voters = vec![
1959			(1, 11, bounded_vec![10, 20, 30]),
1960			(2, 12, bounded_vec![10, 20, 30]),
1961			(3, 13, bounded_vec![10, 20, 30]),
1962			(4, 14, bounded_vec![10, 20, 30]),
1963			(5, 15, bounded_vec![10, 20, 40]),
1964		];
1965		let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() };
1966		let (round, desired_targets) = (1, 3);
1967
1968		// election with unbounded max backers per winnner.
1969		ExtBuilder::default().max_backers_per_winner(u32::MAX).build_and_execute(|| {
1970			assert_eq!(MaxBackersPerWinner::get(), u32::MAX);
1971
1972			let (solution, expected_score_unbounded, _, trimming_status) =
1973				Miner::<Runtime>::mine_solution_with_snapshot::<<Runtime as Config>::Solver>(
1974					voters.clone(),
1975					targets.clone(),
1976					desired_targets,
1977				)
1978				.unwrap();
1979
1980			let ready_solution = Miner::<Runtime>::feasibility_check(
1981				RawSolution { solution, score: expected_score_unbounded, round },
1982				Default::default(),
1983				desired_targets,
1984				snapshot.clone(),
1985				round,
1986				Default::default(),
1987			)
1988			.unwrap();
1989
1990			assert_eq!(
1991				ready_solution.supports.into_iter().collect::<Vec<_>>(),
1992				vec![
1993					(
1994						10,
1995						BoundedSupport { total: 25, voters: bounded_vec![(1, 11), (4, 9), (5, 5)] }
1996					),
1997					(20, BoundedSupport { total: 22, voters: bounded_vec![(2, 12), (5, 10)] }),
1998					(30, BoundedSupport { total: 18, voters: bounded_vec![(3, 13), (4, 5)] })
1999				]
2000			);
2001
2002			// no trimmed edges.
2003			assert_eq!(trimming_status.trimmed_edges(), 0);
2004		});
2005
2006		// election with max 1 backer per winnner.
2007		ExtBuilder::default().max_backers_per_winner(1).build_and_execute(|| {
2008			assert_eq!(MaxBackersPerWinner::get(), 1);
2009
2010			let (solution, expected_score_bounded, _, trimming_status) =
2011				Miner::<Runtime>::mine_solution_with_snapshot::<<Runtime as Config>::Solver>(
2012					voters,
2013					targets,
2014					desired_targets,
2015				)
2016				.unwrap();
2017
2018			let ready_solution = Miner::<Runtime>::feasibility_check(
2019				RawSolution { solution, score: expected_score_bounded, round },
2020				Default::default(),
2021				desired_targets,
2022				snapshot,
2023				round,
2024				Default::default(),
2025			)
2026			.unwrap();
2027
2028			for (_, supports) in ready_solution.supports.iter() {
2029				assert!((supports.voters.len() as u32) <= MaxBackersPerWinner::get());
2030			}
2031
2032			assert_eq!(
2033				ready_solution.supports.into_iter().collect::<Vec<_>>(),
2034				vec![
2035					(10, BoundedSupport { total: 11, voters: bounded_vec![(1, 11)] }),
2036					(20, BoundedSupport { total: 12, voters: bounded_vec![(2, 12)] }),
2037					(30, BoundedSupport { total: 13, voters: bounded_vec![(3, 13)] })
2038				]
2039			);
2040
2041			// four trimmed edges.
2042			assert_eq!(trimming_status.trimmed_edges(), 4);
2043		});
2044	}
2045
2046	#[test]
2047	fn max_backers_edges_trims_lowest_stake() {
2048		use crate::mock::MaxBackersPerWinner;
2049
2050		ExtBuilder::default().build_and_execute(|| {
2051			let targets = vec![10, 20, 30, 40];
2052
2053			let voters = vec![
2054				(1, 100, bounded_vec![10, 20]),
2055				(2, 200, bounded_vec![10, 20, 30]),
2056				(3, 300, bounded_vec![10, 30]),
2057				(4, 400, bounded_vec![10, 30]),
2058				(5, 500, bounded_vec![10, 20, 30]),
2059				(6, 600, bounded_vec![10, 20, 30, 40]),
2060			];
2061			let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() };
2062			let (round, desired_targets) = (1, 4);
2063
2064			let max_backers_bound = u32::MAX;
2065			let trim_backers_bound = 2;
2066
2067			// election with unbounded max backers per winnner.
2068			MaxBackersPerWinner::set(max_backers_bound);
2069			let (solution, score, _, trimming_status) =
2070				Miner::<Runtime>::mine_solution_with_snapshot::<<Runtime as Config>::Solver>(
2071					voters.clone(),
2072					targets.clone(),
2073					desired_targets,
2074				)
2075				.unwrap();
2076
2077			assert_eq!(trimming_status.trimmed_edges(), 0);
2078
2079			let ready_solution = Miner::<Runtime>::feasibility_check(
2080				RawSolution { solution, score, round },
2081				Default::default(),
2082				desired_targets,
2083				snapshot.clone(),
2084				round,
2085				Default::default(),
2086			)
2087			.unwrap();
2088
2089			let full_supports = ready_solution.supports.into_iter().collect::<Vec<_>>();
2090
2091			// gather the expected trimmed supports (lowest stake from supports with more backers
2092			// than expected when MaxBackersPerWinner is 2) from the full, unbounded supports.
2093			let expected_trimmed_supports = full_supports
2094				.into_iter()
2095				.filter(|(_, s)| s.voters.len() as u32 > trim_backers_bound)
2096				.map(|(t, s)| (t, s.voters.into_iter().min_by(|a, b| a.1.cmp(&b.1)).unwrap()))
2097				.collect::<Vec<_>>();
2098
2099			// election with bounded 2 max backers per winnner.
2100			MaxBackersPerWinner::set(trim_backers_bound);
2101			let (solution, score, _, trimming_status) =
2102				Miner::<Runtime>::mine_solution_with_snapshot::<<Runtime as Config>::Solver>(
2103					voters.clone(),
2104					targets.clone(),
2105					desired_targets,
2106				)
2107				.unwrap();
2108
2109			assert_eq!(trimming_status.trimmed_edges(), 2);
2110
2111			let ready_solution = Miner::<Runtime>::feasibility_check(
2112				RawSolution { solution, score, round },
2113				Default::default(),
2114				desired_targets,
2115				snapshot.clone(),
2116				round,
2117				Default::default(),
2118			)
2119			.unwrap();
2120
2121			let trimmed_supports = ready_solution.supports.into_iter().collect::<Vec<_>>();
2122
2123			// gather all trimmed_supports edges from the trimmed solution.
2124			let mut trimmed_supports_edges_full = vec![];
2125			for (t, s) in trimmed_supports {
2126				for v in s.voters {
2127					trimmed_supports_edges_full.push((t, v));
2128				}
2129			}
2130
2131			// expected trimmed supports set should be disjoint to the trimmed_supports full set of
2132			// edges.
2133			for edge in trimmed_supports_edges_full {
2134				assert!(!expected_trimmed_supports.contains(&edge));
2135			}
2136		})
2137	}
2138
2139	#[test]
2140	fn trim_assignments_length_does_not_modify_when_short_enough() {
2141		ExtBuilder::default().build_and_execute(|| {
2142			roll_to_unsigned();
2143
2144			// given
2145			let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
2146			let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
2147			let encoded_len = solution.encoded_size() as u32;
2148			let solution_clone = solution.clone();
2149
2150			// when
2151			let trimmed_len = Miner::<Runtime>::trim_assignments_length(
2152				encoded_len,
2153				&mut assignments,
2154				encoded_size_of,
2155			)
2156			.unwrap();
2157
2158			// then
2159			let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
2160			assert_eq!(solution, solution_clone);
2161			assert_eq!(trimmed_len, 0);
2162		});
2163	}
2164
2165	#[test]
2166	fn trim_assignments_length_modifies_when_too_long() {
2167		ExtBuilder::default().build().execute_with(|| {
2168			roll_to_unsigned();
2169
2170			// given
2171			let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
2172			let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
2173			let encoded_len = solution.encoded_size();
2174			let solution_clone = solution.clone();
2175
2176			// when
2177			let trimmed_len = Miner::<Runtime>::trim_assignments_length(
2178				encoded_len as u32 - 1,
2179				&mut assignments,
2180				encoded_size_of,
2181			)
2182			.unwrap();
2183
2184			// then
2185			let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
2186			assert_ne!(solution, solution_clone);
2187			assert!(solution.encoded_size() < encoded_len);
2188			assert_eq!(trimmed_len, 1);
2189		});
2190	}
2191
2192	#[test]
2193	fn trim_assignments_length_trims_lowest_stake() {
2194		ExtBuilder::default().build().execute_with(|| {
2195			roll_to_unsigned();
2196
2197			// given
2198			let TrimHelpers { voters, mut assignments, encoded_size_of, voter_index } =
2199				trim_helpers();
2200			let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
2201			let encoded_len = solution.encoded_size() as u32;
2202			let count = assignments.len();
2203			let min_stake_voter = voters
2204				.iter()
2205				.map(|(id, weight, _)| (weight, id))
2206				.min()
2207				.and_then(|(_, id)| voter_index(id))
2208				.unwrap();
2209
2210			// when
2211			Miner::<Runtime>::trim_assignments_length(
2212				encoded_len - 1,
2213				&mut assignments,
2214				encoded_size_of,
2215			)
2216			.unwrap();
2217
2218			// then
2219			assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment");
2220			assert!(
2221				assignments.iter().all(|IndexAssignment { who, .. }| *who != min_stake_voter),
2222				"min_stake_voter must no longer be in the set of voters",
2223			);
2224		});
2225	}
2226
2227	#[test]
2228	fn trim_assignments_length_wont_panic() {
2229		// we shan't panic if assignments are initially empty.
2230		ExtBuilder::default().build_and_execute(|| {
2231			let encoded_size_of = Box::new(|assignments: &[IndexAssignmentOf<Runtime>]| {
2232				SolutionOf::<Runtime>::try_from(assignments).map(|solution| solution.encoded_size())
2233			});
2234
2235			let mut assignments = vec![];
2236
2237			// since we have 16 fields, we need to store the length fields of 16 vecs, thus 16 bytes
2238			// minimum.
2239			let min_solution_size = encoded_size_of(&assignments).unwrap();
2240			assert_eq!(min_solution_size, SolutionOf::<Runtime>::LIMIT);
2241
2242			// all of this should not panic.
2243			Miner::<Runtime>::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
2244				.unwrap();
2245			Miner::<Runtime>::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
2246				.unwrap();
2247			Miner::<Runtime>::trim_assignments_length(
2248				min_solution_size as u32,
2249				&mut assignments,
2250				encoded_size_of,
2251			)
2252			.unwrap();
2253		});
2254
2255		// or when we trim it to zero.
2256		ExtBuilder::default().build_and_execute(|| {
2257			// we need snapshot for `trim_helpers` to work.
2258			roll_to_unsigned();
2259			let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
2260			assert!(assignments.len() > 0);
2261
2262			// trim to min solution size.
2263			let min_solution_size = SolutionOf::<Runtime>::LIMIT as u32;
2264			Miner::<Runtime>::trim_assignments_length(
2265				min_solution_size,
2266				&mut assignments,
2267				encoded_size_of,
2268			)
2269			.unwrap();
2270			assert_eq!(assignments.len(), 0);
2271		});
2272	}
2273
2274	// all the other solution-generation functions end up delegating to `mine_solution`, so if we
2275	// demonstrate that `mine_solution` solutions are all trimmed to an acceptable length, then
2276	// we know that higher-level functions will all also have short-enough solutions.
2277	#[test]
2278	fn mine_solution_solutions_always_within_acceptable_length() {
2279		ExtBuilder::default().build_and_execute(|| {
2280			roll_to_unsigned();
2281
2282			// how long would the default solution be?
2283			let solution = MultiPhase::mine_solution().unwrap();
2284			let max_length = <Runtime as MinerConfig>::MaxLength::get();
2285			let solution_size = solution.0.solution.encoded_size();
2286			assert!(solution_size <= max_length as usize);
2287
2288			// now set the max size to less than the actual size and regenerate
2289			<Runtime as MinerConfig>::MaxLength::set(solution_size as u32 - 1);
2290			let solution = MultiPhase::mine_solution().unwrap();
2291			let max_length = <Runtime as MinerConfig>::MaxLength::get();
2292			let solution_size = solution.0.solution.encoded_size();
2293			assert!(solution_size <= max_length as usize);
2294		});
2295	}
2296}