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