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						ValidSolution::Y =>
344							QueuedSolutionY::<T>::insert(Self::round(), page, support),
345					}
346				}
347				QueuedSolutionScore::<T>::insert(Self::round(), score);
348			});
349		}
350
351		/// Clear all storage items.
352		///
353		/// Should only be called once everything is done.
354		pub(crate) fn kill() {
355			Self::mutate_checked(|| {
356				clear_round_based_map!(QueuedSolutionX::<T>, Self::round());
357				clear_round_based_map!(QueuedSolutionY::<T>, Self::round());
358				QueuedValidVariant::<T>::remove(Self::round());
359				clear_round_based_map!(QueuedSolutionBackings::<T>, Self::round());
360				QueuedSolutionScore::<T>::remove(Self::round());
361			})
362		}
363
364		// -- non-mutating methods.
365
366		/// Return the `score` and `winner_count` of verifying solution.
367		///
368		/// Computes the final score of the solution that is currently at the end of its
369		/// verification process.
370		///
371		/// Does NOT check for completeness of all the corresponding pages of
372		/// `QueuedSolutionBackings`. This function is called during finalization logic, which can
373		/// be reached even with missing/empty pages (treated as Default::default()). Missing
374		/// pages are handled gracefully by the verification process before reaching this point.
375		/// This avoids unnecessary storage reads and redundant checks.
376		///
377		/// This solution corresponds to whatever is stored in the INVALID variant of
378		/// `QueuedSolution`. Recall that the score of this solution is not yet verified, so it
379		/// should never become `valid`.
380		pub(crate) fn compute_invalid_score() -> Result<(ElectionScore, u32), FeasibilityError> {
381			let mut total_supports: BTreeMap<T::AccountId, PartialBackings> = Default::default();
382			for (who, PartialBackings { backers, total }) in
383				QueuedSolutionBackings::<T>::iter_prefix(Self::round()).flat_map(|(_, pb)| pb)
384			{
385				let entry = total_supports.entry(who).or_default();
386				entry.total = entry.total.saturating_add(total);
387				entry.backers = entry.backers.saturating_add(backers);
388
389				if entry.backers > T::MaxBackersPerWinnerFinal::get() {
390					return Err(FeasibilityError::FailedToBoundSupport)
391				}
392			}
393
394			let winner_count = total_supports.len() as u32;
395			let score = evaluate_support(total_supports.into_values());
396
397			Ok((score, winner_count))
398		}
399
400		/// The score of the current best solution, if any.
401		pub(crate) fn queued_score() -> Option<ElectionScore> {
402			QueuedSolutionScore::<T>::get(Self::round())
403		}
404
405		/// Get a page of the current queued (aka valid) solution.
406		pub(crate) fn get_queued_solution_page(
407			page: PageIndex,
408		) -> Option<SupportsOfVerifier<Pallet<T>>> {
409			match Self::valid() {
410				ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
411				ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
412			}
413		}
414
415		fn valid() -> ValidSolution {
416			QueuedValidVariant::<T>::get(Self::round())
417		}
418
419		fn invalid() -> ValidSolution {
420			Self::valid().other()
421		}
422	}
423
424	#[allow(unused)]
425	#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime", debug_assertions))]
426	impl<T: Config> QueuedSolution<T> {
427		pub(crate) fn valid_iter(
428		) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
429			match Self::valid() {
430				ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
431				ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
432			}
433		}
434
435		pub(crate) fn invalid_iter(
436		) -> impl Iterator<Item = (PageIndex, SupportsOfVerifier<Pallet<T>>)> {
437			match Self::invalid() {
438				ValidSolution::X => QueuedSolutionX::<T>::iter_prefix(Self::round()),
439				ValidSolution::Y => QueuedSolutionY::<T>::iter_prefix(Self::round()),
440			}
441		}
442
443		pub(crate) fn get_valid_page(page: PageIndex) -> Option<SupportsOfVerifier<Pallet<T>>> {
444			match Self::valid() {
445				ValidSolution::X => QueuedSolutionX::<T>::get(Self::round(), page),
446				ValidSolution::Y => QueuedSolutionY::<T>::get(Self::round(), page),
447			}
448		}
449
450		pub(crate) fn backing_iter() -> impl Iterator<
451			Item = (PageIndex, BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>),
452		> {
453			QueuedSolutionBackings::<T>::iter_prefix(Self::round())
454		}
455
456		/// Ensure that all the storage items managed by this struct are in `kill` state, meaning
457		/// that in the expect state after an election is OVER.
458		pub(crate) fn assert_killed() {
459			use frame_support::assert_storage_noop;
460			assert_storage_noop!(Self::kill());
461		}
462
463		/// Ensure this storage item group is in correct state.
464		pub(crate) fn sanity_check() -> Result<(), sp_runtime::DispatchError> {
465			// score is correct and better than min-score.
466			ensure!(
467				Pallet::<T>::minimum_score()
468					.zip(Self::queued_score())
469					.map_or(true, |(min_score, score)| score.strict_better(min_score)),
470				"queued solution has weak score (min-score)"
471			);
472
473			if let Some(queued_score) = Self::queued_score() {
474				let mut backing_map: BTreeMap<T::AccountId, PartialBackings> = BTreeMap::new();
475				Self::valid_iter()
476					.flat_map(|(_, supports)| supports)
477					.for_each(|(who, support)| {
478						let entry = backing_map.entry(who).or_default();
479						entry.total = entry.total.saturating_add(support.total);
480					});
481				let real_score = evaluate_support(backing_map.into_values());
482				ensure!(real_score == queued_score, "queued solution has wrong score");
483			} else {
484				assert!(Self::valid_iter().count() == 0, "nothing should be stored if no score");
485			}
486
487			// The number of existing keys in `QueuedSolutionBackings` must always match that of
488			// the INVALID variant.
489			ensure!(
490				QueuedSolutionBackings::<T>::iter_prefix(Self::round()).count() ==
491					Self::invalid_iter().count(),
492				"incorrect number of backings pages",
493			);
494
495			if let Status::Nothing = StatusStorage::<T>::get() {
496				ensure!(Self::invalid_iter().count() == 0, "dangling data in invalid variant");
497			}
498
499			Ok(())
500		}
501	}
502
503	// -- private storage items, managed by `QueuedSolution`.
504
505	/// The `X` variant of the current queued solution. Might be the valid one or not.
506	///
507	/// The two variants of this storage item is to avoid the need of copying. Recall that once a
508	/// `VerifyingSolution` is being processed, it needs to write its partial supports *somewhere*.
509	/// Writing theses supports on top of a *good* queued supports is wrong, since we might bail.
510	/// Writing them to a bugger and copying at the ned is slightly better, but expensive. This flag
511	/// system is best of both worlds.
512	#[pallet::storage]
513	type QueuedSolutionX<T: Config> = StorageDoubleMap<
514		_,
515		Twox64Concat,
516		u32,
517		Twox64Concat,
518		PageIndex,
519		SupportsOfVerifier<Pallet<T>>,
520	>;
521
522	/// The `Y` variant of the current queued solution. Might be the valid one or not.
523	#[pallet::storage]
524	type QueuedSolutionY<T: Config> = StorageDoubleMap<
525		_,
526		Twox64Concat,
527		u32,
528		Twox64Concat,
529		PageIndex,
530		SupportsOfVerifier<Pallet<T>>,
531	>;
532	/// Pointer to the variant of [`QueuedSolutionX`] or [`QueuedSolutionY`] that is currently
533	/// valid.
534
535	#[pallet::storage]
536	type QueuedValidVariant<T: Config> =
537		StorageMap<_, Twox64Concat, u32, ValidSolution, ValueQuery>;
538
539	/// The `(amount, count)` of backings, divided per page.
540	///
541	/// This is stored because in the last block of verification we need them to compute the score,
542	/// and check `MaxBackersPerWinnerFinal`.
543	///
544	/// This can only ever live for the invalid variant of the solution. Once it is valid, we don't
545	/// need this information anymore; the score is already computed once in
546	/// [`QueuedSolutionScore`], and the backing counts are checked.
547	#[pallet::storage]
548	type QueuedSolutionBackings<T: Config> = StorageDoubleMap<
549		_,
550		Twox64Concat,
551		u32,
552		Twox64Concat,
553		PageIndex,
554		BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>,
555	>;
556
557	/// The score of the valid variant of [`QueuedSolution`].
558	///
559	/// This only ever lives for the `valid` variant.
560	#[pallet::storage]
561	type QueuedSolutionScore<T: Config> = StorageMap<_, Twox64Concat, u32, ElectionScore>;
562
563	// -- ^^ private storage items, managed by `QueuedSolution`.
564
565	/// The minimum score that each solution must attain in order to be considered feasible.
566	#[pallet::storage]
567	#[pallet::getter(fn minimum_score)]
568	pub(crate) type MinimumScore<T: Config> = StorageValue<_, ElectionScore>;
569
570	/// Storage item for [`Status`].
571	#[pallet::storage]
572	#[pallet::getter(fn status_storage)]
573	pub(crate) type StatusStorage<T: Config> = StorageValue<_, Status, ValueQuery>;
574
575	#[pallet::pallet]
576	pub struct Pallet<T>(PhantomData<T>);
577
578	#[pallet::genesis_config]
579	#[derive(frame_support::DefaultNoBound)]
580	pub struct GenesisConfig<T: Config> {
581		/// Initial value for [`MinimumScore`]
582		pub(crate) minimum_score: ElectionScore,
583		_marker: PhantomData<T>,
584	}
585
586	#[pallet::genesis_build]
587	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
588		fn build(&self) {
589			MinimumScore::<T>::put(self.minimum_score);
590		}
591	}
592
593	#[pallet::call]
594	impl<T: Config> Pallet<T> {}
595
596	#[pallet::hooks]
597	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
598		fn integrity_test() {
599			// ensure that we have funneled some of our type parameters EXACTLY as-is to the
600			// verifier trait interface we implement.
601			assert_eq!(T::MaxWinnersPerPage::get(), <Self as Verifier>::MaxWinnersPerPage::get());
602			assert_eq!(
603				T::MaxBackersPerWinner::get(),
604				<Self as Verifier>::MaxBackersPerWinner::get()
605			);
606			assert!(T::MaxBackersPerWinner::get() <= T::MaxBackersPerWinnerFinal::get());
607		}
608
609		#[cfg(feature = "try-runtime")]
610		fn try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
611			Self::do_try_state(_now)
612		}
613	}
614}
615
616impl<T: Config> Pallet<T> {
617	fn do_per_block_exec() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
618		let Status::Ongoing(current_page) = Self::status_storage() else {
619			let weight = T::DbWeight::get().reads(1);
620			return (weight, Box::new(move |meter: &mut WeightMeter| meter.consume(weight)))
621		};
622
623		// before executing, we don't know which weight we will consume; return the max.
624		let worst_case_weight = VerifierWeightsOf::<T>::verification_valid_non_terminal()
625			.max(VerifierWeightsOf::<T>::verification_valid_terminal())
626			.max(VerifierWeightsOf::<T>::verification_invalid_non_terminal(T::Pages::get()))
627			.max(VerifierWeightsOf::<T>::verification_invalid_terminal());
628
629		let execute = Box::new(move |meter: &mut WeightMeter| {
630			let page_solution =
631				<T::SolutionDataProvider as SolutionDataProvider>::get_page(current_page);
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			match maybe_supports {
642				Ok(supports) => {
643					Self::deposit_event(Event::<T>::Verified(current_page, supports.len() as u32));
644					QueuedSolution::<T>::set_invalid_page(current_page, supports);
645
646					if current_page > crate::Pallet::<T>::lsp() {
647						// not last page, just move forward.
648						StatusStorage::<T>::put(Status::Ongoing(
649							current_page.defensive_saturating_sub(1),
650						));
651						meter.consume(VerifierWeightsOf::<T>::verification_valid_non_terminal())
652					} else {
653						// last page, finalize everything. Get the claimed score.
654						let claimed_score = T::SolutionDataProvider::get_score();
655
656						// in both cases of the following match, we are back to the nothing
657						// state.
658						StatusStorage::<T>::put(Status::Nothing);
659
660						match Self::finalize_async_verification(claimed_score) {
661							Ok(_) => {
662								T::SolutionDataProvider::report_result(VerificationResult::Queued);
663								meter.consume(VerifierWeightsOf::<T>::verification_valid_terminal())
664							},
665							Err(_) => {
666								T::SolutionDataProvider::report_result(
667									VerificationResult::Rejected,
668								);
669								// In case of any of the errors, kill the solution.
670								QueuedSolution::<T>::clear_invalid_and_backings();
671								meter
672									.consume(VerifierWeightsOf::<T>::verification_invalid_terminal())
673							},
674						}
675					}
676				},
677				Err(err) => {
678					// the page solution was invalid.
679					Self::deposit_event(Event::<T>::VerificationFailed(current_page, err));
680
681					sublog!(warn, "verifier", "Clearing any ongoing unverified solution.");
682					// Clear any ongoing solution that has not been verified, regardless of
683					// the current state.
684					QueuedSolution::<T>::clear_invalid_and_backings_unchecked();
685
686					// we also mutate the status back to doing nothing.
687					let was_ongoing = matches!(StatusStorage::<T>::get(), Status::Ongoing(_));
688					StatusStorage::<T>::put(Status::Nothing);
689
690					if was_ongoing {
691						T::SolutionDataProvider::report_result(VerificationResult::Rejected);
692					}
693					let wasted_pages = T::Pages::get().saturating_sub(current_page);
694					meter.consume(VerifierWeightsOf::<T>::verification_invalid_non_terminal(
695						wasted_pages,
696					))
697				},
698			}
699		});
700
701		(worst_case_weight, execute)
702	}
703
704	fn do_verify_synchronous_multi(
705		partial_solutions: Vec<SolutionOf<T::MinerConfig>>,
706		solution_pages: Vec<PageIndex>,
707		claimed_score: ElectionScore,
708	) -> Result<(), (PageIndex, FeasibilityError)> {
709		let first_page = solution_pages.first().cloned().unwrap_or_default();
710		let last_page = solution_pages.last().cloned().unwrap_or_default();
711		// first, ensure this score will be good enough, even if valid.
712		let _ = Self::ensure_score_quality(claimed_score).map_err(|fe| (first_page, fe))?;
713		ensure!(
714			partial_solutions.len() == solution_pages.len(),
715			(first_page, FeasibilityError::Incomplete)
716		);
717
718		// verify each page, and amalgamate into a final support.
719		let mut backings =
720			sp_std::collections::btree_map::BTreeMap::<T::AccountId, PartialBackings>::new();
721		let mut linked_supports = Vec::with_capacity(partial_solutions.len());
722
723		for (solution_page, page) in partial_solutions.into_iter().zip(solution_pages.iter()) {
724			let page_supports = Self::feasibility_check_page_inner(solution_page, *page)
725				.map_err(|fe| (*page, fe))?;
726
727			linked_supports.push(page_supports.clone());
728			let support_len = page_supports.len() as u32;
729			for (who, support) in page_supports.into_iter() {
730				let entry = backings.entry(who).or_default();
731				entry.total = entry.total.saturating_add(support.total);
732				// Note we assume snapshots are always disjoint, and therefore we can easily extend
733				// here.
734				entry.backers = entry.backers.saturating_add(support.voters.len() as u32);
735				if entry.backers > T::MaxBackersPerWinnerFinal::get() {
736					return Err((*page, FeasibilityError::FailedToBoundSupport))
737				}
738			}
739
740			Self::deposit_event(Event::<T>::Verified(*page, support_len));
741		}
742
743		// then check that the number of winners was exactly enough..
744		let desired_targets = crate::Snapshot::<T>::desired_targets()
745			.ok_or(FeasibilityError::SnapshotUnavailable)
746			.map_err(|fe| (last_page, fe))?;
747		ensure!(
748			backings.len() as u32 == desired_targets,
749			(last_page, FeasibilityError::WrongWinnerCount)
750		);
751
752		// then check the score was truth..
753		let truth_score = evaluate_support(backings.into_values());
754		ensure!(truth_score == claimed_score, (last_page, FeasibilityError::InvalidScore));
755
756		let maybe_current_score = QueuedSolution::<T>::queued_score();
757
758		// then store it.
759		sublog!(
760			debug,
761			"verifier",
762			"queued sync solution with score {:?} for pages {:?}",
763			truth_score,
764			solution_pages
765		);
766		QueuedSolution::<T>::force_set_multi_page_valid(
767			solution_pages,
768			linked_supports,
769			truth_score,
770		);
771		Self::deposit_event(Event::<T>::Queued(truth_score, maybe_current_score));
772
773		Ok(())
774	}
775
776	/// Finalize an asynchronous verification. Checks the final score for correctness, and ensures
777	/// that it matches all of the criteria.
778	///
779	/// This should only be called when all pages of an async verification are done.
780	///
781	/// Returns:
782	/// - `Ok()` if everything is okay, at which point the valid variant of the queued solution will
783	/// be updated. Returns
784	/// - `Err(Feasibility)` if any of the last verification steps fail.
785	fn finalize_async_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> {
786		let outcome = QueuedSolution::<T>::compute_invalid_score()
787			.and_then(|(final_score, winner_count)| {
788				let desired_targets =
789					crate::Snapshot::<T>::desired_targets().defensive_unwrap_or(u32::MAX);
790				// claimed_score checked prior in seal_unverified_solution
791				match (final_score == claimed_score, winner_count == desired_targets) {
792					(true, true) => {
793						// all good, finalize this solution
794						// NOTE: must be before the call to `finalize_correct`.
795						Self::deposit_event(Event::<T>::Queued(
796							final_score,
797							QueuedSolution::<T>::queued_score(), /* the previous score, now
798							                                      * ejected. */
799						));
800						QueuedSolution::<T>::finalize_correct(final_score);
801						Ok(())
802					},
803					(false, true) => Err(FeasibilityError::InvalidScore),
804					(true, false) => Err(FeasibilityError::WrongWinnerCount),
805					(false, false) => Err(FeasibilityError::InvalidScore),
806				}
807			})
808			.map_err(|err| {
809				sublog!(warn, "verifier", "Finalizing solution was invalid due to {:?}.", err);
810				// and deposit an event about it.
811				Self::deposit_event(Event::<T>::VerificationFailed(0, err.clone()));
812				err
813			});
814		sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome);
815		outcome
816	}
817
818	/// Ensure that the given score is:
819	///
820	/// - better than the queued solution, if one exists.
821	/// - greater than the minimum untrusted score.
822	pub(crate) fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> {
823		let is_improvement = <Self as Verifier>::queued_score()
824			.map_or(true, |best_score| score.strict_better(best_score));
825		ensure!(is_improvement, FeasibilityError::ScoreTooLow);
826
827		let is_greater_than_min_untrusted =
828			Self::minimum_score().map_or(true, |min_score| score.strict_better(min_score));
829		ensure!(is_greater_than_min_untrusted, FeasibilityError::ScoreTooLow);
830
831		Ok(())
832	}
833
834	/// Do the full feasibility check:
835	///
836	/// - check all edges.
837	/// - checks `MaxBackersPerWinner` to be respected IN THIS PAGE.
838	/// - checks the number of winners to be less than or equal to `DesiredTargets` IN THIS PAGE
839	///   ONLY.
840	pub(super) fn feasibility_check_page_inner(
841		partial_solution: SolutionOf<T::MinerConfig>,
842		page: PageIndex,
843	) -> Result<SupportsOfVerifier<Self>, FeasibilityError> {
844		// Read the corresponding snapshots.
845		let snapshot_targets =
846			crate::Snapshot::<T>::targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
847		let snapshot_voters =
848			crate::Snapshot::<T>::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?;
849		let desired_targets =
850			crate::Snapshot::<T>::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
851
852		feasibility_check_page_inner_with_snapshot::<T::MinerConfig>(
853			partial_solution,
854			&snapshot_voters,
855			&snapshot_targets,
856			desired_targets,
857		)
858		.and_then(|miner_supports| {
859			SupportsOfVerifier::<Self>::try_from_other_bounds(miner_supports)
860				.defensive_map_err(|_| FeasibilityError::FailedToBoundSupport)
861		})
862	}
863
864	#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
865	pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
866		QueuedSolution::<T>::sanity_check()
867	}
868}
869
870/// Same as `feasibility_check_page_inner`, but with a snapshot.
871///
872/// This is exported as a standalone function, relying on `MinerConfig` rather than `Config` so that
873/// it can be used in any offchain miner.
874pub fn feasibility_check_page_inner_with_snapshot<T: MinerConfig>(
875	partial_solution: SolutionOf<T>,
876	snapshot_voters: &BoundedVec<VoterOf<T>, T::VoterSnapshotPerBlock>,
877	snapshot_targets: &BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
878	desired_targets: u32,
879) -> Result<PageSupportsOfMiner<T>, FeasibilityError> {
880	// ----- Start building. First, we need some closures.
881	let cache = helpers::generate_voter_cache::<T, _>(snapshot_voters);
882	let voter_at = helpers::voter_at_fn::<T>(snapshot_voters);
883	let target_at = helpers::target_at_fn::<T>(snapshot_targets);
884	let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
885
886	// Then convert solution -> assignment. This will fail if any of the indices are
887	// gibberish. It will also ensure each assignemnt (voter) is unique, and all targets within it
888	// are unique.
889	let assignments = partial_solution
890		.into_assignment(voter_at, target_at)
891		.map_err::<FeasibilityError, _>(Into::into)?;
892
893	// Ensure that assignments are all correct.
894	let _ = assignments
895		.iter()
896		.map(|ref assignment| {
897			// Check that assignment.who is actually a voter (defensive-only). NOTE: while
898			// using the index map from `voter_index` is better than a blind linear search,
899			// this *still* has room for optimization. Note that we had the index when we
900			// did `solution -> assignment` and we lost it. Ideal is to keep the index
901			// around.
902
903			// Defensive-only: must exist in the snapshot.
904			let snapshot_index =
905				voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
906			// Defensive-only: index comes from the snapshot, must exist.
907			let (_voter, _stake, targets) =
908				snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
909			debug_assert!(*_voter == assignment.who);
910
911			// Check that all of the targets are valid based on the snapshot.
912			if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) {
913				return Err(FeasibilityError::InvalidVote)
914			}
915			Ok(())
916		})
917		.collect::<Result<(), FeasibilityError>>()?;
918
919	// ----- Start building support. First, we need one more closure.
920	let stake_of = helpers::stake_of_fn::<T, _>(&snapshot_voters, &cache);
921
922	// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
923	let staked_assignments =
924		sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of)
925			.map_err::<FeasibilityError, _>(Into::into)?;
926
927	let supports = sp_npos_elections::to_supports(&staked_assignments);
928
929	// Ensure some heuristics. These conditions must hold in the **entire** support, this is
930	// just a single page. But, they must hold in a single page as well.
931	ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount);
932
933	// almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of
934	// `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which
935	// is ALSO checked, so this conversion can almost never fail.
936	let bounded_supports =
937		supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?;
938	Ok(bounded_supports)
939}
940
941impl<T: Config> Verifier for Pallet<T> {
942	type AccountId = T::AccountId;
943	type Solution = SolutionOf<T::MinerConfig>;
944	type MaxBackersPerWinner = T::MaxBackersPerWinner;
945	type MaxWinnersPerPage = T::MaxWinnersPerPage;
946	type MaxBackersPerWinnerFinal = T::MaxBackersPerWinnerFinal;
947
948	fn set_minimum_score(score: ElectionScore) {
949		MinimumScore::<T>::put(score);
950	}
951
952	fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool {
953		Self::ensure_score_quality(claimed_score).is_ok()
954	}
955
956	fn queued_score() -> Option<ElectionScore> {
957		QueuedSolution::<T>::queued_score()
958	}
959
960	fn kill() {
961		QueuedSolution::<T>::kill();
962		<StatusStorage<T>>::put(Status::Nothing);
963	}
964
965	fn get_queued_solution_page(page: PageIndex) -> Option<SupportsOfVerifier<Self>> {
966		QueuedSolution::<T>::get_queued_solution_page(page)
967	}
968
969	fn verify_synchronous_multi(
970		partial_solutions: Vec<Self::Solution>,
971		solution_pages: Vec<PageIndex>,
972		claimed_score: ElectionScore,
973	) -> Result<(), FeasibilityError> {
974		Self::do_verify_synchronous_multi(partial_solutions, solution_pages, claimed_score).map_err(
975			|(page, fe)| {
976				sublog!(
977					warn,
978					"verifier",
979					"sync verification of page {:?} failed due to {:?}.",
980					page,
981					fe
982				);
983				Self::deposit_event(Event::<T>::VerificationFailed(page, fe.clone()));
984				fe
985			},
986		)
987	}
988
989	fn force_set_single_page_valid(
990		partial_supports: SupportsOfVerifier<Self>,
991		page: PageIndex,
992		score: ElectionScore,
993	) {
994		Self::deposit_event(Event::<T>::Queued(score, QueuedSolution::<T>::queued_score()));
995		QueuedSolution::<T>::force_set_single_page_valid(page, partial_supports, score);
996	}
997
998	fn per_block_exec() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
999		Self::do_per_block_exec()
1000	}
1001}
1002
1003impl<T: Config> AsynchronousVerifier for Pallet<T> {
1004	type SolutionDataProvider = T::SolutionDataProvider;
1005
1006	fn status() -> Status {
1007		Pallet::<T>::status_storage()
1008	}
1009
1010	fn start() -> Result<(), &'static str> {
1011		sublog!(debug, "verifier", "start signal received.");
1012		if let Status::Nothing = Self::status() {
1013			let claimed_score = Self::SolutionDataProvider::get_score();
1014			if Self::ensure_score_quality(claimed_score).is_err() {
1015				// don't do anything, report back that this solution was garbage.
1016				Self::deposit_event(Event::<T>::VerificationFailed(
1017					crate::Pallet::<T>::msp(),
1018					FeasibilityError::ScoreTooLow,
1019				));
1020				T::SolutionDataProvider::report_result(VerificationResult::Rejected);
1021				// Despite being an instant-reject, this was a successful `start` operation.
1022				Ok(())
1023			} else {
1024				// This solution is good enough to win, we start verifying it in the next block.
1025				StatusStorage::<T>::put(Status::Ongoing(crate::Pallet::<T>::msp()));
1026				Ok(())
1027			}
1028		} else {
1029			sublog!(warn, "verifier", "start signal received while busy. This will be ignored.");
1030			Err("verification ongoing")
1031		}
1032	}
1033}