referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_block/
lib.rs

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