pezpallet_election_provider_multi_phase/
lib.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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, offchain election provider pezpallet.
19//!
20//! Currently, this election-provider has two distinct phases (see [`Phase`]), **signed** and
21//! **unsigned**.
22//!
23//! ## Phases
24//!
25//! The timeline of pezpallet is as follows. At each block,
26//! [`pezframe_election_provider_support::ElectionDataProvider::next_election_prediction`] is used
27//! to estimate the time remaining to the next call to
28//! [`pezframe_election_provider_support::ElectionProvider::elect`]. Based on this, a phase is
29//! chosen. The timeline is as follows.
30//!
31//! ```ignore
32//!                                                                    elect()
33//!                 +   <--T::SignedPhase-->  +  <--T::UnsignedPhase-->   +
34//!   +-------------------------------------------------------------------+
35//!    Phase::Off   +       Phase::Signed     +      Phase::Unsigned      +
36//! ```
37//!
38//! Note that the unsigned phase starts [`pezpallet::Config::UnsignedPhase`] blocks before the
39//! `next_election_prediction`, but only ends when a call to [`ElectionProvider::elect`] happens. If
40//! no `elect` happens, the signed phase is extended.
41//!
42//! > Given this, it is rather important for the user of this pezpallet to ensure it always
43//! > terminates
44//! election via `elect` before requesting a new one.
45//!
46//! Each of the phases can be disabled by essentially setting their length to zero. If both phases
47//! have length zero, then the pezpallet essentially runs only the fallback strategy, denoted by
48//! [`Config::Fallback`].
49//!
50//! ### Signed Phase
51//!
52//! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A
53//! deposit is reserved, based on the size of the solution, for the cost of keeping this solution
54//! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A
55//! maximum of `pezpallet::Config::SignedMaxSubmissions` solutions are stored. The queue is always
56//! sorted based on score (worse to best).
57//!
58//! Upon arrival of a new solution:
59//!
60//! 1. If the queue is not full, it is stored in the appropriate sorted index.
61//! 2. If the queue is full but the submitted solution is better than one of the queued ones, the
62//!    worse solution is discarded, the bond of the outgoing solution is returned, and the new
63//!    solution is stored in the correct index.
64//! 3. If the queue is full and the solution is not an improvement compared to any of the queued
65//!    ones, it is instantly rejected and no additional bond is reserved.
66//!
67//! A signed solution cannot be reversed, taken back, updated, or retracted. In other words, the
68//! origin can not bail out in any way, if their solution is queued.
69//!
70//! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed
71//! until drained). Each solution undergoes an expensive `Pezpallet::feasibility_check`, which
72//! ensures the score claimed by this score was correct, and it is valid based on the election data
73//! (i.e. votes and targets). At each step, if the current best solution passes the feasibility
74//! check, it is considered to be the best one. The sender of the origin is rewarded, and the rest
75//! of the queued solutions get their deposit back and are discarded, without being checked.
76//!
77//! The following example covers all of the cases at the end of the signed phase:
78//!
79//! ```ignore
80//! Queue
81//! +-------------------------------+
82//! |Solution(score=20, valid=false)| +-->  Slashed
83//! +-------------------------------+
84//! |Solution(score=15, valid=true )| +-->  Rewarded, Saved
85//! +-------------------------------+
86//! |Solution(score=10, valid=true )| +-->  Discarded
87//! +-------------------------------+
88//! |Solution(score=05, valid=false)| +-->  Discarded
89//! +-------------------------------+
90//! |             None              |
91//! +-------------------------------+
92//! ```
93//!
94//! Note that both of the bottom solutions end up being discarded and get their deposit back,
95//! despite one of them being *invalid*.
96//!
97//! ## Unsigned Phase
98//!
99//! The unsigned phase will always follow the signed phase, with the specified duration. In this
100//! phase, only validator nodes can submit solutions. A validator node who has offchain workers
101//! enabled will start to mine a solution in this phase and submits it back to the chain as an
102//! unsigned transaction, thus the name _unsigned_ phase. This unsigned transaction can never be
103//! valid if propagated, and it acts similar to an inherent.
104//!
105//! Validators will only submit solutions if the one that they have computed is strictly better than
106//! the best queued one and will limit the weight of the solution to [`MinerConfig::MaxWeight`].
107//!
108//! The unsigned phase can be made passive depending on how the previous signed phase went, by
109//! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always
110//! active.
111//!
112//! ### Fallback
113//!
114//! If we reach the end of both phases (i.e. call to [`ElectionProvider::elect`] happens) and no
115//! good solution is queued, then the fallback strategy [`pezpallet::Config::Fallback`] is used to
116//! determine what needs to be done. The on-chain election is slow, and contains no balancing or
117//! reduction post-processing. If [`pezpallet::Config::Fallback`] fails, the next phase
118//! [`Phase::Emergency`] is enabled, which is a more *fail-safe* approach.
119//!
120//! ### Emergency Phase
121//!
122//! If, for any of the below reasons:
123//!
124//! 1. No **signed** or **unsigned** solution submitted, and no successful [`Config::Fallback`] is
125//!    provided
126//! 2. Any other unforeseen internal error
127//!
128//! A call to `T::ElectionProvider::elect` is made, and `Ok(_)` cannot be returned, then the
129//! pezpallet proceeds to the [`Phase::Emergency`]. During this phase, any solution can be submitted
130//! from [`Config::ForceOrigin`], without any checking, via
131//! [`Pezpallet::set_emergency_election_result`] transaction. Hence, `[`Config::ForceOrigin`]`
132//! should only be set to a trusted origin, such as the council or root. Once submitted, the forced
133//! solution is kept in [`QueuedSolution`] until the next call to `T::ElectionProvider::elect`,
134//! where it is returned and [`Phase`] goes back to `Off`.
135//!
136//! This implies that the user of this pezpallet (i.e. a staking pezpallet) should re-try calling
137//! `T::ElectionProvider::elect` in case of error, until `OK(_)` is returned.
138//!
139//! To generate an emergency solution, one must only provide one argument: [`Supports`]. This is
140//! essentially a collection of elected winners for the election, and voters who support them. The
141//! supports can be generated by any means. In the simplest case, it could be manual. For example,
142//! in the case of massive network failure or misbehavior, [`Config::ForceOrigin`] might decide to
143//! select only a small number of emergency winners (which would greatly restrict the next validator
144//! set, if this pezpallet is used with `pezpallet-staking`). If the failure is for other technical
145//! reasons, then a simple and safe way to generate supports is using the staking-miner binary
146//! provided in the Pezkuwi repository. This binary has a subcommand named `emergency-solution`
147//! which is capable of connecting to a live network, and generating appropriate `supports` using a
148//! standard algorithm, and outputting the `supports` in hex format, ready for submission. Note that
149//! while this binary lives in the Pezkuwi repository, this particular subcommand of it can work
150//! against any bizinikiwi-based chain.
151//!
152//! See the [`staking-miner`](https://github.com/paritytech/staking-miner-v2) docs for more
153//! information.
154//!
155//! ## Feasible Solution (correct solution)
156//!
157//! All submissions must undergo a feasibility check. Signed solutions are checked one by one at the
158//! end of the signed phase, and the unsigned solutions are checked on the spot. A feasible solution
159//! is as follows:
160//!
161//! 0. **all** of the used indices must be correct.
162//! 1. present *exactly* correct number of winners.
163//! 2. any assignment is checked to match with [`RoundSnapshot::voters`].
164//! 3. the claimed score is valid, based on the fixed point arithmetic accuracy.
165//!
166//! ## Accuracy
167//!
168//! The accuracy of the election is configured via [`SolutionAccuracyOf`] which is the accuracy that
169//! the submitted solutions must adhere to.
170//!
171//! Note that the accuracy is of great importance. The offchain solution should be as small as
172//! possible, reducing solutions size/weight.
173//!
174//! ## Error types
175//!
176//! This pezpallet provides a verbose error system to ease future debugging and debugging. The
177//! overall hierarchy of errors is as follows:
178//!
179//! 1. [`pezpallet::Error`]: These are the errors that can be returned in the dispatchables of the
180//!    pezpallet, either signed or unsigned. Since decomposition with nested enums is not possible
181//!    here, they are prefixed with the logical sub-system to which they belong.
182//! 2. [`ElectionError`]: These are the errors that can be generated while the pezpallet is doing
183//!    something in automatic scenarios, such as `offchain_worker` or `on_initialize`. These errors
184//!    are helpful for logging and are thus nested as:
185//!    - [`ElectionError::Miner`]: wraps a [`unsigned::MinerError`].
186//!    - [`ElectionError::Feasibility`]: wraps a [`FeasibilityError`].
187//!    - [`ElectionError::Fallback`]: wraps a fallback error.
188//!    - [`ElectionError::DataProvider`]: wraps a static str.
189//!
190//! Note that there could be an overlap between these sub-errors. For example, A
191//! `SnapshotUnavailable` can happen in both miner and feasibility check phase.
192//!
193//!	## Multi-page election support
194//!
195//! The [`pezframe_election_provider_support::ElectionDataProvider`] and
196//! [`pezframe_election_provider_support::ElectionProvider`] traits used by this pezpallet can
197//! support a multi-page election.
198//!
199//! However, this pezpallet only supports single-page election and data
200//! provider and all the relevant trait implementation and configurations reflect that assumption.
201//!
202//! If external callers request the election of a page index higher than 0, the election will fail
203//! with [`ElectionError::MultiPageNotSupported`].
204//!
205//! ## Future Plans
206//!
207//! **Emergency-phase recovery script**: This script should be taken out of staking-miner in
208//! pezkuwi and ideally live in `bizinikiwi/utils/pezframe/elections`.
209//!
210//! **Challenge Phase**. We plan on adding a third phase to the pezpallet, called the challenge
211//! phase. This is a phase in which no further solutions are processed, and the current best
212//! solution might be challenged by anyone (signed or unsigned). The main plan here is to enforce
213//! the solution to be PJR. Checking PJR on-chain is quite expensive, yet proving that a solution is
214//! **not** PJR is rather cheap. If a queued solution is successfully proven bad:
215//!
216//! 1. We must surely slash whoever submitted that solution (might be a challenge for unsigned
217//!    solutions).
218//! 2. We will fallback to the emergency strategy (likely extending the current era).
219//!
220//! **Bailing out**. The functionality of bailing out of a queued solution is nice. A miner can
221//! submit a solution as soon as they _think_ it is high probability feasible, and do the checks
222//! afterwards, and remove their solution (for a small cost of probably just transaction fees, or a
223//! portion of the bond).
224//!
225//! **Conditionally open unsigned phase**: Currently, the unsigned phase is always opened. This is
226//! useful because an honest validator will run bizinikiwi OCW code, which should be good enough to
227//! trump a mediocre or malicious signed submission (assuming in the absence of honest signed bots).
228//! If there are signed submissions, they can be checked against an absolute measure (e.g. PJR),
229//! then we can only open the unsigned phase in extreme conditions (i.e. "no good signed solution
230//! received") to spare some work for the active validators.
231//!
232//! **Allow smaller solutions and build up**: For now we only allow solutions that are exactly
233//! [`DesiredTargets`], no more, no less. Over time, we can change this to a [min, max] where any
234//! solution within this range is acceptable, where bigger solutions are prioritized.
235//!
236//! **Score based on (byte) size**: We should always prioritize small solutions over bigger ones, if
237//! there is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm.
238//!
239//! **Take into account the encode/decode weight in benchmarks.** Currently, we only take into
240//! account the weight of encode/decode in the `submit_unsigned` given its priority. Nonetheless,
241//! all operations on the solution and the snapshot are worthy of taking this into account.
242
243#![cfg_attr(not(feature = "std"), no_std)]
244
245extern crate alloc;
246
247use alloc::{boxed::Box, vec::Vec};
248use codec::{Decode, DecodeWithMemTracking, Encode};
249use pezframe_election_provider_support::{
250	bounds::{CountBound, ElectionBounds, SizeBound},
251	BoundedSupports, BoundedSupportsOf, ElectionDataProvider, ElectionProvider,
252	InstantElectionProvider, NposSolution, PageIndex,
253};
254use pezframe_support::{
255	dispatch::DispatchClass,
256	ensure,
257	traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
258	weights::Weight,
259	DefaultNoBound, EqNoBound, PartialEqNoBound,
260};
261use pezframe_system::{ensure_none, offchain::CreateBare, pezpallet_prelude::BlockNumberFor};
262use pezsp_arithmetic::{
263	traits::{CheckedAdd, Zero},
264	UpperOf,
265};
266use pezsp_npos_elections::{ElectionScore, IdentifierT, Supports, VoteWeight};
267use pezsp_runtime::{
268	transaction_validity::{
269		InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
270		TransactionValidityError, ValidTransaction,
271	},
272	DispatchError, ModuleError, PerThing, Perbill, RuntimeDebug, SaturatedConversion,
273};
274use scale_info::TypeInfo;
275
276#[cfg(feature = "try-runtime")]
277use pezsp_runtime::TryRuntimeError;
278
279#[cfg(feature = "runtime-benchmarks")]
280mod benchmarking;
281#[cfg(test)]
282mod mock;
283#[cfg(all(test, feature = "remote-mining"))]
284mod remote_mining;
285#[macro_use]
286pub mod helpers;
287
288/// This pezpallet only supports a single page election flow.
289pub(crate) const SINGLE_PAGE: u32 = 0;
290const LOG_TARGET: &str = "runtime::election-provider";
291
292pub mod migrations;
293pub mod signed;
294pub mod unsigned;
295pub mod weights;
296
297pub use signed::{
298	BalanceOf, GeometricDepositBase, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission,
299	SignedSubmissionOf, SignedSubmissions, SubmissionIndicesOf,
300};
301use unsigned::VoterOf;
302pub use unsigned::{Miner, MinerConfig};
303pub use weights::WeightInfo;
304
305/// The solution type used by this crate.
306pub type SolutionOf<T> = <T as MinerConfig>::Solution;
307/// The voter index. Derived from [`SolutionOf`].
308pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
309/// The target index. Derived from [`SolutionOf`].
310pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
311/// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`].
312pub type SolutionAccuracyOf<T> =
313	<SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
314/// A ready solution parameterized with this pezpallet's miner config.
315pub type ReadySolutionOf<T> = ReadySolution<
316	<T as MinerConfig>::AccountId,
317	<T as MinerConfig>::MaxWinners,
318	<T as MinerConfig>::MaxBackersPerWinner,
319>;
320/// The fallback election type.
321pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;
322
323/// Configuration for the benchmarks of the pezpallet.
324pub trait BenchmarkingConfig {
325	/// Range of voters.
326	const VOTERS: [u32; 2];
327	/// Range of targets.
328	const TARGETS: [u32; 2];
329	/// Range of active voters.
330	const ACTIVE_VOTERS: [u32; 2];
331	/// Range of desired targets.
332	const DESIRED_TARGETS: [u32; 2];
333	/// Maximum number of voters expected. This is used only for memory-benchmarking of snapshot.
334	const SNAPSHOT_MAXIMUM_VOTERS: u32;
335	/// Maximum number of voters expected. This is used only for memory-benchmarking of miner.
336	const MINER_MAXIMUM_VOTERS: u32;
337	/// Maximum number of targets expected. This is used only for memory-benchmarking.
338	const MAXIMUM_TARGETS: u32;
339}
340
341/// Current phase of the pezpallet.
342#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
343pub enum Phase<Bn> {
344	/// Nothing, the election is not happening.
345	Off,
346	/// Signed phase is open.
347	Signed,
348	/// Unsigned phase. First element is whether it is active or not, second the starting block
349	/// number.
350	///
351	/// We do not yet check whether the unsigned phase is active or passive. The intent is for the
352	/// blockchain to be able to declare: "I believe that there exists an adequate signed
353	/// solution," advising validators not to bother running the unsigned offchain worker.
354	///
355	/// As validator nodes are free to edit their OCW code, they could simply ignore this advisory
356	/// and always compute their own solution. However, by default, when the unsigned phase is
357	/// passive, the offchain workers will not bother running.
358	Unsigned((bool, Bn)),
359	/// The emergency phase. This is enabled upon a failing call to `T::ElectionProvider::elect`.
360	/// After that, the only way to leave this phase is through a successful
361	/// `T::ElectionProvider::elect`.
362	Emergency,
363}
364
365impl<Bn> Default for Phase<Bn> {
366	fn default() -> Self {
367		Phase::Off
368	}
369}
370
371impl<Bn: PartialEq + Eq> Phase<Bn> {
372	/// Whether the phase is emergency or not.
373	pub fn is_emergency(&self) -> bool {
374		matches!(self, Phase::Emergency)
375	}
376
377	/// Whether the phase is signed or not.
378	pub fn is_signed(&self) -> bool {
379		matches!(self, Phase::Signed)
380	}
381
382	/// Whether the phase is unsigned or not.
383	pub fn is_unsigned(&self) -> bool {
384		matches!(self, Phase::Unsigned(_))
385	}
386
387	/// Whether the phase is unsigned and open or not, with specific start.
388	pub fn is_unsigned_open_at(&self, at: Bn) -> bool {
389		matches!(self, Phase::Unsigned((true, real)) if *real == at)
390	}
391
392	/// Whether the phase is unsigned and open or not.
393	pub fn is_unsigned_open(&self) -> bool {
394		matches!(self, Phase::Unsigned((true, _)))
395	}
396
397	/// Whether the phase is off or not.
398	pub fn is_off(&self) -> bool {
399		matches!(self, Phase::Off)
400	}
401}
402
403/// The type of `Computation` that provided this election data.
404#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
405pub enum ElectionCompute {
406	/// Election was computed on-chain.
407	OnChain,
408	/// Election was computed with a signed submission.
409	Signed,
410	/// Election was computed with an unsigned submission.
411	Unsigned,
412	/// Election was computed using the fallback
413	Fallback,
414	/// Election was computed with emergency status.
415	Emergency,
416}
417
418impl Default for ElectionCompute {
419	fn default() -> Self {
420		ElectionCompute::OnChain
421	}
422}
423
424/// A raw, unchecked solution.
425///
426/// This is what will get submitted to the chain.
427///
428/// Such a solution should never become effective in anyway before being checked by the
429/// `Pezpallet::feasibility_check`.
430#[derive(
431	PartialEq,
432	Eq,
433	Clone,
434	Encode,
435	Decode,
436	DecodeWithMemTracking,
437	RuntimeDebug,
438	PartialOrd,
439	Ord,
440	TypeInfo,
441)]
442pub struct RawSolution<S> {
443	/// the solution itself.
444	pub solution: S,
445	/// The _claimed_ score of the solution.
446	pub score: ElectionScore,
447	/// The round at which this solution should be submitted.
448	pub round: u32,
449}
450
451impl<C: Default> Default for RawSolution<C> {
452	fn default() -> Self {
453		// Round 0 is always invalid, only set this to 1.
454		Self { round: 1, solution: Default::default(), score: Default::default() }
455	}
456}
457
458/// A checked solution, ready to be enacted.
459#[derive(
460	PartialEqNoBound,
461	EqNoBound,
462	Clone,
463	Encode,
464	Decode,
465	RuntimeDebug,
466	DefaultNoBound,
467	scale_info::TypeInfo,
468)]
469#[scale_info(skip_type_params(AccountId, MaxWinners, MaxBackersPerWinner))]
470pub struct ReadySolution<AccountId, MaxWinners, MaxBackersPerWinner>
471where
472	AccountId: IdentifierT,
473	MaxWinners: Get<u32>,
474	MaxBackersPerWinner: Get<u32>,
475{
476	/// The final supports of the solution.
477	///
478	/// This is target-major vector, storing each winners, total backing, and each individual
479	/// backer.
480	pub supports: BoundedSupports<AccountId, MaxWinners, MaxBackersPerWinner>,
481	/// The score of the solution.
482	///
483	/// This is needed to potentially challenge the solution.
484	pub score: ElectionScore,
485	/// How this election was computed.
486	pub compute: ElectionCompute,
487}
488
489/// A snapshot of all the data that is needed for en entire round. They are provided by
490/// [`ElectionDataProvider`] and are kept around until the round is finished.
491///
492/// These are stored together because they are often accessed together.
493#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
494#[scale_info(skip_type_params(T))]
495pub struct RoundSnapshot<AccountId, VoterType> {
496	/// All of the voters.
497	pub voters: Vec<VoterType>,
498	/// All of the targets.
499	pub targets: Vec<AccountId>,
500}
501
502/// Encodes the length of a solution or a snapshot.
503///
504/// This is stored automatically on-chain, and it contains the **size of the entire snapshot**.
505/// This is also used in dispatchables as weight witness data and should **only contain the size of
506/// the presented solution**, not the entire snapshot.
507#[derive(
508	PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, Debug, Default, TypeInfo,
509)]
510pub struct SolutionOrSnapshotSize {
511	/// The length of voters.
512	#[codec(compact)]
513	pub voters: u32,
514	/// The length of targets.
515	#[codec(compact)]
516	pub targets: u32,
517}
518
519/// Internal errors of the pezpallet.
520///
521/// Note that this is different from [`pezpallet::Error`].
522#[derive(pezframe_support::DebugNoBound)]
523#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))]
524pub enum ElectionError<T: Config> {
525	/// An error happened in the feasibility check sub-system.
526	Feasibility(FeasibilityError),
527	/// An error in the miner (offchain) sub-system.
528	Miner(unsigned::MinerError),
529	/// An error happened in the data provider.
530	DataProvider(&'static str),
531	/// An error nested in the fallback.
532	Fallback(FallbackErrorOf<T>),
533	/// An error occurred when requesting an election result. The caller expects a multi-paged
534	/// election, which this pezpallet does not support.
535	MultiPageNotSupported,
536	/// No solution has been queued.
537	NothingQueued,
538}
539
540// NOTE: we have to do this manually because of the additional where clause needed on
541// `FallbackErrorOf<T>`.
542impl<T: Config> PartialEq for ElectionError<T>
543where
544	FallbackErrorOf<T>: PartialEq,
545{
546	fn eq(&self, other: &Self) -> bool {
547		use ElectionError::*;
548		match (self, other) {
549			(Feasibility(x), Feasibility(y)) if x == y => true,
550			(Miner(x), Miner(y)) if x == y => true,
551			(DataProvider(x), DataProvider(y)) if x == y => true,
552			(Fallback(x), Fallback(y)) if x == y => true,
553			(MultiPageNotSupported, MultiPageNotSupported) => true,
554			(NothingQueued, NothingQueued) => true,
555			_ => false,
556		}
557	}
558}
559
560impl<T: Config> From<FeasibilityError> for ElectionError<T> {
561	fn from(e: FeasibilityError) -> Self {
562		ElectionError::Feasibility(e)
563	}
564}
565
566impl<T: Config> From<unsigned::MinerError> for ElectionError<T> {
567	fn from(e: unsigned::MinerError) -> Self {
568		ElectionError::Miner(e)
569	}
570}
571
572/// Errors that can happen in the feasibility check.
573#[derive(Debug, Eq, PartialEq)]
574#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))]
575pub enum FeasibilityError {
576	/// Wrong number of winners presented.
577	WrongWinnerCount,
578	/// The snapshot is not available.
579	///
580	/// Kinda defensive: The pezpallet should technically never attempt to do a feasibility check
581	/// when no snapshot is present.
582	SnapshotUnavailable,
583	/// Internal error from the election crate.
584	NposElection(pezsp_npos_elections::Error),
585	/// A vote is invalid.
586	InvalidVote,
587	/// A voter is invalid.
588	InvalidVoter,
589	/// The given score was invalid.
590	InvalidScore,
591	/// The provided round is incorrect.
592	InvalidRound,
593	/// Comparison against `MinimumUntrustedScore` failed.
594	UntrustedScoreTooLow,
595	/// Data Provider returned too many desired targets
596	TooManyDesiredTargets,
597	/// Conversion into bounded types failed.
598	///
599	/// Should never happen under correct configurations.
600	BoundedConversionFailed,
601}
602
603impl From<pezsp_npos_elections::Error> for FeasibilityError {
604	fn from(e: pezsp_npos_elections::Error) -> Self {
605		FeasibilityError::NposElection(e)
606	}
607}
608
609pub use pezpallet::*;
610#[pezframe_support::pezpallet]
611pub mod pezpallet {
612	use super::*;
613	use pezframe_election_provider_support::{InstantElectionProvider, NposSolver};
614	use pezframe_support::{pezpallet_prelude::*, traits::EstimateCallFee};
615	use pezframe_system::pezpallet_prelude::*;
616	use pezsp_runtime::traits::Convert;
617
618	#[pezpallet::config]
619	pub trait Config: pezframe_system::Config + CreateBare<Call<Self>> {
620		#[allow(deprecated)]
621		type RuntimeEvent: From<Event<Self>>
622			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>
623			+ TryInto<Event<Self>>;
624
625		/// Currency type.
626		type Currency: ReservableCurrency<Self::AccountId> + Currency<Self::AccountId>;
627
628		/// Something that can predict the fee of a call. Used to sensibly distribute rewards.
629		type EstimateCallFee: EstimateCallFee<Call<Self>, BalanceOf<Self>>;
630
631		/// Duration of the unsigned phase.
632		type UnsignedPhase: Get<BlockNumberFor<Self>>;
633		/// Duration of the signed phase.
634		type SignedPhase: Get<BlockNumberFor<Self>>;
635
636		/// The minimum amount of improvement to the solution score that defines a solution as
637		/// "better" in the Signed phase.
638		#[pezpallet::constant]
639		type BetterSignedThreshold: Get<Perbill>;
640
641		/// The repeat threshold of the offchain worker.
642		///
643		/// For example, if it is 5, that means that at least 5 blocks will elapse between attempts
644		/// to submit the worker's solution.
645		#[pezpallet::constant]
646		type OffchainRepeat: Get<BlockNumberFor<Self>>;
647
648		/// The priority of the unsigned transaction submitted in the unsigned-phase
649		#[pezpallet::constant]
650		type MinerTxPriority: Get<TransactionPriority>;
651
652		/// Configurations of the embedded miner.
653		///
654		/// Any external software implementing this can use the [`unsigned::Miner`] type provided,
655		/// which can mine new solutions and trim them accordingly.
656		type MinerConfig: crate::unsigned::MinerConfig<
657			AccountId = Self::AccountId,
658			MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
659			MaxWinners = Self::MaxWinners,
660			MaxBackersPerWinner = Self::MaxBackersPerWinner,
661		>;
662
663		/// Maximum number of signed submissions that can be queued.
664		///
665		/// It is best to avoid adjusting this during an election, as it impacts downstream data
666		/// structures. In particular, `SignedSubmissionIndices<T>` is bounded on this value. If you
667		/// update this value during an election, you _must_ ensure that
668		/// `SignedSubmissionIndices.len()` is less than or equal to the new value. Otherwise,
669		/// attempts to submit new solutions may cause a runtime panic.
670		#[pezpallet::constant]
671		type SignedMaxSubmissions: Get<u32>;
672
673		/// Maximum weight of a signed solution.
674		///
675		/// If [`Config::MinerConfig`] is being implemented to submit signed solutions (outside of
676		/// this pezpallet), then [`MinerConfig::solution_weight`] is used to compare against
677		/// this value.
678		#[pezpallet::constant]
679		type SignedMaxWeight: Get<Weight>;
680
681		/// The maximum amount of unchecked solutions to refund the call fee for.
682		#[pezpallet::constant]
683		type SignedMaxRefunds: Get<u32>;
684
685		/// Base reward for a signed solution
686		#[pezpallet::constant]
687		type SignedRewardBase: Get<BalanceOf<Self>>;
688
689		/// Per-byte deposit for a signed solution.
690		#[pezpallet::constant]
691		type SignedDepositByte: Get<BalanceOf<Self>>;
692
693		/// Per-weight deposit for a signed solution.
694		#[pezpallet::constant]
695		type SignedDepositWeight: Get<BalanceOf<Self>>;
696
697		/// Maximum number of winners that an election supports.
698		///
699		/// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`.
700		#[pezpallet::constant]
701		type MaxWinners: Get<u32>;
702
703		/// Maximum number of voters that can support a winner in an election solution.
704		///
705		/// This is needed to ensure election computation is bounded.
706		#[pezpallet::constant]
707		type MaxBackersPerWinner: Get<u32>;
708
709		/// Something that calculates the signed deposit base based on the signed submissions queue
710		/// size.
711		type SignedDepositBase: Convert<usize, BalanceOf<Self>>;
712
713		/// The maximum number of electing voters and electable targets to put in the snapshot.
714		type ElectionBounds: Get<ElectionBounds>;
715
716		/// Handler for the slashed deposits.
717		type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
718
719		/// Handler for the rewards.
720		type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>;
721
722		/// Something that will provide the election data.
723		type DataProvider: ElectionDataProvider<
724			AccountId = Self::AccountId,
725			BlockNumber = BlockNumberFor<Self>,
726		>;
727
728		/// Configuration for the fallback.
729		type Fallback: InstantElectionProvider<
730			AccountId = Self::AccountId,
731			BlockNumber = BlockNumberFor<Self>,
732			DataProvider = Self::DataProvider,
733			MaxBackersPerWinner = Self::MaxBackersPerWinner,
734			MaxWinnersPerPage = Self::MaxWinners,
735		>;
736
737		/// Configuration of the governance-only fallback.
738		///
739		/// As a side-note, it is recommend for test-nets to use `type ElectionProvider =
740		/// BoundedExecution<_>` if the test-net is not expected to have thousands of nominators.
741		type GovernanceFallback: InstantElectionProvider<
742			AccountId = Self::AccountId,
743			BlockNumber = BlockNumberFor<Self>,
744			DataProvider = Self::DataProvider,
745			MaxWinnersPerPage = Self::MaxWinners,
746			MaxBackersPerWinner = Self::MaxBackersPerWinner,
747		>;
748
749		/// OCW election solution miner algorithm implementation.
750		type Solver: NposSolver<AccountId = Self::AccountId>;
751
752		/// Origin that can control this pezpallet. Note that any action taken by this origin (such)
753		/// as providing an emergency solution is not checked. Thus, it must be a trusted origin.
754		type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
755
756		/// The configuration of benchmarking.
757		type BenchmarkingConfig: BenchmarkingConfig;
758
759		/// The weight of the pezpallet.
760		type WeightInfo: WeightInfo;
761	}
762
763	// Expose miner configs over the metadata such that they can be re-implemented.
764	#[pezpallet::extra_constants]
765	impl<T: Config> Pezpallet<T> {
766		#[pezpallet::constant_name(MinerMaxLength)]
767		fn max_length() -> u32 {
768			<T::MinerConfig as MinerConfig>::MaxLength::get()
769		}
770
771		#[pezpallet::constant_name(MinerMaxWeight)]
772		fn max_weight() -> Weight {
773			<T::MinerConfig as MinerConfig>::MaxWeight::get()
774		}
775
776		#[pezpallet::constant_name(MinerMaxVotesPerVoter)]
777		fn max_votes_per_voter() -> u32 {
778			<T::MinerConfig as MinerConfig>::MaxVotesPerVoter::get()
779		}
780
781		#[pezpallet::constant_name(MinerMaxWinners)]
782		fn max_winners() -> u32 {
783			<T::MinerConfig as MinerConfig>::MaxWinners::get()
784		}
785	}
786
787	#[pezpallet::hooks]
788	impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
789		fn on_initialize(now: BlockNumberFor<T>) -> Weight {
790			let next_election = T::DataProvider::next_election_prediction(now).max(now);
791
792			let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get();
793			let unsigned_deadline = T::UnsignedPhase::get();
794
795			let remaining = next_election - now;
796			let current_phase = CurrentPhase::<T>::get();
797
798			log!(
799				trace,
800				"current phase {:?}, next election {:?}, queued? {:?}, metadata: {:?}",
801				current_phase,
802				next_election,
803				QueuedSolution::<T>::get().map(|rs| (rs.supports.len(), rs.compute, rs.score)),
804				SnapshotMetadata::<T>::get()
805			);
806			match current_phase {
807				Phase::Off if remaining <= signed_deadline && remaining > unsigned_deadline => {
808					// NOTE: if signed-phase length is zero, second part of the if-condition fails.
809					match Self::create_snapshot() {
810						Ok(_) => {
811							Self::phase_transition(Phase::Signed);
812							T::WeightInfo::on_initialize_open_signed()
813						},
814						Err(why) => {
815							// Not much we can do about this at this point.
816							log!(warn, "failed to open signed phase due to {:?}", why);
817							T::WeightInfo::on_initialize_nothing()
818						},
819					}
820				},
821				Phase::Signed | Phase::Off
822					if remaining <= unsigned_deadline && remaining > Zero::zero() =>
823				{
824					// our needs vary according to whether or not the unsigned phase follows a
825					// signed phase
826					let (need_snapshot, enabled) = if current_phase == Phase::Signed {
827						// there was previously a signed phase: close the signed phase, no need for
828						// snapshot.
829						//
830						// Notes:
831						//
832						//   - `Self::finalize_signed_phase()` also appears in `fn do_elect`. This
833						//     is a guard against the case that `elect` is called prematurely. This
834						//     adds a small amount of overhead, but that is unfortunately
835						//     unavoidable.
836						let _ = Self::finalize_signed_phase();
837						// In the future we can consider disabling the unsigned phase if the signed
838						// phase completes successfully, but for now we're enabling it
839						// unconditionally as a defensive measure.
840						(false, true)
841					} else {
842						// No signed phase: create a new snapshot, definitely `enable` the unsigned
843						// phase.
844						(true, true)
845					};
846
847					if need_snapshot {
848						match Self::create_snapshot() {
849							Ok(_) => {
850								Self::phase_transition(Phase::Unsigned((enabled, now)));
851								T::WeightInfo::on_initialize_open_unsigned()
852							},
853							Err(why) => {
854								log!(warn, "failed to open unsigned phase due to {:?}", why);
855								T::WeightInfo::on_initialize_nothing()
856							},
857						}
858					} else {
859						Self::phase_transition(Phase::Unsigned((enabled, now)));
860						T::WeightInfo::on_initialize_open_unsigned()
861					}
862				},
863				_ => T::WeightInfo::on_initialize_nothing(),
864			}
865		}
866
867		fn offchain_worker(now: BlockNumberFor<T>) {
868			use pezsp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock};
869
870			// Create a lock with the maximum deadline of number of blocks in the unsigned phase.
871			// This should only come useful in an **abrupt** termination of execution, otherwise the
872			// guard will be dropped upon successful execution.
873			let mut lock =
874				StorageLock::<BlockAndTime<pezframe_system::Pezpallet<T>>>::with_block_deadline(
875					unsigned::OFFCHAIN_LOCK,
876					T::UnsignedPhase::get().saturated_into(),
877				);
878
879			match lock.try_lock() {
880				Ok(_guard) => {
881					Self::do_synchronized_offchain_worker(now);
882				},
883				Err(deadline) => {
884					log!(debug, "offchain worker lock not released, deadline is {:?}", deadline);
885				},
886			};
887		}
888
889		fn integrity_test() {
890			use core::mem::size_of;
891			// The index type of both voters and targets need to be smaller than that of usize (very
892			// unlikely to be the case, but anyhow)..
893			assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
894			assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
895
896			// ----------------------------
897			// Based on the requirements of [`pezsp_npos_elections::Assignment::try_normalize`].
898			let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
899
900			// 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf<OffchainAccuracy>`.
901			let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote)
902				.map(|_| {
903					<UpperOf<SolutionAccuracyOf<T>>>::from(
904						SolutionAccuracyOf::<T>::one().deconstruct(),
905					)
906				})
907				.collect();
908			let _: UpperOf<SolutionAccuracyOf<T>> = maximum_chain_accuracy
909				.iter()
910				.fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap());
911
912			// We only accept data provider who's maximum votes per voter matches our
913			// `T::Solution`'s `LIMIT`.
914			//
915			// NOTE that this pezpallet does not really need to enforce this in runtime. The
916			// solution cannot represent any voters more than `LIMIT` anyhow.
917			assert_eq!(
918				<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
919				<SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
920			);
921
922			// While it won't cause any failures, setting `SignedMaxRefunds` gt
923			// `SignedMaxSubmissions` is a red flag that the developer does not understand how to
924			// configure this pezpallet.
925			assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get());
926		}
927
928		#[cfg(feature = "try-runtime")]
929		fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
930			Self::do_try_state()
931		}
932	}
933
934	#[pezpallet::call]
935	impl<T: Config> Pezpallet<T> {
936		/// Submit a solution for the unsigned phase.
937		///
938		/// The dispatch origin fo this call must be __none__.
939		///
940		/// This submission is checked on the fly. Moreover, this unsigned solution is only
941		/// validated when submitted to the pool from the **local** node. Effectively, this means
942		/// that only active validators can submit this transaction when authoring a block (similar
943		/// to an inherent).
944		///
945		/// To prevent any incorrect solution (and thus wasted time/weight), this transaction will
946		/// panic if the solution submitted by the validator is invalid in any way, effectively
947		/// putting their authoring reward at risk.
948		///
949		/// No deposit or reward is associated with this submission.
950		#[pezpallet::call_index(0)]
951		#[pezpallet::weight((
952			T::WeightInfo::submit_unsigned(
953				witness.voters,
954				witness.targets,
955				raw_solution.solution.voter_count() as u32,
956				raw_solution.solution.unique_targets().len() as u32
957			),
958			DispatchClass::Operational,
959		))]
960		pub fn submit_unsigned(
961			origin: OriginFor<T>,
962			raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
963			witness: SolutionOrSnapshotSize,
964		) -> DispatchResult {
965			ensure_none(origin)?;
966			let error_message = "Invalid unsigned submission must produce invalid block and \
967				 deprive validator from their authoring reward.";
968
969			// Check score being an improvement, phase, and desired targets.
970			Self::unsigned_pre_dispatch_checks(&raw_solution).expect(error_message);
971
972			// Ensure witness was correct.
973			let SolutionOrSnapshotSize { voters, targets } =
974				SnapshotMetadata::<T>::get().expect(error_message);
975
976			// NOTE: we are asserting, not `ensure`ing -- we want to panic here.
977			assert!(voters as u32 == witness.voters, "{}", error_message);
978			assert!(targets as u32 == witness.targets, "{}", error_message);
979
980			let ready = Self::feasibility_check(*raw_solution, ElectionCompute::Unsigned)
981				.expect(error_message);
982
983			// Store the newly received solution.
984			log!(debug, "queued unsigned solution with score {:?}", ready.score);
985			let ejected_a_solution = QueuedSolution::<T>::exists();
986			QueuedSolution::<T>::put(ready);
987			Self::deposit_event(Event::SolutionStored {
988				compute: ElectionCompute::Unsigned,
989				origin: None,
990				prev_ejected: ejected_a_solution,
991			});
992
993			Ok(())
994		}
995
996		/// Set a new value for `MinimumUntrustedScore`.
997		///
998		/// Dispatch origin must be aligned with `T::ForceOrigin`.
999		///
1000		/// This check can be turned off by setting the value to `None`.
1001		#[pezpallet::call_index(1)]
1002		#[pezpallet::weight(T::DbWeight::get().writes(1))]
1003		pub fn set_minimum_untrusted_score(
1004			origin: OriginFor<T>,
1005			maybe_next_score: Option<ElectionScore>,
1006		) -> DispatchResult {
1007			T::ForceOrigin::ensure_origin(origin)?;
1008			MinimumUntrustedScore::<T>::set(maybe_next_score);
1009			Ok(())
1010		}
1011
1012		/// Set a solution in the queue, to be handed out to the client of this pezpallet in the
1013		/// next call to `ElectionProvider::elect`.
1014		///
1015		/// This can only be set by `T::ForceOrigin`, and only when the phase is `Emergency`.
1016		///
1017		/// The solution is not checked for any feasibility and is assumed to be trustworthy, as any
1018		/// feasibility check itself can in principle cause the election process to fail (due to
1019		/// memory/weight constrains).
1020		#[pezpallet::call_index(2)]
1021		#[pezpallet::weight(T::DbWeight::get().reads_writes(1, 1))]
1022		pub fn set_emergency_election_result(
1023			origin: OriginFor<T>,
1024			supports: Supports<T::AccountId>,
1025		) -> DispatchResult {
1026			T::ForceOrigin::ensure_origin(origin)?;
1027			ensure!(CurrentPhase::<T>::get().is_emergency(), Error::<T>::CallNotAllowed);
1028
1029			// bound supports with T::MaxWinners.
1030			let supports: BoundedSupportsOf<Self> =
1031				supports.try_into().map_err(|_| Error::<T>::TooManyWinners)?;
1032
1033			// Note: we don't `rotate_round` at this point; the next call to
1034			// `ElectionProvider::elect` will succeed and take care of that.
1035			let solution = ReadySolution {
1036				supports,
1037				score: Default::default(),
1038				compute: ElectionCompute::Emergency,
1039			};
1040
1041			Self::deposit_event(Event::SolutionStored {
1042				compute: ElectionCompute::Emergency,
1043				origin: None,
1044				prev_ejected: QueuedSolution::<T>::exists(),
1045			});
1046
1047			QueuedSolution::<T>::put(solution);
1048			Ok(())
1049		}
1050
1051		/// Submit a solution for the signed phase.
1052		///
1053		/// The dispatch origin fo this call must be __signed__.
1054		///
1055		/// The solution is potentially queued, based on the claimed score and processed at the end
1056		/// of the signed phase.
1057		///
1058		/// A deposit is reserved and recorded for the solution. Based on the outcome, the solution
1059		/// might be rewarded, slashed, or get all or a part of the deposit back.
1060		#[pezpallet::call_index(3)]
1061		#[pezpallet::weight(T::WeightInfo::submit())]
1062		pub fn submit(
1063			origin: OriginFor<T>,
1064			raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
1065		) -> DispatchResult {
1066			let who = ensure_signed(origin)?;
1067
1068			// ensure solution is timely.
1069			ensure!(CurrentPhase::<T>::get().is_signed(), Error::<T>::PreDispatchEarlySubmission);
1070			ensure!(raw_solution.round == Round::<T>::get(), Error::<T>::PreDispatchDifferentRound);
1071
1072			// NOTE: this is the only case where having separate snapshot would have been better
1073			// because could do just decode_len. But we can create abstractions to do this.
1074
1075			// build size. Note: this is not needed for weight calc, thus not input.
1076			// unlikely to ever return an error: if phase is signed, snapshot will exist.
1077			let size = SnapshotMetadata::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1078
1079			ensure!(
1080				Self::solution_weight_of(&raw_solution, size).all_lt(T::SignedMaxWeight::get()),
1081				Error::<T>::SignedTooMuchWeight,
1082			);
1083
1084			// create the submission
1085			let deposit = Self::deposit_for(&raw_solution, size);
1086			let call_fee = {
1087				let call = Call::submit { raw_solution: raw_solution.clone() };
1088				T::EstimateCallFee::estimate_call_fee(&call, None::<Weight>.into())
1089			};
1090
1091			let submission = SignedSubmission {
1092				who: who.clone(),
1093				deposit,
1094				raw_solution: *raw_solution,
1095				call_fee,
1096			};
1097
1098			// insert the submission if the queue has space or it's better than the weakest
1099			// eject the weakest if the queue was full
1100			let mut signed_submissions = Self::signed_submissions();
1101			let maybe_removed = match signed_submissions.insert(submission) {
1102				// it's an error if we failed to insert a submission: this indicates the queue was
1103				// full but our solution had insufficient score to eject any solution
1104				signed::InsertResult::NotInserted => return Err(Error::<T>::SignedQueueFull.into()),
1105				signed::InsertResult::Inserted => None,
1106				signed::InsertResult::InsertedEjecting(weakest) => Some(weakest),
1107			};
1108
1109			// collect deposit. Thereafter, the function cannot fail.
1110			T::Currency::reserve(&who, deposit).map_err(|_| Error::<T>::SignedCannotPayDeposit)?;
1111
1112			let ejected_a_solution = maybe_removed.is_some();
1113			// if we had to remove the weakest solution, unreserve its deposit
1114			if let Some(removed) = maybe_removed {
1115				let _remainder = T::Currency::unreserve(&removed.who, removed.deposit);
1116				debug_assert!(_remainder.is_zero());
1117			}
1118
1119			signed_submissions.put();
1120			Self::deposit_event(Event::SolutionStored {
1121				compute: ElectionCompute::Signed,
1122				origin: Some(who),
1123				prev_ejected: ejected_a_solution,
1124			});
1125			Ok(())
1126		}
1127
1128		/// Trigger the governance fallback.
1129		///
1130		/// This can only be called when [`Phase::Emergency`] is enabled, as an alternative to
1131		/// calling [`Call::set_emergency_election_result`].
1132		#[pezpallet::call_index(4)]
1133		#[pezpallet::weight(T::DbWeight::get().reads_writes(1, 1))]
1134		pub fn governance_fallback(origin: OriginFor<T>) -> DispatchResult {
1135			T::ForceOrigin::ensure_origin(origin)?;
1136			ensure!(CurrentPhase::<T>::get().is_emergency(), Error::<T>::CallNotAllowed);
1137
1138			let RoundSnapshot { voters, targets } =
1139				Snapshot::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1140			let desired_targets =
1141				DesiredTargets::<T>::get().ok_or(Error::<T>::MissingSnapshotMetadata)?;
1142
1143			let supports = T::GovernanceFallback::instant_elect(voters, targets, desired_targets)
1144				.map_err(|e| {
1145				log!(error, "GovernanceFallback failed: {:?}", e);
1146				Error::<T>::FallbackFailed
1147			})?;
1148
1149			let solution = ReadySolution {
1150				supports,
1151				score: Default::default(),
1152				compute: ElectionCompute::Fallback,
1153			};
1154
1155			Self::deposit_event(Event::SolutionStored {
1156				compute: ElectionCompute::Fallback,
1157				origin: None,
1158				prev_ejected: QueuedSolution::<T>::exists(),
1159			});
1160
1161			QueuedSolution::<T>::put(solution);
1162			Ok(())
1163		}
1164	}
1165
1166	#[pezpallet::event]
1167	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
1168	pub enum Event<T: Config> {
1169		/// A solution was stored with the given compute.
1170		///
1171		/// The `origin` indicates the origin of the solution. If `origin` is `Some(AccountId)`,
1172		/// the stored solution was submitted in the signed phase by a miner with the `AccountId`.
1173		/// Otherwise, the solution was stored either during the unsigned phase or by
1174		/// `T::ForceOrigin`. The `bool` is `true` when a previous solution was ejected to make
1175		/// room for this one.
1176		SolutionStored {
1177			compute: ElectionCompute,
1178			origin: Option<T::AccountId>,
1179			prev_ejected: bool,
1180		},
1181		/// The election has been finalized, with the given computation and score.
1182		ElectionFinalized { compute: ElectionCompute, score: ElectionScore },
1183		/// An election failed.
1184		///
1185		/// Not much can be said about which computes failed in the process.
1186		ElectionFailed,
1187		/// An account has been rewarded for their signed submission being finalized.
1188		Rewarded { account: <T as pezframe_system::Config>::AccountId, value: BalanceOf<T> },
1189		/// An account has been slashed for submitting an invalid signed submission.
1190		Slashed { account: <T as pezframe_system::Config>::AccountId, value: BalanceOf<T> },
1191		/// There was a phase transition in a given round.
1192		PhaseTransitioned {
1193			from: Phase<BlockNumberFor<T>>,
1194			to: Phase<BlockNumberFor<T>>,
1195			round: u32,
1196		},
1197	}
1198
1199	/// Error of the pezpallet that can be returned in response to dispatches.
1200	#[pezpallet::error]
1201	pub enum Error<T> {
1202		/// Submission was too early.
1203		PreDispatchEarlySubmission,
1204		/// Wrong number of winners presented.
1205		PreDispatchWrongWinnerCount,
1206		/// Submission was too weak, score-wise.
1207		PreDispatchWeakSubmission,
1208		/// The queue was full, and the solution was not better than any of the existing ones.
1209		SignedQueueFull,
1210		/// The origin failed to pay the deposit.
1211		SignedCannotPayDeposit,
1212		/// Witness data to dispatchable is invalid.
1213		SignedInvalidWitness,
1214		/// The signed submission consumes too much weight
1215		SignedTooMuchWeight,
1216		/// OCW submitted solution for wrong round
1217		OcwCallWrongEra,
1218		/// Snapshot metadata should exist but didn't.
1219		MissingSnapshotMetadata,
1220		/// `Self::insert_submission` returned an invalid index.
1221		InvalidSubmissionIndex,
1222		/// The call is not allowed at this point.
1223		CallNotAllowed,
1224		/// The fallback failed
1225		FallbackFailed,
1226		/// Some bound not met
1227		BoundNotMet,
1228		/// Submitted solution has too many winners
1229		TooManyWinners,
1230		/// Submission was prepared for a different round.
1231		PreDispatchDifferentRound,
1232	}
1233
1234	#[pezpallet::validate_unsigned]
1235	impl<T: Config> ValidateUnsigned for Pezpallet<T> {
1236		type Call = Call<T>;
1237		fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
1238			if let Call::submit_unsigned { raw_solution, .. } = call {
1239				// Discard solution not coming from the local OCW.
1240				match source {
1241					TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
1242					_ => return InvalidTransaction::Call.into(),
1243				}
1244
1245				Self::unsigned_pre_dispatch_checks(raw_solution)
1246					.inspect_err(|err| {
1247						log!(debug, "unsigned transaction validation failed due to {:?}", err);
1248					})
1249					.map_err(dispatch_error_to_invalid)?;
1250
1251				ValidTransaction::with_tag_prefix("OffchainElection")
1252					// The higher the score.minimal_stake, the better a solution is.
1253					.priority(
1254						T::MinerTxPriority::get()
1255							.saturating_add(raw_solution.score.minimal_stake.saturated_into()),
1256					)
1257					// Used to deduplicate unsigned solutions: each validator should produce one
1258					// solution per round at most, and solutions are not propagate.
1259					.and_provides(raw_solution.round)
1260					// Transaction should stay in the pool for the duration of the unsigned phase.
1261					.longevity(T::UnsignedPhase::get().saturated_into::<u64>())
1262					// We don't propagate this. This can never be validated at a remote node.
1263					.propagate(false)
1264					.build()
1265			} else {
1266				InvalidTransaction::Call.into()
1267			}
1268		}
1269
1270		fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
1271			if let Call::submit_unsigned { raw_solution, .. } = call {
1272				Self::unsigned_pre_dispatch_checks(raw_solution)
1273					.map_err(dispatch_error_to_invalid)
1274					.map_err(Into::into)
1275			} else {
1276				Err(InvalidTransaction::Call.into())
1277			}
1278		}
1279	}
1280
1281	#[pezpallet::type_value]
1282	pub fn DefaultForRound() -> u32 {
1283		1
1284	}
1285
1286	/// Internal counter for the number of rounds.
1287	///
1288	/// This is useful for de-duplication of transactions submitted to the pool, and general
1289	/// diagnostics of the pezpallet.
1290	///
1291	/// This is merely incremented once per every time that an upstream `elect` is called.
1292	#[pezpallet::storage]
1293	pub type Round<T: Config> = StorageValue<_, u32, ValueQuery, DefaultForRound>;
1294
1295	/// Current phase.
1296	#[pezpallet::storage]
1297	pub type CurrentPhase<T: Config> = StorageValue<_, Phase<BlockNumberFor<T>>, ValueQuery>;
1298
1299	/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
1300	///
1301	/// Always sorted by score.
1302	#[pezpallet::storage]
1303	pub type QueuedSolution<T: Config> = StorageValue<_, ReadySolutionOf<T::MinerConfig>>;
1304
1305	/// Snapshot data of the round.
1306	///
1307	/// This is created at the beginning of the signed phase and cleared upon calling `elect`.
1308	/// Note: This storage type must only be mutated through [`SnapshotWrapper`].
1309	#[pezpallet::storage]
1310	pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T::AccountId, VoterOf<T>>>;
1311
1312	/// Desired number of targets to elect for this round.
1313	///
1314	/// Only exists when [`Snapshot`] is present.
1315	/// Note: This storage type must only be mutated through [`SnapshotWrapper`].
1316	#[pezpallet::storage]
1317	pub type DesiredTargets<T> = StorageValue<_, u32>;
1318
1319	/// The metadata of the [`RoundSnapshot`]
1320	///
1321	/// Only exists when [`Snapshot`] is present.
1322	/// Note: This storage type must only be mutated through [`SnapshotWrapper`].
1323	#[pezpallet::storage]
1324	pub type SnapshotMetadata<T: Config> = StorageValue<_, SolutionOrSnapshotSize>;
1325
1326	// The following storage items collectively comprise `SignedSubmissions<T>`, and should never be
1327	// accessed independently. Instead, get `Self::signed_submissions()`, modify it as desired, and
1328	// then do `signed_submissions.put()` when you're done with it.
1329
1330	/// The next index to be assigned to an incoming signed submission.
1331	///
1332	/// Every accepted submission is assigned a unique index; that index is bound to that particular
1333	/// submission for the duration of the election. On election finalization, the next index is
1334	/// reset to 0.
1335	///
1336	/// We can't just use `SignedSubmissionIndices.len()`, because that's a bounded set; past its
1337	/// capacity, it will simply saturate. We can't just iterate over `SignedSubmissionsMap`,
1338	/// because iteration is slow. Instead, we store the value here.
1339	#[pezpallet::storage]
1340	pub type SignedSubmissionNextIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
1341
1342	/// A sorted, bounded vector of `(score, block_number, index)`, where each `index` points to a
1343	/// value in `SignedSubmissions`.
1344	///
1345	/// We never need to process more than a single signed submission at a time. Signed submissions
1346	/// can be quite large, so we're willing to pay the cost of multiple database accesses to access
1347	/// them one at a time instead of reading and decoding all of them at once.
1348	#[pezpallet::storage]
1349	pub type SignedSubmissionIndices<T: Config> =
1350		StorageValue<_, SubmissionIndicesOf<T>, ValueQuery>;
1351
1352	/// Unchecked, signed solutions.
1353	///
1354	/// Together with `SubmissionIndices`, this stores a bounded set of `SignedSubmissions` while
1355	/// allowing us to keep only a single one in memory at a time.
1356	///
1357	/// Twox note: the key of the map is an auto-incrementing index which users cannot inspect or
1358	/// affect; we shouldn't need a cryptographically secure hasher.
1359	#[pezpallet::storage]
1360	pub type SignedSubmissionsMap<T: Config> =
1361		StorageMap<_, Twox64Concat, u32, SignedSubmissionOf<T>, OptionQuery>;
1362
1363	// `SignedSubmissions` items end here.
1364
1365	/// The minimum score that each 'untrusted' solution must attain in order to be considered
1366	/// feasible.
1367	///
1368	/// Can be set via `set_minimum_untrusted_score`.
1369	#[pezpallet::storage]
1370	pub type MinimumUntrustedScore<T: Config> = StorageValue<_, ElectionScore>;
1371
1372	/// The in-code storage version.
1373	///
1374	/// v1: https://github.com/pezkuwichain/pezkuwi-sdk/issues/207/
1375	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
1376
1377	#[pezpallet::pezpallet]
1378	#[pezpallet::without_storage_info]
1379	#[pezpallet::storage_version(STORAGE_VERSION)]
1380	pub struct Pezpallet<T>(_);
1381}
1382
1383/// This wrapper is created for handling the synchronization of [`Snapshot`], [`SnapshotMetadata`]
1384/// and [`DesiredTargets`] storage items.
1385pub struct SnapshotWrapper<T>(core::marker::PhantomData<T>);
1386
1387impl<T: Config> SnapshotWrapper<T> {
1388	/// Kill all snapshot related storage items at the same time.
1389	pub fn kill() {
1390		Snapshot::<T>::kill();
1391		SnapshotMetadata::<T>::kill();
1392		DesiredTargets::<T>::kill();
1393	}
1394	/// Set all snapshot related storage items at the same time.
1395	pub fn set(metadata: SolutionOrSnapshotSize, desired_targets: u32, buffer: &[u8]) {
1396		SnapshotMetadata::<T>::put(metadata);
1397		DesiredTargets::<T>::put(desired_targets);
1398		pezsp_io::storage::set(&Snapshot::<T>::hashed_key(), &buffer);
1399	}
1400
1401	/// Check if all of the storage items exist at the same time or all of the storage items do not
1402	/// exist.
1403	#[cfg(feature = "try-runtime")]
1404	pub fn is_consistent() -> bool {
1405		let snapshots = [
1406			Snapshot::<T>::exists(),
1407			SnapshotMetadata::<T>::exists(),
1408			DesiredTargets::<T>::exists(),
1409		];
1410
1411		// All should either exist or not exist
1412		snapshots.iter().skip(1).all(|v| snapshots[0] == *v)
1413	}
1414}
1415
1416impl<T: Config> Pezpallet<T> {
1417	/// Internal counter for the number of rounds.
1418	///
1419	/// This is useful for de-duplication of transactions submitted to the pool, and general
1420	/// diagnostics of the pezpallet.
1421	///
1422	/// This is merely incremented once per every time that an upstream `elect` is called.
1423	pub fn round() -> u32 {
1424		Round::<T>::get()
1425	}
1426
1427	/// Current phase.
1428	pub fn current_phase() -> Phase<BlockNumberFor<T>> {
1429		CurrentPhase::<T>::get()
1430	}
1431
1432	/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
1433	///
1434	/// Always sorted by score.
1435	pub fn queued_solution() -> Option<ReadySolutionOf<T::MinerConfig>> {
1436		QueuedSolution::<T>::get()
1437	}
1438
1439	/// Snapshot data of the round.
1440	///
1441	/// This is created at the beginning of the signed phase and cleared upon calling `elect`.
1442	/// Note: This storage type must only be mutated through [`SnapshotWrapper`].
1443	pub fn snapshot() -> Option<RoundSnapshot<T::AccountId, VoterOf<T>>> {
1444		Snapshot::<T>::get()
1445	}
1446
1447	/// Desired number of targets to elect for this round.
1448	///
1449	/// Only exists when [`Snapshot`] is present.
1450	/// Note: This storage type must only be mutated through [`SnapshotWrapper`].
1451	pub fn desired_targets() -> Option<u32> {
1452		DesiredTargets::<T>::get()
1453	}
1454
1455	/// The metadata of the [`RoundSnapshot`]
1456	///
1457	/// Only exists when [`Snapshot`] is present.
1458	/// Note: This storage type must only be mutated through [`SnapshotWrapper`].
1459	pub fn snapshot_metadata() -> Option<SolutionOrSnapshotSize> {
1460		SnapshotMetadata::<T>::get()
1461	}
1462
1463	/// The minimum score that each 'untrusted' solution must attain in order to be considered
1464	/// feasible.
1465	///
1466	/// Can be set via `set_minimum_untrusted_score`.
1467	pub fn minimum_untrusted_score() -> Option<ElectionScore> {
1468		MinimumUntrustedScore::<T>::get()
1469	}
1470
1471	/// Internal logic of the offchain worker, to be executed only when the offchain lock is
1472	/// acquired with success.
1473	fn do_synchronized_offchain_worker(now: BlockNumberFor<T>) {
1474		let current_phase = CurrentPhase::<T>::get();
1475		log!(trace, "lock for offchain worker acquired. Phase = {:?}", current_phase);
1476		match current_phase {
1477			Phase::Unsigned((true, opened)) if opened == now => {
1478				// Mine a new solution, cache it, and attempt to submit it
1479				let initial_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
1480					// This is executed at the beginning of each round. Any cache is now invalid.
1481					// Clear it.
1482					unsigned::kill_ocw_solution::<T>();
1483					Self::mine_check_save_submit()
1484				});
1485				log!(debug, "initial offchain thread output: {:?}", initial_output);
1486			},
1487			Phase::Unsigned((true, opened)) if opened < now => {
1488				// Try and resubmit the cached solution, and recompute ONLY if it is not
1489				// feasible.
1490				let resubmit_output = Self::ensure_offchain_repeat_frequency(now)
1491					.and_then(|_| Self::restore_or_compute_then_maybe_submit());
1492				log!(debug, "resubmit offchain thread output: {:?}", resubmit_output);
1493			},
1494			_ => {},
1495		}
1496	}
1497
1498	/// Phase transition helper.
1499	pub(crate) fn phase_transition(to: Phase<BlockNumberFor<T>>) {
1500		log!(info, "Starting phase {:?}, round {}.", to, Round::<T>::get());
1501		Self::deposit_event(Event::PhaseTransitioned {
1502			from: CurrentPhase::<T>::get(),
1503			to,
1504			round: Round::<T>::get(),
1505		});
1506		CurrentPhase::<T>::put(to);
1507	}
1508
1509	/// Parts of [`create_snapshot`] that happen inside of this pezpallet.
1510	///
1511	/// Extracted for easier weight calculation.
1512	fn create_snapshot_internal(
1513		targets: Vec<T::AccountId>,
1514		voters: Vec<VoterOf<T>>,
1515		desired_targets: u32,
1516	) {
1517		let metadata =
1518			SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 };
1519		log!(info, "creating a snapshot with metadata {:?}", metadata);
1520
1521		// instead of using storage APIs, we do a manual encoding into a fixed-size buffer.
1522		// `encoded_size` encodes it without storing it anywhere, this should not cause any
1523		// allocation.
1524		let snapshot = RoundSnapshot::<T::AccountId, VoterOf<T>> { voters, targets };
1525		let size = snapshot.encoded_size();
1526		log!(debug, "snapshot pre-calculated size {:?}", size);
1527		let mut buffer = Vec::with_capacity(size);
1528		snapshot.encode_to(&mut buffer);
1529
1530		// do some checks.
1531		debug_assert_eq!(buffer, snapshot.encode());
1532		// buffer should have not re-allocated since.
1533		debug_assert!(buffer.len() == size && size == buffer.capacity());
1534
1535		SnapshotWrapper::<T>::set(metadata, desired_targets, &buffer);
1536	}
1537
1538	/// Parts of [`create_snapshot`] that happen outside of this pezpallet.
1539	///
1540	/// Extracted for easier weight calculation.
1541	///
1542	/// Note: this pezpallet only supports one page of voter and target snapshots.
1543	fn create_snapshot_external(
1544	) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
1545		let election_bounds = T::ElectionBounds::get();
1546		let targets = T::DataProvider::electable_targets_stateless(election_bounds.targets)
1547			.and_then(|t| {
1548				election_bounds.ensure_targets_limits(
1549					CountBound(t.len() as u32),
1550					SizeBound(t.encoded_size() as u32),
1551				)?;
1552				Ok(t)
1553			})
1554			.map_err(ElectionError::DataProvider)?;
1555
1556		let voters = T::DataProvider::electing_voters_stateless(election_bounds.voters)
1557			.and_then(|v| {
1558				election_bounds.ensure_voters_limits(
1559					CountBound(v.len() as u32),
1560					SizeBound(v.encoded_size() as u32),
1561				)?;
1562				Ok(v)
1563			})
1564			.map_err(ElectionError::DataProvider)?;
1565
1566		let mut desired_targets = <Pezpallet<T> as ElectionProvider>::desired_targets_checked()
1567			.map_err(|e| ElectionError::DataProvider(e))?;
1568
1569		// If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a
1570		// warning
1571		let max_desired_targets: u32 = targets.len() as u32;
1572		if desired_targets > max_desired_targets {
1573			log!(
1574				warn,
1575				"desired_targets: {} > targets.len(): {}, capping desired_targets",
1576				desired_targets,
1577				max_desired_targets
1578			);
1579			desired_targets = max_desired_targets;
1580		}
1581
1582		Ok((targets, voters, desired_targets))
1583	}
1584
1585	/// Creates the snapshot. Writes new data to:
1586	///
1587	/// 1. [`SnapshotMetadata`]
1588	/// 2. [`RoundSnapshot`]
1589	/// 3. [`DesiredTargets`]
1590	///
1591	/// Returns `Ok(())` if operation is okay.
1592	///
1593	/// This is a *self-weighing* function, it will register its own extra weight as
1594	/// [`DispatchClass::Mandatory`] with the system pezpallet.
1595	pub fn create_snapshot() -> Result<(), ElectionError<T>> {
1596		// this is self-weighing itself..
1597		let (targets, voters, desired_targets) = Self::create_snapshot_external()?;
1598
1599		// ..therefore we only measure the weight of this and add it.
1600		let internal_weight =
1601			T::WeightInfo::create_snapshot_internal(voters.len() as u32, targets.len() as u32);
1602		Self::create_snapshot_internal(targets, voters, desired_targets);
1603		Self::register_weight(internal_weight);
1604		Ok(())
1605	}
1606
1607	/// Register some amount of weight directly with the system pezpallet.
1608	///
1609	/// This is always mandatory weight.
1610	fn register_weight(weight: Weight) {
1611		pezframe_system::Pezpallet::<T>::register_extra_weight_unchecked(
1612			weight,
1613			DispatchClass::Mandatory,
1614		);
1615	}
1616
1617	/// Checks the feasibility of a solution.
1618	pub fn feasibility_check(
1619		raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
1620		compute: ElectionCompute,
1621	) -> Result<ReadySolutionOf<T::MinerConfig>, FeasibilityError> {
1622		let desired_targets =
1623			DesiredTargets::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;
1624
1625		let snapshot = Snapshot::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;
1626		let round = Round::<T>::get();
1627		let minimum_untrusted_score = MinimumUntrustedScore::<T>::get();
1628
1629		Miner::<T::MinerConfig>::feasibility_check(
1630			raw_solution,
1631			compute,
1632			desired_targets,
1633			snapshot,
1634			round,
1635			minimum_untrusted_score,
1636		)
1637	}
1638
1639	/// Perform the tasks to be done after a new `elect` has been triggered:
1640	///
1641	/// 1. Increment round.
1642	/// 2. Change phase to [`Phase::Off`]
1643	/// 3. Clear all snapshot data.
1644	fn rotate_round() {
1645		// Inc round.
1646		Round::<T>::mutate(|r| *r += 1);
1647
1648		// Phase is off now.
1649		Self::phase_transition(Phase::Off);
1650
1651		// Kill snapshot and relevant metadata (everything created by [`SnapshotMetadata::set`]).
1652		SnapshotWrapper::<T>::kill();
1653	}
1654
1655	fn do_elect() -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
1656		// We have to unconditionally try finalizing the signed phase here. There are only two
1657		// possibilities:
1658		//
1659		// - signed phase was open, in which case this is essential for correct functioning of the
1660		//   system
1661		// - signed phase was complete or not started, in which case finalization is idempotent and
1662		//   inexpensive (1 read of an empty vector).
1663		let _ = Self::finalize_signed_phase();
1664
1665		QueuedSolution::<T>::take()
1666			.ok_or(ElectionError::<T>::NothingQueued)
1667			.or_else(|_| {
1668				log!(warn, "No solution queued, falling back to instant fallback.",);
1669
1670				#[cfg(feature = "runtime-benchmarks")]
1671				Self::asap();
1672
1673				let (voters, targets, desired_targets) = if T::Fallback::bother() {
1674					let RoundSnapshot { voters, targets } = Snapshot::<T>::get().ok_or(
1675						ElectionError::<T>::Feasibility(FeasibilityError::SnapshotUnavailable),
1676					)?;
1677					let desired_targets = DesiredTargets::<T>::get().ok_or(
1678						ElectionError::<T>::Feasibility(FeasibilityError::SnapshotUnavailable),
1679					)?;
1680					(voters, targets, desired_targets)
1681				} else {
1682					(Default::default(), Default::default(), Default::default())
1683				};
1684				T::Fallback::instant_elect(voters, targets, desired_targets)
1685					.map_err(|fe| ElectionError::Fallback(fe))
1686					.and_then(|supports| {
1687						Ok(ReadySolution {
1688							supports,
1689							score: Default::default(),
1690							compute: ElectionCompute::Fallback,
1691						})
1692					})
1693			})
1694			.map(|ReadySolution { compute, score, supports }| {
1695				Self::deposit_event(Event::ElectionFinalized { compute, score });
1696				log!(info, "Finalized election round with compute {:?}.", compute);
1697				supports
1698			})
1699			.map_err(|err| {
1700				Self::deposit_event(Event::ElectionFailed);
1701				log!(warn, "Failed to finalize election round. reason {:?}", err);
1702				err
1703			})
1704	}
1705
1706	/// record the weight of the given `supports`.
1707	fn weigh_supports(supports: &BoundedSupportsOf<Self>) {
1708		let active_voters = supports
1709			.iter()
1710			.map(|(_, x)| x)
1711			.fold(Zero::zero(), |acc, next| acc + next.voters.len() as u32);
1712		let desired_targets = supports.len() as u32;
1713		Self::register_weight(T::WeightInfo::elect_queued(active_voters, desired_targets));
1714	}
1715}
1716
1717#[cfg(feature = "try-runtime")]
1718impl<T: Config> Pezpallet<T> {
1719	fn do_try_state() -> Result<(), TryRuntimeError> {
1720		Self::try_state_snapshot()?;
1721		Self::try_state_signed_submissions_map()?;
1722		Self::try_state_phase_off()
1723	}
1724
1725	// [`Snapshot`] state check. Invariants:
1726	// - [`DesiredTargets`] exists if and only if [`Snapshot`] is present.
1727	// - [`SnapshotMetadata`] exist if and only if [`Snapshot`] is present.
1728	fn try_state_snapshot() -> Result<(), TryRuntimeError> {
1729		if SnapshotWrapper::<T>::is_consistent() {
1730			Ok(())
1731		} else {
1732			Err("If snapshot exists, metadata and desired targets should be set too. Otherwise, none should be set.".into())
1733		}
1734	}
1735
1736	// [`SignedSubmissionsMap`] state check. Invariants:
1737	// - All [`SignedSubmissionIndices`] are present in [`SignedSubmissionsMap`], and no more;
1738	// - [`SignedSubmissionNextIndex`] is not present in [`SignedSubmissionsMap`];
1739	// - [`SignedSubmissionIndices`] is sorted by election score.
1740	fn try_state_signed_submissions_map() -> Result<(), TryRuntimeError> {
1741		let mut last_score: ElectionScore = Default::default();
1742		let indices = SignedSubmissionIndices::<T>::get();
1743
1744		for (i, indice) in indices.iter().enumerate() {
1745			let submission = SignedSubmissionsMap::<T>::get(indice.2);
1746			if submission.is_none() {
1747				return Err(
1748					"All signed submissions indices must be part of the submissions map".into()
1749				);
1750			}
1751
1752			if i == 0 {
1753				last_score = indice.0
1754			} else {
1755				if last_score.strict_better(indice.0) {
1756					return Err(
1757						"Signed submission indices vector must be ordered by election score".into(),
1758					);
1759				}
1760				last_score = indice.0;
1761			}
1762		}
1763
1764		if SignedSubmissionsMap::<T>::iter().nth(indices.len()).is_some() {
1765			return Err(
1766				"Signed submissions map length should be the same as the indices vec length".into(),
1767			);
1768		}
1769
1770		match SignedSubmissionNextIndex::<T>::get() {
1771			0 => Ok(()),
1772			next => {
1773				if SignedSubmissionsMap::<T>::get(next).is_some() {
1774					return Err(
1775						"The next submissions index should not be in the submissions maps already"
1776							.into(),
1777					);
1778				} else {
1779					Ok(())
1780				}
1781			},
1782		}
1783	}
1784
1785	// [`Phase::Off`] state check. Invariants:
1786	// - If phase is `Phase::Off`, [`Snapshot`] must be none.
1787	fn try_state_phase_off() -> Result<(), TryRuntimeError> {
1788		match CurrentPhase::<T>::get().is_off() {
1789			false => Ok(()),
1790			true => {
1791				if Snapshot::<T>::get().is_some() {
1792					Err("Snapshot must be none when in Phase::Off".into())
1793				} else {
1794					Ok(())
1795				}
1796			},
1797		}
1798	}
1799}
1800
1801impl<T: Config> ElectionProvider for Pezpallet<T> {
1802	type AccountId = T::AccountId;
1803	type BlockNumber = BlockNumberFor<T>;
1804	type Error = ElectionError<T>;
1805	type MaxWinnersPerPage = T::MaxWinners;
1806	type MaxBackersPerWinner = T::MaxBackersPerWinner;
1807	type MaxBackersPerWinnerFinal = T::MaxBackersPerWinner;
1808	type Pages = pezsp_core::ConstU32<1>;
1809	type DataProvider = T::DataProvider;
1810
1811	fn elect(page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
1812		// Note: this pezpallet **MUST** only by used in the single-page mode.
1813		ensure!(page == SINGLE_PAGE, ElectionError::<T>::MultiPageNotSupported);
1814
1815		let res = match Self::do_elect() {
1816			Ok(bounded_supports) => {
1817				// All went okay, record the weight, put sign to be Off, clean snapshot, etc.
1818				Self::weigh_supports(&bounded_supports);
1819				Self::rotate_round();
1820				Ok(bounded_supports)
1821			},
1822			Err(why) => {
1823				log!(error, "Entering emergency mode: {:?}", why);
1824				Self::phase_transition(Phase::Emergency);
1825				Err(why)
1826			},
1827		};
1828
1829		log!(info, "ElectionProvider::elect({}) => {:?}", page, res.as_ref().map(|s| s.len()));
1830		res
1831	}
1832
1833	fn duration() -> Self::BlockNumber {
1834		let signed: BlockNumberFor<T> = T::SignedPhase::get().saturated_into();
1835		let unsigned: BlockNumberFor<T> = T::UnsignedPhase::get().saturated_into();
1836		signed + unsigned
1837	}
1838
1839	fn start() -> Result<(), Self::Error> {
1840		log!(
1841			warn,
1842			"we received signal, but this pezpallet works in the basis of legacy pull based election"
1843		);
1844		Ok(())
1845	}
1846
1847	fn status() -> Result<bool, ()> {
1848		let has_queued = QueuedSolution::<T>::exists();
1849		let phase = CurrentPhase::<T>::get();
1850		match (phase, has_queued) {
1851			(Phase::Unsigned(_), true) => Ok(true),
1852			(Phase::Off, _) => Err(()),
1853			_ => Ok(false),
1854		}
1855	}
1856
1857	#[cfg(feature = "runtime-benchmarks")]
1858	fn asap() {
1859		// prepare our snapshot so we can "hopefully" run a fallback.
1860		if !Snapshot::<T>::exists() {
1861			Self::create_snapshot()
1862				.inspect_err(|e| {
1863					crate::log!(error, "failed to create snapshot while asap-preparing: {:?}", e)
1864				})
1865				.unwrap()
1866		}
1867	}
1868}
1869
1870/// convert a DispatchError to a custom InvalidTransaction with the inner code being the error
1871/// number.
1872pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction {
1873	let error_number = match error {
1874		DispatchError::Module(ModuleError { error, .. }) => error[0],
1875		_ => 0,
1876	};
1877	InvalidTransaction::Custom(error_number)
1878}
1879
1880#[cfg(test)]
1881mod feasibility_check {
1882	//! All of the tests here should be dedicated to only testing the feasibility check and nothing
1883	//! more. The best way to audit and review these tests is to try and come up with a solution
1884	//! that is invalid, but gets through the system as valid.
1885
1886	use super::*;
1887	use crate::mock::{
1888		raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase,
1889		TargetIndex, UnsignedPhase, VoterIndex,
1890	};
1891	use pezframe_support::{assert_noop, assert_ok};
1892
1893	const COMPUTE: ElectionCompute = ElectionCompute::OnChain;
1894
1895	#[test]
1896	fn snapshot_is_there() {
1897		ExtBuilder::default().build_and_execute(|| {
1898			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1899			assert!(CurrentPhase::<Runtime>::get().is_signed());
1900			let solution = raw_solution();
1901
1902			// kill `Snapshot`, `SnapshotMetadata` and `DesiredTargets` for the storage state to
1903			// be consistent, by using the `SnapshotWrapper` for the try_state checks to pass.
1904			SnapshotWrapper::<Runtime>::kill();
1905
1906			assert_noop!(
1907				MultiPhase::feasibility_check(solution, COMPUTE),
1908				FeasibilityError::SnapshotUnavailable
1909			);
1910		})
1911	}
1912
1913	#[test]
1914	fn round() {
1915		ExtBuilder::default().build_and_execute(|| {
1916			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1917			assert!(CurrentPhase::<Runtime>::get().is_signed());
1918
1919			let mut solution = raw_solution();
1920			solution.round += 1;
1921			assert_noop!(
1922				MultiPhase::feasibility_check(solution, COMPUTE),
1923				FeasibilityError::InvalidRound
1924			);
1925		})
1926	}
1927
1928	#[test]
1929	fn desired_targets_gets_capped() {
1930		ExtBuilder::default().desired_targets(8).build_and_execute(|| {
1931			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1932			assert!(CurrentPhase::<Runtime>::get().is_signed());
1933
1934			let raw = raw_solution();
1935
1936			assert_eq!(raw.solution.unique_targets().len(), 4);
1937			// desired_targets is capped to the number of targets which is 4
1938			assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 4);
1939
1940			// It should succeed
1941			assert_ok!(MultiPhase::feasibility_check(raw, COMPUTE));
1942		})
1943	}
1944
1945	#[test]
1946	fn less_than_desired_targets_fails() {
1947		ExtBuilder::default().desired_targets(8).build_and_execute(|| {
1948			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1949			assert!(CurrentPhase::<Runtime>::get().is_signed());
1950
1951			let mut raw = raw_solution();
1952
1953			assert_eq!(raw.solution.unique_targets().len(), 4);
1954			// desired_targets is capped to the number of targets which is 4
1955			assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 4);
1956
1957			// Force the number of winners to be bigger to fail
1958			raw.solution.votes1[0].1 = 4;
1959
1960			// It should succeed
1961			assert_noop!(
1962				MultiPhase::feasibility_check(raw, COMPUTE),
1963				FeasibilityError::WrongWinnerCount,
1964			);
1965		})
1966	}
1967
1968	#[test]
1969	fn winner_indices() {
1970		ExtBuilder::default().desired_targets(2).build_and_execute(|| {
1971			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
1972			assert!(CurrentPhase::<Runtime>::get().is_signed());
1973
1974			let mut raw = raw_solution();
1975			assert_eq!(Snapshot::<Runtime>::get().unwrap().targets.len(), 4);
1976			// ----------------------------------------------------^^ valid range is [0..3].
1977
1978			// Swap all votes from 3 to 4. This will ensure that the number of unique winners will
1979			// still be 4, but one of the indices will be gibberish. Requirement is to make sure 3 a
1980			// winner, which we don't do here.
1981			raw.solution
1982				.votes1
1983				.iter_mut()
1984				.filter(|(_, t)| *t == TargetIndex::from(3u16))
1985				.for_each(|(_, t)| *t += 1);
1986			raw.solution.votes2.iter_mut().for_each(|(_, [(t0, _)], t1)| {
1987				if *t0 == TargetIndex::from(3u16) {
1988					*t0 += 1
1989				};
1990				if *t1 == TargetIndex::from(3u16) {
1991					*t1 += 1
1992				};
1993			});
1994			assert_noop!(
1995				MultiPhase::feasibility_check(raw, COMPUTE),
1996				FeasibilityError::NposElection(pezsp_npos_elections::Error::SolutionInvalidIndex)
1997			);
1998		})
1999	}
2000
2001	#[test]
2002	fn voter_indices() {
2003		// Should be caught in `solution.into_assignment`.
2004		ExtBuilder::default().desired_targets(2).build_and_execute(|| {
2005			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
2006			assert!(CurrentPhase::<Runtime>::get().is_signed());
2007
2008			let mut solution = raw_solution();
2009			assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2010			// ----------------------------------------------------^^ valid range is [0..7].
2011
2012			// Check that there is an index 7 in votes1, and flip to 8.
2013			assert!(
2014				solution
2015					.solution
2016					.votes1
2017					.iter_mut()
2018					.filter(|(v, _)| *v == VoterIndex::from(7u32))
2019					.map(|(v, _)| *v = 8)
2020					.count() > 0
2021			);
2022			assert_noop!(
2023				MultiPhase::feasibility_check(solution, COMPUTE),
2024				FeasibilityError::NposElection(pezsp_npos_elections::Error::SolutionInvalidIndex),
2025			);
2026		})
2027	}
2028
2029	#[test]
2030	fn voter_votes() {
2031		ExtBuilder::default().desired_targets(2).build_and_execute(|| {
2032			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
2033			assert!(CurrentPhase::<Runtime>::get().is_signed());
2034
2035			let mut solution = raw_solution();
2036			assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2037			// ----------------------------------------------------^^ valid range is [0..7].
2038
2039			// First, check that voter at index 7 (40) actually voted for 3 (40) -- this is self
2040			// vote. Then, change the vote to 2 (30).
2041			assert_eq!(
2042				solution
2043					.solution
2044					.votes1
2045					.iter_mut()
2046					.filter(|(v, t)| *v == 7 && *t == 3)
2047					.map(|(_, t)| *t = 2)
2048					.count(),
2049				1,
2050			);
2051			assert_noop!(
2052				MultiPhase::feasibility_check(solution, COMPUTE),
2053				FeasibilityError::InvalidVote,
2054			);
2055		})
2056	}
2057
2058	#[test]
2059	fn score() {
2060		ExtBuilder::default().desired_targets(2).build_and_execute(|| {
2061			roll_to(<EpochLength>::get() - <SignedPhase>::get() - <UnsignedPhase>::get());
2062			assert!(CurrentPhase::<Runtime>::get().is_signed());
2063
2064			let mut solution = raw_solution();
2065			assert_eq!(Snapshot::<Runtime>::get().unwrap().voters.len(), 8);
2066
2067			// Simply faff with the score.
2068			solution.score.minimal_stake += 1;
2069
2070			assert_noop!(
2071				MultiPhase::feasibility_check(solution, COMPUTE),
2072				FeasibilityError::InvalidScore,
2073			);
2074		})
2075	}
2076}
2077
2078#[cfg(test)]
2079mod tests {
2080	use super::*;
2081	use crate::{
2082		mock::{
2083			multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned,
2084			ElectionsBounds, ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime,
2085			RuntimeOrigin, SignedMaxSubmissions, System, Voters,
2086		},
2087		Phase,
2088	};
2089	use pezframe_election_provider_support::bounds::ElectionBoundsBuilder;
2090	use pezframe_support::{assert_noop, assert_ok};
2091	use pezsp_npos_elections::{BalancingConfig, Support};
2092
2093	#[test]
2094	fn phase_rotation_works() {
2095		ExtBuilder::default().build_and_execute(|| {
2096			// 0 ------- 15 ------- 25 ------- 30 ------- ------- 45 ------- 55 ------- 60
2097			//           |           |          |                 |           |          |
2098			//         Signed      Unsigned   Elect             Signed     Unsigned    Elect
2099
2100			assert_eq!(System::block_number(), 0);
2101			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2102			assert_eq!(Round::<Runtime>::get(), 1);
2103
2104			roll_to(4);
2105			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2106			assert!(Snapshot::<Runtime>::get().is_none());
2107			assert_eq!(Round::<Runtime>::get(), 1);
2108
2109			roll_to_signed();
2110			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2111			assert_eq!(
2112				multi_phase_events(),
2113				vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2114			);
2115			assert!(Snapshot::<Runtime>::get().is_some());
2116			assert_eq!(Round::<Runtime>::get(), 1);
2117
2118			roll_to(24);
2119			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2120			assert!(Snapshot::<Runtime>::get().is_some());
2121			assert_eq!(Round::<Runtime>::get(), 1);
2122
2123			roll_to_unsigned();
2124			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2125			assert_eq!(
2126				multi_phase_events(),
2127				vec![
2128					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2129					Event::PhaseTransitioned {
2130						from: Phase::Signed,
2131						to: Phase::Unsigned((true, 25)),
2132						round: 1
2133					},
2134				],
2135			);
2136			assert!(Snapshot::<Runtime>::get().is_some());
2137
2138			roll_to(29);
2139			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2140			assert!(Snapshot::<Runtime>::get().is_some());
2141
2142			roll_to(30);
2143			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2144			assert!(Snapshot::<Runtime>::get().is_some());
2145
2146			// We close when upstream tells us to elect.
2147			roll_to(32);
2148			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2149			assert!(Snapshot::<Runtime>::get().is_some());
2150
2151			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2152
2153			assert!(CurrentPhase::<Runtime>::get().is_off());
2154			assert!(Snapshot::<Runtime>::get().is_none());
2155			assert_eq!(Round::<Runtime>::get(), 2);
2156
2157			roll_to(44);
2158			assert!(CurrentPhase::<Runtime>::get().is_off());
2159
2160			roll_to_signed();
2161			assert!(CurrentPhase::<Runtime>::get().is_signed());
2162
2163			roll_to(55);
2164			assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(55));
2165
2166			assert_eq!(
2167				multi_phase_events(),
2168				vec![
2169					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2170					Event::PhaseTransitioned {
2171						from: Phase::Signed,
2172						to: Phase::Unsigned((true, 25)),
2173						round: 1
2174					},
2175					Event::ElectionFinalized {
2176						compute: ElectionCompute::Fallback,
2177						score: ElectionScore {
2178							minimal_stake: 0,
2179							sum_stake: 0,
2180							sum_stake_squared: 0
2181						}
2182					},
2183					Event::PhaseTransitioned {
2184						from: Phase::Unsigned((true, 25)),
2185						to: Phase::Off,
2186						round: 2
2187					},
2188					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 2 },
2189					Event::PhaseTransitioned {
2190						from: Phase::Signed,
2191						to: Phase::Unsigned((true, 55)),
2192						round: 2
2193					},
2194				]
2195			);
2196		})
2197	}
2198
2199	#[test]
2200	fn signed_phase_void() {
2201		ExtBuilder::default().phases(0, 10).build_and_execute(|| {
2202			roll_to(15);
2203			assert!(CurrentPhase::<Runtime>::get().is_off());
2204
2205			roll_to(19);
2206			assert!(CurrentPhase::<Runtime>::get().is_off());
2207
2208			roll_to(20);
2209			assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(20));
2210			assert!(Snapshot::<Runtime>::get().is_some());
2211
2212			roll_to(30);
2213			assert!(CurrentPhase::<Runtime>::get().is_unsigned_open_at(20));
2214
2215			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2216
2217			assert!(CurrentPhase::<Runtime>::get().is_off());
2218			assert!(Snapshot::<Runtime>::get().is_none());
2219
2220			assert_eq!(
2221				multi_phase_events(),
2222				vec![
2223					Event::PhaseTransitioned {
2224						from: Phase::Off,
2225						to: Phase::Unsigned((true, 20)),
2226						round: 1
2227					},
2228					Event::ElectionFinalized {
2229						compute: ElectionCompute::Fallback,
2230						score: ElectionScore {
2231							minimal_stake: 0,
2232							sum_stake: 0,
2233							sum_stake_squared: 0
2234						}
2235					},
2236					Event::PhaseTransitioned {
2237						from: Phase::Unsigned((true, 20)),
2238						to: Phase::Off,
2239						round: 2
2240					},
2241				]
2242			);
2243		});
2244	}
2245
2246	#[test]
2247	fn unsigned_phase_void() {
2248		ExtBuilder::default().phases(10, 0).build_and_execute(|| {
2249			roll_to(15);
2250			assert!(CurrentPhase::<Runtime>::get().is_off());
2251
2252			roll_to(19);
2253			assert!(CurrentPhase::<Runtime>::get().is_off());
2254
2255			roll_to_signed();
2256			assert!(CurrentPhase::<Runtime>::get().is_signed());
2257			assert!(Snapshot::<Runtime>::get().is_some());
2258
2259			roll_to(30);
2260			assert!(CurrentPhase::<Runtime>::get().is_signed());
2261
2262			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2263
2264			assert!(CurrentPhase::<Runtime>::get().is_off());
2265			assert!(Snapshot::<Runtime>::get().is_none());
2266
2267			assert_eq!(
2268				multi_phase_events(),
2269				vec![
2270					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2271					Event::ElectionFinalized {
2272						compute: ElectionCompute::Fallback,
2273						score: ElectionScore {
2274							minimal_stake: 0,
2275							sum_stake: 0,
2276							sum_stake_squared: 0
2277						}
2278					},
2279					Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2280				]
2281			)
2282		});
2283	}
2284
2285	#[test]
2286	fn early_termination() {
2287		// An early termination in the signed phase, with no queued solution.
2288		ExtBuilder::default().build_and_execute(|| {
2289			// Signed phase started at block 15 and will end at 25.
2290
2291			roll_to_signed();
2292			assert_eq!(
2293				multi_phase_events(),
2294				vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2295			);
2296			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2297			assert_eq!(Round::<Runtime>::get(), 1);
2298
2299			// An unexpected call to elect.
2300			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2301
2302			// We surely can't have any feasible solutions. This will cause an on-chain election.
2303			assert_eq!(
2304				multi_phase_events(),
2305				vec![
2306					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2307					Event::ElectionFinalized {
2308						compute: ElectionCompute::Fallback,
2309						score: Default::default()
2310					},
2311					Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2312				],
2313			);
2314			// All storage items must be cleared.
2315			assert_eq!(Round::<Runtime>::get(), 2);
2316			assert!(Snapshot::<Runtime>::get().is_none());
2317			assert!(SnapshotMetadata::<Runtime>::get().is_none());
2318			assert!(DesiredTargets::<Runtime>::get().is_none());
2319			assert!(QueuedSolution::<Runtime>::get().is_none());
2320			assert!(MultiPhase::signed_submissions().is_empty());
2321		})
2322	}
2323
2324	#[test]
2325	fn early_termination_with_submissions() {
2326		// an early termination in the signed phase, with no queued solution.
2327		ExtBuilder::default().build_and_execute(|| {
2328			// signed phase started at block 15 and will end at 25.
2329
2330			roll_to_signed();
2331			assert_eq!(
2332				multi_phase_events(),
2333				vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }]
2334			);
2335			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2336			assert_eq!(Round::<Runtime>::get(), 1);
2337
2338			// fill the queue with signed submissions
2339			for s in 0..SignedMaxSubmissions::get() {
2340				let solution = RawSolution {
2341					score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
2342					..Default::default()
2343				};
2344				assert_ok!(MultiPhase::submit(
2345					crate::mock::RuntimeOrigin::signed(99),
2346					Box::new(solution)
2347				));
2348			}
2349
2350			// an unexpected call to elect.
2351			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2352
2353			// all storage items must be cleared.
2354			assert_eq!(Round::<Runtime>::get(), 2);
2355			assert!(Snapshot::<Runtime>::get().is_none());
2356			assert!(SnapshotMetadata::<Runtime>::get().is_none());
2357			assert!(DesiredTargets::<Runtime>::get().is_none());
2358			assert!(QueuedSolution::<Runtime>::get().is_none());
2359			assert!(MultiPhase::signed_submissions().is_empty());
2360
2361			assert_eq!(
2362				multi_phase_events(),
2363				vec![
2364					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2365					Event::SolutionStored {
2366						compute: ElectionCompute::Signed,
2367						origin: Some(99),
2368						prev_ejected: false
2369					},
2370					Event::SolutionStored {
2371						compute: ElectionCompute::Signed,
2372						origin: Some(99),
2373						prev_ejected: false
2374					},
2375					Event::SolutionStored {
2376						compute: ElectionCompute::Signed,
2377						origin: Some(99),
2378						prev_ejected: false
2379					},
2380					Event::SolutionStored {
2381						compute: ElectionCompute::Signed,
2382						origin: Some(99),
2383						prev_ejected: false
2384					},
2385					Event::SolutionStored {
2386						compute: ElectionCompute::Signed,
2387						origin: Some(99),
2388						prev_ejected: false
2389					},
2390					Event::Slashed { account: 99, value: 5 },
2391					Event::Slashed { account: 99, value: 5 },
2392					Event::Slashed { account: 99, value: 5 },
2393					Event::Slashed { account: 99, value: 5 },
2394					Event::Slashed { account: 99, value: 5 },
2395					Event::ElectionFinalized {
2396						compute: ElectionCompute::Fallback,
2397						score: ElectionScore {
2398							minimal_stake: 0,
2399							sum_stake: 0,
2400							sum_stake_squared: 0
2401						}
2402					},
2403					Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 },
2404				]
2405			);
2406		})
2407	}
2408
2409	#[test]
2410	fn check_events_with_compute_signed() {
2411		ExtBuilder::default().build_and_execute(|| {
2412			roll_to_signed();
2413			assert!(CurrentPhase::<Runtime>::get().is_signed());
2414
2415			let solution = raw_solution();
2416			assert_ok!(MultiPhase::submit(
2417				crate::mock::RuntimeOrigin::signed(99),
2418				Box::new(solution)
2419			));
2420
2421			roll_to(30);
2422			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2423
2424			assert_eq!(
2425				multi_phase_events(),
2426				vec![
2427					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2428					Event::SolutionStored {
2429						compute: ElectionCompute::Signed,
2430						origin: Some(99),
2431						prev_ejected: false
2432					},
2433					Event::Rewarded { account: 99, value: 7 },
2434					Event::PhaseTransitioned {
2435						from: Phase::Signed,
2436						to: Phase::Unsigned((true, 25)),
2437						round: 1
2438					},
2439					Event::ElectionFinalized {
2440						compute: ElectionCompute::Signed,
2441						score: ElectionScore {
2442							minimal_stake: 40,
2443							sum_stake: 100,
2444							sum_stake_squared: 5200
2445						}
2446					},
2447					Event::PhaseTransitioned {
2448						from: Phase::Unsigned((true, 25)),
2449						to: Phase::Off,
2450						round: 2
2451					},
2452				],
2453			);
2454		})
2455	}
2456
2457	#[test]
2458	fn check_events_with_compute_unsigned() {
2459		ExtBuilder::default().build_and_execute(|| {
2460			roll_to_unsigned();
2461			assert!(CurrentPhase::<Runtime>::get().is_unsigned());
2462
2463			// ensure we have snapshots in place.
2464			assert!(Snapshot::<Runtime>::get().is_some());
2465			assert_eq!(DesiredTargets::<Runtime>::get().unwrap(), 2);
2466
2467			// mine seq_phragmen solution with 2 iters.
2468			let (solution, witness, _) = MultiPhase::mine_solution().unwrap();
2469
2470			// ensure this solution is valid.
2471			assert!(QueuedSolution::<Runtime>::get().is_none());
2472			assert_ok!(MultiPhase::submit_unsigned(
2473				crate::mock::RuntimeOrigin::none(),
2474				Box::new(solution),
2475				witness
2476			));
2477			assert!(QueuedSolution::<Runtime>::get().is_some());
2478
2479			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2480
2481			assert_eq!(
2482				multi_phase_events(),
2483				vec![
2484					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2485					Event::PhaseTransitioned {
2486						from: Phase::Signed,
2487						to: Phase::Unsigned((true, 25)),
2488						round: 1
2489					},
2490					Event::SolutionStored {
2491						compute: ElectionCompute::Unsigned,
2492						origin: None,
2493						prev_ejected: false
2494					},
2495					Event::ElectionFinalized {
2496						compute: ElectionCompute::Unsigned,
2497						score: ElectionScore {
2498							minimal_stake: 40,
2499							sum_stake: 100,
2500							sum_stake_squared: 5200
2501						}
2502					},
2503					Event::PhaseTransitioned {
2504						from: Phase::Unsigned((true, 25)),
2505						to: Phase::Off,
2506						round: 2
2507					},
2508				],
2509			);
2510		})
2511	}
2512
2513	#[test]
2514	fn try_elect_multi_page_fails() {
2515		let prepare_election = || {
2516			roll_to_signed();
2517			assert!(Snapshot::<Runtime>::get().is_some());
2518
2519			// submit solution and assert it is queued and ready for elect to be called.
2520			let (solution, _, _) = MultiPhase::mine_solution().unwrap();
2521			assert_ok!(MultiPhase::submit(
2522				crate::mock::RuntimeOrigin::signed(99),
2523				Box::new(solution),
2524			));
2525			roll_to(30);
2526			assert!(QueuedSolution::<Runtime>::get().is_some());
2527		};
2528
2529		ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2530			prepare_election();
2531			// single page elect call works as expected.
2532			assert_ok!(MultiPhase::elect(SINGLE_PAGE));
2533		});
2534
2535		ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2536			prepare_election();
2537			// multi page calls will fail with multi-page not supported error.
2538			assert_noop!(MultiPhase::elect(SINGLE_PAGE + 1), ElectionError::MultiPageNotSupported);
2539		})
2540	}
2541
2542	#[test]
2543	fn fallback_strategy_works() {
2544		ExtBuilder::default().onchain_fallback(true).build_and_execute(|| {
2545			roll_to_unsigned();
2546			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2547
2548			// Zilch solutions thus far, but we get a result.
2549			assert!(QueuedSolution::<Runtime>::get().is_none());
2550			let supports = MultiPhase::elect(SINGLE_PAGE).unwrap();
2551
2552			let expected_supports = vec![
2553				(30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }),
2554				(40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }),
2555			]
2556			.try_into()
2557			.unwrap();
2558
2559			assert_eq!(supports, expected_supports);
2560
2561			assert_eq!(
2562				multi_phase_events(),
2563				vec![
2564					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2565					Event::PhaseTransitioned {
2566						from: Phase::Signed,
2567						to: Phase::Unsigned((true, 25)),
2568						round: 1
2569					},
2570					Event::ElectionFinalized {
2571						compute: ElectionCompute::Fallback,
2572						score: ElectionScore {
2573							minimal_stake: 0,
2574							sum_stake: 0,
2575							sum_stake_squared: 0
2576						}
2577					},
2578					Event::PhaseTransitioned {
2579						from: Phase::Unsigned((true, 25)),
2580						to: Phase::Off,
2581						round: 2
2582					},
2583				]
2584			);
2585		});
2586
2587		ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2588			roll_to_unsigned();
2589			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2590
2591			// Zilch solutions thus far.
2592			assert!(QueuedSolution::<Runtime>::get().is_none());
2593			assert_eq!(
2594				MultiPhase::elect(SINGLE_PAGE).unwrap_err(),
2595				ElectionError::Fallback("NoFallback.")
2596			);
2597			// phase is now emergency.
2598			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2599			// snapshot is still there until election finalizes.
2600			assert!(Snapshot::<Runtime>::get().is_some());
2601
2602			assert_eq!(
2603				multi_phase_events(),
2604				vec![
2605					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2606					Event::PhaseTransitioned {
2607						from: Phase::Signed,
2608						to: Phase::Unsigned((true, 25)),
2609						round: 1
2610					},
2611					Event::ElectionFailed,
2612					Event::PhaseTransitioned {
2613						from: Phase::Unsigned((true, 25)),
2614						to: Phase::Emergency,
2615						round: 1
2616					},
2617				]
2618			);
2619		})
2620	}
2621
2622	#[test]
2623	fn governance_fallback_works() {
2624		ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
2625			roll_to_unsigned();
2626			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Unsigned((true, 25)));
2627
2628			// Zilch solutions thus far.
2629			assert!(QueuedSolution::<Runtime>::get().is_none());
2630			assert_eq!(
2631				MultiPhase::elect(SINGLE_PAGE).unwrap_err(),
2632				ElectionError::Fallback("NoFallback.")
2633			);
2634
2635			// phase is now emergency.
2636			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Emergency);
2637			assert!(QueuedSolution::<Runtime>::get().is_none());
2638			assert!(Snapshot::<Runtime>::get().is_some());
2639
2640			// no single account can trigger this
2641			assert_noop!(
2642				MultiPhase::governance_fallback(RuntimeOrigin::signed(99)),
2643				DispatchError::BadOrigin
2644			);
2645
2646			// only root can
2647			assert_ok!(MultiPhase::governance_fallback(RuntimeOrigin::root()));
2648			// something is queued now
2649			assert!(QueuedSolution::<Runtime>::get().is_some());
2650			// next election call with fix everything.;
2651			assert!(MultiPhase::elect(SINGLE_PAGE).is_ok());
2652			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Off);
2653
2654			assert_eq!(
2655				multi_phase_events(),
2656				vec![
2657					Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 },
2658					Event::PhaseTransitioned {
2659						from: Phase::Signed,
2660						to: Phase::Unsigned((true, 25)),
2661						round: 1
2662					},
2663					Event::ElectionFailed,
2664					Event::PhaseTransitioned {
2665						from: Phase::Unsigned((true, 25)),
2666						to: Phase::Emergency,
2667						round: 1
2668					},
2669					Event::SolutionStored {
2670						compute: ElectionCompute::Fallback,
2671						origin: None,
2672						prev_ejected: false
2673					},
2674					Event::ElectionFinalized {
2675						compute: ElectionCompute::Fallback,
2676						score: Default::default()
2677					},
2678					Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off, round: 2 },
2679				]
2680			);
2681		})
2682	}
2683
2684	#[test]
2685	fn snapshot_too_big_truncate() {
2686		// but if there are too many voters, we simply truncate them.
2687		ExtBuilder::default().build_and_execute(|| {
2688			// we have 8 voters in total.
2689			assert_eq!(Voters::get().len(), 8);
2690			// but we want to take 2.
2691			let new_bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build();
2692			ElectionsBounds::set(new_bounds);
2693
2694			// Signed phase opens just fine.
2695			roll_to_signed();
2696			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2697
2698			assert_eq!(
2699				SnapshotMetadata::<Runtime>::get().unwrap(),
2700				SolutionOrSnapshotSize { voters: 2, targets: 4 }
2701			);
2702		})
2703	}
2704
2705	#[test]
2706	fn untrusted_score_verification_is_respected() {
2707		ExtBuilder::default().build_and_execute(|| {
2708			roll_to_signed();
2709			assert_eq!(CurrentPhase::<Runtime>::get(), Phase::Signed);
2710
2711			// set the solution balancing to get the desired score.
2712			crate::mock::Balancing::set(Some(BalancingConfig { iterations: 2, tolerance: 0 }));
2713
2714			let (solution, _, _) = MultiPhase::mine_solution().unwrap();
2715			// Default solution's score.
2716			assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));
2717
2718			MinimumUntrustedScore::<Runtime>::put(ElectionScore {
2719				minimal_stake: 49,
2720				..Default::default()
2721			});
2722			assert_ok!(MultiPhase::feasibility_check(solution.clone(), ElectionCompute::Signed));
2723
2724			MinimumUntrustedScore::<Runtime>::put(ElectionScore {
2725				minimal_stake: 51,
2726				..Default::default()
2727			});
2728			assert_noop!(
2729				MultiPhase::feasibility_check(solution, ElectionCompute::Signed),
2730				FeasibilityError::UntrustedScoreTooLow,
2731			);
2732		})
2733	}
2734
2735	#[test]
2736	fn number_of_voters_allowed_2sec_block() {
2737		// Just a rough estimate with the bizinikiwi weights.
2738		assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real);
2739
2740		let all_voters: u32 = 10_000;
2741		let all_targets: u32 = 5_000;
2742		let desired: u32 = 1_000;
2743		let weight_with = |active| {
2744			<Runtime as Config>::WeightInfo::submit_unsigned(
2745				all_voters,
2746				all_targets,
2747				active,
2748				desired,
2749			)
2750		};
2751
2752		let mut active = 1;
2753		while weight_with(active)
2754			.all_lte(<Runtime as pezframe_system::Config>::BlockWeights::get().max_block)
2755			|| active == all_voters
2756		{
2757			active += 1;
2758		}
2759
2760		println!("can support {} voters to yield a weight of {}", active, weight_with(active));
2761	}
2762}