referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_block/
types.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Common types and traits of the EPMB pallet group.
19//!
20//! ## [`SolutionOf`]
21//!
22//! This type is among the most cryptic used in the EPMB pallet. The origins of this type go back to
23//! the fact that sending a solution, with hundreds or thousands of account-ids in it would be too
24//! large for a chain to handle. This was particularly the case in a single page solution, as
25//! developed in `election-provider-multi-phase`. To combat this, a "compact" custom type is
26//! generated to encapsulate a solution. This type is generated by
27//! [`frame_election_provider_support::generate_solution_type`]. See the documentation of this macro
28//! for more information about the hacks used to reduce the size of the solution.
29//!
30//! Consequently, the [`SolutionVoterIndexOf`] and [`SolutionTargetIndexOf`] and
31//! [`SolutionAccuracyOf`] are derived from this type.
32//!
33//! ## [`Phase`]
34//!
35//! This is the most important type of this pallet, demonstrating the state-machine used
36//! to manage the election process and its various phases.
37
38use crate::{unsigned::miner::MinerConfig, verifier};
39use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
40use frame_election_provider_support::ElectionProvider;
41pub use frame_election_provider_support::{NposSolution, PageIndex};
42use frame_support::{
43	traits::DefensiveSaturating, BoundedVec, CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound,
44	PartialEqNoBound,
45};
46use frame_system::pallet_prelude::BlockNumberFor;
47use scale_info::TypeInfo;
48use sp_core::Get;
49pub use sp_npos_elections::{ElectionResult, ElectionScore};
50use sp_runtime::{
51	traits::{CheckedSub, One, Zero},
52	SaturatedConversion, Saturating,
53};
54use sp_std::{collections::btree_set::BTreeSet, fmt::Debug, prelude::*};
55
56/// The solution type used by this crate.
57pub type SolutionOf<T> = <T as MinerConfig>::Solution;
58/// The voter index. Derived from [`SolutionOf`].
59pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
60/// The target index. Derived from [`SolutionOf`].
61pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
62/// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`].
63pub type SolutionAccuracyOf<T> = <SolutionOf<T> as NposSolution>::Accuracy;
64/// The fallback election type.
65pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;
66
67/// The relative distribution of a voter's stake among the winning targets.
68pub type AssignmentOf<T> =
69	sp_npos_elections::Assignment<<T as MinerConfig>::AccountId, SolutionAccuracyOf<T>>;
70
71/// A paginated raw solution type.
72///
73/// This is the representation of a stored, unverified solution.
74///
75/// After feasibility, it is converted into `Supports`.
76#[derive(
77	TypeInfo,
78	Encode,
79	Decode,
80	DecodeWithMemTracking,
81	DebugNoBound,
82	CloneNoBound,
83	EqNoBound,
84	PartialEqNoBound,
85	DefaultNoBound,
86)]
87#[codec(mel_bound(T: crate::Config))]
88#[scale_info(skip_type_params(T))]
89pub struct PagedRawSolution<T: MinerConfig> {
90	/// The individual pages.
91	pub solution_pages: Vec<SolutionOf<T>>,
92	/// The final claimed score post feasibility and concatenation of all pages.
93	pub score: ElectionScore,
94	/// The designated round.
95	pub round: u32,
96}
97
98impl<T: MinerConfig> PagedRawSolution<T> {
99	/// Get the total number of voters, assuming that voters in each page are unique.
100	pub fn voter_count(&self) -> usize {
101		self.solution_pages
102			.iter()
103			.map(|page| page.voter_count())
104			.fold(0usize, |acc, x| acc.saturating_add(x))
105	}
106
107	/// Get the total number of winners, assuming that there's only a single page of targets.
108	pub fn winner_count_single_page_target_snapshot(&self) -> usize {
109		self.solution_pages
110			.iter()
111			.map(|page| page.unique_targets())
112			.into_iter()
113			.flatten()
114			.collect::<BTreeSet<_>>()
115			.len()
116	}
117
118	/// Get the total number of edges.
119	pub fn edge_count(&self) -> usize {
120		self.solution_pages
121			.iter()
122			.map(|page| page.edge_count())
123			.fold(0usize, |acc, x| acc.saturating_add(x))
124	}
125}
126
127/// A helper trait to deal with the page index of partial solutions.
128///
129/// This should only be called on the `Vec<Solution>` or similar types. If the solution is *full*,
130/// then it returns a normal iterator that is just mapping the index (usize) to `PageIndex`.
131///
132/// if the solution is partial, it shifts the indices sufficiently so that the most significant page
133/// of the solution matches with the most significant page of the snapshot onchain.
134///
135/// See the tests below for examples.
136pub trait Pagify<T> {
137	/// Pagify a reference.
138	fn pagify(&self, bound: PageIndex) -> Box<dyn Iterator<Item = (PageIndex, &T)> + '_>;
139}
140
141impl<T> Pagify<T> for Vec<T> {
142	fn pagify(&self, desired_pages: PageIndex) -> Box<dyn Iterator<Item = (PageIndex, &T)> + '_> {
143		Box::new(
144			self.into_iter()
145				.enumerate()
146				.map(|(p, s)| (p.saturated_into::<PageIndex>(), s))
147				.map(move |(p, s)| {
148					let desired_pages_usize = desired_pages as usize;
149					// TODO: this could be an error.
150					debug_assert!(self.len() <= desired_pages_usize);
151					let padding = desired_pages_usize.saturating_sub(self.len());
152					let new_page = p.saturating_add(padding.saturated_into::<PageIndex>());
153					(new_page, s)
154				}),
155		)
156	}
157}
158
159/// Helper trait to pad a partial solution such that the leftover pages are filled with zero.
160///
161/// See the tests below for examples.
162pub trait PadSolutionPages: Sized {
163	/// Pad the solution to the given number of pages.
164	fn pad_solution_pages(self, desired_pages: PageIndex) -> Self;
165}
166
167impl<T: Default + Clone + Debug> PadSolutionPages for Vec<T> {
168	fn pad_solution_pages(self, desired_pages: PageIndex) -> Self {
169		let desired_pages_usize = desired_pages as usize;
170		debug_assert!(self.len() <= desired_pages_usize);
171		if self.len() == desired_pages_usize {
172			return self
173		}
174
175		// we basically need to prepend the list with this many items.
176		let empty_slots = desired_pages_usize.saturating_sub(self.len());
177		sp_std::iter::repeat(Default::default())
178			.take(empty_slots)
179			.chain(self.into_iter())
180			.collect::<Vec<_>>()
181	}
182}
183
184impl<T: Default + Clone + Debug, Bound: frame_support::traits::Get<u32>> PadSolutionPages
185	for BoundedVec<T, Bound>
186{
187	fn pad_solution_pages(self, desired_pages: PageIndex) -> Self {
188		let desired_pages_usize = (desired_pages).min(Bound::get()) as usize;
189		debug_assert!(self.len() <= desired_pages_usize);
190		if self.len() == desired_pages_usize {
191			return self
192		}
193
194		// we basically need to prepend the list with this many items.
195		let empty_slots = desired_pages_usize.saturating_sub(self.len());
196		let self_as_vec = sp_std::iter::repeat(Default::default())
197			.take(empty_slots)
198			.chain(self.into_iter())
199			.collect::<Vec<_>>();
200		self_as_vec.try_into().expect("sum of both iterators has at most `desired_pages_usize` items; `desired_pages_usize` is `min`-ed by `Bound`; conversion cannot fail; qed")
201	}
202}
203
204/// Alias for a voter, parameterized by the miner config.
205pub type VoterOf<T> = frame_election_provider_support::Voter<
206	<T as MinerConfig>::AccountId,
207	<T as MinerConfig>::MaxVotesPerVoter,
208>;
209
210/// Alias for a page of voters, parameterized by this crate's config.
211pub type VoterPageOf<T> = BoundedVec<VoterOf<T>, <T as MinerConfig>::VoterSnapshotPerBlock>;
212
213/// Alias for all pages of voters, parameterized by this crate's config.
214pub type AllVoterPagesOf<T> = BoundedVec<VoterPageOf<T>, <T as MinerConfig>::Pages>;
215
216/// Maximum number of items that [`AllVoterPagesOf`] can contain, when flattened.
217pub struct MaxFlattenedVoters<T: MinerConfig>(sp_std::marker::PhantomData<T>);
218impl<T: MinerConfig> Get<u32> for MaxFlattenedVoters<T> {
219	fn get() -> u32 {
220		T::VoterSnapshotPerBlock::get().saturating_mul(T::Pages::get())
221	}
222}
223
224/// Same as [`AllVoterPagesOf`], but instead of being a nested bounded vec, the entire voters are
225/// flattened into one outer, unbounded `Vec` type.
226///
227/// This is bounded by [`MaxFlattenedVoters`].
228pub type AllVoterPagesFlattenedOf<T> = BoundedVec<VoterOf<T>, MaxFlattenedVoters<T>>;
229
230/// Current phase of the pallet.
231#[derive(
232	PartialEqNoBound,
233	EqNoBound,
234	CloneNoBound,
235	Encode,
236	Decode,
237	DecodeWithMemTracking,
238	MaxEncodedLen,
239	DebugNoBound,
240	TypeInfo,
241)]
242#[codec(mel_bound(T: crate::Config))]
243#[scale_info(skip_type_params(T))]
244pub enum Phase<T: crate::Config> {
245	/// Nothing is happening, but it might.
246	Off,
247	/// Signed phase is open.
248	///
249	/// The inner value is the number of blocks left in this phase.
250	Signed(BlockNumberFor<T>),
251	/// We are validating results.
252	///
253	/// This always follows the signed phase, and is a window of time in which we try to validate
254	/// our signed results.
255	///
256	/// The inner value is the number of blocks left in this phase.
257	SignedValidation(BlockNumberFor<T>),
258	/// Unsigned phase.
259	///
260	/// The inner value is the number of blocks left in this phase.
261	///
262	/// We do not yet check whether the unsigned phase is active or passive. The intent is for the
263	/// blockchain to be able to declare: "I believe that there exists an adequate signed
264	/// solution," advising validators not to bother running the unsigned offchain worker.
265	///
266	/// As validator nodes are free to edit their OCW code, they could simply ignore this advisory
267	/// and always compute their own solution. However, by default, when the unsigned phase is
268	/// passive, the offchain workers will not bother running.
269	Unsigned(BlockNumberFor<T>),
270	/// Snapshot is being created. No other operation is allowed. This can be one or more blocks.
271	/// The inner value should be read as "`remaining` number of pages are left to be fetched".
272	/// Thus, if inner value is `0` if the snapshot is complete and we are ready to move on.
273	///
274	/// This value should be interpreted after `on_initialize`/`on_poll` of this pallet has already
275	/// been called.
276	Snapshot(PageIndex),
277	/// Snapshot is done, and we are waiting for `Export` to kick in.
278	Done,
279	/// Exporting has begun, and the given page was the last one received.
280	///
281	/// Once this is active, no more signed or solutions will be accepted.
282	Export(PageIndex),
283	/// The emergency phase. This is could be enabled by one of the fallbacks, and locks the pallet
284	/// such that only governance can change the state.
285	Emergency,
286}
287
288impl<T: crate::Config> Copy for Phase<T> {}
289
290impl<T: crate::Config> Default for Phase<T> {
291	fn default() -> Self {
292		Phase::Off
293	}
294}
295
296impl<T: crate::Config> Phase<T> {
297	/// Get the phase that we should set in storage once we receive the start signal.
298	pub(crate) fn start_phase() -> Self {
299		// note that we add one block because we want the target snapshot to happen one block
300		// before.
301		Self::Snapshot(T::Pages::get())
302	}
303
304	fn are_we_done() -> Self {
305		let query = T::AreWeDone::get();
306		query
307	}
308
309	/// A hack to make sure we don't finish the signed verification phase just yet if the status is
310	/// not yet set back to `Nothing`.
311	fn verifier_done() -> bool {
312		matches!(
313			<T::Verifier as verifier::AsynchronousVerifier>::status(),
314			verifier::Status::Nothing
315		)
316	}
317
318	/// Consume self and return the next variant, as per what the current phase is.
319	pub fn next(self) -> Self {
320		match self {
321			// for these phases, we do nothing.
322			Self::Off => Self::Off,
323			Self::Emergency => Self::Emergency,
324
325			// snapshot phase
326			Self::Snapshot(0) =>
327				if let Some(signed_duration) = T::SignedPhase::get().checked_sub(&One::one()) {
328					Self::Signed(signed_duration)
329				} else if let Some(unsigned_duration) =
330					T::UnsignedPhase::get().checked_sub(&One::one())
331				{
332					Self::Unsigned(unsigned_duration)
333				} else {
334					Self::are_we_done()
335				},
336			Self::Snapshot(non_zero_remaining) =>
337				Self::Snapshot(non_zero_remaining.defensive_saturating_sub(One::one())),
338
339			// signed phase
340			Self::Signed(zero) if zero == BlockNumberFor::<T>::zero() =>
341				Self::SignedValidation(T::SignedValidationPhase::get()),
342			Self::Signed(non_zero_left) =>
343				Self::Signed(non_zero_left.defensive_saturating_sub(One::one())),
344
345			// signed validation
346			Self::SignedValidation(zero)
347				if zero == BlockNumberFor::<T>::zero() && Self::verifier_done() =>
348				if let Some(unsigned_duration) = T::UnsignedPhase::get().checked_sub(&One::one()) {
349					Self::Unsigned(unsigned_duration)
350				} else {
351					Self::are_we_done()
352				},
353			Self::SignedValidation(non_zero_left) =>
354				Self::SignedValidation(non_zero_left.saturating_sub(One::one())),
355
356			// unsigned phase -- at this phase we will
357			Self::Unsigned(zero) if zero == BlockNumberFor::<T>::zero() => Self::are_we_done(),
358			Self::Unsigned(non_zero_left) =>
359				Self::Unsigned(non_zero_left.defensive_saturating_sub(One::one())),
360
361			// Done. Wait for export to start.
362			Self::Done => Self::Done,
363
364			// Export never moves forward via this function, and is always manually set in the
365			// `elect` code path.
366			Self::Export(x) => Self::Export(x),
367		}
368	}
369
370	/// Whether the phase is emergency or not.
371	pub fn is_emergency(&self) -> bool {
372		matches!(self, Phase::Emergency)
373	}
374
375	/// Whether the phase is signed or not.
376	pub fn is_signed(&self) -> bool {
377		matches!(self, Phase::Signed(_))
378	}
379
380	/// Whether the phase is unsigned or not.
381	pub fn is_unsigned(&self) -> bool {
382		matches!(self, Phase::Unsigned(_))
383	}
384
385	/// Whether the phase is off or not.
386	pub fn is_off(&self) -> bool {
387		matches!(self, Phase::Off)
388	}
389
390	/// Whether the phase is snapshot or not.
391	pub fn is_snapshot(&self) -> bool {
392		matches!(self, Phase::Snapshot(_))
393	}
394
395	/// Whether the phase is done or not.
396	pub fn is_done(&self) -> bool {
397		matches!(self, Phase::Done)
398	}
399
400	/// Whether the phase is export or not.
401	pub fn is_export(&self) -> bool {
402		matches!(self, Phase::Export(_))
403	}
404
405	/// Whether the phase is signed validation or not.
406	pub fn is_signed_validation(&self) -> bool {
407		matches!(self, Phase::SignedValidation(_))
408	}
409
410	/// Whether the signed phase is opened now.
411	pub fn is_signed_validation_opened_now(&self) -> bool {
412		self == &Phase::SignedValidation(T::SignedValidationPhase::get().saturating_sub(One::one()))
413	}
414
415	/// Whether the unsigned phase is opened now.
416	pub fn is_unsigned_opened_now(&self) -> bool {
417		self == &Phase::Unsigned(T::UnsignedPhase::get().saturating_sub(One::one()))
418	}
419}
420
421/// Slim interface for the parent pallet to be able to inspect the state of the signed pallet.
422///
423/// Intentionally left different from [`crate::verifier::SolutionDataProvider`], as that is
424/// specialized for communication between `verifier <> signed`.
425pub trait SignedInterface {
426	/// Returns `true` if there is a candidate solution to be verified.
427	fn has_leader(round: u32) -> bool;
428}
429
430#[cfg(test)]
431mod pagify {
432	use super::{PadSolutionPages, Pagify};
433
434	#[test]
435	fn pagify_works() {
436		// is a noop when you have the same length
437		assert_eq!(
438			vec![10, 11, 12].pagify(3).collect::<Vec<_>>(),
439			vec![(0, &10), (1, &11), (2, &12)]
440		);
441
442		// pads the values otherwise
443		assert_eq!(vec![10, 11].pagify(3).collect::<Vec<_>>(), vec![(1, &10), (2, &11)]);
444		assert_eq!(vec![10].pagify(3).collect::<Vec<_>>(), vec![(2, &10)]);
445	}
446
447	#[test]
448	fn pad_solution_pages_works() {
449		// noop if the solution is complete, as with pagify.
450		let solution = vec![1u32, 2, 3];
451		assert_eq!(solution.pad_solution_pages(3), vec![1, 2, 3]);
452
453		// pads the solution with default if partial..
454		let solution = vec![2, 3];
455		assert_eq!(solution.pad_solution_pages(3), vec![0, 2, 3]);
456	}
457}