referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_phase/
benchmarking.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//! Two phase election pallet benchmarking.
19
20use core::cmp::Reverse;
21use frame_benchmarking::{v2::*, BenchmarkError};
22use frame_election_provider_support::{bounds::DataProviderBounds, IndexAssignment};
23use frame_support::{
24	assert_ok,
25	traits::{Hooks, TryCollect},
26	BoundedVec,
27};
28use frame_system::RawOrigin;
29use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng};
30use sp_arithmetic::{per_things::Percent, traits::One};
31use sp_runtime::InnerOf;
32
33use crate::{unsigned::IndexAssignmentOf, *};
34
35const SEED: u32 = 999;
36
37/// Creates a **valid** solution with exactly the given size.
38///
39/// The snapshot is also created internally.
40fn solution_with_size<T: Config>(
41	size: SolutionOrSnapshotSize,
42	active_voters_count: u32,
43	desired_targets: u32,
44) -> Result<RawSolution<SolutionOf<T::MinerConfig>>, &'static str> {
45	ensure!(size.targets >= desired_targets, "must have enough targets");
46	ensure!(
47		size.targets >= (<SolutionOf<T::MinerConfig>>::LIMIT * 2) as u32,
48		"must have enough targets for unique votes."
49	);
50	ensure!(size.voters >= active_voters_count, "must have enough voters");
51	ensure!(
52		(<SolutionOf<T::MinerConfig>>::LIMIT as u32) < desired_targets,
53		"must have enough winners to give them votes."
54	);
55
56	let ed: VoteWeight = T::Currency::minimum_balance().saturated_into::<u64>();
57	let stake: VoteWeight = ed.max(One::one()).saturating_mul(100);
58
59	// first generates random targets.
60	let targets: Vec<T::AccountId> = (0..size.targets)
61		.map(|i| frame_benchmarking::account("Targets", i, SEED))
62		.collect();
63
64	let mut rng = SmallRng::seed_from_u64(SEED.into());
65
66	// decide who are the winners.
67	let winners = targets
68		.as_slice()
69		.choose_multiple(&mut rng, desired_targets as usize)
70		.cloned()
71		.collect::<Vec<_>>();
72
73	// first generate active voters who must vote for a subset of winners.
74	let active_voters = (0..active_voters_count)
75		.map(|i| {
76			// chose a random subset of winners.
77			let winner_votes: BoundedVec<_, _> = winners
78				.as_slice()
79				.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
80				.cloned()
81				.try_collect()
82				.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
83			let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
84			(voter, stake, winner_votes)
85		})
86		.collect::<Vec<_>>();
87
88	// rest of the voters. They can only vote for non-winners.
89	let non_winners = targets
90		.iter()
91		.filter(|t| !winners.contains(t))
92		.cloned()
93		.collect::<Vec<T::AccountId>>();
94	let rest_voters = (active_voters_count..size.voters)
95		.map(|i| {
96			let votes: BoundedVec<_, _> = (&non_winners)
97				.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
98				.cloned()
99				.try_collect()
100				.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
101			let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
102			(voter, stake, votes)
103		})
104		.collect::<Vec<_>>();
105
106	let mut all_voters = active_voters.clone();
107	all_voters.extend(rest_voters);
108	all_voters.shuffle(&mut rng);
109
110	assert_eq!(active_voters.len() as u32, active_voters_count);
111	assert_eq!(all_voters.len() as u32, size.voters);
112	assert_eq!(winners.len() as u32, desired_targets);
113
114	SnapshotMetadata::<T>::put(SolutionOrSnapshotSize {
115		voters: all_voters.len() as u32,
116		targets: targets.len() as u32,
117	});
118	DesiredTargets::<T>::put(desired_targets);
119	Snapshot::<T>::put(RoundSnapshot { voters: all_voters.clone(), targets: targets.clone() });
120
121	// write the snapshot to staking or whoever is the data provider, in case it is needed further
122	// down the road.
123	T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake));
124
125	let cache = helpers::generate_voter_cache::<T::MinerConfig>(&all_voters);
126	let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&all_voters, &cache);
127	let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
128	let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
129	let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&all_voters);
130	let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
131
132	let assignments = active_voters
133		.iter()
134		.map(|(voter, _stake, votes)| {
135			let percent_per_edge: InnerOf<SolutionAccuracyOf<T>> =
136				(100 / votes.len()).try_into().unwrap_or_else(|_| panic!("failed to convert"));
137			unsigned::Assignment::<T> {
138				who: voter.clone(),
139				distribution: votes
140					.iter()
141					.map(|t| (t.clone(), SolutionAccuracyOf::<T>::from_percent(percent_per_edge)))
142					.collect::<Vec<_>>(),
143			}
144		})
145		.collect::<Vec<_>>();
146
147	let solution =
148		<SolutionOf<T::MinerConfig>>::from_assignment(&assignments, &voter_index, &target_index)
149			.unwrap();
150	let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
151	let round = Round::<T>::get();
152
153	assert!(
154		score.minimal_stake > 0,
155		"score is zero, this probably means that the stakes are not set."
156	);
157	Ok(RawSolution { solution, score, round })
158}
159
160fn set_up_data_provider<T: Config>(v: u32, t: u32) {
161	T::DataProvider::clear();
162	log!(
163		info,
164		"setting up with voters = {} [degree = {}], targets = {}",
165		v,
166		<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
167		t
168	);
169
170	// fill targets.
171	let mut targets = (0..t)
172		.map(|i| {
173			let target = frame_benchmarking::account::<T::AccountId>("Target", i, SEED);
174
175			T::DataProvider::add_target(target.clone());
176			target
177		})
178		.collect::<Vec<_>>();
179
180	// we should always have enough voters to fill.
181	assert!(
182		targets.len() > <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize
183	);
184	targets.truncate(<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize);
185
186	// fill voters.
187	(0..v).for_each(|i| {
188		let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
189		let weight = T::Currency::minimum_balance().saturated_into::<u64>() * 1000;
190		T::DataProvider::add_voter(voter, weight, targets.clone().try_into().unwrap());
191	});
192}
193
194#[benchmarks]
195mod benchmarks {
196	use super::*;
197
198	#[benchmark]
199	fn on_initialize_nothing() {
200		assert!(CurrentPhase::<T>::get().is_off());
201
202		#[block]
203		{
204			Pallet::<T>::on_initialize(1_u32.into());
205		}
206
207		assert!(CurrentPhase::<T>::get().is_off());
208	}
209
210	#[benchmark]
211	fn on_initialize_open_signed() {
212		assert!(Snapshot::<T>::get().is_none());
213		assert!(CurrentPhase::<T>::get().is_off());
214
215		#[block]
216		{
217			Pallet::<T>::phase_transition(Phase::Signed);
218		}
219
220		assert!(Snapshot::<T>::get().is_none());
221		assert!(CurrentPhase::<T>::get().is_signed());
222	}
223
224	#[benchmark]
225	fn on_initialize_open_unsigned() {
226		assert!(Snapshot::<T>::get().is_none());
227		assert!(CurrentPhase::<T>::get().is_off());
228
229		#[block]
230		{
231			let now = frame_system::Pallet::<T>::block_number();
232			Pallet::<T>::phase_transition(Phase::Unsigned((true, now)));
233		}
234
235		assert!(Snapshot::<T>::get().is_none());
236		assert!(CurrentPhase::<T>::get().is_unsigned());
237	}
238
239	#[benchmark]
240	fn finalize_signed_phase_accept_solution() {
241		let receiver = account("receiver", 0, SEED);
242		let initial_balance = T::Currency::minimum_balance() + 10_u32.into();
243		T::Currency::make_free_balance_be(&receiver, initial_balance);
244		let ready = Default::default();
245		let deposit: BalanceOf<T> = 10_u32.into();
246
247		let reward: BalanceOf<T> = T::SignedRewardBase::get();
248		let call_fee: BalanceOf<T> = 30_u32.into();
249
250		assert_ok!(T::Currency::reserve(&receiver, deposit));
251		assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
252
253		#[block]
254		{
255			Pallet::<T>::finalize_signed_phase_accept_solution(ready, &receiver, deposit, call_fee);
256		}
257
258		assert_eq!(T::Currency::free_balance(&receiver), initial_balance + reward + call_fee);
259		assert_eq!(T::Currency::reserved_balance(&receiver), 0_u32.into());
260	}
261
262	#[benchmark]
263	fn finalize_signed_phase_reject_solution() {
264		let receiver = account("receiver", 0, SEED);
265		let initial_balance = T::Currency::minimum_balance() + 10_u32.into();
266		let deposit: BalanceOf<T> = 10_u32.into();
267		T::Currency::make_free_balance_be(&receiver, initial_balance);
268		assert_ok!(T::Currency::reserve(&receiver, deposit));
269
270		assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
271		assert_eq!(T::Currency::reserved_balance(&receiver), 10_u32.into());
272
273		#[block]
274		{
275			Pallet::<T>::finalize_signed_phase_reject_solution(&receiver, deposit)
276		}
277
278		assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
279		assert_eq!(T::Currency::reserved_balance(&receiver), 0_u32.into());
280	}
281
282	#[benchmark]
283	fn create_snapshot_internal(
284		// Number of votes in snapshot.
285		v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
286		// Number of targets in snapshot.
287		t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
288	) -> Result<(), BenchmarkError> {
289		// We don't directly need the data-provider to be populated, but it is just easy to use it.
290		set_up_data_provider::<T>(v, t);
291		// default bounds are unbounded.
292		let targets =
293			T::DataProvider::electable_targets(DataProviderBounds::default(), Zero::zero())?;
294		let voters = T::DataProvider::electing_voters(DataProviderBounds::default(), Zero::zero())?;
295
296		let desired_targets = T::DataProvider::desired_targets()?;
297		assert!(Snapshot::<T>::get().is_none());
298
299		#[block]
300		{
301			Pallet::<T>::create_snapshot_internal(targets, voters, desired_targets)
302		}
303
304		assert!(Snapshot::<T>::get().is_some());
305		assert_eq!(SnapshotMetadata::<T>::get().ok_or("metadata missing")?.voters, v);
306		assert_eq!(SnapshotMetadata::<T>::get().ok_or("metadata missing")?.targets, t);
307
308		Ok(())
309	}
310
311	// A call to `<Pallet as ElectionProvider>::elect` where we only return the queued solution.
312	#[benchmark]
313	fn elect_queued(
314		// Number of assignments, i.e. `solution.len()`.
315		// This means the active nominators, thus must be a subset of `v`.
316		a: Linear<
317			{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
318			{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
319		>,
320		// Number of desired targets. Must be a subset of `t`.
321		d: Linear<
322			{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
323			{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
324		>,
325	) -> Result<(), BenchmarkError> {
326		// Number of votes in snapshot. Not dominant.
327		let v = T::BenchmarkingConfig::VOTERS[1];
328		// Number of targets in snapshot. Not dominant.
329		let t = T::BenchmarkingConfig::TARGETS[1];
330
331		let witness = SolutionOrSnapshotSize { voters: v, targets: t };
332		let raw_solution = solution_with_size::<T>(witness, a, d)?;
333		let ready_solution = Pallet::<T>::feasibility_check(raw_solution, ElectionCompute::Signed)
334			.map_err(<&str>::from)?;
335		CurrentPhase::<T>::put(Phase::Signed);
336		// Assume a queued solution is stored, regardless of where it comes from.
337		QueuedSolution::<T>::put(ready_solution);
338
339		// These are set by the `solution_with_size` function.
340		assert!(DesiredTargets::<T>::get().is_some());
341		assert!(Snapshot::<T>::get().is_some());
342		assert!(SnapshotMetadata::<T>::get().is_some());
343
344		let result;
345
346		#[block]
347		{
348			result = <Pallet<T> as ElectionProvider>::elect(Zero::zero());
349		}
350
351		assert!(result.is_ok());
352		assert!(QueuedSolution::<T>::get().is_none());
353		assert!(DesiredTargets::<T>::get().is_none());
354		assert!(Snapshot::<T>::get().is_none());
355		assert!(SnapshotMetadata::<T>::get().is_none());
356		assert_eq!(
357			CurrentPhase::<T>::get(),
358			<Phase<frame_system::pallet_prelude::BlockNumberFor::<T>>>::Off
359		);
360
361		Ok(())
362	}
363
364	#[benchmark]
365	fn submit() -> Result<(), BenchmarkError> {
366		// The queue is full and the solution is only better than the worse.
367		Pallet::<T>::create_snapshot().map_err(<&str>::from)?;
368		Pallet::<T>::phase_transition(Phase::Signed);
369		Round::<T>::put(1);
370
371		let mut signed_submissions = SignedSubmissions::<T>::get();
372
373		// Insert `max` submissions
374		for i in 0..(T::SignedMaxSubmissions::get() - 1) {
375			let raw_solution = RawSolution {
376				score: ElectionScore {
377					minimal_stake: 10_000_000u128 + (i as u128),
378					..Default::default()
379				},
380				..Default::default()
381			};
382			let signed_submission = SignedSubmission {
383				raw_solution,
384				who: account("submitters", i, SEED),
385				deposit: Default::default(),
386				call_fee: Default::default(),
387			};
388			signed_submissions.insert(signed_submission);
389		}
390		signed_submissions.put();
391
392		// This score will eject the weakest one.
393		let solution = RawSolution {
394			score: ElectionScore { minimal_stake: 10_000_000u128 + 1, ..Default::default() },
395			..Default::default()
396		};
397
398		let caller = frame_benchmarking::whitelisted_caller();
399		let deposit =
400			Pallet::<T>::deposit_for(&solution, SnapshotMetadata::<T>::get().unwrap_or_default());
401		T::Currency::make_free_balance_be(
402			&caller,
403			T::Currency::minimum_balance() * 1000u32.into() + deposit,
404		);
405
406		#[extrinsic_call]
407		_(RawOrigin::Signed(caller), Box::new(solution));
408
409		assert!(Pallet::<T>::signed_submissions().len() as u32 == T::SignedMaxSubmissions::get());
410
411		Ok(())
412	}
413
414	#[benchmark]
415	fn submit_unsigned(
416		// Number of votes in snapshot.
417		v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
418		// Number of targets in snapshot.
419		t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
420		// Number of assignments, i.e. `solution.len()`.
421		// This means the active nominators, thus must be a subset of `v` component.
422		a: Linear<
423			{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
424			{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
425		>,
426		// Number of desired targets. Must be a subset of `t` component.
427		d: Linear<
428			{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
429			{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
430		>,
431	) -> Result<(), BenchmarkError> {
432		let witness = SolutionOrSnapshotSize { voters: v, targets: t };
433		let raw_solution = solution_with_size::<T>(witness, a, d)?;
434
435		assert!(QueuedSolution::<T>::get().is_none());
436		CurrentPhase::<T>::put(Phase::Unsigned((true, 1_u32.into())));
437
438		#[extrinsic_call]
439		_(RawOrigin::None, Box::new(raw_solution), witness);
440
441		assert!(QueuedSolution::<T>::get().is_some());
442
443		Ok(())
444	}
445
446	// This is checking a valid solution. The worse case is indeed a valid solution.
447	#[benchmark]
448	fn feasibility_check(
449		// Number of votes in snapshot.
450		v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
451		// Number of targets in snapshot.
452		t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
453		// Number of assignments, i.e. `solution.len()`.
454		// This means the active nominators, thus must be a subset of `v` component.
455		a: Linear<
456			{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
457			{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
458		>,
459		// Number of desired targets. Must be a subset of `t` component.
460		d: Linear<
461			{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
462			{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
463		>,
464	) -> Result<(), BenchmarkError> {
465		let size = SolutionOrSnapshotSize { voters: v, targets: t };
466		let raw_solution = solution_with_size::<T>(size, a, d)?;
467
468		assert_eq!(raw_solution.solution.voter_count() as u32, a);
469		assert_eq!(raw_solution.solution.unique_targets().len() as u32, d);
470
471		let result;
472
473		#[block]
474		{
475			result = Pallet::<T>::feasibility_check(raw_solution, ElectionCompute::Unsigned);
476		}
477
478		assert!(result.is_ok());
479
480		Ok(())
481	}
482
483	// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in
484	// isolation is vital to ensure memory-safety. For the same reason, we don't care about the
485	// components iterating, we merely check that this operation will work with the "maximum"
486	// numbers.
487	//
488	// ONLY run this benchmark in isolation, and pass the `--extra` flag to enable it.
489	//
490	// NOTE: If this benchmark does not run out of memory with a given heap pages, it means that the
491	// OCW process can SURELY succeed with the given configuration, but the opposite is not true.
492	// This benchmark is doing more work than a raw call to `OffchainWorker_offchain_worker` runtime
493	// api call, since it is also setting up some mock data, which will itself exhaust the heap to
494	// some extent.
495	#[benchmark(extra)]
496	fn mine_solution_offchain_memory() {
497		// Number of votes in snapshot. Fixed to maximum.
498		let v = T::BenchmarkingConfig::MINER_MAXIMUM_VOTERS;
499		// Number of targets in snapshot. Fixed to maximum.
500		let t = T::BenchmarkingConfig::MAXIMUM_TARGETS;
501
502		set_up_data_provider::<T>(v, t);
503		let now = frame_system::Pallet::<T>::block_number();
504		CurrentPhase::<T>::put(Phase::Unsigned((true, now)));
505		Pallet::<T>::create_snapshot().unwrap();
506
507		#[block]
508		{
509			// we can't really verify this as it won't write anything to state, check logs.
510			Pallet::<T>::offchain_worker(now)
511		}
512	}
513
514	// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in
515	// isolation is vital to ensure memory-safety. For the same reason, we don't care about the
516	// components iterating, we merely check that this operation will work with the "maximum"
517	// numbers.
518	//
519	// ONLY run this benchmark in isolation, and pass the `--extra` flag to enable it.
520	#[benchmark(extra)]
521	fn create_snapshot_memory() -> Result<(), BenchmarkError> {
522		// Number of votes in snapshot. Fixed to maximum.
523		let v = T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS;
524		// Number of targets in snapshot. Fixed to maximum.
525		let t = T::BenchmarkingConfig::MAXIMUM_TARGETS;
526
527		set_up_data_provider::<T>(v, t);
528		assert!(Snapshot::<T>::get().is_none());
529
530		#[block]
531		{
532			Pallet::<T>::create_snapshot().map_err(|_| "could not create snapshot")?;
533		}
534
535		assert!(Snapshot::<T>::get().is_some());
536		assert_eq!(SnapshotMetadata::<T>::get().ok_or("snapshot missing")?.voters, v);
537		assert_eq!(SnapshotMetadata::<T>::get().ok_or("snapshot missing")?.targets, t);
538
539		Ok(())
540	}
541
542	#[benchmark(extra)]
543	fn trim_assignments_length(
544		// Number of votes in snapshot.
545		v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
546		// Number of targets in snapshot.
547		t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
548		// Number of assignments, i.e. `solution.len()`.
549		// This means the active nominators, thus must be a subset of `v` component.
550		a: Linear<
551			{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
552			{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
553		>,
554		// Number of desired targets. Must be a subset of `t` component.
555		d: Linear<
556			{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
557			{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
558		>,
559		// Subtract this percentage from the actual encoded size.
560		f: Linear<0, 95>,
561	) -> Result<(), BenchmarkError> {
562		// Compute a random solution, then work backwards to get the lists of voters, targets, and
563		// assignments
564		let witness = SolutionOrSnapshotSize { voters: v, targets: t };
565		let RawSolution { solution, .. } = solution_with_size::<T>(witness, a, d)?;
566		let RoundSnapshot { voters, targets } = Snapshot::<T>::get().ok_or("snapshot missing")?;
567		let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&voters);
568		let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
569		let mut assignments = solution
570			.into_assignment(voter_at, target_at)
571			.expect("solution generated by `solution_with_size` must be valid.");
572
573		// make a voter cache and some helper functions for access
574		let cache = helpers::generate_voter_cache::<T::MinerConfig>(&voters);
575		let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
576		let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
577
578		// sort assignments by decreasing voter stake
579		assignments.sort_by_key(|unsigned::Assignment::<T> { who, .. }| {
580			let stake = cache
581				.get(who)
582				.map(|idx| {
583					let (_, stake, _) = voters[*idx];
584					stake
585				})
586				.unwrap_or_default();
587			Reverse(stake)
588		});
589
590		let mut index_assignments = assignments
591			.into_iter()
592			.map(|assignment| IndexAssignment::new(&assignment, &voter_index, &target_index))
593			.collect::<Result<Vec<_>, _>>()
594			.unwrap();
595
596		let encoded_size_of = |assignments: &[IndexAssignmentOf<T::MinerConfig>]| {
597			SolutionOf::<T::MinerConfig>::try_from(assignments)
598				.map(|solution| solution.encoded_size())
599		};
600
601		let desired_size = Percent::from_percent(100 - f.saturated_into::<u8>())
602			.mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap());
603		log!(trace, "desired_size = {}", desired_size);
604
605		#[block]
606		{
607			Miner::<T::MinerConfig>::trim_assignments_length(
608				desired_size.saturated_into(),
609				&mut index_assignments,
610				&encoded_size_of,
611			)
612			.unwrap();
613		}
614
615		let solution =
616			SolutionOf::<T::MinerConfig>::try_from(index_assignments.as_slice()).unwrap();
617		let encoding = solution.encode();
618		log!(
619			trace,
620			"encoded size prediction = {}",
621			encoded_size_of(index_assignments.as_slice()).unwrap(),
622		);
623		log!(trace, "actual encoded size = {}", encoding.len());
624		assert!(encoding.len() <= desired_size);
625
626		Ok(())
627	}
628
629	impl_benchmark_test_suite! {
630		Pallet,
631		mock::ExtBuilder::default().build_offchainify(10).0,
632		mock::Runtime,
633	}
634}