referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_block/
lib.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//! # Multi-phase, multi-block, election provider pallet.
19//!
20//! > This pallet is sometimes abbreviated as `EPMB`, and `pallet_election_provider_multi_phase` as
21//! > `EPM`.
22//!
23//! ## Overall idea
24//!
25//! `pallet_election_provider_multi_phase` provides the basic ability for NPoS solutions to be
26//! computed offchain (essentially anywhere) and submitted back to the chain as signed or unsigned
27//! transaction, with sensible configurations and fail-safe mechanisms to ensure system safety.
28//! Nonetheless, it has a limited capacity in terms of number of voters it can process in a **single
29//! block**.
30//!
31//! This pallet takes `EPM` system, keeps most of its ideas and core premises, and extends it to
32//! support paginated, multi-block operations. The final goal of this pallet is to scale linearly
33//! with the number of blocks allocated to the elections. Moreover, the amount of work that it does
34//! in one block should be bounded and measurable, making it suitable for a parachain. In principle,
35//! with large enough blocks (in a dedicated parachain), the number of voters included in the NPoS
36//! system can grow significantly (yet, obviously not indefinitely).
37//!
38//! Note that this pallet does not consider how the recipient is processing the results. To ensure
39//! scalability, the recipient of this pallet's data (i.e. `pallet-staking`) must also be capable of
40//! pagination and multi-block processing.
41//!
42//! ## Companion pallets
43//!
44//! This pallet will only function in a sensible way if it is peered with its companion pallets.
45//!
46//! - The [`verifier`] pallet provides a standard implementation of the [`verifier::Verifier`].
47//! - The [`unsigned`] module provides the implementation of unsigned submission by validators. If
48//!   this pallet is included, then [`Config::UnsignedPhase`] will determine its duration.
49//! - The [`signed`] module provides the implementation of the signed submission by any account. If
50//!   this pallet is included, the combined [`Config::SignedPhase`] and
51//!   [`Config::SignedValidationPhase`] will determine its duration
52//!
53//! These pallets are in fact hierarchical. This particular one is the top level one. It contains
54//! the shared information that all child pallets use. All child pallets depend on the top level
55//! pallet ONLY, but not the other way around. For those cases, traits are used.
56//!
57//! For reverse linking, or child-linking, only explicit traits with clear interfaces are used. For
58//! example, the following traits facilitate other communication:
59//!
60//! * [`crate::types::SignedInterface`]: Parent talking to signed.
61//! * [`crate::verifier::Verifier`]: Parent talking to verifier.
62//! * [`crate::verifier::SolutionDataProvider`]: Verifier talking to signed.
63
64//!
65//! ## Pagination
66//!
67//! Most of the external APIs of this pallet are paginated. All pagination follow a pattern where if
68//! `N` pages exist, the first paginated call is `function(N-1)` and the last one is `function(0)`.
69//! For example, with 3 pages, the `elect` of [`ElectionProvider`] is expected to be called as
70//! `elect(2) -> elect(1) -> elect(0)`. In essence, calling a paginated function with index 0 is
71//! always a signal of termination, meaning that no further calls will follow.
72//!
73//! The snapshot creation for voters (Nominators in staking), submission of signed pages, validation
74//! of signed solutions and exporting of pages are all paginated. Note that this pallet is yet to
75//! support paginated target (Validators in staking) snapshotting.
76//!
77//! ### Terminology Note: `msp` and `lsp`
78//!
79//! Stand for _most significant page_ (n-1) and _least significant page_ (0).
80//!
81//! See [`ElectionProvider::msp`] and [`ElectionProvider::lsp`], and their usage.
82//!
83//! ## Phases
84//!
85//! The operations in this pallet are divided into rounds, a `u32` number stored in [`Round`].
86//! This value helps this pallet organize itself, and leaves the door open for lazy deletion of any
87//! stale data. A round, under the happy path, starts by receiving the call to
88//! [`ElectionProvider::start`], and is terminated by receiving a call to
89//! [`ElectionProvider::elect`] with value 0.
90//!
91//! The timeline of pallet is overall as follows:
92//!
93//! ```ignore
94//!  <  Off  >
95//! 0 ------------ 12 13 14 15 ----------- 20 ---------25 ------- 30
96//! 	           |       |              |            |          |
97//! 	     Snapshot      Signed   SignedValidation  Unsigned   Elect
98//! ```
99//!
100//! * Duration of `Snapshot` is determined by [`Config::Pages`] + 1.
101//! 	* Whereby in the first page we take the "Targets" snapshot, and in the subsequent pages we take
102//!    the voter snapshot.
103//! 	* For example, with `Pages = 4`:
104//! 		* `Snapshot(4)` -> `Targets(all)`
105//! 		* `Snapshot(3)` -> `Voters(3)`
106//! 		* `Snapshot(2)` -> `Voters(2)`
107//! 		* `Snapshot(1)` -> `Voters(1)`
108//! 		* `Snapshot(0)` -> `Voters(0)`
109//! * Duration of `Signed`, `SignedValidation` and `Unsigned` are determined by
110//!   [`Config::SignedPhase`], [`Config::SignedValidationPhase`] and [`Config::UnsignedPhase`]
111//!   respectively.
112//! * [`Config::Pages`] calls to elect are expected, but all in all the pallet will close a round
113//!   once `elect(0)` is called.
114//!
115//! > Given this, it is rather important for the user of this pallet to ensure it always terminates
116//! > election via `elect` before requesting a new one.
117//!
118//! ### Phase Transition
119//!
120//! Within all 4 pallets only the parent pallet is allowed to move the phases forward. As of now,
121//! the transition happens `on-poll`, ensuring that we don't consume too much weight. The parent
122//! pallet is in charge of aggregating the work to be done by all pallets, checking if it can fit
123//! within the current block's weight limits, and executing it if so.
124//!
125//! Occasional phase transition stalling is not a critical issue. Every instance of phase transition
126//! failing is accompanied by a [`Event::UnexpectedPhaseTransitionOutOfWeight`] for visibility.
127//!
128//! Note this pallet transitions phases all the way into [`crate::types::Phase::Done`]. At this
129//! point, we will move to `Export` phase an onwards by calls into `elect`. A call to `elect(0)`
130//! rotates the round, as stated above.
131//!
132//! ## Feasible Solution (correct solution)
133//!
134//! All submissions must undergo a feasibility check. Signed solutions are checked one by one at the
135//! end of the signed phase, and the unsigned solutions are checked on the spot. A feasible solution
136//! is as follows:
137//!
138//! 0. **all** of the used indices must be correct.
139//! 1. present *exactly* correct number of winners.
140//! 2. any assignment is checked to match with `PagedVoterSnapshot`.
141//! 3. the claimed score is valid, based on the fixed point arithmetic accuracy.
142//!
143//! More about this in [`verifier`], who is responsible for doing all of the above.
144//!
145//! ### Fallback and Emergency
146//!
147//! If at any page, [`ElectionProvider::elect`] fails, a call with the same page-index is dispatched
148//! to [`Config::Fallback`]. [`Config::Fallback`] is itself (yet) another implementation of
149//! [`ElectionProvider`], and can decide to do anything, but a few reasonable options are provided
150//! here:
151//!
152//! 1. Do nothing: [`Continue`]
153//! 2. Force us into the emergency phase: [`crate::InitiateEmergencyPhase`]. This initiates
154//!    [`Phase::Emergency`], which will halt almost all operations of this pallet, and it can only
155//!    be recovered by [`AdminOperation`], dispatched via [`Call::manage`].
156//! 3. compute an onchain from the give page of snapshot.
157//!
158//! Note that configuring the fallback to be onchain computation is not recommended, unless for
159//! test-nets for a number of reasons:
160//!
161//! 1. The solution score of fallback is never checked to match the "minimum" score. That being
162//!    said, the computation happens onchain so we can trust it.
163//! 2. The onchain fallback runs on the same number of voters and targets that reside on a single
164//!    page of a snapshot, which will very likely be too much for actual onchain computation. Yet,
165//!    we don't have another choice as we cannot request another smaller snapshot from the data
166//!    provider mid-election without more bookkeeping on the staking side.
167//!
168//! If onchain solution is to be seriously considered, an improvement to this pallet should
169//! re-request a smaller set of voters from `T::DataProvider` in a stateless manner.
170//!
171//! ### Signed Phase
172//!
173//! Signed phase is when an offchain miner, aka, `polkadot-staking-miner` should operate upon. See
174//! [`signed`] for more information.
175//!
176//! ## Unsigned Phase
177//!
178//! Unsigned phase is a built-in fallback in which validators may submit a single page election,
179//! taking into account only the [`ElectionProvider::msp`] (_most significant page_). See
180//! [`crate::unsigned`] for more information.
181
182// Implementation notes:
183//
184// - Naming convention is: `${singular}_page` for singular, e.g. `voter_page` for `Vec<Voter>`.
185//   `paged_${plural}` for plural, e.g. `paged_voters` for `Vec<Vec<Voter>>`.
186//
187// - Since this crate has multiple `Pallet` and `Configs`, in each sub-pallet, we only reference the
188//   local `Pallet` without a prefix and allow it to be imported via `use`. Avoid `super::Pallet`
189//   except for the case of a modules that want to reference their local `Pallet` . The
190//   `crate::Pallet` is always reserved for the parent pallet. Other sibling pallets must be
191//   referenced with full path, e.g. `crate::Verifier::Pallet`. Do NOT write something like `use
192//   unsigned::Pallet as UnsignedPallet`.
193//
194// - Respecting private storage items with wrapper We move all implementations out of the `mod
195//   pallet` as much as possible to ensure we NEVER access the internal storage items directly. All
196//   operations should happen with the wrapper types.
197
198#![cfg_attr(not(feature = "std"), no_std)]
199
200#[cfg(any(feature = "runtime-benchmarks", test))]
201use crate::signed::{CalculateBaseDeposit, CalculatePageDeposit};
202use crate::verifier::{AsynchronousVerifier, Verifier};
203use codec::{Decode, Encode, MaxEncodedLen};
204use frame_election_provider_support::{
205	onchain, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
206	InstantElectionProvider,
207};
208use frame_support::{
209	dispatch::PostDispatchInfo,
210	pallet_prelude::*,
211	traits::{Defensive, EnsureOrigin},
212	weights::WeightMeter,
213	DebugNoBound, Twox64Concat,
214};
215use frame_system::pallet_prelude::*;
216use scale_info::TypeInfo;
217use sp_arithmetic::{
218	traits::{CheckedAdd, Zero},
219	PerThing, UpperOf,
220};
221use sp_npos_elections::{EvaluateSupport, VoteWeight};
222use sp_runtime::{
223	traits::{Hash, Saturating},
224	SaturatedConversion,
225};
226use sp_std::{borrow::ToOwned, boxed::Box, prelude::*};
227
228#[cfg(test)]
229mod mock;
230#[macro_use]
231pub mod helpers;
232#[cfg(feature = "runtime-benchmarks")]
233pub mod benchmarking;
234
235/// The common logging prefix of all pallets in this crate.
236pub const LOG_PREFIX: &'static str = "runtime::multiblock-election";
237
238macro_rules! clear_round_based_map {
239	($map: ty, $round: expr) => {{
240		let __r = <$map>::clear_prefix($round, u32::MAX, None);
241		debug_assert!(__r.unique <= T::Pages::get(), "clearing map caused too many removals")
242	}};
243}
244
245/// The signed pallet
246pub mod signed;
247/// Common types of the pallet
248pub mod types;
249/// The unsigned pallet
250pub mod unsigned;
251/// The verifier pallet
252pub mod verifier;
253/// The weight module
254pub mod weights;
255
256pub use pallet::*;
257pub use types::*;
258pub use weights::traits::pallet_election_provider_multi_block::WeightInfo;
259
260/// A fallback implementation that transitions the pallet to the emergency phase.
261pub struct InitiateEmergencyPhase<T>(sp_std::marker::PhantomData<T>);
262impl<T: Config> ElectionProvider for InitiateEmergencyPhase<T> {
263	type AccountId = T::AccountId;
264	type BlockNumber = BlockNumberFor<T>;
265	type DataProvider = T::DataProvider;
266	type Error = &'static str;
267	type Pages = T::Pages;
268	type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
269	type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
270	type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
271
272	fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
273		Pallet::<T>::phase_transition(Phase::Emergency);
274		Err("Emergency phase started.")
275	}
276
277	fn status() -> Result<Option<Weight>, ()> {
278		Ok(Some(Default::default()))
279	}
280
281	fn start() -> Result<(), Self::Error> {
282		Ok(())
283	}
284
285	fn duration() -> Self::BlockNumber {
286		Zero::zero()
287	}
288}
289
290impl<T: Config> InstantElectionProvider for InitiateEmergencyPhase<T> {
291	fn instant_elect(
292		_voters: Vec<VoterOf<T::MinerConfig>>,
293		_targets: Vec<Self::AccountId>,
294		_desired_targets: u32,
295	) -> Result<BoundedSupportsOf<Self>, Self::Error> {
296		Self::elect(0)
297	}
298
299	fn bother() -> bool {
300		false
301	}
302}
303
304/// A fallback implementation that silently continues into the next page.
305///
306/// This is suitable for onchain usage.
307pub struct Continue<T>(sp_std::marker::PhantomData<T>);
308impl<T: Config> ElectionProvider for Continue<T> {
309	type AccountId = T::AccountId;
310	type BlockNumber = BlockNumberFor<T>;
311	type DataProvider = T::DataProvider;
312	type Error = &'static str;
313	type Pages = T::Pages;
314	type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
315	type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
316	type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
317
318	fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
319		Err("'Continue' fallback will do nothing")
320	}
321
322	fn start() -> Result<(), Self::Error> {
323		Ok(())
324	}
325
326	fn duration() -> Self::BlockNumber {
327		Zero::zero()
328	}
329
330	fn status() -> Result<Option<Weight>, ()> {
331		Ok(Some(Default::default()))
332	}
333}
334
335impl<T: Config> InstantElectionProvider for Continue<T> {
336	fn instant_elect(
337		_voters: Vec<VoterOf<T::MinerConfig>>,
338		_targets: Vec<Self::AccountId>,
339		_desired_targets: u32,
340	) -> Result<BoundedSupportsOf<Self>, Self::Error> {
341		Self::elect(0)
342	}
343
344	fn bother() -> bool {
345		false
346	}
347}
348
349/// A easy means to configure [`Config::AreWeDone`].
350///
351/// With this, you can say what to do if a solution is queued, or what to do if not.
352///
353/// Two common shorthands of this are provided:
354/// * [`ProceedRegardlessOf`]
355/// * [`RevertToSignedIfNotQueuedOf`]
356pub struct IfSolutionQueuedElse<T, Queued, NotQueued>(
357	sp_std::marker::PhantomData<(T, Queued, NotQueued)>,
358);
359
360/// A `Get` impl for `Phase::Done`
361pub struct GetDone<T>(sp_std::marker::PhantomData<T>);
362impl<T: Config> Get<Phase<T>> for GetDone<T> {
363	fn get() -> Phase<T> {
364		Phase::Done
365	}
366}
367
368/// A `Get` impl for `Phase::Signed(T::SignedPhase::get())`
369pub struct GetSigned<T>(sp_std::marker::PhantomData<T>);
370impl<T: Config> Get<Phase<T>> for GetSigned<T> {
371	fn get() -> Phase<T> {
372		Phase::Signed(T::SignedPhase::get().saturating_sub(1u32.into()))
373	}
374}
375
376/// A shorthand for [`IfSolutionQueuedElse`] that proceeds regardless of the solution being queued.
377pub type ProceedRegardlessOf<T> = IfSolutionQueuedElse<T, GetDone<T>, GetDone<T>>;
378
379/// A shorthand for [`IfSolutionQueuedElse`] that proceeds to `Phase::Done` if the solution is
380/// queued. Otherwise, it proceeds to `Phase::Signed`.
381pub type RevertToSignedIfNotQueuedOf<T> = IfSolutionQueuedElse<T, GetDone<T>, GetSigned<T>>;
382
383impl<T: Config, Queued, NotQueued> IfSolutionQueuedElse<T, Queued, NotQueued> {
384	fn something_queued() -> bool {
385		let queued_score = <T::Verifier as verifier::Verifier>::queued_score().is_some();
386		#[cfg(debug_assertions)]
387		{
388			let any_pages_queued = (Pallet::<T>::lsp()..=Pallet::<T>::msp()).any(|p| {
389				<T::Verifier as verifier::Verifier>::get_queued_solution_page(p).is_some()
390			});
391			assert_eq!(
392				queued_score, any_pages_queued,
393				"queued score ({}) and queued pages ({}) must match",
394				queued_score, any_pages_queued
395			);
396		}
397		queued_score
398	}
399}
400
401impl<T: Config, Queued: Get<Phase<T>>, NotQueued: Get<Phase<T>>> Get<Phase<T>>
402	for IfSolutionQueuedElse<T, Queued, NotQueued>
403{
404	fn get() -> Phase<T> {
405		if Self::something_queued() {
406			Queued::get()
407		} else {
408			NotQueued::get()
409		}
410	}
411}
412
413/// Internal errors of the pallet. This is used in the implementation of [`ElectionProvider`].
414///
415/// Note that this is different from [`pallet::Error`].
416#[derive(
417	frame_support::DebugNoBound, frame_support::PartialEqNoBound, frame_support::EqNoBound,
418)]
419pub enum ElectionError<T: Config> {
420	/// An error happened in the feasibility check sub-system.
421	Feasibility(verifier::FeasibilityError),
422	/// An error in the fallback.
423	Fallback(FallbackErrorOf<T>),
424	/// An error in the onchain seq-phragmen implementation
425	OnChain(onchain::Error),
426	/// An error happened in the data provider.
427	DataProvider(&'static str),
428	/// the corresponding page in the queued supports is not available.
429	SupportPageNotAvailable,
430	/// The election is not ongoing and therefore no results may be queried.
431	NotOngoing,
432	/// The election is currently ongoing, and therefore we cannot start again.
433	Ongoing,
434	/// Called elect() with wrong page order or in wrong phase.
435	OutOfOrder,
436	/// Other misc error
437	Other(&'static str),
438}
439
440impl<T: Config> From<onchain::Error> for ElectionError<T> {
441	fn from(e: onchain::Error) -> Self {
442		ElectionError::OnChain(e)
443	}
444}
445
446impl<T: Config> From<verifier::FeasibilityError> for ElectionError<T> {
447	fn from(e: verifier::FeasibilityError) -> Self {
448		ElectionError::Feasibility(e)
449	}
450}
451
452/// Different operations that only the [`Config::AdminOrigin`] can perform on the pallet.
453#[derive(
454	Encode,
455	Decode,
456	DecodeWithMemTracking,
457	MaxEncodedLen,
458	TypeInfo,
459	DebugNoBound,
460	CloneNoBound,
461	PartialEqNoBound,
462	EqNoBound,
463)]
464#[codec(mel_bound(T: Config))]
465#[scale_info(skip_type_params(T))]
466pub enum AdminOperation<T: Config> {
467	/// Set the given (single page) emergency solution.
468	///
469	/// Can only be called in emergency phase.
470	EmergencySetSolution(Box<BoundedSupportsOf<Pallet<T>>>, ElectionScore),
471	/// Set the minimum untrusted score. This is directly communicated to the verifier component to
472	/// be taken into account.
473	///
474	/// This is useful in preventing any serious issue where due to a bug we accept a very bad
475	/// solution.
476	SetMinUntrustedScore(ElectionScore),
477}
478
479/// Different operations that the [`Config::ManagerOrigin`] (or [`Config::AdminOrigin`]) can perform
480/// on the pallet.
481#[derive(
482	Encode,
483	Decode,
484	DecodeWithMemTracking,
485	MaxEncodedLen,
486	TypeInfo,
487	DebugNoBound,
488	CloneNoBound,
489	PartialEqNoBound,
490	EqNoBound,
491)]
492#[codec(mel_bound(T: Config))]
493#[scale_info(skip_type_params(T))]
494pub enum ManagerOperation<T: Config> {
495	/// Forcefully go to the next round, starting from the Off Phase.
496	ForceRotateRound,
497	/// Force-set the phase to the given phase.
498	///
499	/// This can have many many combinations, use only with care and sufficient testing.
500	ForceSetPhase(Phase<T>),
501	/// Trigger the (single page) fallback in `instant` mode, with the given parameters, and
502	/// queue it if correct.
503	///
504	/// Can only be called in emergency phase.
505	EmergencyFallback,
506}
507
508/// Trait to notify other sub-systems that a round has ended.
509pub trait OnRoundRotation {
510	/// `ending` round has ended. Implies we are now at round `ending + 1`
511	fn on_round_rotation(ending: u32);
512}
513
514impl OnRoundRotation for () {
515	fn on_round_rotation(_: u32) {}
516}
517
518/// An implementation of [`OnRoundRotation`] that immediately deletes all the data in all the
519/// pallets, once the round is over.
520///
521/// This is intended to be phased out once we move to fully lazy deletion system to spare more PoV.
522/// In that case, simply use `()` on [`pallet::Config::OnRoundRotation`].
523pub struct CleanRound<T>(core::marker::PhantomData<T>);
524impl<T: Config> OnRoundRotation for CleanRound<T> {
525	fn on_round_rotation(_ending: u32) {
526		// Kill everything in the verifier.
527		T::Verifier::kill();
528
529		// Kill the snapshot.
530		pallet::Snapshot::<T>::kill();
531
532		// Nothing to do in the signed pallet -- it is already in lazy-deletion mode.
533	}
534}
535
536#[frame_support::pallet]
537pub mod pallet {
538	use super::*;
539
540	#[pallet::config]
541	pub trait Config: frame_system::Config {
542		/// Duration of the unsigned phase.
543		#[pallet::constant]
544		type UnsignedPhase: Get<BlockNumberFor<Self>>;
545		/// Duration of the signed phase.
546		#[pallet::constant]
547		type SignedPhase: Get<BlockNumberFor<Self>>;
548		/// Duration of the singed validation phase.
549		///
550		/// The duration of this should not be less than `T::Pages`, and there is no point in it
551		/// being more than `SignedPhase::MaxSubmission::get() * T::Pages`.
552		#[pallet::constant]
553		type SignedValidationPhase: Get<BlockNumberFor<Self>>;
554
555		/// The number of snapshot voters to fetch per block.
556		#[pallet::constant]
557		type VoterSnapshotPerBlock: Get<u32>;
558
559		/// The number of snapshot targets to fetch per block.
560		#[pallet::constant]
561		type TargetSnapshotPerBlock: Get<u32>;
562
563		/// The number of pages.
564		///
565		/// The snapshot is created with this many keys in the storage map.
566		///
567		/// The solutions may contain at MOST this many pages, but less pages are acceptable as
568		/// well.
569		#[pallet::constant]
570		type Pages: Get<PageIndex>;
571
572		/// Something that will provide the election data.
573		type DataProvider: ElectionDataProvider<
574			AccountId = Self::AccountId,
575			BlockNumber = BlockNumberFor<Self>,
576		>;
577
578		/// The miner configuration.
579		///
580		/// These configurations are passed to [`crate::unsigned::miner::BaseMiner`]. An external
581		/// miner implementation should implement this trait, and use the said `BaseMiner`.
582		type MinerConfig: crate::unsigned::miner::MinerConfig<
583			Pages = Self::Pages,
584			AccountId = <Self as frame_system::Config>::AccountId,
585			MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
586			VoterSnapshotPerBlock = Self::VoterSnapshotPerBlock,
587			TargetSnapshotPerBlock = Self::TargetSnapshotPerBlock,
588			MaxBackersPerWinner = <Self::Verifier as verifier::Verifier>::MaxBackersPerWinner,
589			MaxWinnersPerPage = <Self::Verifier as verifier::Verifier>::MaxWinnersPerPage,
590		>;
591
592		/// The fallback type used for the election.
593		type Fallback: InstantElectionProvider<
594			AccountId = Self::AccountId,
595			BlockNumber = BlockNumberFor<Self>,
596			DataProvider = Self::DataProvider,
597			MaxBackersPerWinner = <Self::Verifier as verifier::Verifier>::MaxBackersPerWinner,
598			MaxWinnersPerPage = <Self::Verifier as verifier::Verifier>::MaxWinnersPerPage,
599		>;
600
601		/// The verifier pallet's interface.
602		type Verifier: verifier::Verifier<
603				Solution = SolutionOf<Self::MinerConfig>,
604				AccountId = Self::AccountId,
605			> + verifier::AsynchronousVerifier;
606
607		/// Interface signed pallet's interface.
608		type Signed: SignedInterface;
609
610		/// The origin that can perform administration operations on this pallet.
611		///
612		/// This is the highest privilege origin of this pallet, and should be configured
613		/// restrictively.
614		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
615
616		/// A less privileged origin than [`Config::AdminOrigin`], that can manage some election
617		/// aspects, without critical power.
618		type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
619
620		/// An indicator of whether we should move to do the [`crate::types::Phase::Done`] or not?
621		/// This is called at the end of the election process.
622		///
623		/// Common implementation is [`ProceedRegardlessOf`] or [`RevertToSignedIfNotQueuedOf`].
624		type AreWeDone: Get<Phase<Self>>;
625
626		/// The weight of the pallet.
627		type WeightInfo: WeightInfo;
628
629		/// Single type that implement [`super::OnRoundRotation`] to do something when the round
630		/// ends.
631		type OnRoundRotation: super::OnRoundRotation;
632	}
633
634	#[pallet::call]
635	impl<T: Config> Pallet<T> {
636		/// Manage this pallet.
637		///
638		/// The origin of this call must be [`Config::ManagerOrigin`].
639		///
640		/// See [`ManagerOperation`] for various operations that are possible.
641		#[pallet::weight(T::WeightInfo::manage_fallback().max(T::WeightInfo::export_terminal()))]
642		#[pallet::call_index(0)]
643		pub fn manage(origin: OriginFor<T>, op: ManagerOperation<T>) -> DispatchResultWithPostInfo {
644			T::ManagerOrigin::ensure_origin(origin.clone()).map(|_| ()).or_else(|_| {
645				// try admin origin as well as admin is a superset.
646				T::AdminOrigin::ensure_origin(origin).map(|_| ())
647			})?;
648			match op {
649				ManagerOperation::EmergencyFallback => {
650					ensure!(Self::current_phase() == Phase::Emergency, Error::<T>::UnexpectedPhase);
651					// note: for now we run this on the msp, but we can make it configurable if need
652					// be.
653					let voters = Snapshot::<T>::voters(Self::msp()).ok_or(Error::<T>::Snapshot)?;
654					let targets = Snapshot::<T>::targets().ok_or(Error::<T>::Snapshot)?;
655					let desired_targets =
656						Snapshot::<T>::desired_targets().ok_or(Error::<T>::Snapshot)?;
657					let fallback = T::Fallback::instant_elect(
658						voters.into_inner(),
659						targets.into_inner(),
660						desired_targets,
661					)
662					.map_err(|e| {
663						log!(warn, "Fallback failed: {:?}", e);
664						Error::<T>::Fallback
665					})?;
666					let score = fallback.evaluate();
667					T::Verifier::force_set_single_page_valid(fallback, 0, score);
668					Ok(PostDispatchInfo {
669						actual_weight: Some(T::WeightInfo::manage_fallback()),
670						pays_fee: Pays::No,
671					})
672				},
673				ManagerOperation::ForceSetPhase(phase) => {
674					Self::phase_transition(phase);
675					Ok(PostDispatchInfo {
676						actual_weight: Some(T::DbWeight::get().reads_writes(1, 1)),
677						pays_fee: Pays::No,
678					})
679				},
680				ManagerOperation::ForceRotateRound => {
681					Self::rotate_round();
682					Ok(PostDispatchInfo {
683						actual_weight: Some(T::WeightInfo::export_terminal()),
684						pays_fee: Pays::No,
685					})
686				},
687			}
688		}
689
690		#[pallet::call_index(1)]
691		#[pallet::weight(T::WeightInfo::admin_set())]
692		pub fn admin(origin: OriginFor<T>, op: AdminOperation<T>) -> DispatchResultWithPostInfo {
693			T::AdminOrigin::ensure_origin(origin)?;
694			match op {
695				AdminOperation::EmergencySetSolution(supports, score) => {
696					ensure!(Self::current_phase() == Phase::Emergency, Error::<T>::UnexpectedPhase);
697					T::Verifier::force_set_single_page_valid(*supports, 0, score);
698					Ok(PostDispatchInfo {
699						actual_weight: Some(T::WeightInfo::admin_set()),
700						pays_fee: Pays::No,
701					})
702				},
703				AdminOperation::SetMinUntrustedScore(score) => {
704					T::Verifier::set_minimum_score(score);
705					Ok(PostDispatchInfo {
706						actual_weight: Some(T::DbWeight::get().reads_writes(1, 1)),
707						pays_fee: Pays::No,
708					})
709				},
710			}
711		}
712	}
713
714	#[pallet::hooks]
715	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
716		fn on_poll(_now: BlockNumberFor<T>, weight_meter: &mut WeightMeter) {
717			// first check we can at least read one storage.
718			if !weight_meter.can_consume(T::DbWeight::get().reads(1)) {
719				Self::deposit_event(Event::UnexpectedPhaseTransitionHalt {
720					required: T::DbWeight::get().reads(1),
721					had: weight_meter.remaining(),
722				});
723				return;
724			}
725
726			// if so, consume and prepare the next phase.
727			let current_phase = Self::current_phase();
728			weight_meter.consume(T::DbWeight::get().reads(1));
729
730			let (self_weight, self_exec) = Self::per_block_exec(current_phase);
731			let (verifier_weight, verifier_exc) = T::Verifier::per_block_exec();
732
733			// The following will combine `Self::per_block_exec` and `T::Verifier::per_block_exec`
734			// into a single tuple of `(Weight, Box<_>)`. Can be moved into a reusable combinator
735			// function if we have this pattern in more places.
736			let (combined_weight, combined_exec) = (
737				// pre-exec weight is simply addition.
738				self_weight.saturating_add(verifier_weight),
739				// our new exec is..
740				Box::new(move |meter: &mut WeightMeter| {
741					self_exec(meter);
742					verifier_exc(meter);
743				}),
744			);
745
746			log!(
747				trace,
748				"worst-case required weight for transition from {:?} to {:?} is {:?}, has {:?}",
749				current_phase,
750				current_phase.next(),
751				combined_weight,
752				weight_meter.remaining()
753			);
754			if weight_meter.can_consume(combined_weight) {
755				combined_exec(weight_meter);
756			} else {
757				Self::deposit_event(Event::UnexpectedPhaseTransitionOutOfWeight {
758					from: current_phase,
759					to: current_phase.next(),
760					required: combined_weight,
761					had: weight_meter.remaining(),
762				});
763			}
764
765			// NOTE: why in here? because it is more accessible, for example `roll_to_with_ocw`.
766			#[cfg(test)]
767			{
768				if _now > 200u32.into() {
769					panic!("Looping to death: in case of errors in election start time in tests, we might loop \
770					infinitely. This panic is preventing you from that. Double check `mock::ElectionStart` or increase \
771					the 200 limit");
772				}
773				let test_election_start: BlockNumberFor<T> =
774					(crate::mock::ElectionStart::get() as u32).into();
775				if _now == test_election_start {
776					crate::log!(info, "TESTING: Starting election at block {}", _now);
777					crate::mock::MultiBlock::start().unwrap();
778				}
779			}
780		}
781
782		fn integrity_test() {
783			use sp_std::mem::size_of;
784			// The index type of both voters and targets need to be smaller than that of usize (very
785			// unlikely to be the case, but anyhow).
786			assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
787			assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
788
789			// also, because `VoterSnapshotPerBlock` and `TargetSnapshotPerBlock` are in u32, we
790			// assert that both of these types are smaller than u32 as well.
791			assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<u32>());
792			assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<u32>());
793
794			// pages must be at least 1.
795			assert!(T::Pages::get() > 0);
796
797			// Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`].
798			let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
799
800			// 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf<OffchainAccuracy>`.
801			let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T::MinerConfig>>> = (0..
802				max_vote)
803				.map(|_| {
804					<UpperOf<SolutionAccuracyOf<T::MinerConfig>>>::from(
805						<SolutionAccuracyOf<T::MinerConfig>>::one().deconstruct(),
806					)
807				})
808				.collect();
809			let _: UpperOf<SolutionAccuracyOf<T::MinerConfig>> = maximum_chain_accuracy
810				.iter()
811				.fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap());
812
813			// We only accept data provider who's maximum votes per voter matches our
814			// `T::Solution`'s `LIMIT`.
815			//
816			// NOTE that this pallet does not really need to enforce this in runtime. The
817			// solution cannot represent any voters more than `LIMIT` anyhow.
818			assert_eq!(
819				<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
820				<SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
821			);
822
823			// Either (signed + signed validation) is non-zero, or unsigned is non-zero
824			let has_signed = !T::SignedPhase::get().is_zero();
825			let signed_validation = T::SignedValidationPhase::get();
826			let has_signed_validation = !signed_validation.is_zero();
827			let has_unsigned = !T::UnsignedPhase::get().is_zero();
828			assert!(
829				has_signed == has_signed_validation,
830				"Signed phase not set correct -- both should be set or unset"
831			);
832			assert!(
833				signed_validation.is_zero() ||
834					signed_validation % T::Pages::get().into() == Zero::zero(),
835				"signed validation phase should be a multiple of the number of pages."
836			);
837
838			assert!(has_signed || has_unsigned, "either signed or unsigned phase must be set");
839		}
840
841		#[cfg(feature = "try-runtime")]
842		fn try_state(now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
843			Self::do_try_state(now).map_err(Into::into)
844		}
845	}
846
847	#[pallet::event]
848	#[pallet::generate_deposit(pub(super) fn deposit_event)]
849	pub enum Event<T: Config> {
850		/// A phase transition happened. Only checks major changes in the variants, not minor inner
851		/// values.
852		PhaseTransitioned {
853			/// the source phase
854			from: Phase<T>,
855			/// The target phase
856			to: Phase<T>,
857		},
858		/// Target snapshot creation failed.
859		UnexpectedTargetSnapshotFailed,
860		/// Voter snapshot creation failed.
861		UnexpectedVoterSnapshotFailed,
862		/// Phase transition could not proceed due to being out of weight.
863		UnexpectedPhaseTransitionOutOfWeight {
864			from: Phase<T>,
865			to: Phase<T>,
866			required: Weight,
867			had: Weight,
868		},
869		/// Phase transition could not even begin becaseu of being out of weight.
870		UnexpectedPhaseTransitionHalt { required: Weight, had: Weight },
871	}
872
873	/// Error of the pallet that can be returned in response to dispatches.
874	#[pallet::error]
875	pub enum Error<T> {
876		/// Triggering the `Fallback` failed.
877		Fallback,
878		/// Unexpected phase
879		UnexpectedPhase,
880		/// Snapshot was unavailable.
881		Snapshot,
882	}
883
884	/// Common errors in all sub-pallets and miner.
885	#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)]
886	pub enum CommonError {
887		/// Submission is too early (or too late, depending on your point of reference).
888		EarlySubmission,
889		/// The round counter is wrong.
890		WrongRound,
891		/// Submission is too weak to be considered an improvement.
892		WeakSubmission,
893		/// Wrong number of pages in the solution.
894		WrongPageCount,
895		/// Wrong number of winners presented.
896		WrongWinnerCount,
897		/// The snapshot fingerprint is not a match. The solution is likely outdated.
898		WrongFingerprint,
899		/// Snapshot was not available.
900		Snapshot,
901	}
902
903	/// Internal counter for the number of rounds.
904	///
905	/// This is useful for de-duplication of transactions submitted to the pool, and general
906	/// diagnostics of the pallet.
907	///
908	/// This is merely incremented once per every time that an upstream `elect` is called.
909	#[pallet::storage]
910	#[pallet::getter(fn round)]
911	pub type Round<T: Config> = StorageValue<_, u32, ValueQuery>;
912
913	/// Current phase.
914	#[pallet::storage]
915	#[pallet::getter(fn current_phase)]
916	pub type CurrentPhase<T: Config> = StorageValue<_, Phase<T>, ValueQuery>;
917
918	/// Wrapper struct for working with snapshots.
919	///
920	/// It manages the following storage items:
921	///
922	/// - `DesiredTargets`: The number of targets that we wish to collect.
923	/// - `PagedVoterSnapshot`: Paginated map of voters.
924	/// - `PagedVoterSnapshotHash`: Hash of the aforementioned.
925	/// - `PagedTargetSnapshot`: Paginated map of targets.
926	/// - `PagedTargetSnapshotHash`: Hash of the aforementioned.
927	///
928	/// ### Round
929	///
930	/// All inner storage items are keyed by the round number. Yet, none of the interface in this
931	/// type expose this. This is because a snapshot is really only ever meaningful in the current
932	/// round. Moreover, doing this will allow us to possibly lazy-delete the old round data, such
933	/// as the sizeable snapshot, in a lazy manner. If any of these storage items, key-ed by a round
934	/// index, are in a round that has passed, now they can be lazy deleted.
935	///
936	/// ### Invariants
937	///
938	/// The following invariants must be met at **all times** for this storage item to be "correct".
939	///
940	/// - `PagedVoterSnapshotHash` must always contain the correct the same number of keys, and the
941	///   corresponding hash of the `PagedVoterSnapshot`.
942	/// - `PagedTargetSnapshotHash` must always contain the correct the same number of keys, and the
943	///   corresponding hash of the `PagedTargetSnapshot`.
944	///
945	/// - If any page from the paged voters/targets exists, then the aforementioned (desired
946	///   targets) must also exist.
947	///
948	/// The following invariants might need to hold based on the current phase.
949	///
950	///   - If `Phase` IS `Snapshot(_)`, then partial voter/target pages must exist from `msp` to
951	///     `lsp` based on the inner value.
952	///   - If `Phase` IS `Off`, then, no snapshot must exist.
953	///   - In all other phases, the snapshot must FULLY exist.
954	pub(crate) struct Snapshot<T>(sp_std::marker::PhantomData<T>);
955	impl<T: Config> Snapshot<T> {
956		// ----------- mutable methods
957		pub(crate) fn set_desired_targets(d: u32) {
958			DesiredTargets::<T>::insert(Self::round(), d);
959		}
960
961		pub(crate) fn set_targets(targets: BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>) {
962			let hash = Self::write_storage_with_pre_allocate(
963				&PagedTargetSnapshot::<T>::hashed_key_for(Self::round(), Pallet::<T>::msp()),
964				targets,
965			);
966			PagedTargetSnapshotHash::<T>::insert(Self::round(), Pallet::<T>::msp(), hash);
967		}
968
969		pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf<T::MinerConfig>) {
970			let hash = Self::write_storage_with_pre_allocate(
971				&PagedVoterSnapshot::<T>::hashed_key_for(Self::round(), page),
972				voters,
973			);
974			PagedVoterSnapshotHash::<T>::insert(Self::round(), page, hash);
975		}
976
977		/// Destroy the entire snapshot.
978		///
979		/// Should be called only once we transition to [`Phase::Off`].
980		pub(crate) fn kill() {
981			DesiredTargets::<T>::remove(Self::round());
982			clear_round_based_map!(PagedVoterSnapshot::<T>, Self::round());
983			clear_round_based_map!(PagedVoterSnapshotHash::<T>, Self::round());
984			clear_round_based_map!(PagedTargetSnapshot::<T>, Self::round());
985			clear_round_based_map!(PagedTargetSnapshotHash::<T>, Self::round());
986		}
987
988		// ----------- non-mutables
989		pub(crate) fn desired_targets() -> Option<u32> {
990			DesiredTargets::<T>::get(Self::round())
991		}
992
993		pub(crate) fn voters(page: PageIndex) -> Option<VoterPageOf<T::MinerConfig>> {
994			PagedVoterSnapshot::<T>::get(Self::round(), page)
995		}
996
997		pub(crate) fn targets() -> Option<BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>> {
998			// NOTE: targets always have one index, which is 0, aka lsp.
999			PagedTargetSnapshot::<T>::get(Self::round(), Pallet::<T>::msp())
1000		}
1001
1002		/// Get a fingerprint of the snapshot, from all the hashes that are stored for each page of
1003		/// the snapshot.
1004		///
1005		/// This is computed as: `(target_hash, voter_hash_n, voter_hash_(n-1), ..., voter_hash_0)`
1006		/// where `n` is `T::Pages - 1`. In other words, it is the concatenated hash of targets, and
1007		/// voters, from `msp` to `lsp`.
1008		pub fn fingerprint() -> T::Hash {
1009			let mut hashed_target_and_voters =
1010				Self::targets_hash().unwrap_or_default().as_ref().to_vec();
1011			let hashed_voters = (Pallet::<T>::msp()..=Pallet::<T>::lsp())
1012				.map(|i| PagedVoterSnapshotHash::<T>::get(Self::round(), i).unwrap_or_default())
1013				.flat_map(|hash| <T::Hash as AsRef<[u8]>>::as_ref(&hash).to_owned())
1014				.collect::<Vec<u8>>();
1015			hashed_target_and_voters.extend(hashed_voters);
1016			T::Hashing::hash(&hashed_target_and_voters)
1017		}
1018
1019		fn write_storage_with_pre_allocate<E: Encode>(key: &[u8], data: E) -> T::Hash {
1020			let size = data.encoded_size();
1021			let mut buffer = Vec::with_capacity(size);
1022			data.encode_to(&mut buffer);
1023
1024			let hash = T::Hashing::hash(&buffer);
1025
1026			// do some checks.
1027			debug_assert_eq!(buffer, data.encode());
1028			// buffer should have not re-allocated since.
1029			debug_assert!(buffer.len() == size && size == buffer.capacity());
1030			sp_io::storage::set(key, &buffer);
1031
1032			hash
1033		}
1034
1035		pub(crate) fn targets_hash() -> Option<T::Hash> {
1036			PagedTargetSnapshotHash::<T>::get(Self::round(), Pallet::<T>::msp())
1037		}
1038
1039		fn round() -> u32 {
1040			Pallet::<T>::round()
1041		}
1042	}
1043
1044	#[allow(unused)]
1045	#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
1046	impl<T: Config> Snapshot<T> {
1047		///Ensure target snapshot exists.
1048		pub(crate) fn ensure_target_snapshot(exists: bool) -> Result<(), &'static str> {
1049			ensure!(exists ^ Self::desired_targets().is_none(), "desired target mismatch");
1050			ensure!(exists ^ Self::targets().is_none(), "targets mismatch");
1051			ensure!(exists ^ Self::targets_hash().is_none(), "targets hash mismatch");
1052
1053			// and the hash is correct.
1054			if let Some(targets) = Self::targets() {
1055				let hash = Self::targets_hash().expect("must exist; qed");
1056				ensure!(hash == T::Hashing::hash(&targets.encode()), "targets hash mismatch");
1057			}
1058			Ok(())
1059		}
1060
1061		/// Ensure voters exists, from page `T::Pages::get()` for `up_to_page` subsequent pages.
1062		pub(crate) fn ensure_voter_snapshot(
1063			exists: bool,
1064			mut up_to_page: PageIndex,
1065		) -> Result<(), &'static str> {
1066			up_to_page = up_to_page.min(T::Pages::get());
1067			// ensure that voter pages that should exist, indeed to exist..
1068			let mut sum_existing_voters: usize = 0;
1069			for p in (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp())
1070				.rev()
1071				.take(up_to_page as usize)
1072			{
1073				ensure!(
1074					(exists ^ Self::voters(p).is_none()) &&
1075						(exists ^ Self::voters_hash(p).is_none()),
1076					"voter page existence mismatch"
1077				);
1078
1079				if let Some(voters_page) = Self::voters(p) {
1080					sum_existing_voters = sum_existing_voters.saturating_add(voters_page.len());
1081					let hash = Self::voters_hash(p).expect("must exist; qed");
1082					ensure!(hash == T::Hashing::hash(&voters_page.encode()), "voter hash mismatch");
1083				}
1084			}
1085
1086			// ..and those that should not exist, indeed DON'T.
1087			for p in (crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp())
1088				.take((T::Pages::get() - up_to_page) as usize)
1089			{
1090				ensure!(
1091					(exists ^ Self::voters(p).is_some()) &&
1092						(exists ^ Self::voters_hash(p).is_some()),
1093					"voter page non-existence mismatch"
1094				);
1095			}
1096			Ok(())
1097		}
1098
1099		pub(crate) fn ensure_snapshot(
1100			exists: bool,
1101			mut up_to_page: PageIndex,
1102		) -> Result<(), &'static str> {
1103			Self::ensure_target_snapshot(exists)
1104				.and_then(|_| Self::ensure_voter_snapshot(exists, up_to_page))
1105		}
1106
1107		pub(crate) fn ensure_full_snapshot() -> Result<(), &'static str> {
1108			// if any number of pages supposed to exist, these must also exist.
1109			ensure!(Self::desired_targets().is_some(), "desired target mismatch");
1110			ensure!(Self::targets_hash().is_some(), "targets hash mismatch");
1111			ensure!(
1112				Self::targets_decode_len().unwrap_or_default() as u32 ==
1113					T::TargetSnapshotPerBlock::get(),
1114				"targets decode length mismatch"
1115			);
1116
1117			// ensure that voter pages that should exist, indeed to exist..
1118			for p in crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp() {
1119				ensure!(
1120					Self::voters_hash(p).is_some() &&
1121						Self::voters_decode_len(p).unwrap_or_default() as u32 ==
1122							T::VoterSnapshotPerBlock::get(),
1123					"voter page existence mismatch"
1124				);
1125			}
1126
1127			Ok(())
1128		}
1129
1130		pub(crate) fn voters_decode_len(page: PageIndex) -> Option<usize> {
1131			PagedVoterSnapshot::<T>::decode_len(Self::round(), page)
1132		}
1133
1134		pub(crate) fn targets_decode_len() -> Option<usize> {
1135			PagedTargetSnapshot::<T>::decode_len(Self::round(), Pallet::<T>::msp())
1136		}
1137
1138		pub(crate) fn voters_hash(page: PageIndex) -> Option<T::Hash> {
1139			PagedVoterSnapshotHash::<T>::get(Self::round(), page)
1140		}
1141
1142		pub(crate) fn sanity_check() -> Result<(), &'static str> {
1143			// check the snapshot existence based on the phase. This checks all of the needed
1144			// conditions except for the metadata values.
1145			let phase = Pallet::<T>::current_phase();
1146			let _ = match phase {
1147				// no page should exist in this phase.
1148				Phase::Off => Self::ensure_snapshot(false, T::Pages::get()),
1149
1150				// we will star the snapshot in the next phase.
1151				Phase::Snapshot(p) if p == T::Pages::get() =>
1152					Self::ensure_snapshot(false, T::Pages::get()),
1153				// we are mid voter snapshot.
1154				Phase::Snapshot(p) if p < T::Pages::get() && p > 0 =>
1155					Self::ensure_snapshot(true, T::Pages::get() - p - 1),
1156				// we cannot check anything in this block -- we take the last page of the snapshot.
1157				Phase::Snapshot(_) => Ok(()),
1158
1159				// full snapshot must exist in these phases.
1160				Phase::Emergency |
1161				Phase::Signed(_) |
1162				Phase::SignedValidation(_) |
1163				Phase::Export(_) |
1164				Phase::Done |
1165				Phase::Unsigned(_) => Self::ensure_snapshot(true, T::Pages::get()),
1166			}?;
1167
1168			Ok(())
1169		}
1170	}
1171
1172	#[cfg(test)]
1173	impl<T: Config> Snapshot<T> {
1174		pub(crate) fn voter_pages() -> PageIndex {
1175			use sp_runtime::SaturatedConversion;
1176			PagedVoterSnapshot::<T>::iter().count().saturated_into::<PageIndex>()
1177		}
1178
1179		pub(crate) fn target_pages() -> PageIndex {
1180			use sp_runtime::SaturatedConversion;
1181			PagedTargetSnapshot::<T>::iter().count().saturated_into::<PageIndex>()
1182		}
1183
1184		pub(crate) fn voters_iter_flattened() -> impl Iterator<Item = VoterOf<T::MinerConfig>> {
1185			let key_range =
1186				(crate::Pallet::<T>::lsp()..=crate::Pallet::<T>::msp()).collect::<Vec<_>>();
1187			key_range
1188				.into_iter()
1189				.flat_map(|k| PagedVoterSnapshot::<T>::get(Self::round(), k).unwrap_or_default())
1190		}
1191
1192		pub(crate) fn remove_voter_page(page: PageIndex) {
1193			PagedVoterSnapshot::<T>::remove(Self::round(), page);
1194		}
1195
1196		pub(crate) fn kill_desired_targets() {
1197			DesiredTargets::<T>::remove(Self::round());
1198		}
1199
1200		pub(crate) fn remove_target_page() {
1201			PagedTargetSnapshot::<T>::remove(Self::round(), Pallet::<T>::msp());
1202		}
1203
1204		pub(crate) fn remove_target(at: usize) {
1205			PagedTargetSnapshot::<T>::mutate(
1206				Self::round(),
1207				crate::Pallet::<T>::msp(),
1208				|maybe_targets| {
1209					if let Some(targets) = maybe_targets {
1210						targets.remove(at);
1211						// and update the hash.
1212						PagedTargetSnapshotHash::<T>::insert(
1213							Self::round(),
1214							crate::Pallet::<T>::msp(),
1215							T::Hashing::hash(&targets.encode()),
1216						)
1217					} else {
1218						unreachable!();
1219					}
1220				},
1221			)
1222		}
1223	}
1224
1225	/// Desired number of targets to elect for this round.
1226	#[pallet::storage]
1227	pub type DesiredTargets<T> = StorageMap<_, Twox64Concat, u32, u32>;
1228	/// Paginated voter snapshot. At most [`T::Pages`] keys will exist.
1229	#[pallet::storage]
1230	pub type PagedVoterSnapshot<T: Config> = StorageDoubleMap<
1231		_,
1232		Twox64Concat,
1233		u32,
1234		Twox64Concat,
1235		PageIndex,
1236		VoterPageOf<T::MinerConfig>,
1237	>;
1238	/// Same as [`PagedVoterSnapshot`], but it will store the hash of the snapshot.
1239	///
1240	/// The hash is generated using [`frame_system::Config::Hashing`].
1241	#[pallet::storage]
1242	pub type PagedVoterSnapshotHash<T: Config> =
1243		StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, PageIndex, T::Hash>;
1244	/// Paginated target snapshot.
1245	///
1246	/// For the time being, since we assume one pages of targets, at most ONE key will exist.
1247	#[pallet::storage]
1248	pub type PagedTargetSnapshot<T: Config> = StorageDoubleMap<
1249		_,
1250		Twox64Concat,
1251		u32,
1252		Twox64Concat,
1253		PageIndex,
1254		BoundedVec<T::AccountId, T::TargetSnapshotPerBlock>,
1255	>;
1256	/// Same as [`PagedTargetSnapshot`], but it will store the hash of the snapshot.
1257	///
1258	/// The hash is generated using [`frame_system::Config::Hashing`].
1259	#[pallet::storage]
1260	pub type PagedTargetSnapshotHash<T: Config> =
1261		StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, PageIndex, T::Hash>;
1262
1263	#[pallet::pallet]
1264	pub struct Pallet<T>(PhantomData<T>);
1265}
1266
1267impl<T: Config> Pallet<T> {
1268	/// Returns the most significant page of the snapshot.
1269	///
1270	/// Based on the contract of `ElectionDataProvider`, this is the first page that is filled.
1271	fn msp() -> PageIndex {
1272		T::Pages::get().checked_sub(1).defensive_unwrap_or_default()
1273	}
1274
1275	/// Returns the least significant page of the snapshot.
1276	///
1277	/// Based on the contract of `ElectionDataProvider`, this is the last page that is filled.
1278	fn lsp() -> PageIndex {
1279		Zero::zero()
1280	}
1281
1282	/// > Note: Consider this a shared documentation block for [`Pallet::on_poll`] and this
1283	/// > function, as they work together.
1284	///
1285	/// The meta-phase transition logic that applies to all pallets. Includes the following:
1286	///
1287	/// * Creating snapshot once `ElectionProvider::start` has instructed us to do so.
1288	/// * Transition into `Phase::Signed`.
1289	/// * Upon last page of `Phase::Signed`, instruct the `Verifier` to start, if any solution
1290	///   exists.
1291	/// * And moving forward all other phases as necessary.
1292	///
1293	/// What it does not do:
1294	///
1295	/// * Instruct the verifier to move forward. This happens through
1296	///   [`verifier::Verifier::per_block_exec`], called in [`Pallet::on_poll`]. On each block,
1297	///   [`T::Verifier`] is given a chance to do something. Under the hood, if the `Status` is set,
1298	///   it will do something, regardless of which phase we are in. In this pallet we only move
1299	///   [`Phase::SignedValidation`] forward.
1300	/// * Move us forward if we are in either of `Phase::Done` or `Phase::Export`. These are
1301	///   controlled by the caller of our [`T::ElectionProvider`] implementation, i.e. staking. Note
1302	///   that this pallet always transitions us from `current_phase` to `current_phase.next()`, but
1303	///   the [`crate::types::Phase::next`] function is a noop for `Done` and `Export`.
1304	///
1305	/// ### Type
1306	///
1307	/// The commonly used `(Weight, Box<dyn Fn(&mut WeightMeter)>)` should be interpreted as such:
1308	///
1309	/// * The `Weight` is the pre-computed worst case weight of the operation that we are going to
1310	///   do.
1311	/// * The `Box<dyn Fn(&mut WeightMeter)>` is the function that represents that the work that
1312	///   will at most consume the said amount of weight. While executing, it will alter the given
1313	///   weight meter to consume the actual weight used. Indeed, the weight that is registered in
1314	///   the `WeightMeter` must never be more than the `Weight` returned as the first item of the
1315	///   tuple.
1316	///
1317	/// In essence, the caller must:
1318	///
1319	/// 1. given an existing `meter`, receive `(worst_weight, exec)`
1320	/// 2. ensure `meter` can consume up to `worst_weight`.
1321	/// 3. if so, call `exec(meter)`, knowing `meter` will accumulate at most `worst_weight` extra.
1322	///
1323	/// ### Returned Weight
1324	///
1325	/// The weights returned are as follows:
1326	///
1327	/// * `just_next_phase` returns a benchmarked weight that should be equal to only reading and
1328	///   writing the `Phase`. This is used for:
1329	/// 	* `Off` phase, which does not do anything `on_poll`.
1330	/// 	* `Signed` phase except last page, which starts the verifier.
1331	/// 	* `Unsigned` phase, which does not do anything `on_poll` and managed its own extrinsic
1332	///    weights.
1333	/// 	* `Emergency` phase, which does not do anything `on_poll`.
1334	/// 	* `Done` phase, which does not do anything `on_poll`.
1335	/// 	* `SignedValidation` phase, because in this pallet we only move the phase forward and we
1336	///    don't know (or want to know) how much weight `Verifier` is consuming. This is handled by
1337	///    combining the weight of [`T::Verifier::per_block_exec`] with this function in
1338	///    [`Pallet::on_poll`].
1339	/// 	* `Export` phase, which does not do anything `on_poll` on this pallet, yet we return the
1340	///    weight that [`Config::ElectionProvider`]'s caller should register via
1341	///    [`ElectionProvider::status`].
1342	///
1343	/// The only special cases, from the perspective of this pallet, are:
1344	/// 	* Last page of signed phase registers `per_block_start_signed_validation`.
1345	/// 	* The snapshots are handled by this pallet, which registers `per_block_snapshot_msp` and
1346	///    `per_block_snapshot_rest`.
1347	fn per_block_exec(current_phase: Phase<T>) -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
1348		type ExecuteFn = Box<dyn Fn(&mut WeightMeter)>;
1349		let next_phase = current_phase.next();
1350
1351		let just_next_phase: (Weight, ExecuteFn) = (
1352			T::WeightInfo::per_block_nothing(),
1353			Box::new(move |_| {
1354				// Note `Phase.next` for some variants is a noop, for example `Done`.
1355				Self::phase_transition(next_phase);
1356			}),
1357		);
1358
1359		match current_phase {
1360			Phase::Snapshot(x) if x == T::Pages::get() => {
1361				// first snapshot
1362				let weight = T::WeightInfo::per_block_snapshot_msp();
1363				let exec: ExecuteFn = Box::new(move |meter: &mut WeightMeter| {
1364					Self::create_targets_snapshot();
1365					Self::phase_transition(next_phase);
1366					meter.consume(weight)
1367				});
1368				(weight, exec)
1369			},
1370
1371			Phase::Snapshot(x) => {
1372				// rest of the snapshot, incl last one.
1373				let weight = T::WeightInfo::per_block_snapshot_rest();
1374				let exec: ExecuteFn = Box::new(move |meter: &mut WeightMeter| {
1375					Self::create_voters_snapshot_paged(x);
1376					Self::phase_transition(next_phase);
1377					meter.consume(weight)
1378				});
1379				(weight, exec)
1380			},
1381			Phase::Signed(x) => {
1382				// Signed pallet should prep the best winner, and send the start signal, if some
1383				// exists.
1384				if x.is_zero() && T::Signed::has_leader(Self::round()) {
1385					let weight = T::WeightInfo::per_block_start_signed_validation();
1386					let exec: ExecuteFn = Box::new(move |meter: &mut WeightMeter| {
1387						// defensive: signed phase has just began, verifier should be in a clear
1388						// state and ready to accept a solution.
1389						let _ = T::Verifier::start().defensive();
1390						Self::phase_transition(next_phase);
1391						meter.consume(weight)
1392					});
1393					(weight, exec)
1394				} else {
1395					just_next_phase
1396				}
1397			},
1398			Phase::SignedValidation(_) |
1399			Phase::Unsigned(_) |
1400			Phase::Off |
1401			Phase::Emergency |
1402			Phase::Done |
1403			Phase::Export(_) => just_next_phase,
1404		}
1405	}
1406
1407	/// Return the `length` most significant pages.
1408	///
1409	/// For example, if `Pages = 4`, and `length = 2`, our full snapshot range would be [0,
1410	/// 1, 2, 3], with 3 being msp. But, in this case, then this returns `[2, 3]` two most
1411	/// significant pages, in the old order.
1412	pub fn msp_range_for(length: usize) -> Vec<PageIndex> {
1413		(Self::lsp()..Self::msp() + 1).rev().take(length).rev().collect::<Vec<_>>()
1414	}
1415
1416	pub(crate) fn phase_transition(to: Phase<T>) {
1417		let from = Self::current_phase();
1418		if from == to {
1419			return;
1420		}
1421		use sp_std::mem::discriminant;
1422		if discriminant(&from) != discriminant(&to) {
1423			log!(debug, "transitioning phase from {:?} to {:?}", from, to);
1424			Self::deposit_event(Event::PhaseTransitioned { from, to });
1425		} else {
1426			log!(trace, "transitioning phase from {:?} to {:?}", from, to);
1427		}
1428		<CurrentPhase<T>>::put(to);
1429	}
1430
1431	/// Perform all the basic checks that are independent of the snapshot. To be more specific,
1432	/// these are all the checks that you can do without the need to read the massive blob of the
1433	/// actual snapshot. This function only contains a handful of storage reads, with bounded size.
1434	///
1435	/// A sneaky detail is that this does check the `DesiredTargets` aspect of the snapshot, but
1436	/// neither of the large storage items.
1437	///
1438	/// Moreover, we do optionally check the fingerprint of the snapshot, if provided.
1439	///
1440	/// These complement a feasibility-check, which is exactly the opposite: snapshot-dependent
1441	/// checks.
1442	pub(crate) fn snapshot_independent_checks(
1443		paged_solution: &PagedRawSolution<T::MinerConfig>,
1444		maybe_snapshot_fingerprint: Option<T::Hash>,
1445	) -> Result<(), CommonError> {
1446		// Note that the order of these checks are critical for the correctness and performance of
1447		// `restore_or_compute_then_maybe_submit`. We want to make sure that we always check round
1448		// first, so that if it has a wrong round, we can detect and delete it from the cache right
1449		// from the get go.
1450
1451		// ensure round is current
1452		ensure!(Self::round() == paged_solution.round, CommonError::WrongRound);
1453
1454		// ensure score is being improved, if the claim is even correct.
1455		ensure!(
1456			<T::Verifier as Verifier>::ensure_claimed_score_improves(paged_solution.score),
1457			CommonError::WeakSubmission,
1458		);
1459
1460		// ensure solution pages are no more than the snapshot
1461		ensure!(
1462			paged_solution.solution_pages.len().saturated_into::<PageIndex>() <= T::Pages::get(),
1463			CommonError::WrongPageCount
1464		);
1465
1466		// finally, check the winner count being correct.
1467		if let Some(desired_targets) = Snapshot::<T>::desired_targets() {
1468			ensure!(
1469				desired_targets == paged_solution.winner_count_single_page_target_snapshot() as u32,
1470				CommonError::WrongWinnerCount
1471			)
1472		}
1473
1474		// check the snapshot fingerprint, if asked for.
1475		ensure!(
1476			maybe_snapshot_fingerprint
1477				.map_or(true, |snapshot_fingerprint| Snapshot::<T>::fingerprint() ==
1478					snapshot_fingerprint),
1479			CommonError::WrongFingerprint
1480		);
1481
1482		Ok(())
1483	}
1484
1485	/// Creates the target snapshot.
1486	///
1487	/// If snapshot creation fails, emits `UnexpectedTargetSnapshotFailed` event
1488	/// and triggers defensive panic.
1489	pub(crate) fn create_targets_snapshot() {
1490		// if requested, get the targets as well.
1491		let desired_targets = match T::DataProvider::desired_targets() {
1492			Ok(targets) => targets,
1493			Err(e) => {
1494				Self::deposit_event(Event::UnexpectedTargetSnapshotFailed);
1495				defensive!("Failed to get desired targets: {:?}", e);
1496				return;
1497			},
1498		};
1499		Snapshot::<T>::set_desired_targets(desired_targets);
1500
1501		let count = T::TargetSnapshotPerBlock::get();
1502		let bounds = DataProviderBounds { count: Some(count.into()), size: None };
1503		let targets: BoundedVec<_, T::TargetSnapshotPerBlock> =
1504			match T::DataProvider::electable_targets(bounds, 0)
1505				.and_then(|v| v.try_into().map_err(|_| "try-into failed"))
1506			{
1507				Ok(targets) => targets,
1508				Err(e) => {
1509					Self::deposit_event(Event::UnexpectedTargetSnapshotFailed);
1510					defensive!("Failed to create target snapshot: {:?}", e);
1511					return;
1512				},
1513			};
1514
1515		let count = targets.len() as u32;
1516		log!(debug, "created target snapshot with {} targets.", count);
1517		Snapshot::<T>::set_targets(targets);
1518	}
1519
1520	/// Creates the voter snapshot.
1521	///
1522	/// If snapshot creation fails, emits `UnexpectedVoterSnapshotFailed` event
1523	/// and triggers defensive panic.
1524	pub(crate) fn create_voters_snapshot_paged(remaining: PageIndex) {
1525		let count = T::VoterSnapshotPerBlock::get();
1526		let bounds = DataProviderBounds { count: Some(count.into()), size: None };
1527		let voters: BoundedVec<_, T::VoterSnapshotPerBlock> =
1528			match T::DataProvider::electing_voters(bounds, remaining)
1529				.and_then(|v| v.try_into().map_err(|_| "try-into failed"))
1530			{
1531				Ok(voters) => voters,
1532				Err(e) => {
1533					Self::deposit_event(Event::UnexpectedVoterSnapshotFailed);
1534					defensive!("Failed to create voter snapshot: {:?}", e);
1535					return;
1536				},
1537			};
1538
1539		let count = voters.len() as u32;
1540		Snapshot::<T>::set_voters(remaining, voters);
1541		log!(debug, "created voter snapshot with {} voters, {} remaining.", count, remaining);
1542	}
1543
1544	/// Perform the tasks to be done after a new `elect` has been triggered:
1545	///
1546	/// 1. Increment round.
1547	/// 2. Change phase to [`Phase::Off`]
1548	/// 3. Clear all snapshot data.
1549	pub(crate) fn rotate_round() {
1550		// Inc round.
1551		<Round<T>>::mutate(|r| {
1552			// Notify the rest of the world
1553			T::OnRoundRotation::on_round_rotation(*r);
1554			*r += 1
1555		});
1556
1557		// Phase is off now.
1558		Self::phase_transition(Phase::Off);
1559	}
1560
1561	/// Call fallback for the given page.
1562	///
1563	/// This uses the [`ElectionProvider::bother`] to check if the fallback is actually going to do
1564	/// anything. If so, it will re-collect the associated snapshot page and do the fallback. Else,
1565	/// it will early return without touching the snapshot.
1566	fn fallback_for_page(page: PageIndex) -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
1567		use frame_election_provider_support::InstantElectionProvider;
1568		let (voters, targets, desired_targets) = if T::Fallback::bother() {
1569			(
1570				Snapshot::<T>::voters(page).ok_or(ElectionError::Other("snapshot!"))?,
1571				Snapshot::<T>::targets().ok_or(ElectionError::Other("snapshot!"))?,
1572				Snapshot::<T>::desired_targets().ok_or(ElectionError::Other("snapshot!"))?,
1573			)
1574		} else {
1575			(Default::default(), Default::default(), Default::default())
1576		};
1577		T::Fallback::instant_elect(voters.into_inner(), targets.into_inner(), desired_targets)
1578			.map_err(|fe| ElectionError::Fallback(fe))
1579	}
1580
1581	/// A reasonable next election block number.
1582	pub fn average_election_duration() -> u32 {
1583		let signed: u32 = T::SignedPhase::get().saturated_into();
1584		let unsigned: u32 = T::UnsignedPhase::get().saturated_into();
1585		let signed_validation: u32 = T::SignedValidationPhase::get().saturated_into();
1586		let snapshot = T::Pages::get();
1587
1588		// we don't count the export.
1589		let _export = T::Pages::get();
1590
1591		snapshot + signed + signed_validation + unsigned
1592	}
1593
1594	#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
1595	pub(crate) fn do_try_state(_: BlockNumberFor<T>) -> Result<(), &'static str> {
1596		Snapshot::<T>::sanity_check()
1597	}
1598}
1599
1600#[cfg(feature = "std")]
1601impl<T: Config> Pallet<T> {
1602	fn analyze_weight(
1603		op_name: &str,
1604		op_weight: Weight,
1605		limit_weight: Weight,
1606		maybe_max_ratio: Option<sp_runtime::Percent>,
1607		maybe_max_warn_ratio: Option<sp_runtime::Percent>,
1608	) {
1609		use frame_support::weights::constants::{
1610			WEIGHT_PROOF_SIZE_PER_KB, WEIGHT_REF_TIME_PER_MILLIS,
1611		};
1612
1613		let ref_time_ms = op_weight.ref_time() / WEIGHT_REF_TIME_PER_MILLIS;
1614		let ref_time_ratio =
1615			sp_runtime::Percent::from_rational(op_weight.ref_time(), limit_weight.ref_time());
1616		let proof_size_kb = op_weight.proof_size() / WEIGHT_PROOF_SIZE_PER_KB;
1617		let proof_size_ratio =
1618			sp_runtime::Percent::from_rational(op_weight.proof_size(), limit_weight.proof_size());
1619		let limit_ms = limit_weight.ref_time() / WEIGHT_REF_TIME_PER_MILLIS;
1620		let limit_kb = limit_weight.proof_size() / WEIGHT_PROOF_SIZE_PER_KB;
1621		log::info!(
1622			target: crate::LOG_PREFIX,
1623			"weight of {op_name:?} is: ref-time: {ref_time_ms}ms, {ref_time_ratio:?} of total, proof-size: {proof_size_kb}KiB, {proof_size_ratio:?} of total (total: {limit_ms}ms, {limit_kb}KiB)",
1624		);
1625
1626		if let Some(max_ratio) = maybe_max_ratio {
1627			assert!(ref_time_ratio <= max_ratio && proof_size_ratio <= max_ratio,)
1628		}
1629		if let Some(warn_ratio) = maybe_max_warn_ratio {
1630			if ref_time_ratio > warn_ratio || proof_size_ratio > warn_ratio {
1631				log::warn!(
1632					target: crate::LOG_PREFIX,
1633					"weight of {op_name:?} is above {warn_ratio:?} of the block limit",
1634				);
1635			}
1636		}
1637	}
1638
1639	/// Helper function to check the weights of all significant operations of this this pallet
1640	/// against a runtime.
1641	///
1642	/// Will check the weights for:
1643	///
1644	/// * snapshot
1645	/// * signed submission and cleanip
1646	/// * unsigned solution submission
1647	/// * signed validation
1648	/// * export.
1649	///
1650	/// Arguments:
1651	///
1652	/// * `limit_weight` should be the maximum block weight (often obtained from `frame_system`).
1653	/// * `maybe_max_ratio` is the maximum ratio of `limit_weight` that we may consume, else we
1654	///   panic.
1655	/// * `maybe_max_warn_rati` has the same effect, but it emits a warning instead of panic.
1656	///
1657	/// A reasonable value for `maybe_max_weight` would be 75%, and 50% for `maybe_max_warn_ratio`.
1658	pub fn check_all_weights(
1659		limit_weight: Weight,
1660		maybe_max_ratio: Option<sp_runtime::Percent>,
1661		maybe_max_warn_ratio: Option<sp_runtime::Percent>,
1662	) where
1663		T: crate::verifier::Config + crate::signed::Config + crate::unsigned::Config,
1664	{
1665		use crate::weights::traits::{
1666			pallet_election_provider_multi_block_signed::WeightInfo as _,
1667			pallet_election_provider_multi_block_unsigned::WeightInfo as _,
1668			pallet_election_provider_multi_block_verifier::WeightInfo as _,
1669		};
1670
1671		// -------------- snapshot
1672		Self::analyze_weight(
1673			"snapshot_msp",
1674			<T as Config>::WeightInfo::per_block_snapshot_msp(),
1675			limit_weight,
1676			maybe_max_ratio,
1677			maybe_max_warn_ratio,
1678		);
1679
1680		Self::analyze_weight(
1681			"snapshot_rest",
1682			<T as Config>::WeightInfo::per_block_snapshot_rest(),
1683			limit_weight,
1684			maybe_max_ratio,
1685			maybe_max_warn_ratio,
1686		);
1687
1688		// -------------- signed
1689		Self::analyze_weight(
1690			"signed_clear_all_pages",
1691			<T as crate::signed::Config>::WeightInfo::clear_old_round_data(T::Pages::get()),
1692			limit_weight,
1693			maybe_max_ratio,
1694			maybe_max_warn_ratio,
1695		);
1696		Self::analyze_weight(
1697			"signed_submit_single_pages",
1698			<T as crate::signed::Config>::WeightInfo::submit_page(),
1699			limit_weight,
1700			maybe_max_ratio,
1701			maybe_max_warn_ratio,
1702		);
1703
1704		// -------------- unsigned
1705		Self::analyze_weight(
1706			"verify unsigned solution",
1707			<T as crate::unsigned::Config>::WeightInfo::submit_unsigned(),
1708			limit_weight,
1709			maybe_max_ratio,
1710			maybe_max_warn_ratio,
1711		);
1712
1713		// -------------- verification
1714		Self::analyze_weight(
1715			"verifier valid terminal",
1716			<T as crate::verifier::Config>::WeightInfo::verification_valid_terminal(),
1717			limit_weight,
1718			maybe_max_ratio,
1719			maybe_max_warn_ratio,
1720		);
1721		Self::analyze_weight(
1722			"verifier invalid terminal",
1723			<T as crate::verifier::Config>::WeightInfo::verification_invalid_terminal(),
1724			limit_weight,
1725			maybe_max_ratio,
1726			maybe_max_warn_ratio,
1727		);
1728
1729		Self::analyze_weight(
1730			"verifier valid non terminal",
1731			<T as crate::verifier::Config>::WeightInfo::verification_valid_non_terminal(),
1732			limit_weight,
1733			maybe_max_ratio,
1734			maybe_max_warn_ratio,
1735		);
1736
1737		Self::analyze_weight(
1738			"verifier invalid non terminal",
1739			<T as crate::verifier::Config>::WeightInfo::verification_invalid_non_terminal(
1740				T::Pages::get(),
1741			),
1742			limit_weight,
1743			maybe_max_ratio,
1744			maybe_max_warn_ratio,
1745		);
1746
1747		// -------------- export
1748		Self::analyze_weight(
1749			"export non-terminal",
1750			<T as Config>::WeightInfo::export_non_terminal(),
1751			limit_weight,
1752			maybe_max_ratio,
1753			maybe_max_warn_ratio,
1754		);
1755
1756		Self::analyze_weight(
1757			"export terminal",
1758			<T as Config>::WeightInfo::export_terminal(),
1759			limit_weight,
1760			maybe_max_ratio,
1761			maybe_max_warn_ratio,
1762		);
1763	}
1764}
1765
1766#[allow(unused)]
1767#[cfg(any(feature = "runtime-benchmarks", test))]
1768// helper code for testing and benchmarking
1769impl<T> Pallet<T>
1770where
1771	T: Config + crate::signed::Config + crate::unsigned::Config + crate::verifier::Config,
1772	BlockNumberFor<T>: From<u32>,
1773{
1774	/// Progress blocks until the criteria is met.
1775	pub(crate) fn roll_until_matches(criteria: impl FnOnce() -> bool + Copy) {
1776		loop {
1777			Self::roll_next(false);
1778			if criteria() {
1779				break;
1780			}
1781		}
1782	}
1783
1784	/// Progress blocks until one block before the criteria is met.
1785	pub(crate) fn roll_until_before_matches(criteria: impl FnOnce() -> bool + Copy) {
1786		use frame_support::storage::TransactionOutcome;
1787		loop {
1788			let should_break = frame_support::storage::with_transaction(
1789				|| -> TransactionOutcome<Result<_, DispatchError>> {
1790					Pallet::<T>::roll_next(false);
1791					if criteria() {
1792						TransactionOutcome::Rollback(Ok(true))
1793					} else {
1794						TransactionOutcome::Commit(Ok(false))
1795					}
1796				},
1797			)
1798			.unwrap();
1799
1800			if should_break {
1801				break;
1802			}
1803		}
1804	}
1805
1806	pub(crate) fn roll_to_signed_and_mine_full_solution() -> PagedRawSolution<T::MinerConfig> {
1807		use unsigned::miner::OffchainWorkerMiner;
1808		Self::roll_to_signed_and_mine_solution(T::Pages::get())
1809	}
1810
1811	pub(crate) fn roll_to_signed_and_mine_solution(
1812		pages: PageIndex,
1813	) -> PagedRawSolution<T::MinerConfig> {
1814		use unsigned::miner::OffchainWorkerMiner;
1815		Self::roll_until_matches(|| Self::current_phase().is_signed());
1816		// ensure snapshot is full.
1817		crate::Snapshot::<T>::ensure_full_snapshot().expect("Snapshot is not full");
1818		OffchainWorkerMiner::<T>::mine_solution(pages, false).expect("mine_solution failed")
1819	}
1820
1821	pub(crate) fn submit_full_solution(
1822		PagedRawSolution { score, solution_pages, .. }: PagedRawSolution<T::MinerConfig>,
1823	) -> DispatchResultWithPostInfo {
1824		use frame_system::RawOrigin;
1825		use sp_std::boxed::Box;
1826		use types::Pagify;
1827
1828		// register alice
1829		let alice = crate::Pallet::<T>::funded_account("alice", 0);
1830		signed::Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
1831
1832		// submit pages
1833		for (index, page) in solution_pages.pagify(T::Pages::get()) {
1834			signed::Pallet::<T>::submit_page(
1835				RawOrigin::Signed(alice.clone()).into(),
1836				index,
1837				Some(Box::new(page.clone())),
1838			)
1839			.inspect_err(|&e| {
1840				log!(error, "submit_page {:?} failed: {:?}", page, e);
1841			})?;
1842		}
1843
1844		Ok(().into())
1845	}
1846
1847	pub(crate) fn roll_to_signed_and_submit_full_solution() -> DispatchResultWithPostInfo {
1848		Self::submit_full_solution(Self::roll_to_signed_and_mine_full_solution())
1849	}
1850
1851	fn funded_account(seed: &'static str, index: u32) -> T::AccountId {
1852		use frame_benchmarking::whitelist;
1853		use frame_support::traits::fungible::{Inspect, Mutate};
1854		let who: T::AccountId = frame_benchmarking::account(seed, index, 777);
1855		whitelist!(who);
1856
1857		// Calculate deposit for worst-case scenario: full queue + all pages submitted.
1858		// This accounts for the exponential deposit growth in GeometricDepositBase
1859		// where deposit = base * (1 + increase_factor)^queue_len.
1860		// We use maximum possible queue_len to ensure adequate funding regardless
1861		// of queue state changes during benchmark execution.
1862		let worst_case_deposit = {
1863			let max_queue_size = T::MaxSubmissions::get() as usize;
1864			let base = T::DepositBase::calculate_base_deposit(max_queue_size);
1865			let pages =
1866				T::DepositPerPage::calculate_page_deposit(max_queue_size, T::Pages::get() as usize);
1867			base.saturating_add(pages)
1868		};
1869
1870		// Transaction fees: assume as conservativ estimate that each operation costs ~1% of
1871		// minimum_balance
1872		let min_balance = T::Currency::minimum_balance();
1873		let num_operations = 1u32.saturating_add(T::Pages::get()); // 1 register + N submit_page
1874		let tx_fee_buffer = (min_balance / 100u32.into()).saturating_mul(num_operations.into());
1875
1876		let total_needed = worst_case_deposit
1877			.saturating_add(tx_fee_buffer)
1878			.saturating_add(T::Currency::minimum_balance());
1879
1880		T::Currency::mint_into(&who, total_needed).unwrap();
1881		who
1882	}
1883
1884	/// Roll all pallets forward, for the given number of blocks.
1885	pub(crate) fn roll_to(n: BlockNumberFor<T>, try_state: bool) {
1886		let now = frame_system::Pallet::<T>::block_number();
1887		assert!(n > now, "cannot roll to current or past block");
1888		let one: BlockNumberFor<T> = 1u32.into();
1889		let mut i = now + one;
1890		while i <= n {
1891			// remove previous weight usage in system.
1892			frame_system::BlockWeight::<T>::kill();
1893
1894			frame_system::Pallet::<T>::set_block_number(i);
1895			let mut meter = frame_system::Pallet::<T>::remaining_block_weight();
1896			Pallet::<T>::on_poll(i, &mut meter);
1897
1898			// register the new weight in system
1899			frame_system::Pallet::<T>::register_extra_weight_unchecked(
1900				meter.consumed(),
1901				DispatchClass::Mandatory,
1902			);
1903
1904			// invariants must hold at the end of each block.
1905			if try_state {
1906				Pallet::<T>::do_try_state(i).unwrap();
1907				verifier::Pallet::<T>::do_try_state(i).unwrap();
1908				unsigned::Pallet::<T>::do_try_state(i).unwrap();
1909				signed::Pallet::<T>::do_try_state(i).unwrap();
1910			}
1911
1912			i += one;
1913		}
1914	}
1915
1916	/// Roll to next block.
1917	pub(crate) fn roll_next(try_state: bool) {
1918		Self::roll_to(frame_system::Pallet::<T>::block_number() + 1u32.into(), try_state);
1919	}
1920}
1921
1922impl<T: Config> ElectionProvider for Pallet<T> {
1923	type AccountId = T::AccountId;
1924	type BlockNumber = BlockNumberFor<T>;
1925	type Error = ElectionError<T>;
1926	type DataProvider = T::DataProvider;
1927	type Pages = T::Pages;
1928	type MaxWinnersPerPage = <T::Verifier as Verifier>::MaxWinnersPerPage;
1929	type MaxBackersPerWinner = <T::Verifier as Verifier>::MaxBackersPerWinner;
1930	type MaxBackersPerWinnerFinal = <T::Verifier as Verifier>::MaxBackersPerWinnerFinal;
1931
1932	fn elect(remaining: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
1933		match Self::status() {
1934			// we allow `elect` to be called as long as we have received a start signal.
1935			Ok(_) => (),
1936			Err(_) => return Err(ElectionError::NotOngoing),
1937		}
1938
1939		let current_phase = CurrentPhase::<T>::get();
1940		if let Phase::Export(expected) = current_phase {
1941			ensure!(expected == remaining, ElectionError::OutOfOrder);
1942		}
1943
1944		let result = T::Verifier::get_queued_solution_page(remaining)
1945			.ok_or(ElectionError::SupportPageNotAvailable)
1946			.or_else(|err: ElectionError<T>| {
1947				log!(
1948					debug,
1949					"primary election for page {} failed due to: {:?}, trying fallback",
1950					remaining,
1951					err,
1952				);
1953				Self::fallback_for_page(remaining)
1954			})
1955			.map_err(|err| {
1956				// if any pages returns an error, we go into the emergency phase and don't do
1957				// anything else anymore. This will prevent any new submissions to signed and
1958				// unsigned pallet, and thus the verifier will also be almost stuck, except for the
1959				// submission of emergency solutions.
1960				log!(debug, "fallback also ({:?}) failed for page {:?}", err, remaining);
1961				err
1962			})
1963			.map(|supports| {
1964				// convert to bounded
1965				supports.into()
1966			});
1967
1968		// if fallback has possibly put us into the emergency phase, don't do anything else.
1969		if CurrentPhase::<T>::get().is_emergency() && result.is_err() {
1970			log!(error, "Emergency phase triggered, halting the election.");
1971		} else {
1972			if remaining.is_zero() {
1973				Self::rotate_round()
1974			} else {
1975				Self::phase_transition(Phase::Export(remaining - 1))
1976			}
1977		}
1978
1979		result
1980	}
1981
1982	fn start() -> Result<(), Self::Error> {
1983		match Self::status() {
1984			Err(()) => (),
1985			Ok(_) => return Err(ElectionError::Ongoing),
1986		}
1987
1988		Self::phase_transition(Phase::<T>::start_phase());
1989		Ok(())
1990	}
1991
1992	fn duration() -> Self::BlockNumber {
1993		Self::average_election_duration().into()
1994	}
1995
1996	fn status() -> Result<Option<Weight>, ()> {
1997		match <CurrentPhase<T>>::get() {
1998			// we're not doing anything.
1999			Phase::Off => Err(()),
2000
2001			// we're doing something but not ready.
2002			Phase::Signed(_) |
2003			Phase::SignedValidation(_) |
2004			Phase::Unsigned(_) |
2005			Phase::Snapshot(_) |
2006			Phase::Emergency => Ok(None),
2007
2008			// we're ready
2009			Phase::Done => Ok(Some(T::WeightInfo::export_non_terminal())),
2010			Phase::Export(p) =>
2011				if p.is_zero() {
2012					Ok(Some(T::WeightInfo::export_terminal()))
2013				} else {
2014					Ok(Some(T::WeightInfo::export_non_terminal()))
2015				},
2016		}
2017	}
2018
2019	#[cfg(feature = "runtime-benchmarks")]
2020	fn asap() {
2021		// prepare our snapshot so we can "hopefully" run a fallback.
2022		Self::create_targets_snapshot();
2023		for p in (Self::lsp()..=Self::msp()).rev() {
2024			Self::create_voters_snapshot_paged(p)
2025		}
2026	}
2027}
2028
2029#[cfg(test)]
2030mod phase_rotation {
2031	use super::{Event, *};
2032	use crate::{mock::*, verifier::Status, Phase};
2033	use frame_election_provider_support::ElectionProvider;
2034	use frame_support::assert_ok;
2035
2036	#[test]
2037	fn single_page() {
2038		ExtBuilder::full()
2039			.pages(1)
2040			.election_start(13)
2041			.fallback_mode(FallbackModes::Onchain)
2042			.build_and_execute(|| {
2043				// 0 -------- 14 15 --------- 20 ------------- 25 ---------- 30
2044				//            |  |            |                |             |
2045				//    Snapshot Signed  SignedValidation    Unsigned       elect()
2046
2047				assert_eq!(System::block_number(), 0);
2048				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2049				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 1));
2050				assert_eq!(MultiBlock::round(), 0);
2051
2052				roll_to(4);
2053				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2054				assert_eq!(MultiBlock::round(), 0);
2055
2056				roll_to(13);
2057				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2058				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
2059
2060				roll_to(14);
2061				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2062				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
2063
2064				roll_to(15);
2065				assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2066				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2067				assert_eq!(MultiBlock::round(), 0);
2068
2069				assert_eq!(
2070					multi_block_events_since_last_call(),
2071					vec![
2072						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) },
2073						Event::PhaseTransitioned {
2074							from: Phase::Snapshot(0),
2075							to: Phase::Signed(SignedPhase::get() - 1)
2076						}
2077					]
2078				);
2079
2080				roll_to(19);
2081				assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
2082				assert_eq!(MultiBlock::round(), 0);
2083
2084				roll_to(20);
2085				assert_eq!(
2086					MultiBlock::current_phase(),
2087					Phase::SignedValidation(SignedValidationPhase::get())
2088				);
2089				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2090				assert_eq!(MultiBlock::round(), 0);
2091
2092				assert_eq!(
2093					multi_block_events_since_last_call(),
2094					vec![Event::PhaseTransitioned {
2095						from: Phase::Signed(0),
2096						to: Phase::SignedValidation(SignedValidationPhase::get())
2097					}],
2098				);
2099
2100				roll_to(26);
2101				assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2102				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2103				assert_eq!(MultiBlock::round(), 0);
2104
2105				roll_to(27);
2106				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2107				assert_eq!(
2108					multi_block_events_since_last_call(),
2109					vec![Event::PhaseTransitioned {
2110						from: Phase::SignedValidation(0),
2111						to: Phase::Unsigned(UnsignedPhase::get() - 1)
2112					}],
2113				);
2114
2115				roll_to(31);
2116				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2117
2118				// We stay in done otherwise
2119				roll_to(32);
2120				assert!(MultiBlock::current_phase().is_done());
2121
2122				// We stay in done otherwise
2123				roll_to(33);
2124				assert!(MultiBlock::current_phase().is_done());
2125
2126				// We close when upstream tells us to elect.
2127				roll_to(34);
2128				assert!(MultiBlock::current_phase().is_done());
2129				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2130
2131				MultiBlock::elect(0).unwrap();
2132
2133				assert!(MultiBlock::current_phase().is_off());
2134				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 1));
2135				assert_eq!(MultiBlock::round(), 1);
2136
2137				roll_to(42);
2138				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2139			})
2140	}
2141
2142	#[test]
2143	fn multi_page_2() {
2144		ExtBuilder::full()
2145			.pages(2)
2146			.fallback_mode(FallbackModes::Onchain)
2147			.election_start(12)
2148			.build_and_execute(|| {
2149				// 0 -------13 14 15 ------- 20 ---- 25 ------- 30
2150				//           |     |         |       |          |
2151				//    Snapshot    Signed SigValid  Unsigned   Elect
2152
2153				assert_eq!(System::block_number(), 0);
2154				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2155				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
2156				assert_eq!(MultiBlock::round(), 0);
2157
2158				roll_to(4);
2159				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2160				assert_eq!(MultiBlock::round(), 0);
2161
2162				roll_to(11);
2163				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2164				assert_eq!(MultiBlock::round(), 0);
2165
2166				roll_to(12);
2167				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2168				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
2169
2170				roll_to(13);
2171				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2172				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
2173
2174				roll_to(14);
2175				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2176				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2177
2178				roll_to(15);
2179				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2180				assert_eq!(MultiBlock::round(), 0);
2181				assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2182
2183				assert_eq!(
2184					multi_block_events_since_last_call(),
2185					vec![
2186						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) },
2187						Event::PhaseTransitioned {
2188							from: Phase::Snapshot(0),
2189							to: Phase::Signed(SignedPhase::get() - 1)
2190						}
2191					]
2192				);
2193
2194				roll_to(19);
2195				assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
2196				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2197				assert_eq!(MultiBlock::round(), 0);
2198
2199				roll_to(20);
2200				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2201				assert_eq!(MultiBlock::round(), 0);
2202				assert_eq!(
2203					MultiBlock::current_phase(),
2204					Phase::SignedValidation(SignedValidationPhase::get())
2205				);
2206
2207				assert_eq!(
2208					multi_block_events_since_last_call(),
2209					vec![Event::PhaseTransitioned {
2210						from: Phase::Signed(0),
2211						to: Phase::SignedValidation(SignedValidationPhase::get())
2212					}],
2213				);
2214
2215				roll_to(26);
2216				assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2217				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2218				assert_eq!(MultiBlock::round(), 0);
2219
2220				roll_to(27);
2221				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2222				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2223				assert_eq!(MultiBlock::round(), 0);
2224
2225				assert_eq!(
2226					multi_block_events_since_last_call(),
2227					vec![Event::PhaseTransitioned {
2228						from: Phase::SignedValidation(0),
2229						to: Phase::Unsigned(UnsignedPhase::get() - 1)
2230					}],
2231				);
2232
2233				roll_to(31);
2234				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2235				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2236
2237				roll_to(32);
2238				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2239				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2240
2241				// We close when upstream tells us to elect.
2242				roll_to(33);
2243				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2244
2245				// and even this one's coming from the fallback.
2246				MultiBlock::elect(0).unwrap();
2247				assert!(MultiBlock::current_phase().is_off());
2248
2249				// all snapshots are gone.
2250				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 2));
2251				assert_eq!(MultiBlock::round(), 1);
2252			})
2253	}
2254
2255	#[test]
2256	fn multi_page_3() {
2257		ExtBuilder::full()
2258			.pages(3)
2259			.fallback_mode(FallbackModes::Onchain)
2260			.build_and_execute(|| {
2261				// 0 ------- 12 13 14 15 ----------- 20 ---------25 ------- 30
2262				//            |       |              |            |          |
2263				//     Snapshot      Signed   SignedValidation  Unsigned   Elect
2264
2265				assert_eq!(System::block_number(), 0);
2266				assert!(MultiBlock::current_phase().is_off());
2267				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
2268				assert_eq!(MultiBlock::round(), 0);
2269
2270				roll_to(10);
2271				assert!(MultiBlock::current_phase().is_off());
2272				assert_eq!(MultiBlock::round(), 0);
2273
2274				roll_to(11);
2275				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2276				// no snapshot is take yet, we start at the next block
2277				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(false, 3));
2278
2279				roll_to(12);
2280				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2281				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 0));
2282
2283				roll_to(13);
2284				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2285				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 1));
2286
2287				roll_to(14);
2288				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2289				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, 2));
2290
2291				roll_to(15);
2292				assert_ok!(Snapshot::<Runtime>::ensure_snapshot(true, Pages::get()));
2293				assert_eq!(MultiBlock::current_phase(), Phase::Signed(4));
2294				assert_eq!(
2295					multi_block_events_since_last_call(),
2296					vec![
2297						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2298						Event::PhaseTransitioned {
2299							from: Phase::Snapshot(0),
2300							to: Phase::Signed(SignedPhase::get() - 1)
2301						}
2302					]
2303				);
2304				assert_eq!(MultiBlock::round(), 0);
2305
2306				roll_to(19);
2307				assert_eq!(MultiBlock::current_phase(), Phase::Signed(0));
2308				assert_eq!(MultiBlock::round(), 0);
2309
2310				roll_to(20);
2311				assert_eq!(
2312					MultiBlock::current_phase(),
2313					Phase::SignedValidation(SignedValidationPhase::get())
2314				);
2315				assert_eq!(
2316					multi_block_events_since_last_call(),
2317					vec![Event::PhaseTransitioned {
2318						from: Phase::Signed(0),
2319						to: Phase::SignedValidation(SignedValidationPhase::get())
2320					}]
2321				);
2322
2323				roll_to(26);
2324				assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2325				assert_eq!(MultiBlock::round(), 0);
2326
2327				roll_to(27);
2328				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2329				assert_eq!(
2330					multi_block_events_since_last_call(),
2331					vec![Event::PhaseTransitioned {
2332						from: Phase::SignedValidation(0),
2333						to: Phase::Unsigned(UnsignedPhase::get() - 1)
2334					}]
2335				);
2336
2337				roll_to(31);
2338				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2339
2340				roll_to(32);
2341				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2342
2343				// We close when upstream tells us to elect.
2344				roll_to(33);
2345				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2346
2347				MultiBlock::elect(0).unwrap();
2348				assert!(MultiBlock::current_phase().is_off());
2349
2350				// all snapshots are gone.
2351				assert_none_snapshot();
2352				assert_eq!(MultiBlock::round(), 1);
2353			})
2354	}
2355
2356	#[test]
2357	fn weights_registered() {
2358		// ensure we never forget to call `meter.consume` or similar in poll and alike.
2359		// Our mock setup is:
2360		//
2361		// * each db read or write is 1 ref time.
2362		// * each epmb op weight are:
2363		//   * snapshots: 5
2364		//   * validation: 3 to start, rest 7
2365		ExtBuilder::full().build_and_execute(|| {
2366			roll_to(10);
2367			assert!(MultiBlock::current_phase().is_off());
2368			// note: 2 becuase 1 read registered by the parent pallet, 1 by verifier.
2369			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2370
2371			// roll to this phase, no weight meter is consumed yet other than 1 read + 1 write.
2372			roll_next_and_phase(Phase::Snapshot(3));
2373			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2374
2375			roll_next_and_phase(Phase::Snapshot(2));
2376			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2377
2378			roll_next_and_phase(Phase::Snapshot(1));
2379			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2380
2381			roll_next_and_phase(Phase::Snapshot(0));
2382			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2383
2384			roll_next_and_phase(Phase::Signed(SignedPhase::get() - 1));
2385			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 5));
2386
2387			// Now snapshot is done, and during signed phase we do a noop.
2388			roll_next_and_phase(Phase::Signed(SignedPhase::get() - 2));
2389			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2390
2391			// but let's submit a signed solution to be verified while we're here
2392			{
2393				let paged = mine_full_solution().unwrap();
2394				load_signed_for_verification(999, paged.clone());
2395			}
2396
2397			// let's go forward to start of signed validation
2398			roll_to_signed_validation_open();
2399			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 3));
2400
2401			roll_next_and_phase_verifier(
2402				Phase::SignedValidation(SignedValidationPhase::get() - 1),
2403				Status::Ongoing(1),
2404			);
2405			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(1, 7));
2406
2407			roll_next_and_phase_verifier(
2408				Phase::SignedValidation(SignedValidationPhase::get() - 2),
2409				Status::Ongoing(0),
2410			);
2411			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(1, 7));
2412
2413			roll_next_and_phase_verifier(
2414				Phase::SignedValidation(SignedValidationPhase::get() - 3),
2415				Status::Nothing,
2416			);
2417			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(1, 7));
2418
2419			// we also don't do anything during unsigned phase.
2420			roll_to_unsigned_open();
2421			assert!(MultiBlock::current_phase().is_unsigned());
2422			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2423
2424			roll_next_and_phase(Phase::Unsigned(UnsignedPhase::get() - 2));
2425			assert_eq!(System::remaining_block_weight().consumed(), Weight::from_parts(2, 0));
2426
2427			// Export weight is computed by us, but registered by whoever calls `elect`, not our
2428			// business to check.
2429		});
2430	}
2431
2432	#[test]
2433	fn no_unsigned_phase() {
2434		ExtBuilder::full()
2435			.pages(3)
2436			.unsigned_phase(0)
2437			.election_start(16)
2438			.fallback_mode(FallbackModes::Onchain)
2439			.build_and_execute(|| {
2440				// 0 --------------------- 17 ------ 20 ---------25 ------- 30
2441				//            |            |         |            |          |
2442				//                     Snapshot    Signed  SignedValidation   Elect
2443
2444				assert_eq!(System::block_number(), 0);
2445				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2446				assert_none_snapshot();
2447				assert_eq!(MultiBlock::round(), 0);
2448
2449				roll_to(4);
2450				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2451				assert_eq!(MultiBlock::round(), 0);
2452
2453				roll_to(16);
2454				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2455
2456				roll_to(17);
2457				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2458
2459				roll_to(18);
2460				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2461
2462				roll_to(19);
2463				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2464
2465				roll_to(20);
2466				assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
2467
2468				assert_full_snapshot();
2469				assert_eq!(MultiBlock::round(), 0);
2470
2471				roll_to(25);
2472				assert_eq!(
2473					MultiBlock::current_phase(),
2474					Phase::SignedValidation(SignedValidationPhase::get())
2475				);
2476
2477				assert_eq!(
2478					multi_block_events_since_last_call(),
2479					vec![
2480						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2481						Event::PhaseTransitioned {
2482							from: Phase::Snapshot(0),
2483							to: Phase::Signed(SignedPhase::get() - 1)
2484						},
2485						Event::PhaseTransitioned {
2486							from: Phase::Signed(0),
2487							to: Phase::SignedValidation(SignedValidationPhase::get())
2488						},
2489					]
2490				);
2491
2492				// last block of signed validation
2493				roll_to(31);
2494				assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(0));
2495
2496				// we are done now
2497				roll_to(32);
2498				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2499
2500				roll_to(33);
2501				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2502
2503				MultiBlock::elect(0).unwrap();
2504				assert!(MultiBlock::current_phase().is_off());
2505
2506				// all snapshots are gone.
2507				assert_none_snapshot();
2508				assert_eq!(MultiBlock::round(), 1);
2509				assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2510				verifier::QueuedSolution::<Runtime>::assert_killed();
2511			})
2512	}
2513
2514	#[test]
2515	fn no_signed_phase() {
2516		ExtBuilder::full()
2517			.pages(3)
2518			.signed_phase(0, 0)
2519			.election_start(21)
2520			.fallback_mode(FallbackModes::Onchain)
2521			.build_and_execute(|| {
2522				// 0 ------------------------- 22 ------ 25 ------- 30
2523				//                             |         |          |
2524				//                         Snapshot   Unsigned   Elect
2525
2526				assert_eq!(System::block_number(), 0);
2527				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2528				assert_none_snapshot();
2529				assert_eq!(MultiBlock::round(), 0);
2530
2531				roll_to(20);
2532				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2533				assert_eq!(MultiBlock::round(), 0);
2534
2535				roll_to(21);
2536				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(3));
2537				roll_to(22);
2538				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2539				roll_to(23);
2540				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2541				roll_to(24);
2542				assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0));
2543
2544				roll_to(25);
2545				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
2546				assert_full_snapshot();
2547				assert_eq!(MultiBlock::round(), 0);
2548
2549				assert_eq!(
2550					multi_block_events(),
2551					vec![
2552						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2553						Event::PhaseTransitioned {
2554							from: Phase::Snapshot(0),
2555							to: Phase::Unsigned(UnsignedPhase::get() - 1)
2556						},
2557					]
2558				);
2559
2560				roll_to(29);
2561				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2562
2563				roll_to(30);
2564				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2565				roll_to(31);
2566				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2567
2568				// eventually the call to elect comes, and we exit done phase.
2569				MultiBlock::elect(0).unwrap();
2570				assert!(MultiBlock::current_phase().is_off());
2571
2572				// all snapshots are gone.
2573				assert_none_snapshot();
2574				assert_eq!(MultiBlock::round(), 1);
2575				assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2576				verifier::QueuedSolution::<Runtime>::assert_killed();
2577			})
2578	}
2579
2580	#[test]
2581	#[should_panic(expected = "either signed or unsigned phase must be set")]
2582	fn no_signed_and_unsigned_phase() {
2583		ExtBuilder::full()
2584			.pages(3)
2585			.signed_phase(0, 0)
2586			.unsigned_phase(0)
2587			.election_start(10)
2588			.fallback_mode(FallbackModes::Onchain)
2589			.build_and_execute(|| {
2590				// This should panic during integrity test
2591			});
2592	}
2593
2594	#[test]
2595	#[should_panic(
2596		expected = "signed validation phase should be a multiple of the number of pages."
2597	)]
2598	fn incorrect_signed_validation_phase_shorter_than_number_of_pages() {
2599		ExtBuilder::full().pages(3).signed_validation_phase(2).build_and_execute(|| {})
2600	}
2601
2602	#[test]
2603	#[should_panic(
2604		expected = "signed validation phase should be a multiple of the number of pages."
2605	)]
2606	fn incorret_signed_validation_phase_not_a_multiple_of_the_number_of_pages() {
2607		ExtBuilder::full().pages(3).signed_validation_phase(7).build_and_execute(|| {})
2608	}
2609
2610	#[test]
2611	fn are_we_done_back_to_signed() {
2612		ExtBuilder::full()
2613			.are_we_done(AreWeDoneModes::BackToSigned)
2614			.build_and_execute(|| {
2615				// roll to unsigned
2616				roll_to_last_unsigned();
2617
2618				assert_eq!(MultiBlock::round(), 0);
2619				assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(0));
2620				assert_eq!(
2621					multi_block_events_since_last_call(),
2622					vec![
2623						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
2624						Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed(4) },
2625						Event::PhaseTransitioned {
2626							from: Phase::Signed(0),
2627							to: Phase::SignedValidation(SignedValidationPhase::get())
2628						},
2629						Event::PhaseTransitioned {
2630							from: Phase::SignedValidation(0),
2631							to: Phase::Unsigned(4)
2632						}
2633					]
2634				);
2635
2636				// we are back to signed phase
2637				roll_next_and_phase(Phase::Signed(SignedPhase::get() - 1));
2638				// round is still the same
2639				assert_eq!(MultiBlock::round(), 0);
2640
2641				// we proceed to normally again:
2642				roll_next_and_phase(Phase::Signed(SignedPhase::get() - 2));
2643				roll_next_and_phase(Phase::Signed(SignedPhase::get() - 3));
2644			});
2645	}
2646
2647	#[test]
2648	fn export_phase_only_transitions_on_elect() {
2649		ExtBuilder::full()
2650			.pages(3)
2651			.election_start(13)
2652			.fallback_mode(FallbackModes::Onchain)
2653			.build_and_execute(|| {
2654				roll_to_done();
2655
2656				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2657
2658				// Test that on_initialize does NOT advance the phase when in Done
2659				roll_next_and_phase(Phase::Done);
2660
2661				// Start export by calling elect(max_page)
2662				assert_ok!(MultiBlock::elect(2)); // max_page = 2 for 3 pages
2663				assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2664
2665				// Test that on_initialize does NOT advance the phase when in Export
2666				roll_next_and_phase(Phase::Export(1));
2667
2668				// Only elect() should advance the Export phase
2669				assert_ok!(MultiBlock::elect(1));
2670				assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2671
2672				// Test Export(0) also blocks on_initialize transitions
2673				roll_next_and_phase(Phase::Export(0));
2674
2675				// Complete the export manually
2676				assert_ok!(MultiBlock::elect(0));
2677				assert_eq!(MultiBlock::current_phase(), Phase::Off);
2678			});
2679	}
2680
2681	#[test]
2682	fn export_phase_out_of_order_elect_fails() {
2683		ExtBuilder::full()
2684			.pages(3)
2685			.election_start(13)
2686			.fallback_mode(FallbackModes::Onchain)
2687			.build_and_execute(|| {
2688				roll_to_done();
2689
2690				assert_eq!(MultiBlock::current_phase(), Phase::Done);
2691
2692				// Start export by calling elect(max_page)
2693				assert_ok!(MultiBlock::elect(2)); // max_page = 2 for 3 pages
2694				assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
2695
2696				// Out of order: try to call elect(2) again, should fail
2697				assert_eq!(MultiBlock::elect(2), Err(ElectionError::OutOfOrder));
2698
2699				// Out of order: try to call elect(0) before elect(1), should fail
2700				assert_eq!(MultiBlock::elect(0), Err(ElectionError::OutOfOrder));
2701
2702				// Correct order: elect(1) works
2703				assert_ok!(MultiBlock::elect(1));
2704				assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
2705			});
2706	}
2707
2708	#[test]
2709	#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
2710	fn target_snapshot_failed_event_emitted() {
2711		ExtBuilder::full()
2712				.pages(2)
2713				.election_start(13)
2714				.build_and_execute(|| {
2715					// Create way more targets than the TargetSnapshotPerBlock limit (4)
2716					// This will cause bounds.slice_exhausted(&targets) to return true
2717					let too_many_targets: Vec<AccountId> = (1..=100).collect();
2718					Targets::set(too_many_targets);
2719
2720					roll_to(13);
2721					assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2));
2722
2723					// Clear any existing events
2724					let _ = multi_block_events_since_last_call();
2725
2726					// Roll to next block - on_initialize will be in Phase::Snapshot(2) where x == T::Pages::get()
2727					// This triggers target snapshot creation, which should fail due to too many targets
2728					roll_to(14);
2729
2730					// Verify that UnexpectedTargetSnapshotFailed event was emitted
2731					let events = multi_block_events_since_last_call();
2732					assert!(
2733						events.contains(&Event::UnexpectedTargetSnapshotFailed),
2734						"UnexpectedTargetSnapshotFailed event should have been emitted when target snapshot creation fails. Events: {:?}",
2735						events
2736					);
2737
2738					// Verify phase transition still happened despite the failure
2739					assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1));
2740				});
2741	}
2742}
2743
2744#[cfg(test)]
2745mod election_provider {
2746	use super::*;
2747	use crate::{
2748		mock::*,
2749		unsigned::miner::OffchainWorkerMiner,
2750		verifier::{AsynchronousVerifier, Status, Verifier},
2751		Phase,
2752	};
2753	use frame_election_provider_support::{BoundedSupport, BoundedSupports, ElectionProvider};
2754	use frame_support::{
2755		assert_storage_noop, testing_prelude::bounded_vec, unsigned::ValidateUnsigned,
2756	};
2757
2758	// This is probably the most important test of all, a basic, correct scenario. This test should
2759	// be studied in detail, and all of the branches of how it can go wrong or diverge from the
2760	// basic scenario assessed.
2761	#[test]
2762	fn multi_page_elect_simple_works() {
2763		ExtBuilder::full().build_and_execute(|| {
2764			roll_to_signed_open();
2765			assert!(MultiBlock::current_phase().is_signed());
2766
2767			// load a solution into the verifier
2768			let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2769			let score = paged.score;
2770
2771			// now let's submit this one by one, into the signed phase.
2772			load_signed_for_verification(99, paged);
2773
2774			// now the solution should start being verified.
2775			roll_to_signed_validation_open();
2776
2777			assert_eq!(
2778				multi_block_events(),
2779				vec![
2780					Event::PhaseTransitioned {
2781						from: Phase::Off,
2782						to: Phase::Snapshot(Pages::get())
2783					},
2784					Event::PhaseTransitioned {
2785						from: Phase::Snapshot(0),
2786						to: Phase::Signed(SignedPhase::get() - 1)
2787					},
2788					Event::PhaseTransitioned {
2789						from: Phase::Signed(0),
2790						to: Phase::SignedValidation(SignedValidationPhase::get())
2791					}
2792				]
2793			);
2794			assert_eq!(verifier_events_since_last_call(), vec![]);
2795
2796			// there is no queued solution prior to the last page of the solution getting verified
2797			assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2798			assert_eq!(
2799				<Runtime as crate::Config>::Verifier::status(),
2800				verifier::Status::Ongoing(2)
2801			);
2802
2803			// next block, signed will start the verifier, although nothing is verified yet.
2804			roll_next_and_phase_verifier(Phase::SignedValidation(5), Status::Ongoing(1));
2805			assert_eq!(verifier_events_since_last_call(), vec![verifier::Event::Verified(2, 2)]);
2806
2807			roll_next_and_phase_verifier(Phase::SignedValidation(4), Status::Ongoing(0));
2808			assert_eq!(verifier_events_since_last_call(), vec![verifier::Event::Verified(1, 2)]);
2809
2810			roll_next_and_phase_verifier(Phase::SignedValidation(3), Status::Nothing);
2811			assert_eq!(
2812				verifier_events_since_last_call(),
2813				vec![verifier::Event::Verified(0, 2), verifier::Event::Queued(score, None)]
2814			);
2815
2816			// there is now a queued solution.
2817			assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2818
2819			// now let's go to unsigned phase, but we don't expect anything to happen there since we
2820			// don't run OCWs.
2821			roll_to_unsigned_open();
2822
2823			// pre-elect state
2824			assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2825			assert_eq!(MultiBlock::round(), 0);
2826			assert_full_snapshot();
2827
2828			// call elect for each page
2829			let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp())
2830				.rev() // 2, 1, 0
2831				.map(|page| {
2832					MultiBlock::elect(page as PageIndex).unwrap();
2833					if page == 0 {
2834						assert!(MultiBlock::current_phase().is_off())
2835					} else {
2836						assert_eq!(MultiBlock::current_phase(), Phase::Export(page - 1))
2837					}
2838				})
2839				.collect::<Vec<_>>();
2840
2841			// after the last elect, verifier is cleared,
2842			verifier::QueuedSolution::<Runtime>::assert_killed();
2843			// the phase is off,
2844			assert_eq!(MultiBlock::current_phase(), Phase::Off);
2845			// the round is incremented,
2846			assert_eq!(Round::<Runtime>::get(), 1);
2847			// and the snapshot is cleared,
2848			assert_storage_noop!(Snapshot::<Runtime>::kill());
2849			// signed pallet is clean.
2850			// NOTE: signed pallet lazily deletes all other solutions, except the winner, which is
2851			// actually deleted.
2852			assert_ok!(signed::Submissions::<Runtime>::ensure_killed(0));
2853		});
2854	}
2855
2856	#[test]
2857	fn multi_page_elect_fast_track() {
2858		ExtBuilder::full().build_and_execute(|| {
2859			roll_to_signed_open();
2860			let round = MultiBlock::round();
2861			assert!(MultiBlock::current_phase().is_signed());
2862
2863			// load a solution into the verifier
2864			let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2865			let score = paged.score;
2866			load_signed_for_verification_and_start(99, paged, 0);
2867
2868			// there is no queued solution prior to the last page of the solution getting verified
2869			assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2870
2871			// roll to the block it is finalized.
2872			roll_next_and_phase_verifier(Phase::SignedValidation(5), Status::Ongoing(1));
2873			roll_next_and_phase_verifier(Phase::SignedValidation(4), Status::Ongoing(0));
2874			roll_next_and_phase_verifier(Phase::SignedValidation(3), Status::Nothing);
2875
2876			assert_eq!(
2877				verifier_events_since_last_call(),
2878				vec![
2879					verifier::Event::Verified(2, 2),
2880					verifier::Event::Verified(1, 2),
2881					verifier::Event::Verified(0, 2),
2882					verifier::Event::Queued(score, None),
2883				]
2884			);
2885
2886			// there is now a queued solution.
2887			assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2888
2889			// not much impact, just for the sane-ness of the test.
2890			roll_to_unsigned_open();
2891
2892			// pre-elect state:
2893			assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2894			assert_eq!(Round::<Runtime>::get(), 0);
2895			assert_full_snapshot();
2896
2897			// there are 3 pages (indexes 2..=0), but we short circuit by just calling 0.
2898			let _supports = crate::Pallet::<Runtime>::elect(0).unwrap();
2899
2900			// round is incremented.
2901			assert_eq!(MultiBlock::round(), round + 1);
2902			// after elect(0) is called, verifier is cleared,
2903			verifier::QueuedSolution::<Runtime>::assert_killed();
2904			// the phase is off,
2905			assert_eq!(MultiBlock::current_phase(), Phase::Off);
2906			// the round is incremented,
2907			assert_eq!(Round::<Runtime>::get(), 1);
2908			// the snapshot is cleared,
2909			assert_none_snapshot();
2910			// and signed pallet is clean.
2911			assert_ok!(signed::Submissions::<Runtime>::ensure_killed(round));
2912		});
2913	}
2914
2915	#[test]
2916	fn elect_does_not_finish_without_call_of_page_0() {
2917		ExtBuilder::full().build_and_execute(|| {
2918			roll_to_signed_open();
2919			assert!(MultiBlock::current_phase().is_signed());
2920
2921			// load a solution into the verifier
2922			let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
2923			let score = paged.score;
2924			load_signed_for_verification_and_start(99, paged, 0);
2925
2926			// there is no queued solution prior to the last page of the solution getting verified
2927			assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), None);
2928
2929			// roll to the block it is finalized. 1 block to start the verifier, and 3 to verify.
2930			roll_next_and_phase_verifier(Phase::SignedValidation(5), Status::Ongoing(1));
2931			roll_next_and_phase_verifier(Phase::SignedValidation(4), Status::Ongoing(0));
2932			roll_next_and_phase_verifier(Phase::SignedValidation(3), Status::Nothing);
2933
2934			assert_eq!(
2935				verifier_events_since_last_call(),
2936				vec![
2937					verifier::Event::Verified(2, 2),
2938					verifier::Event::Verified(1, 2),
2939					verifier::Event::Verified(0, 2),
2940					verifier::Event::Queued(score, None),
2941				]
2942			);
2943
2944			// there is now a queued solution
2945			assert_eq!(<Runtime as crate::Config>::Verifier::queued_score(), Some(score));
2946
2947			// not much impact, just for the sane-ness of the test.
2948			roll_to_unsigned_open();
2949
2950			// pre-elect state:
2951			assert!(MultiBlock::current_phase().is_unsigned_opened_now());
2952			assert_eq!(Round::<Runtime>::get(), 0);
2953			assert_full_snapshot();
2954
2955			// call elect for page 2 and 1, but NOT 0
2956			let solutions = (1..=MultiBlock::msp())
2957				.rev() // 2, 1
2958				.map(|page| {
2959					crate::Pallet::<Runtime>::elect(page as PageIndex).unwrap();
2960					assert!(MultiBlock::current_phase().is_export());
2961				})
2962				.collect::<Vec<_>>();
2963			assert_eq!(solutions.len(), 2);
2964
2965			// nothing changes from the prelect state, except phase is now export.
2966			assert!(MultiBlock::current_phase().is_export());
2967			assert_eq!(Round::<Runtime>::get(), 0);
2968			assert_full_snapshot();
2969		});
2970	}
2971
2972	#[test]
2973	fn continue_fallback_works() {
2974		// Use Continue fallback to avoid emergency phase when both primary and fallback fail.
2975		ExtBuilder::full().fallback_mode(FallbackModes::Continue).build_and_execute(|| {
2976			// Move to unsigned phase
2977			roll_to_unsigned_open();
2978
2979			// Note: our mock runtime is configured with 1 page for the unsigned phase
2980			let miner_pages = <Runtime as unsigned::Config>::MinerPages::get();
2981			// Mine an unsigned solution
2982			let unsigned_solution =
2983				OffchainWorkerMiner::<Runtime>::mine_solution(miner_pages, true).unwrap();
2984
2985			// Submit the unsigned solution
2986			assert_ok!(UnsignedPallet::submit_unsigned(
2987				RuntimeOrigin::none(),
2988				Box::new(unsigned_solution)
2989			));
2990
2991			// Move to Done phase
2992			roll_to_done();
2993
2994			// In the mock runtime Pages::get() = 3 so unsigned solution has 1 page but we go
2995			// through Done -> Export(2) -> Export(1) -> Export(0) -> Off via elect().
2996			// First elect call should succeed
2997			let result1 = MultiBlock::elect(2);
2998			assert!(result1.is_ok());
2999			assert_eq!(MultiBlock::current_phase(), Phase::Export(1));
3000
3001			// Second elect call should now fail because we have mined a solution with MinerPages
3002			// equal to 1.
3003			// Phase should advance even on error.
3004			let result2 = MultiBlock::elect(1);
3005			assert!(result2.is_err());
3006			assert_eq!(MultiBlock::current_phase(), Phase::Export(0));
3007
3008			// Third elect call should also fail but still advance to Off
3009			let result3 = MultiBlock::elect(0);
3010			assert!(result3.is_err());
3011			assert!(matches!(MultiBlock::current_phase(), Phase::Off));
3012		});
3013	}
3014
3015	#[test]
3016	fn skip_unsigned_phase() {
3017		ExtBuilder::full().build_and_execute(|| {
3018			roll_to_signed_open();
3019			assert!(MultiBlock::current_phase().is_signed());
3020			let round = MultiBlock::round();
3021
3022			// load a solution into the verifier
3023			let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
3024
3025			load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
3026
3027			// and right here, in the middle of the signed verification phase, we close the round.
3028			// Everything should work fine.
3029			assert!(matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)));
3030			assert_eq!(Round::<Runtime>::get(), 0);
3031			assert_full_snapshot();
3032
3033			// fetch all pages.
3034			let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp())
3035				.rev() // 2, 1, 0
3036				.map(|page| {
3037					MultiBlock::elect(page as PageIndex).unwrap();
3038					if page == 0 {
3039						assert!(MultiBlock::current_phase().is_off())
3040					} else {
3041						assert!(MultiBlock::current_phase().is_export())
3042					}
3043				})
3044				.collect::<Vec<_>>();
3045
3046			// round is incremented.
3047			assert_eq!(MultiBlock::round(), round + 1);
3048			// after elect(0) is called, verifier is cleared,
3049			verifier::QueuedSolution::<Runtime>::assert_killed();
3050			// the phase is off,
3051			assert_eq!(MultiBlock::current_phase(), Phase::Off);
3052			// the snapshot is cleared,
3053			assert_none_snapshot();
3054			// and signed pallet is clean.
3055			assert_ok!(signed::Submissions::<Runtime>::ensure_killed(round));
3056		});
3057	}
3058
3059	#[test]
3060	fn call_to_elect_should_prevent_any_submission() {
3061		ExtBuilder::full().build_and_execute(|| {
3062			roll_to_signed_open();
3063			assert!(MultiBlock::current_phase().is_signed());
3064
3065			// load a solution into the verifier
3066			let paged = OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false).unwrap();
3067			load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
3068
3069			assert!(matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)));
3070
3071			// fetch one page.
3072			assert!(MultiBlock::elect(MultiBlock::msp()).is_ok());
3073
3074			// try submit one signed page:
3075			assert_noop!(
3076				SignedPallet::submit_page(RuntimeOrigin::signed(999), 0, Default::default()),
3077				crate::signed::Error::<Runtime>::PhaseNotSigned,
3078			);
3079			assert_noop!(
3080				SignedPallet::register(RuntimeOrigin::signed(999), Default::default()),
3081				crate::signed::Error::<Runtime>::PhaseNotSigned,
3082			);
3083			assert_storage_noop!(assert!(<UnsignedPallet as ValidateUnsigned>::pre_dispatch(
3084				&unsigned::Call::submit_unsigned { paged_solution: Default::default() }
3085			)
3086			.is_err()));
3087		});
3088	}
3089
3090	#[test]
3091	fn multi_page_onchain_elect_fallback_works() {
3092		ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3093			roll_to_signed_open();
3094
3095			// same targets, but voters from page 2 (1, 2, 3, 4, see `mock/staking`).
3096			assert_eq!(
3097				MultiBlock::elect(2).unwrap(),
3098				BoundedSupports(bounded_vec![
3099					(10, BoundedSupport { total: 15, voters: bounded_vec![(1, 10), (4, 5)] }),
3100					(
3101						40,
3102						BoundedSupport {
3103							total: 25,
3104							voters: bounded_vec![(2, 10), (3, 10), (4, 5)]
3105						}
3106					)
3107				])
3108			);
3109			// page 1 of voters
3110			assert_eq!(
3111				MultiBlock::elect(1).unwrap(),
3112				BoundedSupports(bounded_vec![
3113					(10, BoundedSupport { total: 15, voters: bounded_vec![(5, 5), (8, 10)] }),
3114					(
3115						30,
3116						BoundedSupport {
3117							total: 25,
3118							voters: bounded_vec![(5, 5), (6, 10), (7, 10)]
3119						}
3120					)
3121				])
3122			);
3123			// self votes
3124			assert_eq!(
3125				MultiBlock::elect(0).unwrap(),
3126				BoundedSupports(bounded_vec![
3127					(30, BoundedSupport { total: 30, voters: bounded_vec![(30, 30)] }),
3128					(40, BoundedSupport { total: 40, voters: bounded_vec![(40, 40)] })
3129				])
3130			);
3131
3132			assert_eq!(
3133				multi_block_events(),
3134				vec![
3135					Event::PhaseTransitioned {
3136						from: Phase::Off,
3137						to: Phase::Snapshot(Pages::get())
3138					},
3139					Event::PhaseTransitioned {
3140						from: Phase::Snapshot(0),
3141						to: Phase::Signed(SignedPhase::get() - 1)
3142					},
3143					Event::PhaseTransitioned {
3144						from: Phase::Signed(SignedPhase::get() - 1),
3145						to: Phase::Export(1)
3146					},
3147					Event::PhaseTransitioned { from: Phase::Export(0), to: Phase::Off }
3148				]
3149			);
3150			assert_eq!(verifier_events(), vec![]);
3151
3152			// This will set us to emergency phase, because we don't know wtf to do.
3153			assert_eq!(MultiBlock::current_phase(), Phase::Off);
3154		});
3155	}
3156
3157	#[test]
3158	fn multi_page_fallback_shortcut_to_msp_works() {
3159		ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3160			roll_to_signed_open();
3161
3162			// but then we immediately call `elect`, this will work
3163			assert!(MultiBlock::elect(0).is_ok());
3164
3165			assert_eq!(
3166				multi_block_events(),
3167				vec![
3168					Event::PhaseTransitioned {
3169						from: Phase::Off,
3170						to: Phase::Snapshot(Pages::get())
3171					},
3172					Event::PhaseTransitioned {
3173						from: Phase::Snapshot(0),
3174						to: Phase::Signed(SignedPhase::get() - 1)
3175					},
3176					Event::PhaseTransitioned {
3177						from: Phase::Signed(SignedPhase::get() - 1),
3178						to: Phase::Off
3179					}
3180				]
3181			);
3182
3183			// This will set us to the off phase, since fallback saved us.
3184			assert_eq!(MultiBlock::current_phase(), Phase::Off);
3185		});
3186	}
3187
3188	#[test]
3189	fn elect_call_when_not_ongoing() {
3190		ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3191			roll_to_snapshot_created();
3192			assert_eq!(MultiBlock::status(), Ok(None));
3193			assert!(MultiBlock::elect(0).is_ok());
3194		});
3195		ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| {
3196			roll_to(10);
3197			assert_eq!(MultiBlock::status(), Err(()));
3198			assert_eq!(MultiBlock::elect(0), Err(ElectionError::NotOngoing));
3199		});
3200	}
3201}
3202
3203#[cfg(test)]
3204mod manage_ops {
3205	use super::*;
3206	use crate::mock::*;
3207
3208	#[test]
3209	fn trigger_fallback_works() {
3210		ExtBuilder::full()
3211			.fallback_mode(FallbackModes::Emergency)
3212			.build_and_execute(|| {
3213				roll_to_signed_open();
3214
3215				// bad origin cannot call
3216				assert_noop!(
3217					MultiBlock::manage(
3218						RuntimeOrigin::signed(Manager::get() + 1),
3219						ManagerOperation::EmergencyFallback
3220					),
3221					DispatchError::BadOrigin
3222				);
3223
3224				// we get a call to elect(0). this will cause emergency, since no fallback is
3225				// allowed.
3226				assert_eq!(
3227					MultiBlock::elect(0),
3228					Err(ElectionError::Fallback("Emergency phase started.".to_string()))
3229				);
3230				assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3231
3232				// we can now set the solution to emergency, assuming fallback is set to onchain
3233				FallbackMode::set(FallbackModes::Onchain);
3234				assert_ok!(MultiBlock::manage(
3235					RuntimeOrigin::signed(Manager::get()),
3236					ManagerOperation::EmergencyFallback
3237				));
3238
3239				assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3240				assert_ok!(MultiBlock::elect(0));
3241				assert_eq!(MultiBlock::current_phase(), Phase::Off);
3242
3243				assert_eq!(
3244					multi_block_events(),
3245					vec![
3246						Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
3247						Event::PhaseTransitioned {
3248							from: Phase::Snapshot(0),
3249							to: Phase::Signed(SignedPhase::get() - 1)
3250						},
3251						Event::PhaseTransitioned {
3252							from: Phase::Signed(SignedPhase::get() - 1),
3253							to: Phase::Emergency
3254						},
3255						Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off }
3256					]
3257				);
3258				assert_eq!(
3259					verifier_events(),
3260					vec![verifier::Event::Queued(
3261						ElectionScore { minimal_stake: 15, sum_stake: 40, sum_stake_squared: 850 },
3262						None
3263					)]
3264				);
3265			})
3266	}
3267
3268	// This scenario have multiple outcomes:
3269	// 1. rotate in off => almost a noop
3270	// 2. rotate mid signed, validation, unsigned, done, but NOT export => clear all data, move to
3271	//    next round and be off. Note: all of the data in this pallet is indexed by the round index,
3272	//    so moving to the next round will implicitly make the old data unavaioable, even if not
3273	//    cleared out. This secnario needs further testing.
3274	// 3. rotate mid export: same as above, except staking will be out of sync and will also need
3275	//    governance intervention.
3276	//
3277	// This test is primarily checking the origin limits of this call, and will use scenario 2.
3278	#[test]
3279	fn force_rotate_round() {
3280		ExtBuilder::full().build_and_execute(|| {
3281			roll_to_signed_open();
3282			let round = MultiBlock::round();
3283			let paged =
3284				unsigned::miner::OffchainWorkerMiner::<Runtime>::mine_solution(Pages::get(), false)
3285					.unwrap();
3286			load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0);
3287
3288			// we have snapshot data now for this round.
3289			assert_full_snapshot();
3290			// there is some data in the verifier pallet
3291			assert!(verifier::QueuedSolution::<T>::queued_score().is_some());
3292			// phase is
3293			assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(2));
3294
3295			// force new round
3296
3297			// bad origin cannot submit
3298			assert_noop!(
3299				MultiBlock::manage(
3300					RuntimeOrigin::signed(Manager::get() + 1),
3301					ManagerOperation::ForceRotateRound
3302				),
3303				DispatchError::BadOrigin
3304			);
3305			// manager can submit
3306			assert_ok!(MultiBlock::manage(
3307				RuntimeOrigin::signed(Manager::get()),
3308				ManagerOperation::ForceRotateRound
3309			));
3310
3311			// phase is off again
3312			assert_eq!(MultiBlock::current_phase(), Phase::Off);
3313			// round is bumped
3314			assert_eq!(MultiBlock::round(), round + 1);
3315			// snapshot is wiped
3316			assert_none_snapshot();
3317			// verifier is in clean state
3318			verifier::QueuedSolution::<T>::assert_killed();
3319		});
3320	}
3321
3322	#[test]
3323	fn force_set_phase() {
3324		ExtBuilder::full().build_and_execute(|| {
3325			roll_to_signed_open();
3326			assert_eq!(MultiBlock::current_phase(), Phase::Signed(SignedPhase::get() - 1));
3327
3328			// bad origin cannot submit
3329			assert_noop!(
3330				MultiBlock::manage(
3331					RuntimeOrigin::signed(Manager::get() + 1),
3332					ManagerOperation::ForceSetPhase(Phase::Done)
3333				),
3334				DispatchError::BadOrigin
3335			);
3336
3337			// manager can submit. They skip the signed phases.
3338			assert_ok!(MultiBlock::manage(
3339				RuntimeOrigin::signed(Manager::get()),
3340				ManagerOperation::ForceSetPhase(Phase::Unsigned(UnsignedPhase::get() - 1))
3341			));
3342
3343			assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 1));
3344
3345			// admin can also submit
3346			assert_ok!(MultiBlock::manage(
3347				RuntimeOrigin::root(),
3348				ManagerOperation::ForceSetPhase(Phase::Unsigned(UnsignedPhase::get() - 2))
3349			));
3350			assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(UnsignedPhase::get() - 2));
3351		});
3352	}
3353}
3354#[cfg(test)]
3355mod admin_ops {
3356	use super::*;
3357	use crate::mock::*;
3358
3359	#[test]
3360	fn set_solution_emergency_works() {
3361		ExtBuilder::full().build_and_execute(|| {
3362			roll_to_signed_open();
3363
3364			// bad origin cannot call
3365			assert_noop!(
3366				MultiBlock::admin(
3367					RuntimeOrigin::signed(Manager::get() + 1),
3368					AdminOperation::EmergencySetSolution(Default::default(), Default::default())
3369				),
3370				DispatchError::BadOrigin
3371			);
3372
3373			// manager (non-root) cannot call
3374			assert_noop!(
3375				MultiBlock::admin(
3376					RuntimeOrigin::signed(Manager::get()),
3377					AdminOperation::EmergencySetSolution(Default::default(), Default::default())
3378				),
3379				DispatchError::BadOrigin
3380			);
3381
3382			// we get a call to elect(0). this will cause emergency, since no fallback is allowed.
3383			assert_eq!(
3384				MultiBlock::elect(0),
3385				Err(ElectionError::Fallback("Emergency phase started.".to_string()))
3386			);
3387			assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3388
3389			// we can now set the solution to emergency.
3390			let (emergency, score) = emergency_solution();
3391			assert_ok!(MultiBlock::admin(
3392				RuntimeOrigin::root(),
3393				AdminOperation::EmergencySetSolution(Box::new(emergency), score)
3394			));
3395
3396			assert_eq!(MultiBlock::current_phase(), Phase::Emergency);
3397			assert_ok!(MultiBlock::elect(0));
3398			assert_eq!(MultiBlock::current_phase(), Phase::Off);
3399
3400			assert_eq!(
3401				multi_block_events(),
3402				vec![
3403					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) },
3404					Event::PhaseTransitioned {
3405						from: Phase::Snapshot(0),
3406						to: Phase::Signed(SignedPhase::get() - 1)
3407					},
3408					Event::PhaseTransitioned {
3409						from: Phase::Signed(SignedPhase::get() - 1),
3410						to: Phase::Emergency
3411					},
3412					Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off }
3413				]
3414			);
3415			assert_eq!(
3416				verifier_events(),
3417				vec![verifier::Event::Queued(
3418					ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 },
3419					None
3420				)]
3421			);
3422		})
3423	}
3424
3425	#[test]
3426	fn set_minimum_solution_score() {
3427		ExtBuilder::full().build_and_execute(|| {
3428			// bad origin cannot call
3429			assert_noop!(
3430				MultiBlock::admin(
3431					RuntimeOrigin::signed(Manager::get() + 1),
3432					AdminOperation::SetMinUntrustedScore(ElectionScore {
3433						minimal_stake: 100,
3434						..Default::default()
3435					})
3436				),
3437				DispatchError::BadOrigin
3438			);
3439
3440			// manager cannot call, only admin.
3441			assert_noop!(
3442				MultiBlock::admin(
3443					RuntimeOrigin::signed(Manager::get()),
3444					AdminOperation::SetMinUntrustedScore(ElectionScore {
3445						minimal_stake: 100,
3446						..Default::default()
3447					})
3448				),
3449				DispatchError::BadOrigin
3450			);
3451
3452			assert_eq!(VerifierPallet::minimum_score(), None);
3453			assert_ok!(MultiBlock::admin(
3454				RuntimeOrigin::root(),
3455				AdminOperation::SetMinUntrustedScore(ElectionScore {
3456					minimal_stake: 100,
3457					..Default::default()
3458				})
3459			));
3460			assert_eq!(
3461				VerifierPallet::minimum_score().unwrap(),
3462				ElectionScore { minimal_stake: 100, ..Default::default() }
3463			);
3464		});
3465	}
3466}