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 was_validator_exposed(era: EraIndex, validator: &T::AccountId) -> bool {
198 <ErasStakersOverview<T>>::contains_key(era, validator)
199 }
200
201 pub(crate) fn get_next_claimable_page(era: EraIndex, validator: &T::AccountId) -> Option<Page> {
203 let page_count = Self::exposure_page_count(era, validator);
205 let all_claimable_pages: Vec<Page> = (0..page_count).collect();
206 let claimed_pages = ClaimedRewards::<T>::get(era, validator);
207
208 all_claimable_pages.into_iter().find(|p| !claimed_pages.contains(p))
209 }
210
211 pub(crate) fn are_nominators_slashable(era: EraIndex) -> bool {
217 ErasNominatorsSlashable::<T>::get(era).unwrap_or(true)
218 }
219
220 pub(crate) fn set_rewards_as_claimed(era: EraIndex, validator: &T::AccountId, page: Page) {
223 let mut claimed_pages = ClaimedRewards::<T>::get(era, validator).into_inner();
224
225 if claimed_pages.contains(&page) {
227 defensive!("Trying to set an already claimed reward");
228 return;
230 }
231
232 claimed_pages.push(page);
234 ClaimedRewards::<T>::insert(
235 era,
236 validator,
237 WeakBoundedVec::<_, _>::force_from(claimed_pages, Some("set_rewards_as_claimed")),
238 );
239 }
240
241 pub fn upsert_exposure(
248 era: EraIndex,
249 validator: &T::AccountId,
250 mut exposure: Exposure<T::AccountId, BalanceOf<T>>,
251 ) {
252 let page_size = T::MaxExposurePageSize::get().defensive_max(1);
253 if cfg!(debug_assertions) && cfg!(not(feature = "runtime-benchmarks")) {
254 let expected_total = exposure
257 .others
258 .iter()
259 .map(|ie| ie.value)
260 .fold::<BalanceOf<T>, _>(Default::default(), |acc, x| acc + x)
261 .saturating_add(exposure.own);
262 debug_assert_eq!(expected_total, exposure.total, "exposure total must equal own + sum(others) for (era: {:?}, validator: {:?}, exposure: {:?})", era, validator, exposure);
263 }
264
265 if let Some(overview) = ErasStakersOverview::<T>::get(era, &validator) {
266 let last_page_idx = overview.page_count.saturating_sub(1);
268 let mut last_page =
269 ErasStakersPaged::<T>::get((era, validator, last_page_idx)).unwrap_or_default();
270 let last_page_empty_slots =
271 T::MaxExposurePageSize::get().saturating_sub(last_page.others.len() as u32);
272
273 let new_stake_added = exposure.total;
276 let new_nominators_added = exposure.others.len() as u32;
277 let mut updated_overview = overview
278 .update_with::<T::MaxExposurePageSize>(new_stake_added, new_nominators_added);
279
280 match (updated_overview.own.is_zero(), exposure.own.is_zero()) {
282 (true, false) => {
283 updated_overview.own = exposure.own;
286 },
287 (true, true) | (false, true) => {
288 },
290 (false, false) => {
291 debug_assert!(
292 false,
293 "validator own stake already set in overview for (era: {:?}, validator: {:?}, current overview: {:?}, new exposure: {:?})",
294 era,
295 validator,
296 updated_overview,
297 exposure,
298 );
299 defensive!("duplicate validator self stake in election");
300 },
301 };
302
303 ErasStakersOverview::<T>::insert(era, &validator, updated_overview);
304 exposure.total = exposure.total.saturating_sub(exposure.own);
316 exposure.own = Zero::zero();
317
318 let append_to_last_page = exposure.split_others(last_page_empty_slots);
322 let put_in_new_pages = exposure;
323
324 last_page.page_total = last_page.page_total.saturating_add(append_to_last_page.total);
328 last_page.others.extend(append_to_last_page.others);
329 ErasStakersPaged::<T>::insert((era, &validator, last_page_idx), last_page);
330
331 let (_unused_metadata, put_in_new_pages_chunks) =
334 put_in_new_pages.into_pages(page_size);
335
336 put_in_new_pages_chunks
337 .into_iter()
338 .enumerate()
339 .for_each(|(idx, paged_exposure)| {
340 let append_at =
341 (last_page_idx.saturating_add(1).saturating_add(idx as u32)) as Page;
342 <ErasStakersPaged<T>>::insert((era, &validator, append_at), paged_exposure);
343 });
344 } else {
345 let expected_page_count = exposure
347 .others
348 .len()
349 .defensive_saturating_add((page_size as usize).defensive_saturating_sub(1))
350 .saturating_div(page_size as usize);
351
352 let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size);
355 defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count");
356
357 ErasStakersOverview::<T>::insert(era, &validator, exposure_metadata);
359
360 LastValidatorEra::<T>::insert(validator, era);
362
363 exposure_pages.into_iter().enumerate().for_each(|(idx, paged_exposure)| {
365 let append_at = idx as Page;
366 <ErasStakersPaged<T>>::insert((era, &validator, append_at), paged_exposure);
367 });
368 };
369 }
370
371 pub(crate) fn set_stakers_reward(era: EraIndex, amount: BalanceOf<T>) {
372 ErasValidatorReward::<T>::insert(era, amount);
373 }
374
375 pub(crate) fn get_stakers_reward(era: EraIndex) -> Option<BalanceOf<T>> {
376 ErasValidatorReward::<T>::get(era)
377 }
378
379 pub(crate) fn set_validator_incentive_budget(era: EraIndex, amount: BalanceOf<T>) {
380 ErasValidatorIncentiveBudget::<T>::insert(era, amount);
381 }
382
383 pub(crate) fn get_validator_incentive_budget(era: EraIndex) -> BalanceOf<T> {
384 ErasValidatorIncentiveBudget::<T>::get(era)
385 }
386
387 pub(crate) fn add_sum_validator_incentive_weight(
388 era: EraIndex,
389 incentive_weight: BalanceOf<T>,
390 ) {
391 <ErasSumValidatorIncentiveWeight<T>>::mutate(era, |sum| {
392 *sum = sum.saturating_add(incentive_weight);
393 });
394 }
395
396 pub(crate) fn add_total_stake(era: EraIndex, stake: BalanceOf<T>) {
398 <ErasTotalStake<T>>::mutate(era, |total_stake| {
399 *total_stake += stake;
400 });
401 }
402
403 pub(crate) fn is_rewards_claimed(era: EraIndex, validator: &T::AccountId, page: Page) -> bool {
405 ClaimedRewards::<T>::get(era, validator).contains(&page)
406 }
407
408 pub(crate) fn reward_active_era(
415 validators_points: impl IntoIterator<Item = (T::AccountId, u32)>,
416 ) {
417 if let Some(active_era) = ActiveEra::<T>::get() {
418 let mut sum_weighted_points_delta: BalanceOf<T> = Zero::zero();
419 <ErasRewardPoints<T>>::mutate(active_era.index, |era_rewards| {
420 for (validator, points) in validators_points.into_iter() {
421 let weight =
422 ErasValidatorIncentiveWeight::<T>::get(active_era.index, &validator)
423 .unwrap_or_else(Zero::zero);
424
425 let recorded = match era_rewards.individual.get_mut(&validator) {
426 Some(individual) => {
427 individual.saturating_accrue(points);
428 true
429 },
430 None => {
431 era_rewards.individual.try_insert(validator, points).defensive().is_ok()
434 },
435 };
436
437 if recorded && !weight.is_zero() {
442 sum_weighted_points_delta = sum_weighted_points_delta.saturating_add(
443 weight.saturating_mul(IncentiveWeight::<T>::from(points)),
444 );
445 }
446
447 era_rewards.total.saturating_accrue(points);
448 }
449 });
450 if !sum_weighted_points_delta.is_zero() {
451 ErasSumWeightedPoints::<T>::mutate(active_era.index, |sum| {
452 *sum = sum.saturating_add(sum_weighted_points_delta);
453 });
454 }
455 }
456 }
457
458 pub(crate) fn get_reward_points(era: EraIndex) -> EraRewardPoints<T> {
459 ErasRewardPoints::<T>::get(era)
460 }
461
462 pub(crate) fn get_reward_points_for_validator(
463 era: EraIndex,
464 validator: &T::AccountId,
465 ) -> RewardPoint {
466 let points = ErasRewardPoints::<T>::get(era);
467 points.individual.get(validator).copied().unwrap_or_default()
468 }
469
470 pub(crate) fn uses_weighted_points(era: EraIndex) -> bool {
486 crate::WeightedPointsFormulaStartEra::<T>::get().map_or(true, |start| era >= start)
487 }
488}
489
490#[cfg(any(feature = "try-runtime", test, feature = "runtime-benchmarks"))]
491#[allow(unused)]
492impl<T: Config> Eras<T> {
493 pub(crate) fn era_fully_present(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
495 let e0 = ErasValidatorPrefs::<T>::iter_prefix_values(era).count() != 0;
497 let e1 = ErasStakersOverview::<T>::iter_prefix_values(era).count() != 0;
499 ensure!(e0 == e1, "ErasValidatorPrefs and ErasStakersOverview should be consistent");
500
501 let e2 = ErasTotalStake::<T>::contains_key(era);
503
504 let active_era = Rotator::<T>::active_era();
505 let e4 = if era.saturating_sub(1) > 0 &&
506 era.saturating_sub(1) > active_era.saturating_sub(T::HistoryDepth::get() + 1)
507 {
508 ErasValidatorReward::<T>::contains_key(era.saturating_sub(1))
512 } else {
513 e2
515 };
516
517 ensure!(e2 == e4, "era info presence not consistent");
518
519 if e2 {
520 Ok(())
521 } else {
522 Err("era presence mismatch".into())
523 }
524 }
525
526 pub(crate) fn era_pruning_in_progress(era: EraIndex) -> bool {
528 EraPruningState::<T>::contains_key(era)
529 }
530
531 pub(crate) fn era_absent_or_pruning(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
533 if Self::era_pruning_in_progress(era) {
534 Ok(())
535 } else {
536 Self::era_absent(era)
537 }
538 }
539
540 pub(crate) fn era_absent(era: EraIndex) -> Result<(), sp_runtime::TryRuntimeError> {
543 let e0 = ErasValidatorPrefs::<T>::iter_prefix_values(era).count() != 0;
545 let e1 = ErasStakersPaged::<T>::iter_prefix_values((era,)).count() != 0;
546 let e2 = ErasStakersOverview::<T>::iter_prefix_values(era).count() != 0;
547
548 let e3 = ErasValidatorReward::<T>::contains_key(era);
551 let e4 = ErasTotalStake::<T>::contains_key(era);
552
553 let e6 = ClaimedRewards::<T>::iter_prefix_values(era).count() != 0;
555 let e7 = ErasRewardPoints::<T>::contains_key(era);
556
557 if !vec![e0, e1, e2, e3, e4, e6, e7].windows(2).all(|w| w[0] == w[1]) {
559 return Err("era info absence not consistent - partial pruning state".into());
560 }
561
562 if !e0 {
563 Ok(())
564 } else {
565 Err("era absence mismatch".into())
566 }
567 }
568
569 pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
570 let active_era = Rotator::<T>::active_era();
572 let oldest_present_era = active_era.saturating_sub(T::HistoryDepth::get()).max(1);
575
576 for e in oldest_present_era..=active_era {
577 Self::era_fully_present(e)?;
578 Self::check_validator_incentive_weight_consistency(e)?;
579 if Self::uses_weighted_points(e) {
584 Self::check_sum_weighted_points_consistency(e)?;
585 }
586 }
587
588 ensure!(
591 (1..oldest_present_era).all(|e| Self::era_absent_or_pruning(e).is_ok()),
592 "All old eras must be either fully pruned or marked for pruning"
593 );
594
595 Ok(())
596 }
597
598 fn check_validator_incentive_weight_consistency(
600 era: EraIndex,
601 ) -> Result<(), sp_runtime::TryRuntimeError> {
602 use sp_runtime::traits::Zero;
603
604 let stored_total = ErasSumValidatorIncentiveWeight::<T>::get(era);
605 let computed_total: BalanceOf<T> = ErasValidatorIncentiveWeight::<T>::iter_prefix(era)
606 .fold(BalanceOf::<T>::zero(), |acc, (_, w)| acc.saturating_add(w));
607
608 ensure!(
609 stored_total == computed_total,
610 "ErasSumValidatorIncentiveWeight mismatch: \
611 stored vs computed individual weights do not match"
612 );
613
614 Ok(())
615 }
616
617 fn check_sum_weighted_points_consistency(
620 era: EraIndex,
621 ) -> Result<(), sp_runtime::TryRuntimeError> {
622 use sp_runtime::traits::Zero;
623
624 let stored = ErasSumWeightedPoints::<T>::get(era);
625 let reward_points = ErasRewardPoints::<T>::get(era);
626 let computed: BalanceOf<T> =
627 reward_points.individual.iter().fold(BalanceOf::<T>::zero(), |acc, (v, &ep)| {
628 let weight =
629 ErasValidatorIncentiveWeight::<T>::get(era, v).unwrap_or_else(Zero::zero);
630 acc.saturating_add(weight.saturating_mul(BalanceOf::<T>::from(ep)))
631 });
632
633 ensure!(
634 stored == computed,
635 "ErasSumWeightedPoints mismatch: \
636 stored vs computed (Σ weight · era_points) do not match"
637 );
638
639 Ok(())
640 }
641}
642
643pub struct Rotator<T: Config>(core::marker::PhantomData<T>);
652
653impl<T: Config> Rotator<T> {
654 #[cfg(feature = "runtime-benchmarks")]
655 pub(crate) fn legacy_insta_plan_era() -> Vec<T::AccountId> {
656 Self::plan_new_era();
658 <<T as Config>::ElectionProvider as ElectionProvider>::asap();
660 let msp = <T::ElectionProvider as ElectionProvider>::msp();
663 let lsp = 0;
664 for p in (lsp..=msp).rev() {
665 EraElectionPlanner::<T>::do_elect_paged(p);
666 }
667
668 crate::ElectableStashes::<T>::take().into_iter().collect()
669 }
670
671 #[cfg(any(feature = "try-runtime", test))]
672 pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
673 let active_era = ActiveEra::<T>::get();
675 let planned_era = CurrentEra::<T>::get();
676
677 let bonded = BondedEras::<T>::get();
678
679 match (&active_era, &planned_era) {
680 (None, None) => {
681 ensure!(bonded.is_empty(), "BondedEras must be empty when ActiveEra is None");
683 },
684 (Some(active), Some(planned)) => {
685 ensure!(
687 *planned == active.index || *planned == active.index + 1,
688 "planned era is always equal or one more than active"
689 );
690
691 let bonded_eras: Vec<_> = bonded.iter().map(|(era, _sess)| *era).collect();
694 ensure!(
695 bonded_eras ==
696 (active.index.saturating_sub(T::BondingDuration::get())..=active.index)
697 .collect::<Vec<_>>(),
698 "BondedEras range incorrect"
699 );
700
701 let oldest_allowed_era = active.index.saturating_sub(T::HistoryDepth::get()).max(1);
706 for (era, _) in ErasNominatorsSlashable::<T>::iter() {
707 let being_pruned = EraPruningState::<T>::contains_key(era);
709 ensure!(
710 (era >= oldest_allowed_era && era <= active.index) || being_pruned,
711 "ErasNominatorsSlashable entry exists for era outside history depth range and not being pruned"
712 );
713 }
714 },
715 _ => {
716 ensure!(false, "ActiveEra and CurrentEra must both be None or both be Some");
717 },
718 }
719
720 Ok(())
721 }
722
723 #[cfg(any(feature = "try-runtime", feature = "std", feature = "runtime-benchmarks", test))]
724 pub fn assert_election_ongoing() {
725 assert!(Self::is_planning().is_some(), "planning era must exist");
726 assert!(
727 T::ElectionProvider::status().is_ok(),
728 "Election provider must be in a good state during election"
729 );
730 }
731
732 pub fn planned_era() -> EraIndex {
740 CurrentEra::<T>::get().unwrap_or(0)
741 }
742
743 pub fn active_era() -> EraIndex {
744 ActiveEra::<T>::get().map(|a| a.index).defensive_unwrap_or(0)
745 }
746
747 pub fn is_planning() -> Option<EraIndex> {
751 let (active, planned) = (Self::active_era(), Self::planned_era());
752 if planned.defensive_saturating_sub(active) > 1 {
753 defensive!("planned era must always be equal or one more than active");
754 }
755
756 (planned > active).then_some(planned)
757 }
758
759 pub(crate) fn end_session(
761 end_index: SessionIndex,
762 activation_timestamp: Option<(u64, u32)>,
763 rewarded_validators: u32,
764 ) -> Weight {
765 let weight = T::WeightInfo::rc_on_session_report(rewarded_validators);
767
768 let Some(active_era) = ActiveEra::<T>::get() else {
769 defensive!("Active era must always be available.");
770 return weight;
771 };
772 let current_planned_era = Self::is_planning();
773 let starting = end_index + 1;
774 let planning = starting + 1;
776
777 log!(
778 info,
779 "Session: end {:?}, start {:?} (ts: {:?}), planning {:?}",
780 end_index,
781 starting,
782 activation_timestamp,
783 planning
784 );
785 log!(info, "Era: active {:?}, planned {:?}", active_era.index, current_planned_era);
786
787 match activation_timestamp {
788 Some((time, id)) if Some(id) == current_planned_era => {
789 Self::start_era(active_era, starting, time);
791 },
792 Some((_time, id)) => {
793 crate::log!(
795 warn,
796 "received wrong ID with activation timestamp. Got {}, expected {:?}",
797 id,
798 current_planned_era
799 );
800 Pallet::<T>::deposit_event(Event::Unexpected(
801 UnexpectedKind::UnknownValidatorActivation,
802 ));
803 },
804 None => (),
805 }
806
807 let should_plan_era = match ForceEra::<T>::get() {
809 Forcing::NotForcing => Self::is_plan_era_deadline(starting),
811 Forcing::ForceNew => {
813 ForceEra::<T>::put(Forcing::NotForcing);
814 true
815 },
816 Forcing::ForceAlways => true,
818 Forcing::ForceNone => false,
820 };
821
822 let has_pending_era = Self::is_planning().is_some();
825 match (should_plan_era, has_pending_era) {
826 (false, _) => {
827 },
829 (true, false) => {
830 Self::plan_new_era();
832 },
833 (true, true) => {
834 crate::log!(
837 debug,
838 "time to plan a new era {:?}, but waiting for the activation of the previous.",
839 current_planned_era
840 );
841 },
842 }
843
844 Pallet::<T>::deposit_event(Event::SessionRotated {
845 starting_session: starting,
846 active_era: Self::active_era(),
847 planned_era: Self::planned_era(),
848 });
849
850 weight
851 }
852
853 pub(crate) fn start_era(
854 ending_era: ActiveEraInfo,
855 starting_session: SessionIndex,
856 new_era_start_timestamp: u64,
857 ) {
858 debug_assert!(CurrentEra::<T>::get().unwrap_or(0) == ending_era.index + 1);
860
861 let starting_era = ending_era.index + 1;
862
863 Self::end_era(&ending_era, new_era_start_timestamp);
865
866 Self::start_era_inc_active_era(new_era_start_timestamp);
868 Self::start_era_update_bonded_eras(starting_era, starting_session);
869
870 ErasNominatorsSlashable::<T>::insert(starting_era, AreNominatorsSlashable::<T>::get());
873
874 EraElectionPlanner::<T>::cleanup();
876
877 if let Some(old_era) = starting_era.checked_sub(T::HistoryDepth::get() + 1) {
879 reward::EraRewardManager::<T>::cleanup_era(old_era);
880 log!(debug, "Marking era {:?} for lazy pruning", old_era);
881 EraPruningState::<T>::insert(old_era, PruningStep::ErasStakersPaged);
882 }
883 }
884
885 fn start_era_inc_active_era(start_timestamp: u64) {
886 ActiveEra::<T>::mutate(|active_era| {
887 let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0);
888 log!(
889 debug,
890 "starting active era {:?} with RC-provided timestamp {:?}",
891 new_index,
892 start_timestamp
893 );
894 *active_era = Some(ActiveEraInfo { index: new_index, start: Some(start_timestamp) });
895 });
896 }
897
898 pub fn active_era_start_session_index() -> SessionIndex {
902 Self::era_start_session_index(Self::active_era()).defensive_unwrap_or(0)
903 }
904
905 pub fn era_start_session_index(era: EraIndex) -> Option<SessionIndex> {
907 BondedEras::<T>::get()
908 .into_iter()
909 .rev()
910 .find_map(|(e, s)| if e == era { Some(s) } else { None })
911 }
912
913 fn start_era_update_bonded_eras(starting_era: EraIndex, start_session: SessionIndex) {
914 let bonding_duration = T::BondingDuration::get();
915
916 BondedEras::<T>::mutate(|bonded| {
917 if bonded.is_full() {
918 let (era_removed, _) = bonded.remove(0);
920 debug_assert!(
921 era_removed <= (starting_era.saturating_sub(bonding_duration)),
922 "should not delete an era that is not older than bonding duration"
923 );
924 }
925
926 let _ = bonded.try_push((starting_era, start_session)).defensive();
928 });
929 }
930
931 fn end_era(ending_era: &ActiveEraInfo, new_era_start: u64) {
932 if T::DisableMinting::get() {
933 Self::end_era_dap(ending_era);
934 } else {
935 Self::end_era_legacy(ending_era, new_era_start);
936 }
937 }
938
939 fn end_era_legacy(ending_era: &ActiveEraInfo, new_era_start: u64) {
941 let previous_era_start = ending_era.start.defensive_unwrap_or(new_era_start);
942 let era_duration = new_era_start.saturating_sub(previous_era_start);
943
944 let cap = T::MaxEraDuration::get();
945 let era_duration = if cap == 0 || era_duration <= cap {
946 era_duration
947 } else {
948 Pallet::<T>::deposit_event(Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded));
949 log!(
950 warn,
951 "capping era duration for era {:?} from {:?} to max {:?}",
952 ending_era.index,
953 era_duration,
954 cap
955 );
956 cap
957 };
958
959 let staked = ErasTotalStake::<T>::get(ending_era.index);
960 let issuance = asset::total_issuance::<T>();
961 let (validator_payout, remainder) =
962 T::EraPayout::era_payout(staked, issuance, era_duration);
963
964 let total_payout = validator_payout.saturating_add(remainder);
965 let max_staked_rewards = MaxStakedRewards::<T>::get().unwrap_or(Percent::from_percent(100));
966
967 let validator_payout = validator_payout.min(max_staked_rewards * total_payout);
968 let remainder = total_payout.saturating_sub(validator_payout);
969
970 Pallet::<T>::deposit_event(Event::<T>::EraPaid {
971 era_index: ending_era.index,
972 validator_payout,
973 remainder,
974 });
975
976 Eras::<T>::set_stakers_reward(ending_era.index, validator_payout);
977 T::RewardRemainder::on_unbalanced(asset::issue::<T>(remainder));
978 }
979
980 fn end_era_dap(ending_era: &ActiveEraInfo) {
985 let allocation = reward::EraRewardManager::<T>::snapshot_era_rewards(ending_era.index);
986
987 if allocation.staker_rewards.is_zero() {
988 log!(warn, "Era {:?} has zero staker rewards in general pot", ending_era.index);
989 }
990
991 Eras::<T>::set_stakers_reward(ending_era.index, allocation.staker_rewards);
992 Eras::<T>::set_validator_incentive_budget(ending_era.index, allocation.validator_incentive);
993
994 Pallet::<T>::deposit_event(Event::<T>::EraPaid {
996 era_index: ending_era.index,
997 validator_payout: allocation
998 .staker_rewards
999 .saturating_add(allocation.validator_incentive),
1000 remainder: Zero::zero(),
1001 });
1002
1003 if DisableMintingGuard::<T>::get().is_none() {
1004 DisableMintingGuard::<T>::put(ending_era.index);
1005 }
1006 }
1007
1008 fn plan_new_era() {
1012 let _ = CurrentEra::<T>::try_mutate(|x| {
1013 log!(info, "Planning new era: {:?}, sending election start signal", x.unwrap_or(0));
1014 let could_start_election = EraElectionPlanner::<T>::plan_new_election();
1015 *x = Some(x.unwrap_or(0) + 1);
1016 could_start_election
1017 });
1018 }
1019
1020 fn is_plan_era_deadline(start_session: SessionIndex) -> bool {
1022 let planning_era_offset = T::PlanningEraOffset::get().min(T::SessionsPerEra::get());
1023 let target_plan_era_session = T::SessionsPerEra::get().saturating_sub(planning_era_offset);
1025 let era_start_session = Self::active_era_start_session_index();
1026
1027 let session_progress = start_session.defensive_saturating_sub(era_start_session);
1029
1030 log!(
1031 debug,
1032 "Session progress within era: {:?}, target_plan_era_session: {:?}",
1033 session_progress,
1034 target_plan_era_session
1035 );
1036 session_progress >= target_plan_era_session
1037 }
1038}
1039
1040pub(crate) struct EraElectionPlanner<T: Config>(PhantomData<T>);
1066impl<T: Config> EraElectionPlanner<T> {
1067 pub(crate) fn cleanup() {
1069 VoterSnapshotStatus::<T>::kill();
1070 NextElectionPage::<T>::kill();
1071 ElectableStashes::<T>::kill();
1072 Pallet::<T>::register_weight(T::DbWeight::get().writes(3));
1073 }
1074
1075 pub(crate) fn election_pages() -> u32 {
1077 <<T as Config>::ElectionProvider as ElectionProvider>::Pages::get()
1078 }
1079
1080 pub(crate) fn plan_new_election() -> Result<(), <T::ElectionProvider as ElectionProvider>::Error>
1082 {
1083 T::ElectionProvider::start()
1084 .inspect_err(|e| log!(warn, "Election provider failed to start: {:?}", e))
1085 }
1086
1087 pub(crate) fn maybe_fetch_election_results() -> (Weight, Box<dyn Fn(&mut WeightMeter)>) {
1088 let Ok(Some(mut required_weight)) = T::ElectionProvider::status() else {
1089 let weight = T::DbWeight::get().reads(1);
1091 return (weight, Box::new(move |meter: &mut WeightMeter| meter.consume(weight)));
1092 };
1093
1094 required_weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 2));
1103
1104 let exec = Box::new(move |meter: &mut WeightMeter| {
1105 crate::log!(
1106 debug,
1107 "Election provider is ready, our status is {:?}",
1108 NextElectionPage::<T>::get()
1109 );
1110
1111 debug_assert!(
1112 CurrentEra::<T>::get().unwrap_or(0) ==
1113 ActiveEra::<T>::get().map_or(0, |a| a.index) + 1,
1114 "Next era must be already planned."
1115 );
1116
1117 let current_page = NextElectionPage::<T>::get()
1118 .unwrap_or(Self::election_pages().defensive_saturating_sub(1));
1119 let maybe_next_page = current_page.checked_sub(1);
1120 crate::log!(debug, "fetching page {:?}, next {:?}", current_page, maybe_next_page);
1121
1122 Self::do_elect_paged(current_page);
1123 NextElectionPage::<T>::set(maybe_next_page);
1124
1125 if maybe_next_page.is_none() {
1126 let id = CurrentEra::<T>::get().defensive_unwrap_or(0);
1127 let prune_up_to = Self::get_prune_up_to();
1128 let rc_validators = ElectableStashes::<T>::take().into_iter().collect::<Vec<_>>();
1129
1130 crate::log!(
1131 info,
1132 "Sending new validator set of size {:?} to RC. ID: {:?}, prune_up_to: {:?}",
1133 rc_validators.len(),
1134 id,
1135 prune_up_to
1136 );
1137 T::RcClientInterface::validator_set(rc_validators, id, prune_up_to);
1138 }
1139
1140 meter.consume(required_weight)
1142 });
1143
1144 (required_weight, exec)
1145 }
1146
1147 fn get_prune_up_to() -> Option<SessionIndex> {
1150 let bonded_eras = BondedEras::<T>::get();
1151
1152 if bonded_eras.is_full() {
1154 bonded_eras.first().map(|(_, first_session)| first_session.saturating_sub(1))
1155 } else {
1156 None
1157 }
1158 }
1159
1160 pub(crate) fn do_elect_paged(page: PageIndex) {
1172 let election_result = T::ElectionProvider::elect(page);
1173 match election_result {
1174 Ok(supports) => {
1175 let inner_processing_results = Self::do_elect_paged_inner(supports);
1176 if let Err(not_included) = inner_processing_results {
1177 defensive!(
1178 "electable stashes exceeded limit, unexpected but election proceeds.\
1179 {} stashes from election result discarded",
1180 not_included
1181 );
1182 };
1183
1184 Pallet::<T>::deposit_event(Event::PagedElectionProceeded {
1185 page,
1186 result: inner_processing_results.map(|x| x as u32).map_err(|x| x as u32),
1187 });
1188 },
1189 Err(e) => {
1190 log!(warn, "election provider page failed due to {:?} (page: {})", e, page);
1191 Pallet::<T>::deposit_event(Event::PagedElectionProceeded { page, result: Err(0) });
1192 },
1193 }
1194 }
1195
1196 pub(crate) fn do_elect_paged_inner(
1202 mut supports: BoundedSupportsOf<T::ElectionProvider>,
1203 ) -> Result<usize, usize> {
1204 let planning_era = Rotator::<T>::planned_era();
1205
1206 match Self::add_electables(supports.iter().map(|(s, _)| s.clone())) {
1207 Ok(added) => {
1208 let exposures = Self::collect_exposures(supports);
1209 let _ = Self::store_stakers_info(exposures, planning_era);
1210 Ok(added)
1211 },
1212 Err(not_included_idx) => {
1213 let not_included = supports.len().saturating_sub(not_included_idx);
1214
1215 log!(
1216 warn,
1217 "not all winners fit within the electable stashes, excluding {:?} accounts from solution.",
1218 not_included,
1219 );
1220
1221 supports.truncate(not_included_idx);
1224 let exposures = Self::collect_exposures(supports);
1225 let _ = Self::store_stakers_info(exposures, planning_era);
1226
1227 Err(not_included)
1228 },
1229 }
1230 }
1231
1232 pub(crate) fn store_stakers_info(
1236 exposures: BoundedExposuresOf<T>,
1237 new_planned_era: EraIndex,
1238 ) -> BoundedVec<T::AccountId, MaxWinnersPerPageOf<T::ElectionProvider>> {
1239 let mut total_stake_page: BalanceOf<T> = Zero::zero();
1241 let mut elected_stashes_page = Vec::with_capacity(exposures.len());
1242 let mut total_backers = 0u32;
1243
1244 let mut total_incentive_weight_page: BalanceOf<T> = Zero::zero();
1245
1246 exposures.into_iter().for_each(|(stash, exposure)| {
1247 log!(
1248 trace,
1249 "storing exposure for stash {:?} with {:?} own-stake and {:?} backers",
1250 stash,
1251 exposure.own,
1252 exposure.others.len()
1253 );
1254 elected_stashes_page.push(stash.clone());
1256 total_stake_page = total_stake_page.saturating_add(exposure.total);
1258 total_backers += exposure.others.len() as u32;
1259 let own = exposure.own;
1260 Eras::<T>::upsert_exposure(new_planned_era, &stash, exposure);
1261
1262 if !own.is_zero() {
1266 if ErasValidatorIncentiveWeight::<T>::contains_key(new_planned_era, &stash) {
1267 defensive!(
1268 "validator own-stake seen twice in the same era across election pages"
1269 );
1270 } else {
1271 let incentive_weight =
1272 T::StakerRewardCalculator::calculate_validator_incentive_weight(own);
1273 if !incentive_weight.is_zero() {
1274 total_incentive_weight_page =
1275 total_incentive_weight_page.saturating_add(incentive_weight);
1276 ErasValidatorIncentiveWeight::<T>::insert(
1277 new_planned_era,
1278 &stash,
1279 incentive_weight,
1280 );
1281 }
1282 }
1283 }
1284 });
1285
1286 let elected_stashes: BoundedVec<_, MaxWinnersPerPageOf<T::ElectionProvider>> =
1287 elected_stashes_page
1288 .try_into()
1289 .expect("both types are bounded by MaxWinnersPerPageOf; qed");
1290
1291 Eras::<T>::add_total_stake(new_planned_era, total_stake_page);
1293
1294 Eras::<T>::add_sum_validator_incentive_weight(new_planned_era, total_incentive_weight_page);
1296
1297 for stash in &elected_stashes {
1301 let pref = Validators::<T>::get(stash);
1302 Eras::<T>::set_validator_prefs(new_planned_era, stash, pref);
1303 }
1304
1305 log!(
1306 debug,
1307 "stored a page of stakers with {:?} validators and {:?} total backers for era {:?}",
1308 elected_stashes.len(),
1309 total_backers,
1310 new_planned_era,
1311 );
1312
1313 elected_stashes
1314 }
1315
1316 fn collect_exposures(
1322 supports: BoundedSupportsOf<T::ElectionProvider>,
1323 ) -> BoundedExposuresOf<T> {
1324 let total_issuance = asset::total_issuance::<T>();
1325 let to_currency = |e: frame_election_provider_support::ExtendedBalance| {
1326 T::CurrencyToVote::to_currency(e, total_issuance)
1327 };
1328
1329 supports
1330 .into_iter()
1331 .map(|(validator, support)| {
1332 let mut others = Vec::with_capacity(support.voters.len());
1334 let mut own: BalanceOf<T> = Zero::zero();
1335 let mut total: BalanceOf<T> = Zero::zero();
1336 support
1337 .voters
1338 .into_iter()
1339 .map(|(nominator, weight)| (nominator, to_currency(weight)))
1340 .for_each(|(nominator, stake)| {
1341 if nominator == validator {
1342 defensive_assert!(own == Zero::zero(), "own stake should be unique");
1343 own = own.saturating_add(stake);
1344 } else {
1345 others.push(IndividualExposure { who: nominator, value: stake });
1346 }
1347 total = total.saturating_add(stake);
1348 });
1349
1350 let exposure = Exposure { own, others, total };
1351 (validator, exposure)
1352 })
1353 .try_collect()
1354 .expect("we only map through support vector which cannot change the size; qed")
1355 }
1356
1357 pub(crate) fn add_electables(
1364 new_stashes: impl Iterator<Item = T::AccountId>,
1365 ) -> Result<usize, usize> {
1366 ElectableStashes::<T>::mutate(|electable| {
1367 let pre_size = electable.len();
1368
1369 for (idx, stash) in new_stashes.enumerate() {
1370 if electable.try_insert(stash).is_err() {
1371 return Err(idx);
1372 }
1373 }
1374
1375 Ok(electable.len() - pre_size)
1376 })
1377 }
1378}