referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_block/verifier/
impls.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 implementation of the verifier pallet, and an implementation of [`crate::Verifier`] and
19//! [`crate::AsynchronousVerifier`] for [`Pallet`].
20
21use super::*;
22use crate::{
23	helpers,
24	types::VoterOf,
25	unsigned::miner::{MinerConfig, PageSupportsOfMiner},
26	verifier::Verifier,
27	SolutionOf,
28};
29use codec::{Decode, Encode, MaxEncodedLen};
30use frame_election_provider_support::{
31	ExtendedBalance, NposSolution, PageIndex, TryFromOtherBounds,
32};
33use frame_support::{
34	ensure,
35	pallet_prelude::{ValueQuery, *},
36	traits::{defensive_prelude::*, Get},
37};
38use frame_system::pallet_prelude::*;
39use pallet::*;
40use sp_npos_elections::{evaluate_support, ElectionScore};
41use sp_runtime::Perbill;
42use sp_std::{collections::btree_map::BTreeMap, prelude::*};
43
44pub(crate) type SupportsOfVerifier<V> = frame_election_provider_support::BoundedSupports<
45	<V as Verifier>::AccountId,
46	<V as Verifier>::MaxWinnersPerPage,
47	<V as Verifier>::MaxBackersPerWinner,
48>;
49
50pub(crate) type VerifierWeightsOf<T> = <T as super::Config>::WeightInfo;
51
52/// The status of this pallet.
53#[derive(
54	Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, Debug, PartialEq, Eq,
55)]
56pub enum Status {
57	/// A verification is ongoing, and the next page that will be verified is indicated with the
58	/// inner value.
59	Ongoing(PageIndex),
60	/// Nothing is happening.
61	Nothing,
62}
63
64impl Default for Status {
65	fn default() -> Self {
66		Self::Nothing
67	}
68}
69
70/// Enum to point to the valid variant of the [`QueuedSolution`].
71#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen)]
72enum ValidSolution {
73	X,
74	Y,
75}
76
77impl Default for ValidSolution {
78	fn default() -> Self {
79		ValidSolution::Y
80	}
81}
82
83impl ValidSolution {
84	fn other(&self) -> Self {
85		match *self {
86			ValidSolution::X => ValidSolution::Y,
87			ValidSolution::Y => ValidSolution::X,
88		}
89	}
90}
91
92/// A simple newtype that represents the partial backing of a winner. It only stores the total
93/// backing, and the sum of backings, as opposed to a [`sp_npos_elections::Support`] that also
94/// stores all of the backers' individual contribution.
95///
96/// This is mainly here to allow us to implement `Backings` for it.
97#[derive(Default, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)]
98pub struct PartialBackings {
99	/// The total backing of this particular winner.
100	pub total: ExtendedBalance,
101	/// The number of backers.
102	pub backers: u32,
103}
104
105impl sp_npos_elections::Backings for PartialBackings {
106	fn total(&self) -> ExtendedBalance {
107		self.total
108	}
109}
110
111#[frame_support::pallet]
112pub(crate) mod pallet {
113	use super::*;
114
115	#[pallet::config]
116	#[pallet::disable_frame_system_supertrait_check]
117	pub trait Config: crate::Config {
118		/// The minimum amount of improvement to the solution score that defines a solution as
119		/// "better".
120		#[pallet::constant]
121		type SolutionImprovementThreshold: Get<Perbill>;
122
123		/// Maximum number of backers, per winner, among all pages of an election.
124		///
125		/// This can only be checked at the very final step of verification.
126		///
127		/// NOTE: at the moment, we don't check this, and it is in place for future compatibility.
128		#[pallet::constant]
129		type MaxBackersPerWinnerFinal: Get<u32>;
130
131		/// Maximum number of backers, per winner, per page.
132		#[pallet::constant]
133		type MaxBackersPerWinner: Get<u32>;
134
135		/// Maximum number of supports (aka. winners/validators/targets) that can be represented in
136		/// a page of results.
137		#[pallet::constant]
138		type MaxWinnersPerPage: Get<u32>;
139
140		/// Something that can provide the solution data to the verifier.
141		///
142		/// In reality, this will be fulfilled by the signed phase.
143		type SolutionDataProvider: crate::verifier::SolutionDataProvider<
144			Solution = SolutionOf<Self::MinerConfig>,
145		>;
146
147		/// The weight information of this pallet.
148		type WeightInfo: super::WeightInfo;
149	}
150
151	#[pallet::event]
152	#[pallet::generate_deposit(pub(super) fn deposit_event)]
153	pub enum Event<T> {
154		/// A verification failed at the given page.
155		///
156		/// NOTE: if the index is 0, then this could mean either the feasibility of the last page
157		/// was wrong, or the final checks of `finalize_verification` failed.
158		VerificationFailed(PageIndex, FeasibilityError),
159		/// The given page of a solution has been verified, with the given number of winners being
160		/// found in it.
161		Verified(PageIndex, u32),
162		/// A solution with the given score has replaced our current best solution.
163		Queued(ElectionScore, Option<ElectionScore>),
164	}
165
166	/// A wrapper interface for the storage items related to the queued solution.
167	///
168	/// It wraps the following:
169	///
170	/// - `QueuedSolutionX`
171	/// - `QueuedSolutionY`
172	/// - `QueuedValidVariant`
173	/// - `QueuedSolutionScore`
174	/// - `QueuedSolutionBackings`
175	///
176	/// As the name suggests, `QueuedValidVariant` points to the correct variant between
177	/// `QueuedSolutionX` and `QueuedSolutionY`. In the context of this pallet, by VALID and
178	/// INVALID variant we mean either of these two storage items, based on the value of
179	/// `QueuedValidVariant`.
180	///
181	/// ### Round Index
182	///
183	/// Much like `Snapshot` in the parent crate, these storage items are mapping whereby their
184	/// _first_ key is the round index. None of the APIs in [`QueuedSolution`] expose this, as
185	/// on-chain, we should ONLY ever be reading the current round's associated data.
186	///
187	/// Having this extra key paves the way for lazy deletion in the future.
188	///
189	/// ### Invariants
190	///
191	/// The following conditions must be met at all times for this group of storage items to be
192	/// sane.
193	///
194	/// - `QueuedSolutionScore` must always be correct. In other words, it should correctly be the
195	///   score of `QueuedValidVariant`.
196	/// - `QueuedSolutionScore` must always be [`Config::SolutionImprovementThreshold`] better than
197	///   `MinimumScore`.
198	/// - The number of existing keys in `QueuedSolutionBackings` must always match that of the
199	///   INVALID variant.
200	///
201	/// Moreover, the following conditions must be met when this pallet is in [`Status::Nothing`],
202	/// meaning that no ongoing asynchronous verification is ongoing.
203	///
204	/// - No keys should exist in the INVALID variant.
205	/// 	- This implies that no data should exist in `QueuedSolutionBackings`.
206	///
207	/// > Note that some keys *might* exist in the queued variant, but since partial solutions
208	/// > (having less than `T::Pages` pages) are in principle correct, we cannot assert anything on
209	/// > the number of keys in the VALID variant. In fact, an empty solution with score of [0, 0,
210	/// > 0] can also be correct.
211	///
212	/// No additional conditions must be met when the pallet is in [`Status::Ongoing`]. The number
213	/// of pages in
214	pub struct QueuedSolution<T: Config>(sp_std::marker::PhantomData<T>);
215	impl<T: Config> QueuedSolution<T> {
216		/// Private helper for mutating the storage group.
217		fn mutate_checked<R>(mutate: impl FnOnce() -> R) -> R {
218			let r = mutate();
219			#[cfg(debug_assertions)]
220			assert!(Self::sanity_check().is_ok());
221			r
222		}
223
224		fn round() -> u32 {
225			crate::Pallet::<T>::round()
226		}
227
228		/// Finalize a correct solution.
229		///
230		/// Should be called at the end of a verification process, once we are sure that a certain
231		/// solution is 100% correct.
232		///
233		/// It stores its score, flips the pointer to it being the current best one, and clears all
234		/// the backings and the invalid variant. (note: in principle, we can skip clearing the
235		/// backings here)
236		pub(crate) fn finalize_correct(score: ElectionScore) {
237			sublog!(
238				info,
239				"verifier",
240				"finalizing verification a correct solution, replacing old score {:?} with {:?}",
241				QueuedSolutionScore::<T>::get(Self::round()),
242				score
243			);
244
245			Self::mutate_checked(|| {
246				QueuedValidVariant::<T>::mutate(Self::round(), |v| *v = v.other());
247				QueuedSolutionScore::<T>::insert(Self::round(), score);
248
249				// Clear what was previously the valid variant. Also clears the partial backings.
250				Self::clear_invalid_and_backings_unchecked();
251			});
252		}
253
254		/// Clear all relevant information of an invalid solution.
255		///
256		/// Should be called at any step, if we encounter an issue which makes the solution
257		/// infeasible.
258		pub(crate) fn clear_invalid_and_backings() {
259			Self::mutate_checked(Self::clear_invalid_and_backings_unchecked)
260		}
261
262		/// Same as [`clear_invalid_and_backings`], but without any checks for the integrity of the
263		/// storage item group.
264		pub(crate) fn clear_invalid_and_backings_unchecked() {
265			// clear is safe as we delete at most `Pages` entries, and `Pages` is bounded.
266			match Self::invalid() {
267				ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
268				ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
269			};
270			clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
271		}
272
273		/// Write a single page of a valid solution into the `invalid` variant of the storage.
274		///
275		/// This should only be called once we are sure that this particular page is 100% correct.
276		///
277		/// This is called after *a page* has been validated, but the entire solution is not yet
278		/// known to be valid. At this stage, we write to the invalid variant. Once all pages are
279		/// verified, a call to [`finalize_correct`] will seal the correct pages and flip the
280		/// invalid/valid variants.
281		pub(crate) fn set_invalid_page(page: PageIndex, supports: SupportsOfVerifier<Pallet<T>>) {
282			use frame_support::traits::TryCollect;
283			Self::mutate_checked(|| {
284				let backings: BoundedVec<_, _> = supports
285					.iter()
286					.map(|(x, s)| (x.clone(), PartialBackings { total: s.total, backers: s.voters.len() as u32 } ))
287					.try_collect()
288					.expect("`SupportsOfVerifier` is bounded by <Pallet<T> as Verifier>::MaxWinnersPerPage, which is assured to be the same as `T::MaxWinnersPerPage` in an integrity test");
289				QueuedSolutionBackings::<T>::insert(Self::round(), page, backings);
290
291				match Self::invalid() {
292					ValidSolution::X => QueuedSolutionX::<T>::insert(Self::round(), page, supports),
293					ValidSolution::Y => QueuedSolutionY::<T>::insert(Self::round(), page, supports),
294				}
295			})
296		}
297
298		/// Write a single page to the valid variant directly.
299		///
300		/// This is not the normal flow of writing, and the solution is not checked.
301		///
302		/// This is only useful to override the valid solution with a single (likely backup)
303		/// solution.
304		pub(crate) fn force_set_single_page_valid(
305			page: PageIndex,
306			supports: SupportsOfVerifier<Pallet<T>>,
307			score: ElectionScore,
308		) {
309			Self::mutate_checked(|| {
310				// clear everything about valid solutions.
311				match Self::valid() {
312					ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
313					ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
314				};
315				QueuedSolutionScore::<T>::remove(Self::round());
316
317				// write a single new page.
318				match Self::valid() {
319					ValidSolution::X => QueuedSolutionX::<T>::insert(Self::round(), page, supports),
320					ValidSolution::Y => QueuedSolutionY::<T>::insert(Self::round(), page, supports),
321				}
322
323				// write the score.
324				QueuedSolutionScore::<T>::insert(Self::round(), score);
325			})
326		}
327
328		pub(crate) fn force_set_multi_page_valid(
329			pages: Vec<PageIndex>,
330			supports: Vec<SupportsOfVerifier<Pallet<T>>>,
331			score: ElectionScore,
332		) {
333			debug_assert_eq!(pages.len(), supports.len());
334			// queue it in our valid queue
335			Self::mutate_checked(|| {
336				// clear everything about valid solutions.
337				match Self::valid() {
338					ValidSolution::X => clear_round_based_map!(QueuedSolutionX::<T>, Self::round()),
339					ValidSolution::Y => clear_round_based_map!(QueuedSolutionY::<T>, Self::round()),
340				};
341				QueuedSolutionScore::<T>::remove(Self::round());
342
343				// store the valid pages
344				for (support, page) in supports.into_iter().zip(pages.iter()) {
345					match Self::valid() {
346						ValidSolution::X =>
347							QueuedSolutionX::<T>::insert(Self::round(), page, support),
348						ValidSolution::Y =>
349							QueuedSolutionY::<T>::insert(Self::round(), page, support),
350					}
351				}
352				QueuedSolutionScore::<T>::insert(Self::round(), score);
353			});
354		}
355
356		/// Clear all storage items.
357		///
358		/// Should only be called once everything is done.
359		pub(crate) fn kill() {
360			Self::mutate_checked(|| {
361				clear_round_based_map!(QueuedSolutionX::<T>, Self::round());
362				clear_round_based_map!(QueuedSolutionY::<T>, Self::round());
363				QueuedValidVariant::<T>::remove(Self::round());
364				clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
365				QueuedSolutionScore::<T>::remove(Self::round());
366			})
367		}
368
369		// -- non-mutating methods.
370
371		/// Return the `score` and `winner_count` of verifying solution.
372		///
373		/// Computes the final score of the solution that is currently at the end of its
374		/// verification process.
375		///
376		/// Does NOT check for completeness of all the corresponding pages of
377		/// `QueuedSolutionBackings`. This function is called during finalization logic, which can
378		/// be reached even with missing/empty pages (treated as Default::default()). Missing
379		/// pages are handled gracefully by the verification process before reaching this point.
380		/// This avoids unnecessary storage reads and redundant checks.
381		///
382		/// This solution corresponds to whatever is stored in the INVALID variant of
383		/// `QueuedSolution`. Recall that the score of this solution is not yet verified, so it
384		/// should never become `valid`.
385		pub(crate) fn compute_invalid_score() -> Result<(ElectionScore, u32), FeasibilityError> {
386			let mut total_supports: BTreeMap<T::AccountId, PartialBackings> = Default::default();
387			for (who, PartialBackings { backers, total }) in
388				QueuedSolutionBackings::<T>::iter_prefix(Self::round()).flat_map(|(_, pb)| pb)
389			{
390				let entry = total_supports.entry(who).or_default();
391				entry.total = entry.total.saturating_add(total);
392				entry.backers = entry.backers.saturating_add(backers);
393
394				if entry.backers > T::MaxBackersPerWinnerFinal::get() {
395					return Err(FeasibilityError::FailedToBoundSupport)
396				}
397			}
398
399			let winner_count = total_supports.len() as u32;
400			let score = evaluate_support(total_supports.into_values());
401
402			Ok((score, winner_count))
403		}
404
405		/// The score of the current best solution, if any.
406		pub(crate) fn queued_score() -> Option<ElectionScore> {
407			QueuedSolutionScore::<T>::get(Self::round())
408		}
409
410		/// Get a page of the current queued (aka valid) solution.
411		pub(crate) fn get_queued_solution_page(
412			page: PageIndex,
413		) -> Option<SupportsOfVerifier<Pallet<T>>> {
414			match Self::valid() {
415				ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
416				ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
417			}
418		}
419
420		fn valid() -> ValidSolution {
421			QueuedValidVariant::<T>::get(Self::round())
422		}
423
424		fn invalid() -> ValidSolution {
425			Self::valid().other()
426		}
427	}
428
429	#[allow(unused)]
430	#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime", debug_assertions))]
431	impl<T: Config> QueuedSolution<T> {
432		pub(crate) fn valid_iter(
433		) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
434			match Self::valid() {
435				ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
436				ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
437			}
438		}
439
440		pub(crate) fn invalid_iter(
441		) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
442			match Self::invalid() {
443				ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
444				ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
445			}
446		}
447
448		pub(crate) fn get_valid_page(page: PageIndex) -> Option<SupportsOfVerifier<Pallet<T>>> {
449			match Self::valid() {
450				ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
451				ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
452			}
453		}
454
455		pub(crate) fn backing_iter() -> impl Iterator<
456			Item = (PageIndex, BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>),
457		> {
458			QueuedSolutionBackings::<T>::iter_prefix(Self::round())
459		}
460
461		/// Ensure that all the storage items managed by this struct are in `kill` state, meaning
462		/// that in the expect state after an election is OVER.
463		pub(crate) fn assert_killed() {
464			use frame_support::assert_storage_noop;
465			assert_storage_noop!(Self::kill());
466		}
467
468		/// Ensure this storage item group is in correct state.
469		pub(crate) fn sanity_check() -> Result<(), sp_runtime::DispatchError> {
470			// score is correct and better than min-score.
471			ensure!(
472				Pallet::<T>::minimum_score()
473					.zip(Self::queued_score())
474					.map_or(true, |(min_score, score)| score
475						.strict_threshold_better(min_score, Perbill::zero())),
476				"queued solution has weak score (min-score)"
477			);
478
479			if let Some(queued_score) = Self::queued_score() {
480				let mut backing_map: BTreeMap<T::AccountId, PartialBackings> = BTreeMap::new();
481				Self::valid_iter()
482					.flat_map(|(_, supports)| supports)
483					.for_each(|(who, support)| {
484						let entry = backing_map.entry(who).or_default();
485						entry.total = entry.total.saturating_add(support.total);
486					});
487				let real_score = evaluate_support(backing_map.into_values());
488				ensure!(real_score == queued_score, "queued solution has wrong score");
489			} else {
490				assert!(Self::valid_iter().count() == 0, "nothing should be stored if no score");
491			}
492
493			// The number of existing keys in `QueuedSolutionBackings` must always match that of
494			// the INVALID variant.
495			ensure!(
496				QueuedSolutionBackings::<T>::iter_prefix(Self::round()).count() ==
497					Self::invalid_iter().count(),
498				"incorrect number of backings pages",
499			);
500
501			if let Status::Nothing = StatusStorage::<T>::get() {
502				ensure!(Self::invalid_iter().count() == 0, "dangling data in invalid variant");
503			}
504
505			Ok(())
506		}
507	}
508
509	// -- private storage items, managed by `QueuedSolution`.
510
511	/// The `X` variant of the current queued solution. Might be the valid one or not.
512	///
513	/// The two variants of this storage item is to avoid the need of copying. Recall that once a
514	/// `VerifyingSolution` is being processed, it needs to write its partial supports *somewhere*.
515	/// Writing theses supports on top of a *good* queued supports is wrong, since we might bail.
516	/// Writing them to a bugger and copying at the ned is slightly better, but expensive. This flag
517	/// system is best of both worlds.
518	#[pallet::storage]
519	type QueuedSolutionX<T: Config> = StorageDoubleMap<
520		_,
521		Twox64Concat,
522		u32,
523		Twox64Concat,
524		PageIndex,
525		SupportsOfVerifier<Pallet<T>>,
526	>;
527
528	/// The `Y` variant of the current queued solution. Might be the valid one or not.
529	#[pallet::storage]
530	type QueuedSolutionY<T: Config> = StorageDoubleMap<
531		_,
532		Twox64Concat,
533		u32,
534		Twox64Concat,
535		PageIndex,
536		SupportsOfVerifier<Pallet<T>>,
537	>;
538	/// Pointer to the variant of [`QueuedSolutionX`] or [`QueuedSolutionY`] that is currently
539	/// valid.
540
541	#[pallet::storage]
542	type QueuedValidVariant<T: Config> =
543		StorageMap<_, Twox64Concat, u32, ValidSolution, ValueQuery>;
544
545	/// The `(amount, count)` of backings, divided per page.
546	///
547	/// This is stored because in the last block of verification we need them to compute the score,
548	/// and check `MaxBackersPerWinnerFinal`.
549	///
550	/// This can only ever live for the invalid variant of the solution. Once it is valid, we don't
551	/// need this information anymore; the score is already computed once in
552	/// [`QueuedSolutionScore`], and the backing counts are checked.
553	#[pallet::storage]
554	type QueuedSolutionBackings<T: Config> = StorageDoubleMap<
555		_,
556		Twox64Concat,
557		u32,
558		Twox64Concat,
559		PageIndex,
560		BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>,
561	>;
562
563	/// The score of the valid variant of [`QueuedSolution`].
564	///
565	/// This only ever lives for the `valid` variant.
566	#[pallet::storage]
567	type QueuedSolutionScore<T: Config> = StorageMap<_, Twox64Concat, u32, ElectionScore>;
568
569	// -- ^^ private storage items, managed by `QueuedSolution`.
570
571	/// The minimum score that each solution must attain in order to be considered feasible.
572	#[pallet::storage]
573	#[pallet::getter(fn minimum_score)]
574	pub(crate) type MinimumScore<T: Config> = StorageValue<_, ElectionScore>;
575
576	/// Storage item for [`Status`].
577	#[pallet::storage]
578	#[pallet::getter(fn status_storage)]
579	pub(crate) type StatusStorage<T: Config> = StorageValue<_, Status, ValueQuery>;
580
581	#[pallet::pallet]
582	pub struct Pallet<T>(PhantomData<T>);
583
584	#[pallet::genesis_config]
585	#[derive(frame_support::DefaultNoBound)]
586	pub struct GenesisConfig<T: Config> {
587		/// Initial value for [`MinimumScore`]
588		pub(crate) minimum_score: ElectionScore,
589		_marker: PhantomData<T>,
590	}
591
592	#[pallet::genesis_build]
593	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
594		fn build(&self) {
595			MinimumScore::<T>::put(self.minimum_score);
596		}
597	}
598
599	#[pallet::call]
600	impl<T: Config> Pallet<T> {}
601
602	#[pallet::hooks]
603	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
604		fn integrity_test() {
605			// ensure that we have funneled some of our type parameters EXACTLY as-is to the
606			// verifier trait interface we implement.
607			assert_eq!(T::MaxWinnersPerPage::get(), <Self as Verifier>::MaxWinnersPerPage::get());
608			assert_eq!(
609				T::MaxBackersPerWinner::get(),
610				<Self as Verifier>::MaxBackersPerWinner::get()
611			);
612			assert!(T::MaxBackersPerWinner::get() <= T::MaxBackersPerWinnerFinal::get());
613		}
614
615		fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
616			Self::do_on_initialize()
617		}
618
619		#[cfg(feature = "try-runtime")]
620		fn try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
621			Self::do_try_state(_now)
622		}
623	}
624}
625
626impl<T: Config> Pallet<T> {
627	fn do_on_initialize() -> Weight {
628		if let Status::Ongoing(current_page) = Self::status_storage() {
629			let page_solution =
630				<T::SolutionDataProvider as SolutionDataProvider>::get_page(current_page);
631
632			let maybe_supports = Self::feasibility_check_page_inner(page_solution, current_page);
633
634			sublog!(
635				debug,
636				"verifier",
637				"verified page {} of a solution, outcome = {:?}",
638				current_page,
639				maybe_supports.as_ref().map(|s| s.len())
640			);
641
642			match maybe_supports {
643				Ok(supports) => {
644					Self::deposit_event(Event::<T>::Verified(current_page, supports.len() as u32));
645					QueuedSolution::<T>::set_invalid_page(current_page, supports);
646
647					if current_page > crate::Pallet::<T>::lsp() {
648						// not last page, just tick forward.
649						StatusStorage::<T>::put(Status::Ongoing(current_page.saturating_sub(1)));
650						VerifierWeightsOf::<T>::on_initialize_valid_non_terminal()
651					} else {
652						// last page, finalize everything. Get the claimed score.
653						let claimed_score = T::SolutionDataProvider::get_score();
654
655						// in both cases of the following match, we are back to the nothing state.
656						StatusStorage::<T>::put(Status::Nothing);
657
658						match Self::finalize_async_verification(claimed_score) {
659							Ok(_) => {
660								T::SolutionDataProvider::report_result(VerificationResult::Queued);
661								VerifierWeightsOf::<T>::on_initialize_valid_terminal()
662							},
663							Err(_) => {
664								T::SolutionDataProvider::report_result(
665									VerificationResult::Rejected,
666								);
667								// In case of any of the errors, kill the solution.
668								QueuedSolution::<T>::clear_invalid_and_backings();
669								VerifierWeightsOf::<T>::on_initialize_invalid_terminal()
670							},
671						}
672					}
673				},
674				Err(err) => {
675					// the page solution was invalid.
676					Self::deposit_event(Event::<T>::VerificationFailed(current_page, err));
677
678					sublog!(warn, "verifier", "Clearing any ongoing unverified solutions.");
679					// Clear any ongoing solution that has not been verified, regardless of the
680					// current state.
681					QueuedSolution::<T>::clear_invalid_and_backings_unchecked();
682
683					// we also mutate the status back to doing nothing.
684					let was_ongoing = matches!(StatusStorage::<T>::get(), Status::Ongoing(_));
685					StatusStorage::<T>::put(Status::Nothing);
686
687					if was_ongoing {
688						T::SolutionDataProvider::report_result(VerificationResult::Rejected);
689					}
690					let wasted_pages = T::Pages::get().saturating_sub(current_page);
691					VerifierWeightsOf::<T>::on_initialize_invalid_non_terminal(wasted_pages)
692				},
693			}
694		} else {
695			T::DbWeight::get().reads(1)
696		}
697	}
698
699	fn do_verify_synchronous_multi(
700		partial_solutions: Vec<SolutionOf<T::MinerConfig>>,
701		solution_pages: Vec<PageIndex>,
702		claimed_score: ElectionScore,
703	) -> Result<(), (PageIndex, FeasibilityError)> {
704		let first_page = solution_pages.first().cloned().unwrap_or_default();
705		let last_page = solution_pages.last().cloned().unwrap_or_default();
706		// first, ensure this score will be good enough, even if valid..
707		let _ = Self::ensure_score_quality(claimed_score).map_err(|fe| (first_page, fe))?;
708		ensure!(
709			partial_solutions.len() == solution_pages.len(),
710			(first_page, FeasibilityError::Incomplete)
711		);
712
713		// verify each page, and amalgamate into a final support.
714		let mut backings =
715			sp_std::collections::btree_map::BTreeMap::<T::AccountId, PartialBackings>::new();
716		let mut linked_supports = Vec::with_capacity(partial_solutions.len());
717
718		for (solution_page, page) in partial_solutions.into_iter().zip(solution_pages.iter()) {
719			let page_supports = Self::feasibility_check_page_inner(solution_page, *page)
720				.map_err(|fe| (*page, fe))?;
721
722			linked_supports.push(page_supports.clone());
723			let support_len = page_supports.len() as u32;
724			for (who, support) in page_supports.into_iter() {
725				let entry = backings.entry(who).or_default();
726				entry.total = entry.total.saturating_add(support.total);
727				// Note we assume snapshots are always disjoint, and therefore we can easily extend
728				// here.
729				entry.backers = entry.backers.saturating_add(support.voters.len() as u32);
730				if entry.backers > T::MaxBackersPerWinnerFinal::get() {
731					return Err((*page, FeasibilityError::FailedToBoundSupport))
732				}
733			}
734
735			Self::deposit_event(Event::<T>::Verified(*page, support_len));
736		}
737
738		// then check that the number of winners was exactly enough..
739		let desired_targets = crate::Snapshot::<T>::desired_targets()
740			.ok_or(FeasibilityError::SnapshotUnavailable)
741			.map_err(|fe| (last_page, fe))?;
742		ensure!(
743			backings.len() as u32 == desired_targets,
744			(last_page, FeasibilityError::WrongWinnerCount)
745		);
746
747		// then check the score was truth..
748		let truth_score = evaluate_support(backings.into_values());
749		ensure!(truth_score == claimed_score, (last_page, FeasibilityError::InvalidScore));
750
751		let maybe_current_score = QueuedSolution::<T>::queued_score();
752
753		// then store it.
754		sublog!(
755			debug,
756			"verifier",
757			"queued sync solution with score {:?} for pages {:?}",
758			truth_score,
759			solution_pages
760		);
761		QueuedSolution::<T>::force_set_multi_page_valid(
762			solution_pages,
763			linked_supports,
764			truth_score,
765		);
766		Self::deposit_event(Event::<T>::Queued(truth_score, maybe_current_score));
767
768		Ok(())
769	}
770
771	/// Finalize an asynchronous verification. Checks the final score for correctness, and ensures
772	/// that it matches all of the criteria.
773	///
774	/// This should only be called when all pages of an async verification are done.
775	///
776	/// Returns:
777	/// - `Ok()` if everything is okay, at which point the valid variant of the queued solution will
778	/// be updated. Returns
779	/// - `Err(Feasibility)` if any of the last verification steps fail.
780	fn finalize_async_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> {
781		let outcome = QueuedSolution::<T>::compute_invalid_score()
782			.and_then(|(final_score, winner_count)| {
783				let desired_targets =
784					crate::Snapshot::<T>::desired_targets().defensive_unwrap_or(u32::MAX);
785				// claimed_score checked prior in seal_unverified_solution
786				match (final_score == claimed_score, winner_count == desired_targets) {
787					(true, true) => {
788						// all good, finalize this solution
789						// NOTE: must be before the call to `finalize_correct`.
790						Self::deposit_event(Event::<T>::Queued(
791							final_score,
792							QueuedSolution::<T>::queued_score(), /* the previous score, now
793							                                      * ejected. */
794						));
795						QueuedSolution::<T>::finalize_correct(final_score);
796						Ok(())
797					},
798					(false, true) => Err(FeasibilityError::InvalidScore),
799					(true, false) => Err(FeasibilityError::WrongWinnerCount),
800					(false, false) => Err(FeasibilityError::InvalidScore),
801				}
802			})
803			.map_err(|err| {
804				sublog!(warn, "verifier", "Finalizing solution was invalid due to {:?}.", err);
805				// and deposit an event about it.
806				Self::deposit_event(Event::<T>::VerificationFailed(0, err.clone()));
807				err
808			});
809		sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome);
810		outcome
811	}
812
813	/// Ensure that the given score is:
814	///
815	/// - better than the queued solution, if one exists.
816	/// - greater than the minimum untrusted score.
817	pub(crate) fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> {
818		let is_improvement = <Self as Verifier>::queued_score().map_or(true, |best_score| {
819			score.strict_threshold_better(best_score, T::SolutionImprovementThreshold::get())
820		});
821		ensure!(is_improvement, FeasibilityError::ScoreTooLow);
822
823		let is_greater_than_min_untrusted = Self::minimum_score()
824			.map_or(true, |min_score| score.strict_threshold_better(min_score, Perbill::zero()));
825		ensure!(is_greater_than_min_untrusted, FeasibilityError::ScoreTooLow);
826
827		Ok(())
828	}
829
830	/// Do the full feasibility check:
831	///
832	/// - check all edges.
833	/// - checks `MaxBackersPerWinner` to be respected IN THIS PAGE.
834	/// - checks the number of winners to be less than or equal to `DesiredTargets` IN THIS PAGE
835	///   ONLY.
836	pub(super) fn feasibility_check_page_inner(
837		partial_solution: SolutionOf<T::MinerConfig>,
838		page: PageIndex,
839	) -> Result<SupportsOfVerifier<Self>, FeasibilityError> {
840		// Read the corresponding snapshots.
841		let snapshot_targets =
842			crate::Snapshot::<T>::targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
843		let snapshot_voters =
844			crate::Snapshot::<T>::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?;
845		let desired_targets =
846			crate::Snapshot::<T>::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
847
848		feasibility_check_page_inner_with_snapshot::<T::MinerConfig>(
849			partial_solution,
850			&snapshot_voters,
851			&snapshot_targets,
852			desired_targets,
853		)
854		.and_then(|miner_supports| {
855			SupportsOfVerifier::<Self>::try_from_other_bounds(miner_supports)
856				.defensive_map_err(|_| FeasibilityError::FailedToBoundSupport)
857		})
858	}
859
860	#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
861	pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
862		QueuedSolution::<T>::sanity_check()
863	}
864}
865
866/// Same as `feasibility_check_page_inner`, but with a snapshot.
867///
868/// This is exported as a standalone function, relying on `MinerConfig` rather than `Config` so that
869/// it can be used in any offchain miner.
870pub fn feasibility_check_page_inner_with_snapshot<T: MinerConfig>(
871	partial_solution: SolutionOf<T>,
872	snapshot_voters: &BoundedVec<VoterOf<T>, T::VoterSnapshotPerBlock>,
873	snapshot_targets: &BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
874	desired_targets: u32,
875) -> Result<PageSupportsOfMiner<T>, FeasibilityError> {
876	// ----- Start building. First, we need some closures.
877	let cache = helpers::generate_voter_cache::<T, _>(snapshot_voters);
878	let voter_at = helpers::voter_at_fn::<T>(snapshot_voters);
879	let target_at = helpers::target_at_fn::<T>(snapshot_targets);
880	let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
881
882	// Then convert solution -> assignment. This will fail if any of the indices are
883	// gibberish. It will also ensure each assignemnt (voter) is unique, and all targets within it
884	// are unique.
885	let assignments = partial_solution
886		.into_assignment(voter_at, target_at)
887		.map_err::<FeasibilityError, _>(Into::into)?;
888
889	// Ensure that assignments are all correct.
890	let _ = assignments
891		.iter()
892		.map(|ref assignment| {
893			// Check that assignment.who is actually a voter (defensive-only). NOTE: while
894			// using the index map from `voter_index` is better than a blind linear search,
895			// this *still* has room for optimization. Note that we had the index when we
896			// did `solution -> assignment` and we lost it. Ideal is to keep the index
897			// around.
898
899			// Defensive-only: must exist in the snapshot.
900			let snapshot_index =
901				voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
902			// Defensive-only: index comes from the snapshot, must exist.
903			let (_voter, _stake, targets) =
904				snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
905			debug_assert!(*_voter == assignment.who);
906
907			// Check that all of the targets are valid based on the snapshot.
908			if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) {
909				return Err(FeasibilityError::InvalidVote)
910			}
911			Ok(())
912		})
913		.collect::<Result<(), FeasibilityError>>()?;
914
915	// ----- Start building support. First, we need one more closure.
916	let stake_of = helpers::stake_of_fn::<T, _>(&snapshot_voters, &cache);
917
918	// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
919	let staked_assignments =
920		sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of)
921			.map_err::<FeasibilityError, _>(Into::into)?;
922
923	let supports = sp_npos_elections::to_supports(&staked_assignments);
924
925	// Ensure some heuristics. These conditions must hold in the **entire** support, this is
926	// just a single page. But, they must hold in a single page as well.
927	ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount);
928
929	// almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of
930	// `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which
931	// is ALSO checked, so this conversion can almost never fail.
932	let bounded_supports =
933		supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?;
934	Ok(bounded_supports)
935}
936
937impl<T: Config> Verifier for Pallet<T> {
938	type AccountId = T::AccountId;
939	type Solution = SolutionOf<T::MinerConfig>;
940	type MaxBackersPerWinner = T::MaxBackersPerWinner;
941	type MaxWinnersPerPage = T::MaxWinnersPerPage;
942	type MaxBackersPerWinnerFinal = T::MaxBackersPerWinnerFinal;
943
944	fn set_minimum_score(score: ElectionScore) {
945		MinimumScore::<T>::put(score);
946	}
947
948	fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool {
949		Self::ensure_score_quality(claimed_score).is_ok()
950	}
951
952	fn queued_score() -> Option<ElectionScore> {
953		QueuedSolution::<T>::queued_score()
954	}
955
956	fn kill() {
957		QueuedSolution::<T>::kill();
958		<StatusStorage<T>>::put(Status::Nothing);
959	}
960
961	fn get_queued_solution_page(page: PageIndex) -> Option<SupportsOfVerifier<Self>> {
962		QueuedSolution::<T>::get_queued_solution_page(page)
963	}
964
965	fn verify_synchronous_multi(
966		partial_solutions: Vec<Self::Solution>,
967		solution_pages: Vec<PageIndex>,
968		claimed_score: ElectionScore,
969	) -> Result<(), FeasibilityError> {
970		Self::do_verify_synchronous_multi(partial_solutions, solution_pages, claimed_score).map_err(
971			|(page, fe)| {
972				sublog!(
973					warn,
974					"verifier",
975					"sync verification of page {:?} failed due to {:?}.",
976					page,
977					fe
978				);
979				Self::deposit_event(Event::<T>::VerificationFailed(page, fe.clone()));
980				fe
981			},
982		)
983	}
984
985	fn force_set_single_page_valid(
986		partial_supports: SupportsOfVerifier<Self>,
987		page: PageIndex,
988		score: ElectionScore,
989	) {
990		Self::deposit_event(Event::<T>::Queued(score, QueuedSolution::<T>::queued_score()));
991		QueuedSolution::<T>::force_set_single_page_valid(page, partial_supports, score);
992	}
993}
994
995impl<T: Config> AsynchronousVerifier for Pallet<T> {
996	type SolutionDataProvider = T::SolutionDataProvider;
997
998	fn status() -> Status {
999		Pallet::<T>::status_storage()
1000	}
1001
1002	fn start() -> Result<(), &'static str> {
1003		sublog!(debug, "verifier", "start signal received.");
1004		if let Status::Nothing = Self::status() {
1005			let claimed_score = Self::SolutionDataProvider::get_score();
1006			if Self::ensure_score_quality(claimed_score).is_err() {
1007				// don't do anything, report back that this solution was garbage.
1008				Self::deposit_event(Event::<T>::VerificationFailed(
1009					crate::Pallet::<T>::msp(),
1010					FeasibilityError::ScoreTooLow,
1011				));
1012				T::SolutionDataProvider::report_result(VerificationResult::Rejected);
1013				// Despite being an instant-reject, this was a successful `start` operation.
1014				Ok(())
1015			} else {
1016				// This solution is good enough to win, we start verifying it in the next block.
1017				StatusStorage::<T>::put(Status::Ongoing(crate::Pallet::<T>::msp()));
1018				Ok(())
1019			}
1020		} else {
1021			sublog!(warn, "verifier", "start signal received while busy. This will be ignored.");
1022			Err("verification ongoing")
1023		}
1024	}
1025}