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 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
145
146 type Currency: ReservableCurrency<Self::AccountId>;
148
149 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
151
152 type PotId: Get<PalletId>;
154
155 type MaxCandidates: Get<u32>;
159
160 type MinEligibleCollators: Get<u32>;
164
165 type MaxInvulnerables: Get<u32>;
167
168 type KickThreshold: Get<BlockNumberFor<Self>>;
170
171 type ValidatorId: Member + Parameter;
173
174 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
178
179 type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
181
182 type WeightInfo: WeightInfo;
184 }
185
186 #[derive(
188 PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
189 )]
190 pub struct CandidateInfo<AccountId, Balance> {
191 pub who: AccountId,
193 pub deposit: Balance,
195 }
196
197 #[pallet::pallet]
198 #[pallet::storage_version(STORAGE_VERSION)]
199 pub struct Pallet<T>(_);
200
201 #[pallet::storage]
203 pub type Invulnerables<T: Config> =
204 StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
205
206 #[pallet::storage]
212 pub type CandidateList<T: Config> = StorageValue<
213 _,
214 BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
215 ValueQuery,
216 >;
217
218 #[pallet::storage]
220 pub type LastAuthoredBlock<T: Config> =
221 StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
222
223 #[pallet::storage]
227 pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
228
229 #[pallet::storage]
233 pub type CandidacyBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
234
235 #[pallet::genesis_config]
236 #[derive(DefaultNoBound)]
237 pub struct GenesisConfig<T: Config> {
238 pub invulnerables: Vec<T::AccountId>,
239 pub candidacy_bond: BalanceOf<T>,
240 pub desired_candidates: u32,
241 }
242
243 #[pallet::genesis_build]
244 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
245 fn build(&self) {
246 let duplicate_invulnerables = self
247 .invulnerables
248 .iter()
249 .collect::<alloc::collections::btree_set::BTreeSet<_>>();
250 assert!(
251 duplicate_invulnerables.len() == self.invulnerables.len(),
252 "duplicate invulnerables in genesis."
253 );
254
255 let mut bounded_invulnerables =
256 BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())
257 .expect("genesis invulnerables are more than T::MaxInvulnerables");
258 assert!(
259 T::MaxCandidates::get() >= self.desired_candidates,
260 "genesis desired_candidates are more than T::MaxCandidates",
261 );
262
263 bounded_invulnerables.sort();
264
265 DesiredCandidates::<T>::put(self.desired_candidates);
266 CandidacyBond::<T>::put(self.candidacy_bond);
267 Invulnerables::<T>::put(bounded_invulnerables);
268 }
269 }
270
271 #[pallet::event]
272 #[pallet::generate_deposit(pub(super) fn deposit_event)]
273 pub enum Event<T: Config> {
274 NewInvulnerables { invulnerables: Vec<T::AccountId> },
276 InvulnerableAdded { account_id: T::AccountId },
278 InvulnerableRemoved { account_id: T::AccountId },
280 NewDesiredCandidates { desired_candidates: u32 },
282 NewCandidacyBond { bond_amount: BalanceOf<T> },
284 CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
286 CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
288 CandidateRemoved { account_id: T::AccountId },
290 CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
292 InvalidInvulnerableSkipped { account_id: T::AccountId },
295 }
296
297 #[pallet::error]
298 pub enum Error<T> {
299 TooManyCandidates,
301 TooFewEligibleCollators,
303 AlreadyCandidate,
305 NotCandidate,
307 TooManyInvulnerables,
309 AlreadyInvulnerable,
311 NotInvulnerable,
313 NoAssociatedValidatorId,
315 ValidatorNotRegistered,
317 InsertToCandidateListFailed,
319 RemoveFromCandidateListFailed,
321 DepositTooLow,
323 UpdateCandidateListFailed,
325 InsufficientBond,
327 TargetIsNotCandidate,
329 IdenticalDeposit,
331 InvalidUnreserve,
333 }
334
335 #[pallet::hooks]
336 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
337 fn integrity_test() {
338 assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator");
339 assert!(
340 T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get()) >=
341 T::MinEligibleCollators::get(),
342 "invulnerables and candidates must be able to satisfy collator demand"
343 );
344 }
345
346 #[cfg(feature = "try-runtime")]
347 fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
348 Self::do_try_state()
349 }
350 }
351
352 #[pallet::call]
353 impl<T: Config> Pallet<T> {
354 #[pallet::call_index(0)]
368 #[pallet::weight(T::WeightInfo::set_invulnerables(new.len() as u32))]
369 pub fn set_invulnerables(origin: OriginFor<T>, new: Vec<T::AccountId>) -> DispatchResult {
370 T::UpdateOrigin::ensure_origin(origin)?;
371
372 if new.is_empty() {
374 ensure!(
376 CandidateList::<T>::decode_len().unwrap_or_default() >=
377 T::MinEligibleCollators::get() as usize,
378 Error::<T>::TooFewEligibleCollators
379 );
380 }
381
382 ensure!(
385 new.len() as u32 <= T::MaxInvulnerables::get(),
386 Error::<T>::TooManyInvulnerables
387 );
388
389 let mut new_with_keys = Vec::new();
390
391 for account_id in &new {
393 let validator_key = T::ValidatorIdOf::convert(account_id.clone());
395 match validator_key {
396 Some(key) => {
397 if !T::ValidatorRegistration::is_registered(&key) {
399 Self::deposit_event(Event::InvalidInvulnerableSkipped {
400 account_id: account_id.clone(),
401 });
402 continue
403 }
404 },
406 None => {
408 Self::deposit_event(Event::InvalidInvulnerableSkipped {
409 account_id: account_id.clone(),
410 });
411 continue
412 },
413 }
414
415 new_with_keys.push(account_id.clone());
416 }
417
418 let mut bounded_invulnerables =
420 BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
421 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
422
423 bounded_invulnerables.sort();
425
426 Invulnerables::<T>::put(&bounded_invulnerables);
427 Self::deposit_event(Event::NewInvulnerables {
428 invulnerables: bounded_invulnerables.to_vec(),
429 });
430
431 Ok(())
432 }
433
434 #[pallet::call_index(1)]
440 #[pallet::weight(T::WeightInfo::set_desired_candidates())]
441 pub fn set_desired_candidates(
442 origin: OriginFor<T>,
443 max: u32,
444 ) -> DispatchResultWithPostInfo {
445 T::UpdateOrigin::ensure_origin(origin)?;
446 if max > T::MaxCandidates::get() {
448 log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");
449 }
450 DesiredCandidates::<T>::put(max);
451 Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max });
452 Ok(().into())
453 }
454
455 #[pallet::call_index(2)]
463 #[pallet::weight(T::WeightInfo::set_candidacy_bond(
464 T::MaxCandidates::get(),
465 T::MaxCandidates::get()
466 ))]
467 pub fn set_candidacy_bond(
468 origin: OriginFor<T>,
469 bond: BalanceOf<T>,
470 ) -> DispatchResultWithPostInfo {
471 T::UpdateOrigin::ensure_origin(origin)?;
472 let bond_increased = CandidacyBond::<T>::mutate(|old_bond| -> bool {
473 let bond_increased = *old_bond < bond;
474 *old_bond = bond;
475 bond_increased
476 });
477 let initial_len = CandidateList::<T>::decode_len().unwrap_or_default();
478 let kicked = (bond_increased && initial_len > 0)
479 .then(|| {
480 CandidateList::<T>::mutate(|candidates| -> usize {
483 let first_safe_candidate = candidates
484 .iter()
485 .position(|candidate| candidate.deposit >= bond)
486 .unwrap_or(initial_len);
487 let kicked_candidates = candidates.drain(..first_safe_candidate);
488 for candidate in kicked_candidates {
489 T::Currency::unreserve(&candidate.who, candidate.deposit);
490 LastAuthoredBlock::<T>::remove(candidate.who);
491 }
492 first_safe_candidate
493 })
494 })
495 .unwrap_or_default();
496 Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond });
497 Ok(Some(T::WeightInfo::set_candidacy_bond(
498 bond_increased.then(|| initial_len as u32).unwrap_or_default(),
499 kicked as u32,
500 ))
501 .into())
502 }
503
504 #[pallet::call_index(3)]
509 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))]
510 pub fn register_as_candidate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
511 let who = ensure_signed(origin)?;
512
513 let length: u32 = CandidateList::<T>::decode_len()
515 .unwrap_or_default()
516 .try_into()
517 .unwrap_or_default();
518 ensure!(length < T::MaxCandidates::get(), Error::<T>::TooManyCandidates);
519 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
520
521 let validator_key = T::ValidatorIdOf::convert(who.clone())
522 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
523 ensure!(
524 T::ValidatorRegistration::is_registered(&validator_key),
525 Error::<T>::ValidatorNotRegistered
526 );
527
528 let deposit = CandidacyBond::<T>::get();
529 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
531 ensure!(
532 !candidates.iter().any(|candidate_info| candidate_info.who == who),
533 Error::<T>::AlreadyCandidate
534 );
535 T::Currency::reserve(&who, deposit)?;
536 LastAuthoredBlock::<T>::insert(
537 who.clone(),
538 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
539 );
540 candidates
541 .try_insert(0, CandidateInfo { who: who.clone(), deposit })
542 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
543 Ok(())
544 })?;
545
546 Self::deposit_event(Event::CandidateAdded { account_id: who, deposit });
547 Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
551 }
552
553 #[pallet::call_index(4)]
559 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
560 pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
561 let who = ensure_signed(origin)?;
562 ensure!(
563 Self::eligible_collators() > T::MinEligibleCollators::get(),
564 Error::<T>::TooFewEligibleCollators
565 );
566 let length = CandidateList::<T>::decode_len().unwrap_or_default();
567 Self::try_remove_candidate(&who, true)?;
569
570 Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
571 }
572
573 #[pallet::call_index(5)]
578 #[pallet::weight(T::WeightInfo::add_invulnerable(
579 T::MaxInvulnerables::get().saturating_sub(1),
580 T::MaxCandidates::get()
581 ))]
582 pub fn add_invulnerable(
583 origin: OriginFor<T>,
584 who: T::AccountId,
585 ) -> DispatchResultWithPostInfo {
586 T::UpdateOrigin::ensure_origin(origin)?;
587
588 let validator_key = T::ValidatorIdOf::convert(who.clone())
590 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
591 ensure!(
592 T::ValidatorRegistration::is_registered(&validator_key),
593 Error::<T>::ValidatorNotRegistered
594 );
595
596 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
597 match invulnerables.binary_search(&who) {
598 Ok(_) => return Err(Error::<T>::AlreadyInvulnerable)?,
599 Err(pos) => invulnerables
600 .try_insert(pos, who.clone())
601 .map_err(|_| Error::<T>::TooManyInvulnerables)?,
602 }
603 Ok(())
604 })?;
605
606 let _ = Self::try_remove_candidate(&who, false);
609
610 Self::deposit_event(Event::InvulnerableAdded { account_id: who });
611
612 let weight_used = T::WeightInfo::add_invulnerable(
613 Invulnerables::<T>::decode_len()
614 .unwrap_or_default()
615 .try_into()
616 .unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)),
617 CandidateList::<T>::decode_len()
618 .unwrap_or_default()
619 .try_into()
620 .unwrap_or(T::MaxCandidates::get()),
621 );
622
623 Ok(Some(weight_used).into())
624 }
625
626 #[pallet::call_index(6)]
631 #[pallet::weight(T::WeightInfo::remove_invulnerable(T::MaxInvulnerables::get()))]
632 pub fn remove_invulnerable(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
633 T::UpdateOrigin::ensure_origin(origin)?;
634
635 ensure!(
636 Self::eligible_collators() > T::MinEligibleCollators::get(),
637 Error::<T>::TooFewEligibleCollators
638 );
639
640 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
641 let pos =
642 invulnerables.binary_search(&who).map_err(|_| Error::<T>::NotInvulnerable)?;
643 invulnerables.remove(pos);
644 Ok(())
645 })?;
646
647 Self::deposit_event(Event::InvulnerableRemoved { account_id: who });
648 Ok(())
649 }
650
651 #[pallet::call_index(7)]
659 #[pallet::weight(T::WeightInfo::update_bond(T::MaxCandidates::get()))]
660 pub fn update_bond(
661 origin: OriginFor<T>,
662 new_deposit: BalanceOf<T>,
663 ) -> DispatchResultWithPostInfo {
664 let who = ensure_signed(origin)?;
665 ensure!(new_deposit >= CandidacyBond::<T>::get(), Error::<T>::DepositTooLow);
666 let length =
670 CandidateList::<T>::try_mutate(|candidates| -> Result<usize, DispatchError> {
671 let idx = candidates
672 .iter()
673 .position(|candidate_info| candidate_info.who == who)
674 .ok_or_else(|| Error::<T>::NotCandidate)?;
675 let candidate_count = candidates.len();
676 let mut info = candidates.remove(idx);
678 let old_deposit = info.deposit;
679 if new_deposit > old_deposit {
680 T::Currency::reserve(&who, new_deposit - old_deposit)?;
681 } else if new_deposit < old_deposit {
682 ensure!(
684 idx.saturating_add(DesiredCandidates::<T>::get() as usize) <
685 candidate_count,
686 Error::<T>::InvalidUnreserve
687 );
688 T::Currency::unreserve(&who, old_deposit - new_deposit);
689 } else {
690 return Err(Error::<T>::IdenticalDeposit.into())
691 }
692
693 info.deposit = new_deposit;
695 let new_pos = candidates
696 .iter()
697 .position(|candidate| candidate.deposit >= new_deposit)
698 .unwrap_or_else(|| candidates.len());
699 candidates
700 .try_insert(new_pos, info)
701 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
702
703 Ok(candidate_count)
704 })?;
705
706 Self::deposit_event(Event::CandidateBondUpdated {
707 account_id: who,
708 deposit: new_deposit,
709 });
710 Ok(Some(T::WeightInfo::update_bond(length as u32)).into())
711 }
712
713 #[pallet::call_index(8)]
721 #[pallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))]
722 pub fn take_candidate_slot(
723 origin: OriginFor<T>,
724 deposit: BalanceOf<T>,
725 target: T::AccountId,
726 ) -> DispatchResultWithPostInfo {
727 let who = ensure_signed(origin)?;
728
729 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
730 ensure!(deposit >= CandidacyBond::<T>::get(), Error::<T>::InsufficientBond);
731
732 let validator_key = T::ValidatorIdOf::convert(who.clone())
733 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
734 ensure!(
735 T::ValidatorRegistration::is_registered(&validator_key),
736 Error::<T>::ValidatorNotRegistered
737 );
738
739 let length = CandidateList::<T>::decode_len().unwrap_or_default();
740 let target_info = CandidateList::<T>::try_mutate(
745 |candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
746 let mut target_info_idx = None;
748 let mut new_info_idx = None;
749 for (idx, candidate_info) in candidates.iter().enumerate() {
750 ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
754 if candidate_info.who == target {
758 target_info_idx = Some(idx);
759 }
760 if new_info_idx.is_none() && candidate_info.deposit >= deposit {
763 new_info_idx = Some(idx);
764 }
765 }
766 let target_info_idx =
767 target_info_idx.ok_or(Error::<T>::TargetIsNotCandidate)?;
768
769 let target_info = candidates.remove(target_info_idx);
771 ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
772
773 let new_pos = new_info_idx
776 .map(|i| i.saturating_sub(1))
777 .unwrap_or_else(|| candidates.len());
778 let new_info = CandidateInfo { who: who.clone(), deposit };
779 candidates
781 .try_insert(new_pos, new_info)
782 .expect("candidate count previously decremented; qed");
783
784 Ok(target_info)
785 },
786 )?;
787 T::Currency::reserve(&who, deposit)?;
788 T::Currency::unreserve(&target_info.who, target_info.deposit);
789 LastAuthoredBlock::<T>::remove(target_info.who.clone());
790 LastAuthoredBlock::<T>::insert(
791 who.clone(),
792 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
793 );
794
795 Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit });
796 Ok(Some(T::WeightInfo::take_candidate_slot(length as u32)).into())
797 }
798 }
799
800 impl<T: Config> Pallet<T> {
801 pub fn account_id() -> T::AccountId {
803 T::PotId::get().into_account_truncating()
804 }
805
806 fn eligible_collators() -> u32 {
809 CandidateList::<T>::decode_len()
810 .unwrap_or_default()
811 .saturating_add(Invulnerables::<T>::decode_len().unwrap_or_default())
812 .try_into()
813 .unwrap_or(u32::MAX)
814 }
815
816 fn try_remove_candidate(
818 who: &T::AccountId,
819 remove_last_authored: bool,
820 ) -> Result<(), DispatchError> {
821 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
822 let idx = candidates
823 .iter()
824 .position(|candidate_info| candidate_info.who == *who)
825 .ok_or(Error::<T>::NotCandidate)?;
826 let deposit = candidates[idx].deposit;
827 T::Currency::unreserve(who, deposit);
828 candidates.remove(idx);
829 if remove_last_authored {
830 LastAuthoredBlock::<T>::remove(who.clone())
831 };
832 Ok(())
833 })?;
834 Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() });
835 Ok(())
836 }
837
838 pub fn assemble_collators() -> Vec<T::AccountId> {
842 let desired_candidates = DesiredCandidates::<T>::get() as usize;
844 let mut collators = Invulnerables::<T>::get().to_vec();
845 collators.extend(
846 CandidateList::<T>::get()
847 .iter()
848 .rev()
849 .cloned()
850 .take(desired_candidates)
851 .map(|candidate_info| candidate_info.who),
852 );
853 collators
854 }
855
856 pub fn kick_stale_candidates(candidates: impl IntoIterator<Item = T::AccountId>) -> u32 {
861 let now = frame_system::Pallet::<T>::block_number();
862 let kick_threshold = T::KickThreshold::get();
863 let min_collators = T::MinEligibleCollators::get();
864 candidates
865 .into_iter()
866 .filter_map(|c| {
867 let last_block = LastAuthoredBlock::<T>::get(c.clone());
868 let since_last = now.saturating_sub(last_block);
869
870 let is_invulnerable = Invulnerables::<T>::get().contains(&c);
871 let is_lazy = since_last >= kick_threshold;
872
873 if is_invulnerable {
874 let _ = Self::try_remove_candidate(&c, false);
878 None
879 } else {
880 if Self::eligible_collators() <= min_collators || !is_lazy {
881 Some(c)
884 } else {
885 let _ = Self::try_remove_candidate(&c, true);
887 None
888 }
889 }
890 })
891 .count()
892 .try_into()
893 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")
894 }
895
896 #[cfg(any(test, feature = "try-runtime"))]
908 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
909 let desired_candidates = DesiredCandidates::<T>::get();
910
911 frame_support::ensure!(
912 desired_candidates <= T::MaxCandidates::get(),
913 "Shouldn't demand more candidates than the pallet config allows."
914 );
915
916 frame_support::ensure!(
917 desired_candidates.saturating_add(T::MaxInvulnerables::get()) >=
918 T::MinEligibleCollators::get(),
919 "Invulnerable set together with desired candidates should be able to meet the collator quota."
920 );
921
922 Ok(())
923 }
924 }
925
926 impl<T: Config + pallet_authorship::Config>
929 pallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pallet<T>
930 {
931 fn note_author(author: T::AccountId) {
932 let pot = Self::account_id();
933 let reward = T::Currency::free_balance(&pot)
935 .checked_sub(&T::Currency::minimum_balance())
936 .unwrap_or_else(Zero::zero)
937 .div(2u32.into());
938 let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);
940 debug_assert!(_success.is_ok());
941 LastAuthoredBlock::<T>::insert(author, frame_system::Pallet::<T>::block_number());
942
943 frame_system::Pallet::<T>::register_extra_weight_unchecked(
944 T::WeightInfo::note_author(),
945 DispatchClass::Mandatory,
946 );
947 }
948 }
949
950 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {
952 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {
953 log::info!(
954 "assembling new collators for new session {} at #{:?}",
955 index,
956 <frame_system::Pallet<T>>::block_number(),
957 );
958
959 let candidates_len_before: u32 = CandidateList::<T>::decode_len()
963 .unwrap_or_default()
964 .try_into()
965 .expect("length is at most `T::MaxCandidates`, so it must fit in `u32`; qed");
966 let active_candidates_count = Self::kick_stale_candidates(
967 CandidateList::<T>::get()
968 .iter()
969 .map(|candidate_info| candidate_info.who.clone()),
970 );
971 let removed = candidates_len_before.saturating_sub(active_candidates_count);
972 let result = Self::assemble_collators();
973
974 frame_system::Pallet::<T>::register_extra_weight_unchecked(
975 T::WeightInfo::new_session(removed, candidates_len_before),
976 DispatchClass::Mandatory,
977 );
978 Some(result)
979 }
980 fn start_session(_: SessionIndex) {
981 }
983 fn end_session(_: SessionIndex) {
984 }
986 }
987}
988
989pub struct StakingPotAccountId<R>(PhantomData<R>);
991impl<R> TypedGet for StakingPotAccountId<R>
992where
993 R: crate::Config,
994{
995 type Type = <R as frame_system::Config>::AccountId;
996 fn get() -> Self::Type {
997 <crate::Pallet<R>>::account_id()
998 }
999}