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