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