1use 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
94pub 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 pub(crate) fn get_validator_commission(era: EraIndex, stash: &T::AccountId) -> Perbill {
119 Self::get_validator_prefs(era, stash).commission
120 }
121
122 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 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 let validator_stake = if page == 0 { overview.own } else { Zero::zero() };
147
148 let exposure_page = <ErasStakersPaged<T>>::get((era, validator, page)).unwrap_or_default();
151
152 Some(PagedExposure {
154 exposure_metadata: PagedExposureMetadata { own: validator_stake, ..overview },
155 exposure_page: exposure_page.into(),
156 })
157 }
158
159 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 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 1
187 } else {
188 overview.page_count
189 }
190 })
191 .unwrap_or(1)
194 }
195
196 pub(crate) fn get_next_claimable_page(era: EraIndex, validator: &T::AccountId) -> Option<Page> {
198 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 pub(crate) fn are_nominators_slashable(era: EraIndex) -> bool {
212 ErasNominatorsSlashable::<T>::get(era).unwrap_or(true)
213 }
214
215 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 if claimed_pages.contains(&page) {
222 defensive!("Trying to set an already claimed reward");
223 return;
225 }
226
227 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 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 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 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 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 match (updated_overview.own.is_zero(), exposure.own.is_zero()) {
277 (true, false) => {
278 updated_overview.own = exposure.own;
281 },
282 (true, true) | (false, true) => {
283 },
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 exposure.total = exposure.total.saturating_sub(exposure.own);
311 exposure.own = Zero::zero();
312
313 let append_to_last_page = exposure.split_others(last_page_empty_slots);
317 let put_in_new_pages = exposure;
318
319 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 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 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 let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size);
350 defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count");
351
352 ErasStakersOverview::<T>::insert(era, &validator, exposure_metadata);
354
355 LastValidatorEra::<T>::insert(validator, era);
357
358 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 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 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 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 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 pub(crate) fn era_fully_present(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
435 let e0 = ErasValidatorPrefs::<T>::iter_prefix_values(era).count() != 0;
437 let e1 = ErasStakersOverview::<T>::iter_prefix_values(era).count() != 0;
439 ensure!(e0 == e1, "ErasValidatorPrefs and ErasStakersOverview should be consistent");
440
441 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::<T>::contains_key(era.saturating_sub(1))
452 } else {
453 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 pub(crate) fn era_pruning_in_progress(era: EraIndex) -> bool {
468 EraPruningState::<T>::contains_key(era)
469 }
470
471 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 pub(crate) fn era_absent(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
483 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 let e3 = ErasValidatorReward::<T>::contains_key(era);
491 let e4 = ErasTotalStake::<T>::contains_key(era);
492
493 let e6 = ClaimedRewards::<T>::iter_prefix_values(era).count() != 0;
495 let e7 = ErasRewardPoints::<T>::contains_key(era);
496
497 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 let active_era = Rotator::<T>::active_era();
512 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!(
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 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
551pub 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 Self::plan_new_era();
566 <<T as Config>::ElectionProvider as ElectionProvider>::asap();
568 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 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 ensure!(bonded.is_empty(), "BondedEras must be empty when ActiveEra is None");
591 },
592 (Some(active), Some(planned)) => {
593 ensure!(
595 *planned == active.index || *planned == active.index + 1,
596 "planned era is always equal or one more than active"
597 );
598
599 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 let oldest_allowed_era = active.index.saturating_sub(T::HistoryDepth::get()).max(1);
614 for (era, _) in ErasNominatorsSlashable::<T>::iter() {
615 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 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 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 pub(crate) fn end_session(
669 end_index: SessionIndex,
670 activation_timestamp: Option<(u64, u32)>,
671 ) -> Weight {
672 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 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 Self::start_era(active_era, starting, time);
698 },
699 Some((_time, id)) => {
700 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 let should_plan_era = match ForceEra::<T>::get() {
716 Forcing::NotForcing => Self::is_plan_era_deadline(starting),
718 Forcing::ForceNew => {
720 ForceEra::<T>::put(Forcing::NotForcing);
721 true
722 },
723 Forcing::ForceAlways => true,
725 Forcing::ForceNone => false,
727 };
728
729 let has_pending_era = Self::is_planning().is_some();
732 match (should_plan_era, has_pending_era) {
733 (false, _) => {
734 },
736 (true, false) => {
737 Self::plan_new_era();
739 },
740 (true, true) => {
741 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 debug_assert!(CurrentEra::<T>::get().unwrap_or(0) == ending_era.index + 1);
767
768 let starting_era = ending_era.index + 1;
769
770 Self::end_era(&ending_era, new_era_start_timestamp);
772
773 Self::start_era_inc_active_era(new_era_start_timestamp);
775 Self::start_era_update_bonded_eras(starting_era, starting_session);
776
777 ErasNominatorsSlashable::<T>::insert(starting_era, AreNominatorsSlashable::<T>::get());
780
781 EraElectionPlanner::<T>::cleanup();
783
784 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 pub fn active_era_start_session_index() -> SessionIndex {
809 Self::era_start_session_index(Self::active_era()).defensive_unwrap_or(0)
810 }
811
812 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 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 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 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 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 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 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 fn is_plan_era_deadline(start_session: SessionIndex) -> bool {
929 let planning_era_offset = T::PlanningEraOffset::get().min(T::SessionsPerEra::get());
930 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 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
947pub(crate) struct EraElectionPlanner<T: Config>(PhantomData<T>);
973impl<T: Config> EraElectionPlanner<T> {
974 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 pub(crate) fn election_pages() -> u32 {
984 <<T as Config>::ElectionProvider as ElectionProvider>::Pages::get()
985 }
986
987 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 let weight = T::DbWeight::get().reads(1);
998 return (weight, Box::new(move |meter: &mut WeightMeter| meter.consume(weight)));
999 };
1000
1001 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 meter.consume(required_weight)
1049 });
1050
1051 (required_weight, exec)
1052 }
1053
1054 fn get_prune_up_to() -> Option<SessionIndex> {
1057 let bonded_eras = BondedEras::<T>::get();
1058
1059 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 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 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 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 pub(crate) fn store_stakers_info(
1143 exposures: BoundedExposuresOf<T>,
1144 new_planned_era: EraIndex,
1145 ) -> BoundedVec<T::AccountId, MaxWinnersPerPageOf<T::ElectionProvider>> {
1146 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 elected_stashes_page.push(stash.clone());
1163 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 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 Eras::<T>::add_total_stake(new_planned_era, total_stake_page);
1200
1201 Eras::<T>::add_sum_validator_incentive_weight(new_planned_era, total_incentive_weight_page);
1203
1204 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 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 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 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}