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