referrerpolicy=no-referrer-when-downgrade

pallet_staking_async/
session_rotation.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//! Manages all era rotation logic based on session increments.
19//!
20//! # Lifecycle:
21//!
22//! When a session ends in RC, a session report is sent to AH with the ending session index. Given
23//! there are 6 sessions per Era, and we configure the PlanningEraOffset to be 1, the following
24//! happens.
25//!
26//! ## Idle Sessions
27//! In the happy path, first 3 sessions are idle. Nothing much happens in these sessions.
28//!
29//!
30//! ## Planning New Era Session
31//! In the happy path, `planning new era` session is initiated when 3rd session ends and the 4th
32//! starts in the active era.
33//!
34//! **Triggers**
35//! 1. `SessionProgress == SessionsPerEra - PlanningEraOffset`
36//! 2. Forcing is set to `ForceNew` or `ForceAlways`
37//!
38//! **Actions**
39//! 1. Triggers the election process,
40//! 2. Updates the CurrentEra.
41//!
42//! **SkipIf**
43//! CurrentEra = ActiveEra + 1 // this implies planning session has already been triggered.
44//!
45//! **FollowUp**
46//! When the election process is over, we send the new validator set, with the CurrentEra index
47//! as the id of the validator set.
48//!
49//!
50//! ## Era Rotation Session
51//! In the happy path, this happens when the 5th session ends and the 6th starts in the active era.
52//!
53//! **Triggers**
54//! When we receive an activation timestamp from RC.
55//!
56//! **Assertions**
57//! 1. CurrentEra must be ActiveEra + 1.
58//! 2. Id of the activation timestamp same as CurrentEra.
59//!
60//! **Actions**
61//! - Finalize the currently active era.
62//! - Increment ActiveEra by 1.
63//! - Cleanup the old era information.
64//!
65//! **Exceptional Scenarios**
66//! - Delay in exporting validator set: Triggered in a session later than 7th.
67//! - Forcing Era: May triggered in a session earlier than 7th.
68//!
69//! ## Example Flow of a happy path
70//!
71//! * end 0, start 1, plan 2
72//! * end 1, start 2, plan 3
73//! * end 2, start 3, plan 4
74//! * end 3, start 4, plan 5 // `Plan new era` session. Current Era++. Trigger Election.
75//! * **** Somewhere here: Election set is sent to RC, keyed with Current Era
76//! * end 4, start 5, plan 6 // RC::session receives and queues this set.
77//! * end 5, start 6, plan 7 // Session report contains activation timestamp with Current Era.
78
79use crate::*;
80use alloc::{boxed::Box, vec::Vec};
81use frame_election_provider_support::{BoundedSupportsOf, ElectionProvider, PageIndex};
82use frame_support::{
83	pallet_prelude::*,
84	traits::{Defensive, DefensiveMax, DefensiveSaturating, OnUnbalanced, TryCollect},
85	weights::WeightMeter,
86};
87use pallet_staking_async_rc_client::RcClientInterface;
88use sp_runtime::{Perbill, Percent, Saturating};
89use sp_staking::{
90	currency_to_vote::CurrencyToVote, Exposure, Page, PagedExposureMetadata, SessionIndex,
91	StakerRewardCalculator,
92};
93
94/// A handler for all era-based storage items.
95///
96/// All of the following storage items must be controlled by this type:
97///
98/// [`ErasValidatorPrefs`]
99/// [`ClaimedRewards`]
100/// [`ErasStakersPaged`]
101/// [`ErasStakersOverview`]
102/// [`ErasValidatorReward`]
103/// [`ErasRewardPoints`]
104/// [`ErasTotalStake`]
105pub struct Eras<T: Config>(core::marker::PhantomData<T>);
106
107impl<T: Config> Eras<T> {
108	pub(crate) fn set_validator_prefs(era: EraIndex, stash: &T::AccountId, prefs: ValidatorPrefs) {
109		debug_assert_eq!(era, Rotator::<T>::planned_era(), "we only set prefs for planning era");
110		<ErasValidatorPrefs<T>>::insert(era, stash, prefs);
111	}
112
113	pub(crate) fn get_validator_prefs(era: EraIndex, stash: &T::AccountId) -> ValidatorPrefs {
114		<ErasValidatorPrefs<T>>::get(era, stash)
115	}
116
117	/// Returns validator commission for this era and page.
118	pub(crate) fn get_validator_commission(era: EraIndex, stash: &T::AccountId) -> Perbill {
119		Self::get_validator_prefs(era, stash).commission
120	}
121
122	/// Returns true if validator has one or more page of era rewards not claimed yet.
123	pub(crate) fn pending_rewards(era: EraIndex, validator: &T::AccountId) -> bool {
124		<ErasStakersOverview<T>>::get(&era, validator)
125			.map(|overview| {
126				ClaimedRewards::<T>::get(era, validator).len() < overview.page_count as usize
127			})
128			.unwrap_or(false)
129	}
130
131	/// Get exposure for a validator at a given era and page.
132	///
133	/// This is mainly used for rewards and slashing. Validator's self-stake is only returned in
134	/// page 0.
135	///
136	/// This builds a paged exposure from `PagedExposureMetadata` and `ExposurePage` of the
137	/// validator.
138	pub(crate) fn get_paged_exposure(
139		era: EraIndex,
140		validator: &T::AccountId,
141		page: Page,
142	) -> Option<PagedExposure<T::AccountId, BalanceOf<T>>> {
143		let overview = <ErasStakersOverview<T>>::get(&era, validator)?;
144
145		// validator stake is added only in page zero.
146		let validator_stake = if page == 0 { overview.own } else { Zero::zero() };
147
148		// since overview is present, paged exposure will always be present except when a
149		// validator has only own stake and no nominator stake.
150		let exposure_page = <ErasStakersPaged<T>>::get((era, validator, page)).unwrap_or_default();
151
152		// build the exposure
153		Some(PagedExposure {
154			exposure_metadata: PagedExposureMetadata { own: validator_stake, ..overview },
155			exposure_page: exposure_page.into(),
156		})
157	}
158
159	/// Get full exposure of the validator at a given era.
160	pub(crate) fn get_full_exposure(
161		era: EraIndex,
162		validator: &T::AccountId,
163	) -> Exposure<T::AccountId, BalanceOf<T>> {
164		let Some(overview) = <ErasStakersOverview<T>>::get(&era, validator) else {
165			return Exposure::default();
166		};
167
168		let mut others = Vec::with_capacity(overview.nominator_count as usize);
169		for page in 0..overview.page_count {
170			let nominators = <ErasStakersPaged<T>>::get((era, validator, page));
171			others.append(&mut nominators.map(|n| n.others.clone()).defensive_unwrap_or_default());
172		}
173
174		Exposure { total: overview.total, own: overview.own, others }
175	}
176
177	/// Returns the number of pages of exposure a validator has for the given era.
178	///
179	/// For eras where paged exposure does not exist, this returns 1 to keep backward compatibility.
180	pub(crate) fn exposure_page_count(era: EraIndex, validator: &T::AccountId) -> Page {
181		<ErasStakersOverview<T>>::get(&era, validator)
182			.map(|overview| {
183				if overview.page_count == 0 && overview.own > Zero::zero() {
184					// Even though there are no nominator pages, there is still validator's own
185					// stake exposed which needs to be paid out in a page.
186					1
187				} else {
188					overview.page_count
189				}
190			})
191			// Always returns 1 page for older non-paged exposure.
192			// FIXME: Can be cleaned up with issue #13034.
193			.unwrap_or(1)
194	}
195
196	/// Returns the next page that can be claimed or `None` if nothing to claim.
197	pub(crate) fn get_next_claimable_page(era: EraIndex, validator: &T::AccountId) -> Option<Page> {
198		// Find next claimable page of paged exposure.
199		let page_count = Self::exposure_page_count(era, validator);
200		let all_claimable_pages: Vec<Page> = (0..page_count).collect();
201		let claimed_pages = ClaimedRewards::<T>::get(era, validator);
202
203		all_claimable_pages.into_iter().find(|p| !claimed_pages.contains(p))
204	}
205
206	/// Returns whether nominators are slashable for a specific era.
207	///
208	/// This checks the per-era storage [`ErasNominatorsSlashable`] which captures
209	/// the value of [`AreNominatorsSlashable`] at the start of that era.
210	/// If no entry exists for the era, nominators are assumed to be slashable (default).
211	pub(crate) fn are_nominators_slashable(era: EraIndex) -> bool {
212		ErasNominatorsSlashable::<T>::get(era).unwrap_or(true)
213	}
214
215	/// Creates an entry to track validator reward has been claimed for a given era and page.
216	/// Noop if already claimed.
217	pub(crate) fn set_rewards_as_claimed(era: EraIndex, validator: &T::AccountId, page: Page) {
218		let mut claimed_pages = ClaimedRewards::<T>::get(era, validator).into_inner();
219
220		// this should never be called if the reward has already been claimed
221		if claimed_pages.contains(&page) {
222			defensive!("Trying to set an already claimed reward");
223			// nevertheless don't do anything since the page already exist in claimed rewards.
224			return;
225		}
226
227		// add page to claimed entries
228		claimed_pages.push(page);
229		ClaimedRewards::<T>::insert(
230			era,
231			validator,
232			WeakBoundedVec::<_, _>::force_from(claimed_pages, Some("set_rewards_as_claimed")),
233		);
234	}
235
236	/// Store exposure for elected validators at start of an era.
237	///
238	/// If the exposure does not exist yet for the tuple (era, validator), it sets it. Otherwise,
239	/// it updates the existing record by ensuring *intermediate* exposure pages are filled up with
240	/// `T::MaxExposurePageSize` number of backers per page and the remaining exposures are added
241	/// to new exposure pages.
242	pub fn upsert_exposure(
243		era: EraIndex,
244		validator: &T::AccountId,
245		mut exposure: Exposure<T::AccountId, BalanceOf<T>>,
246	) {
247		let page_size = T::MaxExposurePageSize::get().defensive_max(1);
248		if cfg!(debug_assertions) && cfg!(not(feature = "runtime-benchmarks")) {
249			// sanitize the exposure in case some test data from this pallet is wrong.
250			// ignore benchmarks as other pallets might do weird things.
251			let expected_total = exposure
252				.others
253				.iter()
254				.map(|ie| ie.value)
255				.fold::<BalanceOf<T>, _>(Default::default(), |acc, x| acc + x)
256				.saturating_add(exposure.own);
257			debug_assert_eq!(expected_total, exposure.total, "exposure total must equal own + sum(others) for (era: {:?}, validator: {:?}, exposure: {:?})", era, validator, exposure);
258		}
259
260		if let Some(overview) = ErasStakersOverview::<T>::get(era, &validator) {
261			// collect some info from the un-touched overview for later use.
262			let last_page_idx = overview.page_count.saturating_sub(1);
263			let mut last_page =
264				ErasStakersPaged::<T>::get((era, validator, last_page_idx)).unwrap_or_default();
265			let last_page_empty_slots =
266				T::MaxExposurePageSize::get().saturating_sub(last_page.others.len() as u32);
267
268			// update nominator-count, page-count, and total stake in overview (done in
269			// `update_with`).
270			let new_stake_added = exposure.total;
271			let new_nominators_added = exposure.others.len() as u32;
272			let mut updated_overview = overview
273				.update_with::<T::MaxExposurePageSize>(new_stake_added, new_nominators_added);
274
275			// update own stake, if applicable.
276			match (updated_overview.own.is_zero(), exposure.own.is_zero()) {
277				(true, false) => {
278					// first time we see own exposure -- good.
279					// note: `total` is already updated above.
280					updated_overview.own = exposure.own;
281				},
282				(true, true) | (false, true) => {
283					// no new own exposure is added, nothing to do
284				},
285				(false, false) => {
286					debug_assert!(
287						false,
288						"validator own stake already set in overview for (era: {:?}, validator: {:?}, current overview: {:?}, new exposure: {:?})",
289						era,
290						validator,
291						updated_overview,
292						exposure,
293					);
294					defensive!("duplicate validator self stake in election");
295				},
296			};
297
298			ErasStakersOverview::<T>::insert(era, &validator, updated_overview);
299			// we are done updating the overview now, `updated_overview` should not be used anymore.
300			// We've updated:
301			// * nominator count
302			// * total stake
303			// * own stake (if applicable)
304			// * page count
305			//
306			// next step:
307			// * new-keys or updates in `ErasStakersPaged`
308			//
309			// we don't need the information about own stake anymore -- drop it.
310			exposure.total = exposure.total.saturating_sub(exposure.own);
311			exposure.own = Zero::zero();
312
313			// splits the exposure so that `append_to_last_page` will fit within the last exposure
314			// page, up to the max exposure page size. The remaining individual exposures in
315			// `put_in_new_pages` will be added to new pages.
316			let append_to_last_page = exposure.split_others(last_page_empty_slots);
317			let put_in_new_pages = exposure;
318
319			// handle last page first.
320
321			// fill up last page with exposures.
322			last_page.page_total = last_page.page_total.saturating_add(append_to_last_page.total);
323			last_page.others.extend(append_to_last_page.others);
324			ErasStakersPaged::<T>::insert((era, &validator, last_page_idx), last_page);
325
326			// now handle the remaining exposures and append the exposure pages. The metadata update
327			// has been already handled above.
328			let (_unused_metadata, put_in_new_pages_chunks) =
329				put_in_new_pages.into_pages(page_size);
330
331			put_in_new_pages_chunks
332				.into_iter()
333				.enumerate()
334				.for_each(|(idx, paged_exposure)| {
335					let append_at =
336						(last_page_idx.saturating_add(1).saturating_add(idx as u32)) as Page;
337					<ErasStakersPaged<T>>::insert((era, &validator, append_at), paged_exposure);
338				});
339		} else {
340			// expected page count is the number of nominators divided by the page size, rounded up.
341			let expected_page_count = exposure
342				.others
343				.len()
344				.defensive_saturating_add((page_size as usize).defensive_saturating_sub(1))
345				.saturating_div(page_size as usize);
346
347			// no exposures yet for this (era, validator) tuple, calculate paged exposure pages and
348			// metadata from a blank slate.
349			let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size);
350			defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count");
351
352			// insert metadata.
353			ErasStakersOverview::<T>::insert(era, &validator, exposure_metadata);
354
355			// Track that this validator was active in this era for slash liability tracking.
356			LastValidatorEra::<T>::insert(validator, era);
357
358			// insert validator's overview.
359			exposure_pages.into_iter().enumerate().for_each(|(idx, paged_exposure)| {
360				let append_at = idx as Page;
361				<ErasStakersPaged<T>>::insert((era, &validator, append_at), paged_exposure);
362			});
363		};
364	}
365
366	pub(crate) fn set_stakers_reward(era: EraIndex, amount: BalanceOf<T>) {
367		ErasValidatorReward::<T>::insert(era, amount);
368	}
369
370	pub(crate) fn get_stakers_reward(era: EraIndex) -> Option<BalanceOf<T>> {
371		ErasValidatorReward::<T>::get(era)
372	}
373
374	pub(crate) fn set_validator_incentive_budget(era: EraIndex, amount: BalanceOf<T>) {
375		ErasValidatorIncentiveBudget::<T>::insert(era, amount);
376	}
377
378	pub(crate) fn get_validator_incentive_budget(era: EraIndex) -> BalanceOf<T> {
379		ErasValidatorIncentiveBudget::<T>::get(era)
380	}
381
382	pub(crate) fn add_sum_validator_incentive_weight(
383		era: EraIndex,
384		incentive_weight: BalanceOf<T>,
385	) {
386		<ErasSumValidatorIncentiveWeight<T>>::mutate(era, |sum| {
387			*sum = sum.saturating_add(incentive_weight);
388		});
389	}
390
391	/// Update the total exposure for all the elected validators in the era.
392	pub(crate) fn add_total_stake(era: EraIndex, stake: BalanceOf<T>) {
393		<ErasTotalStake<T>>::mutate(era, |total_stake| {
394			*total_stake += stake;
395		});
396	}
397
398	/// Check if the rewards for the given era and page index have been claimed.
399	pub(crate) fn is_rewards_claimed(era: EraIndex, validator: &T::AccountId, page: Page) -> bool {
400		ClaimedRewards::<T>::get(era, validator).contains(&page)
401	}
402
403	/// Add reward points to validators using their stash account ID.
404	pub(crate) fn reward_active_era(
405		validators_points: impl IntoIterator<Item = (T::AccountId, u32)>,
406	) {
407		if let Some(active_era) = ActiveEra::<T>::get() {
408			<ErasRewardPoints<T>>::mutate(active_era.index, |era_rewards| {
409				for (validator, points) in validators_points.into_iter() {
410					match era_rewards.individual.get_mut(&validator) {
411						Some(individual) => individual.saturating_accrue(points),
412						None => {
413							// not much we can do -- validators should always be less than
414							// `MaxValidatorSet`.
415							let _ =
416								era_rewards.individual.try_insert(validator, points).defensive();
417						},
418					}
419					era_rewards.total.saturating_accrue(points);
420				}
421			});
422		}
423	}
424
425	pub(crate) fn get_reward_points(era: EraIndex) -> EraRewardPoints<T> {
426		ErasRewardPoints::<T>::get(era)
427	}
428}
429
430#[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks"))]
431#[allow(unused)]
432impl<T: Config> Eras<T> {
433	/// Ensure the given era's data is fully present (all storage intact and not being pruned).
434	pub(crate) fn era_fully_present(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
435		// these two are only set if we have some validators in an era.
436		let e0 = ErasValidatorPrefs::<T>::iter_prefix_values(era).count() != 0;
437		// note: we don't check `ErasStakersPaged` as a validator can have no backers.
438		let e1 = ErasStakersOverview::<T>::iter_prefix_values(era).count() != 0;
439		ensure!(e0 == e1, "ErasValidatorPrefs and ErasStakersOverview should be consistent");
440
441		// these two must always be set
442		let e2 = ErasTotalStake::<T>::contains_key(era);
443
444		let active_era = Rotator::<T>::active_era();
445		let e4 = if era.saturating_sub(1) > 0 &&
446			era.saturating_sub(1) > active_era.saturating_sub(T::HistoryDepth::get() + 1)
447		{
448			// `ErasValidatorReward` is set at active era n for era n-1, and is not set for era 0 in
449			// our tests. Moreover, it cannot be checked for presence in the oldest present era
450			// (`active_era.saturating_sub(1)`)
451			ErasValidatorReward::<T>::contains_key(era.saturating_sub(1))
452		} else {
453			// ignore
454			e2
455		};
456
457		ensure!(e2 == e4, "era info presence not consistent");
458
459		if e2 {
460			Ok(())
461		} else {
462			Err("era presence mismatch".into())
463		}
464	}
465
466	/// Check if the given era is currently being pruned.
467	pub(crate) fn era_pruning_in_progress(era: EraIndex) -> bool {
468		EraPruningState::<T>::contains_key(era)
469	}
470
471	/// Ensure the given era is either absent or currently being pruned.
472	pub(crate) fn era_absent_or_pruning(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
473		if Self::era_pruning_in_progress(era) {
474			Ok(())
475		} else {
476			Self::era_absent(era)
477		}
478	}
479
480	/// Ensure the given era has indeed been already pruned. This is called by the main pallet in
481	/// do_prune_era_step.
482	pub(crate) fn era_absent(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
483		// check double+ maps
484		let e0 = ErasValidatorPrefs::<T>::iter_prefix_values(era).count() != 0;
485		let e1 = ErasStakersPaged::<T>::iter_prefix_values((era,)).count() != 0;
486		let e2 = ErasStakersOverview::<T>::iter_prefix_values(era).count() != 0;
487
488		// check maps
489		// `ErasValidatorReward` is set at active era n for era n-1
490		let e3 = ErasValidatorReward::<T>::contains_key(era);
491		let e4 = ErasTotalStake::<T>::contains_key(era);
492
493		// these two are only populated conditionally, so we only check them for lack of existence
494		let e6 = ClaimedRewards::<T>::iter_prefix_values(era).count() != 0;
495		let e7 = ErasRewardPoints::<T>::contains_key(era);
496
497		// Check if era info is consistent - if not, era is in partial pruning state
498		if !vec![e0, e1, e2, e3, e4, e6, e7].windows(2).all(|w| w[0] == w[1]) {
499			return Err("era info absence not consistent - partial pruning state".into());
500		}
501
502		if !e0 {
503			Ok(())
504		} else {
505			Err("era absence mismatch".into())
506		}
507	}
508
509	pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
510		// pruning window works.
511		let active_era = Rotator::<T>::active_era();
512		// we max with 1 as in active era 0 we don't do an election and therefore we don't have some
513		// of the maps populated.
514		let oldest_present_era = active_era.saturating_sub(T::HistoryDepth::get()).max(1);
515
516		for e in oldest_present_era..=active_era {
517			Self::era_fully_present(e)?;
518			Self::check_validator_incentive_weight_consistency(e)?;
519		}
520
521		// Ensure all eras older than oldest_present_era are either fully pruned or marked for
522		// pruning
523		ensure!(
524			(1..oldest_present_era).all(|e| Self::era_absent_or_pruning(e).is_ok()),
525			"All old eras must be either fully pruned or marked for pruning"
526		);
527
528		Ok(())
529	}
530
531	/// Verify that the sum of individual validator incentive weights matches the stored total.
532	fn check_validator_incentive_weight_consistency(
533		era: EraIndex,
534	) -> Result<(), sp_runtime::TryRuntimeError> {
535		use sp_runtime::traits::Zero;
536
537		let stored_total = ErasSumValidatorIncentiveWeight::<T>::get(era);
538		let computed_total: BalanceOf<T> = ErasValidatorIncentiveWeight::<T>::iter_prefix(era)
539			.fold(BalanceOf::<T>::zero(), |acc, (_, w)| acc.saturating_add(w));
540
541		ensure!(
542			stored_total == computed_total,
543			"ErasSumValidatorIncentiveWeight mismatch: \
544			 stored vs computed individual weights do not match"
545		);
546
547		Ok(())
548	}
549}
550
551/// Manages session rotation logic.
552///
553/// This controls the following storage items in FULL, meaning that they should not be accessed
554/// directly from anywhere else in this pallet:
555///
556/// * `CurrentEra`: The current planning era
557/// * `ActiveEra`: The current active era
558/// * `BondedEras`: the list of ACTIVE eras and their session index
559pub struct Rotator<T: Config>(core::marker::PhantomData<T>);
560
561impl<T: Config> Rotator<T> {
562	#[cfg(feature = "runtime-benchmarks")]
563	pub(crate) fn legacy_insta_plan_era() -> Vec<T::AccountId> {
564		// Plan the era,
565		Self::plan_new_era();
566		// signal that we are about to call into elect asap.
567		<<T as Config>::ElectionProvider as ElectionProvider>::asap();
568		// immediately call into the election provider to fetch and process the results. We assume
569		// we are using an instant, onchain election here.
570		let msp = <T::ElectionProvider as ElectionProvider>::msp();
571		let lsp = 0;
572		for p in (lsp..=msp).rev() {
573			EraElectionPlanner::<T>::do_elect_paged(p);
574		}
575
576		crate::ElectableStashes::<T>::take().into_iter().collect()
577	}
578
579	#[cfg(any(feature = "try-runtime", test))]
580	pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
581		// Check planned era vs active era relationship
582		let active_era = ActiveEra::<T>::get();
583		let planned_era = CurrentEra::<T>::get();
584
585		let bonded = BondedEras::<T>::get();
586
587		match (&active_era, &planned_era) {
588			(None, None) => {
589				// Uninitialized state - both should be None
590				ensure!(bonded.is_empty(), "BondedEras must be empty when ActiveEra is None");
591			},
592			(Some(active), Some(planned)) => {
593				// Normal state - planned can be at most one more than active
594				ensure!(
595					*planned == active.index || *planned == active.index + 1,
596					"planned era is always equal or one more than active"
597				);
598
599				// If we have an active era, bonded eras must always be the range
600				// [active - bonding_duration .. active_era]
601				let bonded_eras: Vec<_> = bonded.iter().map(|(era, _sess)| *era).collect();
602				ensure!(
603					bonded_eras ==
604						(active.index.saturating_sub(T::BondingDuration::get())..=active.index)
605							.collect::<Vec<_>>(),
606					"BondedEras range incorrect"
607				);
608
609				// ErasNominatorsSlashable entries are cleaned up via lazy pruning at HistoryDepth +
610				// 1. Entries can exist from [active - HistoryDepth, active] inclusive.
611				// Entries older than HistoryDepth should have been pruned (or be in the process of
612				// pruning).
613				let oldest_allowed_era = active.index.saturating_sub(T::HistoryDepth::get()).max(1);
614				for (era, _) in ErasNominatorsSlashable::<T>::iter() {
615					// Allow entries being pruned (EraPruningState exists)
616					let being_pruned = EraPruningState::<T>::contains_key(era);
617					ensure!(
618						(era >= oldest_allowed_era && era <= active.index) || being_pruned,
619						"ErasNominatorsSlashable entry exists for era outside history depth range and not being pruned"
620					);
621				}
622			},
623			_ => {
624				ensure!(false, "ActiveEra and CurrentEra must both be None or both be Some");
625			},
626		}
627
628		Ok(())
629	}
630
631	#[cfg(any(feature = "try-runtime", feature = "std", feature = "runtime-benchmarks", test))]
632	pub fn assert_election_ongoing() {
633		assert!(Self::is_planning().is_some(), "planning era must exist");
634		assert!(
635			T::ElectionProvider::status().is_ok(),
636			"Election provider must be in a good state during election"
637		);
638	}
639
640	/// Latest era that was planned.
641	///
642	/// The returned value does not necessarily indicate that planning for the era with this index
643	/// is underway, but rather the last era that was planned. If `Self::active_era()` is equal to
644	/// this value, it means that the era is currently active and no new era is planned.
645	///
646	/// See [`Self::is_planning()`] to only get the next index if planning in progress.
647	pub fn planned_era() -> EraIndex {
648		CurrentEra::<T>::get().unwrap_or(0)
649	}
650
651	pub fn active_era() -> EraIndex {
652		ActiveEra::<T>::get().map(|a| a.index).defensive_unwrap_or(0)
653	}
654
655	/// Next era that is planned to be started.
656	///
657	/// Returns None if no era is planned.
658	pub fn is_planning() -> Option<EraIndex> {
659		let (active, planned) = (Self::active_era(), Self::planned_era());
660		if planned.defensive_saturating_sub(active) > 1 {
661			defensive!("planned era must always be equal or one more than active");
662		}
663
664		(planned > active).then_some(planned)
665	}
666
667	/// End the session and start the next one.
668	pub(crate) fn end_session(
669		end_index: SessionIndex,
670		activation_timestamp: Option<(u64, u32)>,
671	) -> Weight {
672		// baseline weight for processing the relay chain session report
673		let weight = T::WeightInfo::rc_on_session_report();
674
675		let Some(active_era) = ActiveEra::<T>::get() else {
676			defensive!("Active era must always be available.");
677			return weight;
678		};
679		let current_planned_era = Self::is_planning();
680		let starting = end_index + 1;
681		// the session after the starting session.
682		let planning = starting + 1;
683
684		log!(
685			info,
686			"Session: end {:?}, start {:?} (ts: {:?}), planning {:?}",
687			end_index,
688			starting,
689			activation_timestamp,
690			planning
691		);
692		log!(info, "Era: active {:?}, planned {:?}", active_era.index, current_planned_era);
693
694		match activation_timestamp {
695			Some((time, id)) if Some(id) == current_planned_era => {
696				// We rotate the era if we have the activation timestamp.
697				Self::start_era(active_era, starting, time);
698			},
699			Some((_time, id)) => {
700				// RC has done something wrong -- we received the wrong ID. Don't start a new era.
701				crate::log!(
702					warn,
703					"received wrong ID with activation timestamp. Got {}, expected {:?}",
704					id,
705					current_planned_era
706				);
707				Pallet::<T>::deposit_event(Event::Unexpected(
708					UnexpectedKind::UnknownValidatorActivation,
709				));
710			},
711			None => (),
712		}
713
714		// check if we should plan new era.
715		let should_plan_era = match ForceEra::<T>::get() {
716			// see if it's good time to plan a new era.
717			Forcing::NotForcing => Self::is_plan_era_deadline(starting),
718			// Force plan new era only once.
719			Forcing::ForceNew => {
720				ForceEra::<T>::put(Forcing::NotForcing);
721				true
722			},
723			// always plan the new era.
724			Forcing::ForceAlways => true,
725			// never force.
726			Forcing::ForceNone => false,
727		};
728
729		// Note: we call `planning_era` again, as a new era might have started since we checked
730		// it last.
731		let has_pending_era = Self::is_planning().is_some();
732		match (should_plan_era, has_pending_era) {
733			(false, _) => {
734				// nothing to consider
735			},
736			(true, false) => {
737				// happy path
738				Self::plan_new_era();
739			},
740			(true, true) => {
741				// we are waiting for to start the previously planned era, we cannot plan a new era
742				// now.
743				crate::log!(
744					debug,
745					"time to plan a new era {:?}, but waiting for the activation of the previous.",
746					current_planned_era
747				);
748			},
749		}
750
751		Pallet::<T>::deposit_event(Event::SessionRotated {
752			starting_session: starting,
753			active_era: Self::active_era(),
754			planned_era: Self::planned_era(),
755		});
756
757		weight
758	}
759
760	pub(crate) fn start_era(
761		ending_era: ActiveEraInfo,
762		starting_session: SessionIndex,
763		new_era_start_timestamp: u64,
764	) {
765		// verify that a new era was planned
766		debug_assert!(CurrentEra::<T>::get().unwrap_or(0) == ending_era.index + 1);
767
768		let starting_era = ending_era.index + 1;
769
770		// finalize the ending era.
771		Self::end_era(&ending_era, new_era_start_timestamp);
772
773		// start the next era.
774		Self::start_era_inc_active_era(new_era_start_timestamp);
775		Self::start_era_update_bonded_eras(starting_era, starting_session);
776
777		// Snapshot the current nominators slashable setting for this era.
778		// Cleanup will happen via lazy pruning at HistoryDepth.
779		ErasNominatorsSlashable::<T>::insert(starting_era, AreNominatorsSlashable::<T>::get());
780
781		// cleanup election state
782		EraElectionPlanner::<T>::cleanup();
783
784		// Cleanup era pot accounts and mark for lazy pruning.
785		if let Some(old_era) = starting_era.checked_sub(T::HistoryDepth::get() + 1) {
786			reward::EraRewardManager::<T>::cleanup_era(old_era);
787			log!(debug, "Marking era {:?} for lazy pruning", old_era);
788			EraPruningState::<T>::insert(old_era, PruningStep::ErasStakersPaged);
789		}
790	}
791
792	fn start_era_inc_active_era(start_timestamp: u64) {
793		ActiveEra::<T>::mutate(|active_era| {
794			let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0);
795			log!(
796				debug,
797				"starting active era {:?} with RC-provided timestamp {:?}",
798				new_index,
799				start_timestamp
800			);
801			*active_era = Some(ActiveEraInfo { index: new_index, start: Some(start_timestamp) });
802		});
803	}
804
805	/// The session index of the current active era.
806	///
807	/// This must always exist in the `BondedEras` storage item, ergo the function is infallible.
808	pub fn active_era_start_session_index() -> SessionIndex {
809		Self::era_start_session_index(Self::active_era()).defensive_unwrap_or(0)
810	}
811
812	/// The session index of a given era.
813	pub fn era_start_session_index(era: EraIndex) -> Option<SessionIndex> {
814		BondedEras::<T>::get()
815			.into_iter()
816			.rev()
817			.find_map(|(e, s)| if e == era { Some(s) } else { None })
818	}
819
820	fn start_era_update_bonded_eras(starting_era: EraIndex, start_session: SessionIndex) {
821		let bonding_duration = T::BondingDuration::get();
822
823		BondedEras::<T>::mutate(|bonded| {
824			if bonded.is_full() {
825				// remove oldest
826				let (era_removed, _) = bonded.remove(0);
827				debug_assert!(
828					era_removed <= (starting_era.saturating_sub(bonding_duration)),
829					"should not delete an era that is not older than bonding duration"
830				);
831			}
832
833			// must work -- we were not full, or just removed the oldest era.
834			let _ = bonded.try_push((starting_era, start_session)).defensive();
835		});
836	}
837
838	fn end_era(ending_era: &ActiveEraInfo, new_era_start: u64) {
839		if T::DisableMinting::get() {
840			Self::end_era_dap(ending_era);
841		} else {
842			Self::end_era_legacy(ending_era, new_era_start);
843		}
844	}
845
846	/// Legacy end-era: compute inflation via `EraPayout`, mint, send remainder.
847	fn end_era_legacy(ending_era: &ActiveEraInfo, new_era_start: u64) {
848		let previous_era_start = ending_era.start.defensive_unwrap_or(new_era_start);
849		let era_duration = new_era_start.saturating_sub(previous_era_start);
850
851		let cap = T::MaxEraDuration::get();
852		let era_duration = if cap == 0 || era_duration <= cap {
853			era_duration
854		} else {
855			Pallet::<T>::deposit_event(Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded));
856			log!(
857				warn,
858				"capping era duration for era {:?} from {:?} to max {:?}",
859				ending_era.index,
860				era_duration,
861				cap
862			);
863			cap
864		};
865
866		let staked = ErasTotalStake::<T>::get(ending_era.index);
867		let issuance = asset::total_issuance::<T>();
868		let (validator_payout, remainder) =
869			T::EraPayout::era_payout(staked, issuance, era_duration);
870
871		let total_payout = validator_payout.saturating_add(remainder);
872		let max_staked_rewards = MaxStakedRewards::<T>::get().unwrap_or(Percent::from_percent(100));
873
874		let validator_payout = validator_payout.min(max_staked_rewards * total_payout);
875		let remainder = total_payout.saturating_sub(validator_payout);
876
877		Pallet::<T>::deposit_event(Event::<T>::EraPaid {
878			era_index: ending_era.index,
879			validator_payout,
880			remainder,
881		});
882
883		Eras::<T>::set_stakers_reward(ending_era.index, validator_payout);
884		T::RewardRemainder::on_unbalanced(asset::issue::<T>(remainder));
885	}
886
887	/// DAP end-era: snapshot from general reward pots into era-specific pots.
888	///
889	/// The snapshotted amounts are stored in `ErasValidatorReward` (staker rewards) and
890	/// `ErasValidatorIncentiveBudget` (incentive). Individual payouts draw from the era pots.
891	fn end_era_dap(ending_era: &ActiveEraInfo) {
892		let allocation = reward::EraRewardManager::<T>::snapshot_era_rewards(ending_era.index);
893
894		if allocation.staker_rewards.is_zero() {
895			log!(warn, "Era {:?} has zero staker rewards in general pot", ending_era.index);
896		}
897
898		Eras::<T>::set_stakers_reward(ending_era.index, allocation.staker_rewards);
899		Eras::<T>::set_validator_incentive_budget(ending_era.index, allocation.validator_incentive);
900
901		// Include both staker rewards and validator incentive in the event
902		Pallet::<T>::deposit_event(Event::<T>::EraPaid {
903			era_index: ending_era.index,
904			validator_payout: allocation
905				.staker_rewards
906				.saturating_add(allocation.validator_incentive),
907			remainder: Zero::zero(),
908		});
909
910		if DisableMintingGuard::<T>::get().is_none() {
911			DisableMintingGuard::<T>::put(ending_era.index);
912		}
913	}
914
915	/// Plans a new era by kicking off the election process.
916	///
917	/// The newly planned era is targeted to activate in the next session.
918	fn plan_new_era() {
919		let _ = CurrentEra::<T>::try_mutate(|x| {
920			log!(info, "Planning new era: {:?}, sending election start signal", x.unwrap_or(0));
921			let could_start_election = EraElectionPlanner::<T>::plan_new_election();
922			*x = Some(x.unwrap_or(0) + 1);
923			could_start_election
924		});
925	}
926
927	/// Returns whether we are at the session where we should plan the new era.
928	fn is_plan_era_deadline(start_session: SessionIndex) -> bool {
929		let planning_era_offset = T::PlanningEraOffset::get().min(T::SessionsPerEra::get());
930		// session at which we should plan the new era.
931		let target_plan_era_session = T::SessionsPerEra::get().saturating_sub(planning_era_offset);
932		let era_start_session = Self::active_era_start_session_index();
933
934		// progress of the active era in sessions.
935		let session_progress = start_session.defensive_saturating_sub(era_start_session);
936
937		log!(
938			debug,
939			"Session progress within era: {:?}, target_plan_era_session: {:?}",
940			session_progress,
941			target_plan_era_session
942		);
943		session_progress >= target_plan_era_session
944	}
945}
946
947/// Manager type which collects the election results from [`Config::ElectionProvider`] and
948/// finalizes the planning of a new era.
949///
950/// This type managed 3 storage items:
951///
952/// * [`crate::VoterSnapshotStatus`]
953/// * [`crate::NextElectionPage`]
954/// * [`crate::ElectableStashes`]
955///
956/// A new election is fetched over multiple pages, and finalized upon fetching the last page.
957///
958/// * The intermediate state of fetching the election result is kept in [`NextElectionPage`]. If
959///   `Some(_)` something is ongoing, otherwise not.
960/// * We fully trust [`Config::ElectionProvider`] to give us a full set of validators, with enough
961///   backing after all calls to `maybe_fetch_election_results` are done. Note that older versions
962///   of this pallet had a `MinimumValidatorCount` to double-check this, but we don't check it
963///   anymore.
964/// * `maybe_fetch_election_results` returns a tuple of `(weight, closure)`. The `weight` is the
965///   worst-case weight that `exec` might consume. The caller should check if `weight` fits within
966///   the boundaries of that context, and execute `closure` if so.
967///
968/// TODOs:
969///
970/// * Add a try-state check based on the 3 storage items
971/// * Move snapshot creation functions here as well.
972pub(crate) struct EraElectionPlanner<T: Config>(PhantomData<T>);
973impl<T: Config> EraElectionPlanner<T> {
974	/// Cleanup all associated storage items.
975	pub(crate) fn cleanup() {
976		VoterSnapshotStatus::<T>::kill();
977		NextElectionPage::<T>::kill();
978		ElectableStashes::<T>::kill();
979		Pallet::<T>::register_weight(T::DbWeight::get().writes(3));
980	}
981
982	/// Fetches the number of pages configured by the election provider.
983	pub(crate) fn election_pages() -> u32 {
984		<<T as Config>::ElectionProvider as ElectionProvider>::Pages::get()
985	}
986
987	/// Plan a new election
988	pub(crate) fn plan_new_election() -> Result<(), <T::ElectionProvider as ElectionProvider>::Error>
989	{
990		T::ElectionProvider::start()
991			.inspect_err(|e| log!(warn, "Election provider failed to start: {:?}", e))
992	}
993
994	pub(crate) fn maybe_fetch_election_results() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
995		let Ok(Some(mut required_weight)) = T::ElectionProvider::status() else {
996			// no election ongoing
997			let weight = T::DbWeight::get().reads(1);
998			return (weight, Box::new(move |meter: &mut WeightMeter| meter.consume(weight)));
999		};
1000
1001		// Add a few things to the required weights that are not captured in `do_elect_paged`, which
1002		// is benchmarked via `fetch_page`.
1003		// * 1 extra read and write for `NextElectionPage`
1004		// * 1 extra write for `RcClientInterface::validator_set` (implementation leak -- we assume
1005		//   that we know this writes one storage item under the hood)
1006		// * 1 extra read for `CurrentEra`
1007		// * 1 extra read for `BondedEras` in `get_prune_up_to`
1008		// ElectableStashes already read in `do_elect_paged`
1009		required_weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 2));
1010
1011		let exec = Box::new(move |meter: &mut WeightMeter| {
1012			crate::log!(
1013				debug,
1014				"Election provider is ready, our status is {:?}",
1015				NextElectionPage::<T>::get()
1016			);
1017
1018			debug_assert!(
1019				CurrentEra::<T>::get().unwrap_or(0) ==
1020					ActiveEra::<T>::get().map_or(0, |a| a.index) + 1,
1021				"Next era must be already planned."
1022			);
1023
1024			let current_page = NextElectionPage::<T>::get()
1025				.unwrap_or(Self::election_pages().defensive_saturating_sub(1));
1026			let maybe_next_page = current_page.checked_sub(1);
1027			crate::log!(debug, "fetching page {:?}, next {:?}", current_page, maybe_next_page);
1028
1029			Self::do_elect_paged(current_page);
1030			NextElectionPage::<T>::set(maybe_next_page);
1031
1032			if maybe_next_page.is_none() {
1033				let id = CurrentEra::<T>::get().defensive_unwrap_or(0);
1034				let prune_up_to = Self::get_prune_up_to();
1035				let rc_validators = ElectableStashes::<T>::take().into_iter().collect::<Vec<_>>();
1036
1037				crate::log!(
1038					info,
1039					"Sending new validator set of size {:?} to RC. ID: {:?}, prune_up_to: {:?}",
1040					rc_validators.len(),
1041					id,
1042					prune_up_to
1043				);
1044				T::RcClientInterface::validator_set(rc_validators, id, prune_up_to);
1045			}
1046
1047			// consume the reported worst case weight.
1048			meter.consume(required_weight)
1049		});
1050
1051		(required_weight, exec)
1052	}
1053
1054	/// Get the right value of the first session that needs to be pruned on the RC's historical
1055	/// session pallet.
1056	fn get_prune_up_to() -> Option<SessionIndex> {
1057		let bonded_eras = BondedEras::<T>::get();
1058
1059		// get the first session of the oldest era in the bonded eras.
1060		if bonded_eras.is_full() {
1061			bonded_eras.first().map(|(_, first_session)| first_session.saturating_sub(1))
1062		} else {
1063			None
1064		}
1065	}
1066
1067	/// Paginated elect.
1068	///
1069	/// Fetches the election page with index `page` from the election provider.
1070	///
1071	/// The results from the elect call should be stored in the `ElectableStashes` storage. In
1072	/// addition, it stores stakers' information for next planned era based on the paged
1073	/// solution data returned.
1074	///
1075	/// If any new election winner does not fit in the electable stashes storage, it truncates
1076	/// the result of the election. We ensure that only the winners that are part of the
1077	/// electable stashes have exposures collected for the next era.
1078	pub(crate) fn do_elect_paged(page: PageIndex) {
1079		let election_result = T::ElectionProvider::elect(page);
1080		match election_result {
1081			Ok(supports) => {
1082				let inner_processing_results = Self::do_elect_paged_inner(supports);
1083				if let Err(not_included) = inner_processing_results {
1084					defensive!(
1085						"electable stashes exceeded limit, unexpected but election proceeds.\
1086                		{} stashes from election result discarded",
1087						not_included
1088					);
1089				};
1090
1091				Pallet::<T>::deposit_event(Event::PagedElectionProceeded {
1092					page,
1093					result: inner_processing_results.map(|x| x as u32).map_err(|x| x as u32),
1094				});
1095			},
1096			Err(e) => {
1097				log!(warn, "election provider page failed due to {:?} (page: {})", e, page);
1098				Pallet::<T>::deposit_event(Event::PagedElectionProceeded { page, result: Err(0) });
1099			},
1100		}
1101	}
1102
1103	/// Inner implementation of [`Self::do_elect_paged`].
1104	///
1105	/// Returns an error if adding election winners to the electable stashes storage fails due
1106	/// to exceeded bounds. In case of error, it returns the index of the first stash that
1107	/// failed to be included.
1108	pub(crate) fn do_elect_paged_inner(
1109		mut supports: BoundedSupportsOf<T::ElectionProvider>,
1110	) -> Result<usize, usize> {
1111		let planning_era = Rotator::<T>::planned_era();
1112
1113		match Self::add_electables(supports.iter().map(|(s, _)| s.clone())) {
1114			Ok(added) => {
1115				let exposures = Self::collect_exposures(supports);
1116				let _ = Self::store_stakers_info(exposures, planning_era);
1117				Ok(added)
1118			},
1119			Err(not_included_idx) => {
1120				let not_included = supports.len().saturating_sub(not_included_idx);
1121
1122				log!(
1123					warn,
1124					"not all winners fit within the electable stashes, excluding {:?} accounts from solution.",
1125					not_included,
1126				);
1127
1128				// filter out supports of stashes that do not fit within the electable stashes
1129				// storage bounds to prevent collecting their exposures.
1130				supports.truncate(not_included_idx);
1131				let exposures = Self::collect_exposures(supports);
1132				let _ = Self::store_stakers_info(exposures, planning_era);
1133
1134				Err(not_included)
1135			},
1136		}
1137	}
1138
1139	/// Process the output of a paged election.
1140	///
1141	/// Store staking information for the new planned era of a single election page.
1142	pub(crate) fn store_stakers_info(
1143		exposures: BoundedExposuresOf<T>,
1144		new_planned_era: EraIndex,
1145	) -> BoundedVec<T::AccountId, MaxWinnersPerPageOf<T::ElectionProvider>> {
1146		// populate elected stash, stakers, exposures, and the snapshot of validator prefs.
1147		let mut total_stake_page: BalanceOf<T> = Zero::zero();
1148		let mut elected_stashes_page = Vec::with_capacity(exposures.len());
1149		let mut total_backers = 0u32;
1150
1151		let mut total_incentive_weight_page: BalanceOf<T> = Zero::zero();
1152
1153		exposures.into_iter().for_each(|(stash, exposure)| {
1154			log!(
1155				trace,
1156				"storing exposure for stash {:?} with {:?} own-stake and {:?} backers",
1157				stash,
1158				exposure.own,
1159				exposure.others.len()
1160			);
1161			// build elected stash.
1162			elected_stashes_page.push(stash.clone());
1163			// accumulate total stake and backer count for bookkeeping.
1164			total_stake_page = total_stake_page.saturating_add(exposure.total);
1165			total_backers += exposure.others.len() as u32;
1166			let own = exposure.own;
1167			Eras::<T>::upsert_exposure(new_planned_era, &stash, exposure);
1168
1169			// Calculate incentive weight from own-stake. Own-stake appears only on the
1170			// first page of a multi-page exposure, so if the key already exists with a
1171			// non-zero own, something is wrong upstream.
1172			if !own.is_zero() {
1173				if ErasValidatorIncentiveWeight::<T>::contains_key(new_planned_era, &stash) {
1174					defensive!(
1175						"validator own-stake seen twice in the same era across election pages"
1176					);
1177				} else {
1178					let incentive_weight =
1179						T::StakerRewardCalculator::calculate_validator_incentive_weight(own);
1180					if !incentive_weight.is_zero() {
1181						total_incentive_weight_page =
1182							total_incentive_weight_page.saturating_add(incentive_weight);
1183						ErasValidatorIncentiveWeight::<T>::insert(
1184							new_planned_era,
1185							&stash,
1186							incentive_weight,
1187						);
1188					}
1189				}
1190			}
1191		});
1192
1193		let elected_stashes: BoundedVec<_, MaxWinnersPerPageOf<T::ElectionProvider>> =
1194			elected_stashes_page
1195				.try_into()
1196				.expect("both types are bounded by MaxWinnersPerPageOf; qed");
1197
1198		// adds to total stake in this era.
1199		Eras::<T>::add_total_stake(new_planned_era, total_stake_page);
1200
1201		// adds to total validator self-stake weight for incentive distribution.
1202		Eras::<T>::add_sum_validator_incentive_weight(new_planned_era, total_incentive_weight_page);
1203
1204		// collect or update the pref of all winners.
1205		// TODO: rather inefficient, we can do this once at the last page across all entries in
1206		// `ElectableStashes`.
1207		for stash in &elected_stashes {
1208			let pref = Validators::<T>::get(stash);
1209			Eras::<T>::set_validator_prefs(new_planned_era, stash, pref);
1210		}
1211
1212		log!(
1213			debug,
1214			"stored a page of stakers with {:?} validators and {:?} total backers for era {:?}",
1215			elected_stashes.len(),
1216			total_backers,
1217			new_planned_era,
1218		);
1219
1220		elected_stashes
1221	}
1222
1223	/// Consume a set of [`BoundedSupports`] from [`sp_npos_elections`] and collect them into a
1224	/// [`Exposure`].
1225	///
1226	/// Returns vec of all the exposures of a validator in `paged_supports`, bounded by the
1227	/// number of max winners per page returned by the election provider.
1228	fn collect_exposures(
1229		supports: BoundedSupportsOf<T::ElectionProvider>,
1230	) -> BoundedExposuresOf<T> {
1231		let total_issuance = asset::total_issuance::<T>();
1232		let to_currency = |e: frame_election_provider_support::ExtendedBalance| {
1233			T::CurrencyToVote::to_currency(e, total_issuance)
1234		};
1235
1236		supports
1237			.into_iter()
1238			.map(|(validator, support)| {
1239				// Build `struct exposure` from `support`.
1240				let mut others = Vec::with_capacity(support.voters.len());
1241				let mut own: BalanceOf<T> = Zero::zero();
1242				let mut total: BalanceOf<T> = Zero::zero();
1243				support
1244					.voters
1245					.into_iter()
1246					.map(|(nominator, weight)| (nominator, to_currency(weight)))
1247					.for_each(|(nominator, stake)| {
1248						if nominator == validator {
1249							defensive_assert!(own == Zero::zero(), "own stake should be unique");
1250							own = own.saturating_add(stake);
1251						} else {
1252							others.push(IndividualExposure { who: nominator, value: stake });
1253						}
1254						total = total.saturating_add(stake);
1255					});
1256
1257				let exposure = Exposure { own, others, total };
1258				(validator, exposure)
1259			})
1260			.try_collect()
1261			.expect("we only map through support vector which cannot change the size; qed")
1262	}
1263
1264	/// Adds a new set of stashes to the electable stashes.
1265	///
1266	/// Returns:
1267	///
1268	/// `Ok(newly_added)` if all stashes were added successfully.
1269	/// `Err(first_un_included)` if some stashes cannot be added due to bounds.
1270	pub(crate) fn add_electables(
1271		new_stashes: impl Iterator<Item = T::AccountId>,
1272	) -> Result<usize, usize> {
1273		ElectableStashes::<T>::mutate(|electable| {
1274			let pre_size = electable.len();
1275
1276			for (idx, stash) in new_stashes.enumerate() {
1277				if electable.try_insert(stash).is_err() {
1278					return Err(idx);
1279				}
1280			}
1281
1282			Ok(electable.len() - pre_size)
1283		})
1284	}
1285}