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 Debug,
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(PartialEq, Eq, Clone, Encode, Decode, Debug, scale_info::TypeInfo, MaxEncodedLen)]
202 pub struct CandidateInfo<AccountId, Balance> {
203 pub who: AccountId,
205 pub deposit: Balance,
207 }
208
209 #[pallet::pallet]
210 #[pallet::storage_version(STORAGE_VERSION)]
211 pub struct Pallet<T>(_);
212
213 #[pallet::storage]
215 pub type Invulnerables<T: Config> =
216 StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
217
218 #[pallet::storage]
224 pub type CandidateList<T: Config> = StorageValue<
225 _,
226 BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
227 ValueQuery,
228 >;
229
230 #[pallet::storage]
232 pub type LastAuthoredBlock<T: Config> =
233 StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
234
235 #[pallet::storage]
239 pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
240
241 #[pallet::storage]
245 pub type CandidacyBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
246
247 #[pallet::genesis_config]
248 #[derive(DefaultNoBound)]
249 pub struct GenesisConfig<T: Config> {
250 pub invulnerables: Vec<T::AccountId>,
251 pub candidacy_bond: BalanceOf<T>,
252 pub desired_candidates: u32,
253 }
254
255 #[pallet::genesis_build]
256 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
257 fn build(&self) {
258 let duplicate_invulnerables = self
259 .invulnerables
260 .iter()
261 .collect::<alloc::collections::btree_set::BTreeSet<_>>();
262 assert!(
263 duplicate_invulnerables.len() == self.invulnerables.len(),
264 "duplicate invulnerables in genesis."
265 );
266
267 let mut bounded_invulnerables =
268 BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())
269 .expect("genesis invulnerables are more than T::MaxInvulnerables");
270 assert!(
271 T::MaxCandidates::get() >= self.desired_candidates,
272 "genesis desired_candidates are more than T::MaxCandidates",
273 );
274
275 bounded_invulnerables.sort();
276
277 DesiredCandidates::<T>::put(self.desired_candidates);
278 CandidacyBond::<T>::put(self.candidacy_bond);
279 Invulnerables::<T>::put(bounded_invulnerables);
280 }
281 }
282
283 #[pallet::event]
284 #[pallet::generate_deposit(pub(super) fn deposit_event)]
285 pub enum Event<T: Config> {
286 NewInvulnerables { invulnerables: Vec<T::AccountId> },
288 InvulnerableAdded { account_id: T::AccountId },
290 InvulnerableRemoved { account_id: T::AccountId },
292 NewDesiredCandidates { desired_candidates: u32 },
294 NewCandidacyBond { bond_amount: BalanceOf<T> },
296 CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
298 CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
300 CandidateRemoved { account_id: T::AccountId },
302 CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
304 InvalidInvulnerableSkipped { account_id: T::AccountId },
307 }
308
309 #[pallet::error]
310 pub enum Error<T> {
311 TooManyCandidates,
313 TooFewEligibleCollators,
315 AlreadyCandidate,
317 NotCandidate,
319 TooManyInvulnerables,
321 AlreadyInvulnerable,
323 NotInvulnerable,
325 NoAssociatedValidatorId,
327 ValidatorNotRegistered,
329 InsertToCandidateListFailed,
331 RemoveFromCandidateListFailed,
333 DepositTooLow,
335 UpdateCandidateListFailed,
337 InsufficientBond,
339 TargetIsNotCandidate,
341 IdenticalDeposit,
343 InvalidUnreserve,
345 }
346
347 #[pallet::hooks]
348 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
349 fn integrity_test() {
350 assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator");
351 assert!(
352 T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get()) >=
353 T::MinEligibleCollators::get(),
354 "invulnerables and candidates must be able to satisfy collator demand"
355 );
356 }
357
358 #[cfg(feature = "try-runtime")]
359 fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
360 Self::do_try_state()
361 }
362 }
363
364 #[pallet::call]
365 impl<T: Config> Pallet<T> {
366 #[pallet::call_index(0)]
380 #[pallet::weight(T::WeightInfo::set_invulnerables(new.len() as u32))]
381 pub fn set_invulnerables(origin: OriginFor<T>, new: Vec<T::AccountId>) -> DispatchResult {
382 T::UpdateOrigin::ensure_origin(origin)?;
383
384 if new.is_empty() {
386 ensure!(
388 CandidateList::<T>::decode_len().unwrap_or_default() >=
389 T::MinEligibleCollators::get() as usize,
390 Error::<T>::TooFewEligibleCollators
391 );
392 }
393
394 ensure!(
397 new.len() as u32 <= T::MaxInvulnerables::get(),
398 Error::<T>::TooManyInvulnerables
399 );
400
401 let mut new_with_keys = Vec::new();
402
403 for account_id in &new {
405 let validator_key = T::ValidatorIdOf::convert(account_id.clone());
407 match validator_key {
408 Some(key) => {
409 if !T::ValidatorRegistration::is_registered(&key) {
411 Self::deposit_event(Event::InvalidInvulnerableSkipped {
412 account_id: account_id.clone(),
413 });
414 continue
415 }
416 },
418 None => {
420 Self::deposit_event(Event::InvalidInvulnerableSkipped {
421 account_id: account_id.clone(),
422 });
423 continue
424 },
425 }
426
427 new_with_keys.push(account_id.clone());
428 }
429
430 let mut bounded_invulnerables =
432 BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
433 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
434
435 bounded_invulnerables.sort();
437
438 Invulnerables::<T>::put(&bounded_invulnerables);
439 Self::deposit_event(Event::NewInvulnerables {
440 invulnerables: bounded_invulnerables.to_vec(),
441 });
442
443 Ok(())
444 }
445
446 #[pallet::call_index(1)]
452 #[pallet::weight(T::WeightInfo::set_desired_candidates())]
453 pub fn set_desired_candidates(
454 origin: OriginFor<T>,
455 max: u32,
456 ) -> DispatchResultWithPostInfo {
457 T::UpdateOrigin::ensure_origin(origin)?;
458 if max > T::MaxCandidates::get() {
460 log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");
461 }
462 DesiredCandidates::<T>::put(max);
463 Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max });
464 Ok(().into())
465 }
466
467 #[pallet::call_index(2)]
475 #[pallet::weight(T::WeightInfo::set_candidacy_bond(
476 T::MaxCandidates::get(),
477 T::MaxCandidates::get()
478 ))]
479 pub fn set_candidacy_bond(
480 origin: OriginFor<T>,
481 bond: BalanceOf<T>,
482 ) -> DispatchResultWithPostInfo {
483 T::UpdateOrigin::ensure_origin(origin)?;
484 let bond_increased = CandidacyBond::<T>::mutate(|old_bond| -> bool {
485 let bond_increased = *old_bond < bond;
486 *old_bond = bond;
487 bond_increased
488 });
489 let initial_len = CandidateList::<T>::decode_len().unwrap_or_default();
490 let kicked = (bond_increased && initial_len > 0)
491 .then(|| {
492 CandidateList::<T>::mutate(|candidates| -> usize {
495 let first_safe_candidate = candidates
496 .iter()
497 .position(|candidate| candidate.deposit >= bond)
498 .unwrap_or(initial_len);
499 let kicked_candidates = candidates.drain(..first_safe_candidate);
500 for candidate in kicked_candidates {
501 T::Currency::unreserve(&candidate.who, candidate.deposit);
502 LastAuthoredBlock::<T>::remove(candidate.who);
503 }
504 first_safe_candidate
505 })
506 })
507 .unwrap_or_default();
508 Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond });
509 Ok(Some(T::WeightInfo::set_candidacy_bond(
510 bond_increased.then(|| initial_len as u32).unwrap_or_default(),
511 kicked as u32,
512 ))
513 .into())
514 }
515
516 #[pallet::call_index(3)]
521 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))]
522 pub fn register_as_candidate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
523 let who = ensure_signed(origin)?;
524
525 let length: u32 = CandidateList::<T>::decode_len()
527 .unwrap_or_default()
528 .try_into()
529 .unwrap_or_default();
530 ensure!(length < T::MaxCandidates::get(), Error::<T>::TooManyCandidates);
531 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
532
533 let validator_key = T::ValidatorIdOf::convert(who.clone())
534 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
535 ensure!(
536 T::ValidatorRegistration::is_registered(&validator_key),
537 Error::<T>::ValidatorNotRegistered
538 );
539
540 let deposit = CandidacyBond::<T>::get();
541 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
543 ensure!(
544 !candidates.iter().any(|candidate_info| candidate_info.who == who),
545 Error::<T>::AlreadyCandidate
546 );
547 T::Currency::reserve(&who, deposit)?;
548 LastAuthoredBlock::<T>::insert(
549 who.clone(),
550 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
551 );
552 candidates
553 .try_insert(0, CandidateInfo { who: who.clone(), deposit })
554 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
555 Ok(())
556 })?;
557
558 Self::deposit_event(Event::CandidateAdded { account_id: who, deposit });
559 Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
563 }
564
565 #[pallet::call_index(4)]
571 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
572 pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
573 let who = ensure_signed(origin)?;
574 ensure!(
575 Self::eligible_collators() > T::MinEligibleCollators::get(),
576 Error::<T>::TooFewEligibleCollators
577 );
578 let length = CandidateList::<T>::decode_len().unwrap_or_default();
579 Self::try_remove_candidate(&who, true)?;
581
582 Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
583 }
584
585 #[pallet::call_index(5)]
590 #[pallet::weight(T::WeightInfo::add_invulnerable(
591 T::MaxInvulnerables::get().saturating_sub(1),
592 T::MaxCandidates::get()
593 ))]
594 pub fn add_invulnerable(
595 origin: OriginFor<T>,
596 who: T::AccountId,
597 ) -> DispatchResultWithPostInfo {
598 T::UpdateOrigin::ensure_origin(origin)?;
599
600 let validator_key = T::ValidatorIdOf::convert(who.clone())
602 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
603 ensure!(
604 T::ValidatorRegistration::is_registered(&validator_key),
605 Error::<T>::ValidatorNotRegistered
606 );
607
608 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
609 match invulnerables.binary_search(&who) {
610 Ok(_) => return Err(Error::<T>::AlreadyInvulnerable)?,
611 Err(pos) => invulnerables
612 .try_insert(pos, who.clone())
613 .map_err(|_| Error::<T>::TooManyInvulnerables)?,
614 }
615 Ok(())
616 })?;
617
618 let _ = Self::try_remove_candidate(&who, false);
621
622 Self::deposit_event(Event::InvulnerableAdded { account_id: who });
623
624 let weight_used = T::WeightInfo::add_invulnerable(
625 Invulnerables::<T>::decode_len()
626 .unwrap_or_default()
627 .try_into()
628 .unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)),
629 CandidateList::<T>::decode_len()
630 .unwrap_or_default()
631 .try_into()
632 .unwrap_or(T::MaxCandidates::get()),
633 );
634
635 Ok(Some(weight_used).into())
636 }
637
638 #[pallet::call_index(6)]
643 #[pallet::weight(T::WeightInfo::remove_invulnerable(T::MaxInvulnerables::get()))]
644 pub fn remove_invulnerable(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
645 T::UpdateOrigin::ensure_origin(origin)?;
646
647 ensure!(
648 Self::eligible_collators() > T::MinEligibleCollators::get(),
649 Error::<T>::TooFewEligibleCollators
650 );
651
652 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
653 let pos =
654 invulnerables.binary_search(&who).map_err(|_| Error::<T>::NotInvulnerable)?;
655 invulnerables.remove(pos);
656 Ok(())
657 })?;
658
659 Self::deposit_event(Event::InvulnerableRemoved { account_id: who });
660 Ok(())
661 }
662
663 #[pallet::call_index(7)]
671 #[pallet::weight(T::WeightInfo::update_bond(T::MaxCandidates::get()))]
672 pub fn update_bond(
673 origin: OriginFor<T>,
674 new_deposit: BalanceOf<T>,
675 ) -> DispatchResultWithPostInfo {
676 let who = ensure_signed(origin)?;
677 ensure!(new_deposit >= CandidacyBond::<T>::get(), Error::<T>::DepositTooLow);
678 let length =
682 CandidateList::<T>::try_mutate(|candidates| -> Result<usize, DispatchError> {
683 let idx = candidates
684 .iter()
685 .position(|candidate_info| candidate_info.who == who)
686 .ok_or_else(|| Error::<T>::NotCandidate)?;
687 let candidate_count = candidates.len();
688 let mut info = candidates.remove(idx);
690 let old_deposit = info.deposit;
691 if new_deposit > old_deposit {
692 T::Currency::reserve(&who, new_deposit - old_deposit)?;
693 } else if new_deposit < old_deposit {
694 ensure!(
696 idx.saturating_add(DesiredCandidates::<T>::get() as usize) <
697 candidate_count,
698 Error::<T>::InvalidUnreserve
699 );
700 T::Currency::unreserve(&who, old_deposit - new_deposit);
701 } else {
702 return Err(Error::<T>::IdenticalDeposit.into())
703 }
704
705 info.deposit = new_deposit;
707 let new_pos = candidates
708 .iter()
709 .position(|candidate| candidate.deposit >= new_deposit)
710 .unwrap_or_else(|| candidates.len());
711 candidates
712 .try_insert(new_pos, info)
713 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
714
715 Ok(candidate_count)
716 })?;
717
718 Self::deposit_event(Event::CandidateBondUpdated {
719 account_id: who,
720 deposit: new_deposit,
721 });
722 Ok(Some(T::WeightInfo::update_bond(length as u32)).into())
723 }
724
725 #[pallet::call_index(8)]
733 #[pallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))]
734 pub fn take_candidate_slot(
735 origin: OriginFor<T>,
736 deposit: BalanceOf<T>,
737 target: T::AccountId,
738 ) -> DispatchResultWithPostInfo {
739 let who = ensure_signed(origin)?;
740
741 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
742 ensure!(deposit >= CandidacyBond::<T>::get(), Error::<T>::InsufficientBond);
743
744 let validator_key = T::ValidatorIdOf::convert(who.clone())
745 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
746 ensure!(
747 T::ValidatorRegistration::is_registered(&validator_key),
748 Error::<T>::ValidatorNotRegistered
749 );
750
751 let length = CandidateList::<T>::decode_len().unwrap_or_default();
752 let target_info = CandidateList::<T>::try_mutate(
757 |candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
758 let mut target_info_idx = None;
760 let mut new_info_idx = None;
761 for (idx, candidate_info) in candidates.iter().enumerate() {
762 ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
766 if candidate_info.who == target {
770 target_info_idx = Some(idx);
771 }
772 if new_info_idx.is_none() && candidate_info.deposit >= deposit {
775 new_info_idx = Some(idx);
776 }
777 }
778 let target_info_idx =
779 target_info_idx.ok_or(Error::<T>::TargetIsNotCandidate)?;
780
781 let target_info = candidates.remove(target_info_idx);
783 ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
784
785 let new_pos = new_info_idx
788 .map(|i| i.saturating_sub(1))
789 .unwrap_or_else(|| candidates.len());
790 let new_info = CandidateInfo { who: who.clone(), deposit };
791 candidates
793 .try_insert(new_pos, new_info)
794 .expect("candidate count previously decremented; qed");
795
796 Ok(target_info)
797 },
798 )?;
799 T::Currency::reserve(&who, deposit)?;
800 T::Currency::unreserve(&target_info.who, target_info.deposit);
801 LastAuthoredBlock::<T>::remove(target_info.who.clone());
802 LastAuthoredBlock::<T>::insert(
803 who.clone(),
804 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
805 );
806
807 Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit });
808 Ok(Some(T::WeightInfo::take_candidate_slot(length as u32)).into())
809 }
810 }
811
812 impl<T: Config> Pallet<T> {
813 pub fn account_id() -> T::AccountId {
815 T::PotId::get().into_account_truncating()
816 }
817
818 fn eligible_collators() -> u32 {
821 CandidateList::<T>::decode_len()
822 .unwrap_or_default()
823 .saturating_add(Invulnerables::<T>::decode_len().unwrap_or_default())
824 .try_into()
825 .unwrap_or(u32::MAX)
826 }
827
828 fn try_remove_candidate(
830 who: &T::AccountId,
831 remove_last_authored: bool,
832 ) -> Result<(), DispatchError> {
833 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
834 let idx = candidates
835 .iter()
836 .position(|candidate_info| candidate_info.who == *who)
837 .ok_or(Error::<T>::NotCandidate)?;
838 let deposit = candidates[idx].deposit;
839 T::Currency::unreserve(who, deposit);
840 candidates.remove(idx);
841 if remove_last_authored {
842 LastAuthoredBlock::<T>::remove(who.clone())
843 };
844 Ok(())
845 })?;
846 Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() });
847 Ok(())
848 }
849
850 pub fn assemble_collators() -> Vec<T::AccountId> {
854 let desired_candidates = DesiredCandidates::<T>::get() as usize;
856 let mut collators = Invulnerables::<T>::get().to_vec();
857 collators.extend(
858 CandidateList::<T>::get()
859 .iter()
860 .rev()
861 .cloned()
862 .take(desired_candidates)
863 .map(|candidate_info| candidate_info.who),
864 );
865 collators
866 }
867
868 pub fn kick_stale_candidates(candidates: impl IntoIterator<Item = T::AccountId>) -> u32 {
873 let now = frame_system::Pallet::<T>::block_number();
874 let kick_threshold = T::KickThreshold::get();
875 let min_collators = T::MinEligibleCollators::get();
876 candidates
877 .into_iter()
878 .filter_map(|c| {
879 let last_block = LastAuthoredBlock::<T>::get(c.clone());
880 let since_last = now.saturating_sub(last_block);
881
882 let is_invulnerable = Invulnerables::<T>::get().contains(&c);
883 let is_lazy = since_last >= kick_threshold;
884
885 if is_invulnerable {
886 let _ = Self::try_remove_candidate(&c, false);
890 None
891 } else {
892 if Self::eligible_collators() <= min_collators || !is_lazy {
893 Some(c)
896 } else {
897 let _ = Self::try_remove_candidate(&c, true);
899 None
900 }
901 }
902 })
903 .count()
904 .try_into()
905 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")
906 }
907
908 #[cfg(any(test, feature = "try-runtime"))]
920 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
921 let desired_candidates = DesiredCandidates::<T>::get();
922
923 frame_support::ensure!(
924 desired_candidates <= T::MaxCandidates::get(),
925 "Shouldn't demand more candidates than the pallet config allows."
926 );
927
928 frame_support::ensure!(
929 desired_candidates.saturating_add(T::MaxInvulnerables::get()) >=
930 T::MinEligibleCollators::get(),
931 "Invulnerable set together with desired candidates should be able to meet the collator quota."
932 );
933
934 Ok(())
935 }
936 }
937
938 impl<T: Config + pallet_authorship::Config>
941 pallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pallet<T>
942 {
943 fn note_author(author: T::AccountId) {
944 let pot = Self::account_id();
945 let reward = T::Currency::free_balance(&pot)
947 .checked_sub(&T::Currency::minimum_balance())
948 .unwrap_or_else(Zero::zero)
949 .div(2u32.into());
950 let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);
952 debug_assert!(_success.is_ok());
953 LastAuthoredBlock::<T>::insert(author, frame_system::Pallet::<T>::block_number());
954
955 frame_system::Pallet::<T>::register_extra_weight_unchecked(
956 T::WeightInfo::note_author(),
957 DispatchClass::Mandatory,
958 );
959 }
960 }
961
962 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {
964 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {
965 log::info!(
966 "assembling new collators for new session {} at #{:?}",
967 index,
968 <frame_system::Pallet<T>>::block_number(),
969 );
970
971 let candidates_len_before: u32 = CandidateList::<T>::decode_len()
975 .unwrap_or_default()
976 .try_into()
977 .expect("length is at most `T::MaxCandidates`, so it must fit in `u32`; qed");
978 let active_candidates_count = Self::kick_stale_candidates(
979 CandidateList::<T>::get()
980 .iter()
981 .map(|candidate_info| candidate_info.who.clone()),
982 );
983 let removed = candidates_len_before.saturating_sub(active_candidates_count);
984 let result = Self::assemble_collators();
985
986 frame_system::Pallet::<T>::register_extra_weight_unchecked(
987 T::WeightInfo::new_session(removed, candidates_len_before),
988 DispatchClass::Mandatory,
989 );
990 Some(result)
991 }
992 fn start_session(_: SessionIndex) {
993 }
995 fn end_session(_: SessionIndex) {
996 }
998 }
999}
1000
1001pub struct StakingPotAccountId<R>(PhantomData<R>);
1003impl<R> TypedGet for StakingPotAccountId<R>
1004where
1005 R: crate::Config,
1006{
1007 type Type = <R as frame_system::Config>::AccountId;
1008 fn get() -> Self::Type {
1009 <crate::Pallet<R>>::account_id()
1010 }
1011}