1#![cfg_attr(not(feature = "std"), no_std)]
83
84extern crate alloc;
85
86use core::marker::PhantomData;
87use frame_support::traits::TypedGet;
88pub use pallet::*;
89
90#[cfg(test)]
91mod mock;
92
93#[cfg(test)]
94mod tests;
95
96#[cfg(feature = "runtime-benchmarks")]
97mod benchmarking;
98pub mod migration;
99pub mod weights;
100
101const LOG_TARGET: &str = "runtime::collator-selection";
102
103#[frame_support::pallet]
104pub mod pallet {
105 pub use crate::weights::WeightInfo;
106 use alloc::vec::Vec;
107 use core::ops::Div;
108 use frame_support::{
109 dispatch::{DispatchClass, DispatchResultWithPostInfo},
110 pallet_prelude::*,
111 traits::{
112 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,
113 ValidatorRegistration,
114 },
115 BoundedVec, DefaultNoBound, PalletId,
116 };
117 use frame_system::{pallet_prelude::*, Config as SystemConfig};
118 use pallet_session::SessionManager;
119 use sp_runtime::{
120 traits::{AccountIdConversion, CheckedSub, Convert, Saturating, Zero},
121 RuntimeDebug,
122 };
123 use sp_staking::SessionIndex;
124
125 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
127
128 type BalanceOf<T> =
129 <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
130
131 pub struct IdentityCollator;
134 impl<T> sp_runtime::traits::Convert<T, Option<T>> for IdentityCollator {
135 fn convert(t: T) -> Option<T> {
136 Some(t)
137 }
138 }
139
140 #[pallet::config]
142 pub trait Config: frame_system::Config {
143 #[allow(deprecated)]
145 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
146
147 type Currency: ReservableCurrency<Self::AccountId>;
149
150 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
152
153 #[pallet::constant]
155 type PotId: Get<PalletId>;
156
157 #[pallet::constant]
161 type MaxCandidates: Get<u32>;
162
163 #[pallet::constant]
167 type MinEligibleCollators: Get<u32>;
168
169 #[pallet::constant]
171 type MaxInvulnerables: Get<u32>;
172
173 #[pallet::constant]
175 type KickThreshold: Get<BlockNumberFor<Self>>;
176
177 type ValidatorId: Member + Parameter;
179
180 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
184
185 type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
187
188 type WeightInfo: WeightInfo;
190 }
191
192 #[pallet::extra_constants]
193 impl<T: Config> Pallet<T> {
194 fn pot_account() -> T::AccountId {
196 Self::account_id()
197 }
198 }
199
200 #[derive(
202 PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
203 )]
204 pub struct CandidateInfo<AccountId, Balance> {
205 pub who: AccountId,
207 pub deposit: Balance,
209 }
210
211 #[pallet::pallet]
212 #[pallet::storage_version(STORAGE_VERSION)]
213 pub struct Pallet<T>(_);
214
215 #[pallet::storage]
217 pub type Invulnerables<T: Config> =
218 StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
219
220 #[pallet::storage]
226 pub type CandidateList<T: Config> = StorageValue<
227 _,
228 BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
229 ValueQuery,
230 >;
231
232 #[pallet::storage]
234 pub type LastAuthoredBlock<T: Config> =
235 StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
236
237 #[pallet::storage]
241 pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
242
243 #[pallet::storage]
247 pub type CandidacyBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
248
249 #[pallet::genesis_config]
250 #[derive(DefaultNoBound)]
251 pub struct GenesisConfig<T: Config> {
252 pub invulnerables: Vec<T::AccountId>,
253 pub candidacy_bond: BalanceOf<T>,
254 pub desired_candidates: u32,
255 }
256
257 #[pallet::genesis_build]
258 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
259 fn build(&self) {
260 let duplicate_invulnerables = self
261 .invulnerables
262 .iter()
263 .collect::<alloc::collections::btree_set::BTreeSet<_>>();
264 assert!(
265 duplicate_invulnerables.len() == self.invulnerables.len(),
266 "duplicate invulnerables in genesis."
267 );
268
269 let mut bounded_invulnerables =
270 BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())
271 .expect("genesis invulnerables are more than T::MaxInvulnerables");
272 assert!(
273 T::MaxCandidates::get() >= self.desired_candidates,
274 "genesis desired_candidates are more than T::MaxCandidates",
275 );
276
277 bounded_invulnerables.sort();
278
279 DesiredCandidates::<T>::put(self.desired_candidates);
280 CandidacyBond::<T>::put(self.candidacy_bond);
281 Invulnerables::<T>::put(bounded_invulnerables);
282 }
283 }
284
285 #[pallet::event]
286 #[pallet::generate_deposit(pub(super) fn deposit_event)]
287 pub enum Event<T: Config> {
288 NewInvulnerables { invulnerables: Vec<T::AccountId> },
290 InvulnerableAdded { account_id: T::AccountId },
292 InvulnerableRemoved { account_id: T::AccountId },
294 NewDesiredCandidates { desired_candidates: u32 },
296 NewCandidacyBond { bond_amount: BalanceOf<T> },
298 CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
300 CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
302 CandidateRemoved { account_id: T::AccountId },
304 CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
306 InvalidInvulnerableSkipped { account_id: T::AccountId },
309 }
310
311 #[pallet::error]
312 pub enum Error<T> {
313 TooManyCandidates,
315 TooFewEligibleCollators,
317 AlreadyCandidate,
319 NotCandidate,
321 TooManyInvulnerables,
323 AlreadyInvulnerable,
325 NotInvulnerable,
327 NoAssociatedValidatorId,
329 ValidatorNotRegistered,
331 InsertToCandidateListFailed,
333 RemoveFromCandidateListFailed,
335 DepositTooLow,
337 UpdateCandidateListFailed,
339 InsufficientBond,
341 TargetIsNotCandidate,
343 IdenticalDeposit,
345 InvalidUnreserve,
347 }
348
349 #[pallet::hooks]
350 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
351 fn integrity_test() {
352 assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator");
353 assert!(
354 T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get()) >=
355 T::MinEligibleCollators::get(),
356 "invulnerables and candidates must be able to satisfy collator demand"
357 );
358 }
359
360 #[cfg(feature = "try-runtime")]
361 fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
362 Self::do_try_state()
363 }
364 }
365
366 #[pallet::call]
367 impl<T: Config> Pallet<T> {
368 #[pallet::call_index(0)]
382 #[pallet::weight(T::WeightInfo::set_invulnerables(new.len() as u32))]
383 pub fn set_invulnerables(origin: OriginFor<T>, new: Vec<T::AccountId>) -> DispatchResult {
384 T::UpdateOrigin::ensure_origin(origin)?;
385
386 if new.is_empty() {
388 ensure!(
390 CandidateList::<T>::decode_len().unwrap_or_default() >=
391 T::MinEligibleCollators::get() as usize,
392 Error::<T>::TooFewEligibleCollators
393 );
394 }
395
396 ensure!(
399 new.len() as u32 <= T::MaxInvulnerables::get(),
400 Error::<T>::TooManyInvulnerables
401 );
402
403 let mut new_with_keys = Vec::new();
404
405 for account_id in &new {
407 let validator_key = T::ValidatorIdOf::convert(account_id.clone());
409 match validator_key {
410 Some(key) => {
411 if !T::ValidatorRegistration::is_registered(&key) {
413 Self::deposit_event(Event::InvalidInvulnerableSkipped {
414 account_id: account_id.clone(),
415 });
416 continue
417 }
418 },
420 None => {
422 Self::deposit_event(Event::InvalidInvulnerableSkipped {
423 account_id: account_id.clone(),
424 });
425 continue
426 },
427 }
428
429 new_with_keys.push(account_id.clone());
430 }
431
432 let mut bounded_invulnerables =
434 BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
435 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
436
437 bounded_invulnerables.sort();
439
440 Invulnerables::<T>::put(&bounded_invulnerables);
441 Self::deposit_event(Event::NewInvulnerables {
442 invulnerables: bounded_invulnerables.to_vec(),
443 });
444
445 Ok(())
446 }
447
448 #[pallet::call_index(1)]
454 #[pallet::weight(T::WeightInfo::set_desired_candidates())]
455 pub fn set_desired_candidates(
456 origin: OriginFor<T>,
457 max: u32,
458 ) -> DispatchResultWithPostInfo {
459 T::UpdateOrigin::ensure_origin(origin)?;
460 if max > T::MaxCandidates::get() {
462 log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");
463 }
464 DesiredCandidates::<T>::put(max);
465 Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max });
466 Ok(().into())
467 }
468
469 #[pallet::call_index(2)]
477 #[pallet::weight(T::WeightInfo::set_candidacy_bond(
478 T::MaxCandidates::get(),
479 T::MaxCandidates::get()
480 ))]
481 pub fn set_candidacy_bond(
482 origin: OriginFor<T>,
483 bond: BalanceOf<T>,
484 ) -> DispatchResultWithPostInfo {
485 T::UpdateOrigin::ensure_origin(origin)?;
486 let bond_increased = CandidacyBond::<T>::mutate(|old_bond| -> bool {
487 let bond_increased = *old_bond < bond;
488 *old_bond = bond;
489 bond_increased
490 });
491 let initial_len = CandidateList::<T>::decode_len().unwrap_or_default();
492 let kicked = (bond_increased && initial_len > 0)
493 .then(|| {
494 CandidateList::<T>::mutate(|candidates| -> usize {
497 let first_safe_candidate = candidates
498 .iter()
499 .position(|candidate| candidate.deposit >= bond)
500 .unwrap_or(initial_len);
501 let kicked_candidates = candidates.drain(..first_safe_candidate);
502 for candidate in kicked_candidates {
503 T::Currency::unreserve(&candidate.who, candidate.deposit);
504 LastAuthoredBlock::<T>::remove(candidate.who);
505 }
506 first_safe_candidate
507 })
508 })
509 .unwrap_or_default();
510 Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond });
511 Ok(Some(T::WeightInfo::set_candidacy_bond(
512 bond_increased.then(|| initial_len as u32).unwrap_or_default(),
513 kicked as u32,
514 ))
515 .into())
516 }
517
518 #[pallet::call_index(3)]
523 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))]
524 pub fn register_as_candidate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
525 let who = ensure_signed(origin)?;
526
527 let length: u32 = CandidateList::<T>::decode_len()
529 .unwrap_or_default()
530 .try_into()
531 .unwrap_or_default();
532 ensure!(length < T::MaxCandidates::get(), Error::<T>::TooManyCandidates);
533 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
534
535 let validator_key = T::ValidatorIdOf::convert(who.clone())
536 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
537 ensure!(
538 T::ValidatorRegistration::is_registered(&validator_key),
539 Error::<T>::ValidatorNotRegistered
540 );
541
542 let deposit = CandidacyBond::<T>::get();
543 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
545 ensure!(
546 !candidates.iter().any(|candidate_info| candidate_info.who == who),
547 Error::<T>::AlreadyCandidate
548 );
549 T::Currency::reserve(&who, deposit)?;
550 LastAuthoredBlock::<T>::insert(
551 who.clone(),
552 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
553 );
554 candidates
555 .try_insert(0, CandidateInfo { who: who.clone(), deposit })
556 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
557 Ok(())
558 })?;
559
560 Self::deposit_event(Event::CandidateAdded { account_id: who, deposit });
561 Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
565 }
566
567 #[pallet::call_index(4)]
573 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
574 pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
575 let who = ensure_signed(origin)?;
576 ensure!(
577 Self::eligible_collators() > T::MinEligibleCollators::get(),
578 Error::<T>::TooFewEligibleCollators
579 );
580 let length = CandidateList::<T>::decode_len().unwrap_or_default();
581 Self::try_remove_candidate(&who, true)?;
583
584 Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
585 }
586
587 #[pallet::call_index(5)]
592 #[pallet::weight(T::WeightInfo::add_invulnerable(
593 T::MaxInvulnerables::get().saturating_sub(1),
594 T::MaxCandidates::get()
595 ))]
596 pub fn add_invulnerable(
597 origin: OriginFor<T>,
598 who: T::AccountId,
599 ) -> DispatchResultWithPostInfo {
600 T::UpdateOrigin::ensure_origin(origin)?;
601
602 let validator_key = T::ValidatorIdOf::convert(who.clone())
604 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
605 ensure!(
606 T::ValidatorRegistration::is_registered(&validator_key),
607 Error::<T>::ValidatorNotRegistered
608 );
609
610 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
611 match invulnerables.binary_search(&who) {
612 Ok(_) => return Err(Error::<T>::AlreadyInvulnerable)?,
613 Err(pos) => invulnerables
614 .try_insert(pos, who.clone())
615 .map_err(|_| Error::<T>::TooManyInvulnerables)?,
616 }
617 Ok(())
618 })?;
619
620 let _ = Self::try_remove_candidate(&who, false);
623
624 Self::deposit_event(Event::InvulnerableAdded { account_id: who });
625
626 let weight_used = T::WeightInfo::add_invulnerable(
627 Invulnerables::<T>::decode_len()
628 .unwrap_or_default()
629 .try_into()
630 .unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)),
631 CandidateList::<T>::decode_len()
632 .unwrap_or_default()
633 .try_into()
634 .unwrap_or(T::MaxCandidates::get()),
635 );
636
637 Ok(Some(weight_used).into())
638 }
639
640 #[pallet::call_index(6)]
645 #[pallet::weight(T::WeightInfo::remove_invulnerable(T::MaxInvulnerables::get()))]
646 pub fn remove_invulnerable(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
647 T::UpdateOrigin::ensure_origin(origin)?;
648
649 ensure!(
650 Self::eligible_collators() > T::MinEligibleCollators::get(),
651 Error::<T>::TooFewEligibleCollators
652 );
653
654 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
655 let pos =
656 invulnerables.binary_search(&who).map_err(|_| Error::<T>::NotInvulnerable)?;
657 invulnerables.remove(pos);
658 Ok(())
659 })?;
660
661 Self::deposit_event(Event::InvulnerableRemoved { account_id: who });
662 Ok(())
663 }
664
665 #[pallet::call_index(7)]
673 #[pallet::weight(T::WeightInfo::update_bond(T::MaxCandidates::get()))]
674 pub fn update_bond(
675 origin: OriginFor<T>,
676 new_deposit: BalanceOf<T>,
677 ) -> DispatchResultWithPostInfo {
678 let who = ensure_signed(origin)?;
679 ensure!(new_deposit >= CandidacyBond::<T>::get(), Error::<T>::DepositTooLow);
680 let length =
684 CandidateList::<T>::try_mutate(|candidates| -> Result<usize, DispatchError> {
685 let idx = candidates
686 .iter()
687 .position(|candidate_info| candidate_info.who == who)
688 .ok_or_else(|| Error::<T>::NotCandidate)?;
689 let candidate_count = candidates.len();
690 let mut info = candidates.remove(idx);
692 let old_deposit = info.deposit;
693 if new_deposit > old_deposit {
694 T::Currency::reserve(&who, new_deposit - old_deposit)?;
695 } else if new_deposit < old_deposit {
696 ensure!(
698 idx.saturating_add(DesiredCandidates::<T>::get() as usize) <
699 candidate_count,
700 Error::<T>::InvalidUnreserve
701 );
702 T::Currency::unreserve(&who, old_deposit - new_deposit);
703 } else {
704 return Err(Error::<T>::IdenticalDeposit.into())
705 }
706
707 info.deposit = new_deposit;
709 let new_pos = candidates
710 .iter()
711 .position(|candidate| candidate.deposit >= new_deposit)
712 .unwrap_or_else(|| candidates.len());
713 candidates
714 .try_insert(new_pos, info)
715 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
716
717 Ok(candidate_count)
718 })?;
719
720 Self::deposit_event(Event::CandidateBondUpdated {
721 account_id: who,
722 deposit: new_deposit,
723 });
724 Ok(Some(T::WeightInfo::update_bond(length as u32)).into())
725 }
726
727 #[pallet::call_index(8)]
735 #[pallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))]
736 pub fn take_candidate_slot(
737 origin: OriginFor<T>,
738 deposit: BalanceOf<T>,
739 target: T::AccountId,
740 ) -> DispatchResultWithPostInfo {
741 let who = ensure_signed(origin)?;
742
743 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
744 ensure!(deposit >= CandidacyBond::<T>::get(), Error::<T>::InsufficientBond);
745
746 let validator_key = T::ValidatorIdOf::convert(who.clone())
747 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
748 ensure!(
749 T::ValidatorRegistration::is_registered(&validator_key),
750 Error::<T>::ValidatorNotRegistered
751 );
752
753 let length = CandidateList::<T>::decode_len().unwrap_or_default();
754 let target_info = CandidateList::<T>::try_mutate(
759 |candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
760 let mut target_info_idx = None;
762 let mut new_info_idx = None;
763 for (idx, candidate_info) in candidates.iter().enumerate() {
764 ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
768 if candidate_info.who == target {
772 target_info_idx = Some(idx);
773 }
774 if new_info_idx.is_none() && candidate_info.deposit >= deposit {
777 new_info_idx = Some(idx);
778 }
779 }
780 let target_info_idx =
781 target_info_idx.ok_or(Error::<T>::TargetIsNotCandidate)?;
782
783 let target_info = candidates.remove(target_info_idx);
785 ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
786
787 let new_pos = new_info_idx
790 .map(|i| i.saturating_sub(1))
791 .unwrap_or_else(|| candidates.len());
792 let new_info = CandidateInfo { who: who.clone(), deposit };
793 candidates
795 .try_insert(new_pos, new_info)
796 .expect("candidate count previously decremented; qed");
797
798 Ok(target_info)
799 },
800 )?;
801 T::Currency::reserve(&who, deposit)?;
802 T::Currency::unreserve(&target_info.who, target_info.deposit);
803 LastAuthoredBlock::<T>::remove(target_info.who.clone());
804 LastAuthoredBlock::<T>::insert(
805 who.clone(),
806 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
807 );
808
809 Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit });
810 Ok(Some(T::WeightInfo::take_candidate_slot(length as u32)).into())
811 }
812 }
813
814 impl<T: Config> Pallet<T> {
815 pub fn account_id() -> T::AccountId {
817 T::PotId::get().into_account_truncating()
818 }
819
820 fn eligible_collators() -> u32 {
823 CandidateList::<T>::decode_len()
824 .unwrap_or_default()
825 .saturating_add(Invulnerables::<T>::decode_len().unwrap_or_default())
826 .try_into()
827 .unwrap_or(u32::MAX)
828 }
829
830 fn try_remove_candidate(
832 who: &T::AccountId,
833 remove_last_authored: bool,
834 ) -> Result<(), DispatchError> {
835 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
836 let idx = candidates
837 .iter()
838 .position(|candidate_info| candidate_info.who == *who)
839 .ok_or(Error::<T>::NotCandidate)?;
840 let deposit = candidates[idx].deposit;
841 T::Currency::unreserve(who, deposit);
842 candidates.remove(idx);
843 if remove_last_authored {
844 LastAuthoredBlock::<T>::remove(who.clone())
845 };
846 Ok(())
847 })?;
848 Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() });
849 Ok(())
850 }
851
852 pub fn assemble_collators() -> Vec<T::AccountId> {
856 let desired_candidates = DesiredCandidates::<T>::get() as usize;
858 let mut collators = Invulnerables::<T>::get().to_vec();
859 collators.extend(
860 CandidateList::<T>::get()
861 .iter()
862 .rev()
863 .cloned()
864 .take(desired_candidates)
865 .map(|candidate_info| candidate_info.who),
866 );
867 collators
868 }
869
870 pub fn kick_stale_candidates(candidates: impl IntoIterator<Item = T::AccountId>) -> u32 {
875 let now = frame_system::Pallet::<T>::block_number();
876 let kick_threshold = T::KickThreshold::get();
877 let min_collators = T::MinEligibleCollators::get();
878 candidates
879 .into_iter()
880 .filter_map(|c| {
881 let last_block = LastAuthoredBlock::<T>::get(c.clone());
882 let since_last = now.saturating_sub(last_block);
883
884 let is_invulnerable = Invulnerables::<T>::get().contains(&c);
885 let is_lazy = since_last >= kick_threshold;
886
887 if is_invulnerable {
888 let _ = Self::try_remove_candidate(&c, false);
892 None
893 } else {
894 if Self::eligible_collators() <= min_collators || !is_lazy {
895 Some(c)
898 } else {
899 let _ = Self::try_remove_candidate(&c, true);
901 None
902 }
903 }
904 })
905 .count()
906 .try_into()
907 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")
908 }
909
910 #[cfg(any(test, feature = "try-runtime"))]
922 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
923 let desired_candidates = DesiredCandidates::<T>::get();
924
925 frame_support::ensure!(
926 desired_candidates <= T::MaxCandidates::get(),
927 "Shouldn't demand more candidates than the pallet config allows."
928 );
929
930 frame_support::ensure!(
931 desired_candidates.saturating_add(T::MaxInvulnerables::get()) >=
932 T::MinEligibleCollators::get(),
933 "Invulnerable set together with desired candidates should be able to meet the collator quota."
934 );
935
936 Ok(())
937 }
938 }
939
940 impl<T: Config + pallet_authorship::Config>
943 pallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pallet<T>
944 {
945 fn note_author(author: T::AccountId) {
946 let pot = Self::account_id();
947 let reward = T::Currency::free_balance(&pot)
949 .checked_sub(&T::Currency::minimum_balance())
950 .unwrap_or_else(Zero::zero)
951 .div(2u32.into());
952 let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);
954 debug_assert!(_success.is_ok());
955 LastAuthoredBlock::<T>::insert(author, frame_system::Pallet::<T>::block_number());
956
957 frame_system::Pallet::<T>::register_extra_weight_unchecked(
958 T::WeightInfo::note_author(),
959 DispatchClass::Mandatory,
960 );
961 }
962 }
963
964 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {
966 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {
967 log::info!(
968 "assembling new collators for new session {} at #{:?}",
969 index,
970 <frame_system::Pallet<T>>::block_number(),
971 );
972
973 let candidates_len_before: u32 = CandidateList::<T>::decode_len()
977 .unwrap_or_default()
978 .try_into()
979 .expect("length is at most `T::MaxCandidates`, so it must fit in `u32`; qed");
980 let active_candidates_count = Self::kick_stale_candidates(
981 CandidateList::<T>::get()
982 .iter()
983 .map(|candidate_info| candidate_info.who.clone()),
984 );
985 let removed = candidates_len_before.saturating_sub(active_candidates_count);
986 let result = Self::assemble_collators();
987
988 frame_system::Pallet::<T>::register_extra_weight_unchecked(
989 T::WeightInfo::new_session(removed, candidates_len_before),
990 DispatchClass::Mandatory,
991 );
992 Some(result)
993 }
994 fn start_session(_: SessionIndex) {
995 }
997 fn end_session(_: SessionIndex) {
998 }
1000 }
1001}
1002
1003pub struct StakingPotAccountId<R>(PhantomData<R>);
1005impl<R> TypedGet for StakingPotAccountId<R>
1006where
1007 R: crate::Config,
1008{
1009 type Type = <R as frame_system::Config>::AccountId;
1010 fn get() -> Self::Type {
1011 <crate::Pallet<R>>::account_id()
1012 }
1013}