Skip to main content

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