pallet_collator_selection/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Collator Selection pallet.
17//!
18//! A pallet to manage collators in a parachain.
19//!
20//! ## Overview
21//!
22//! The Collator Selection pallet manages the collators of a parachain. **Collation is _not_ a
23//! secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT
24//! safety assumptions of the chosen set.
25//!
26//! ## Terminology
27//!
28//! - Collator: A parachain block producer.
29//! - Bond: An amount of `Balance` _reserved_ for candidate registration.
30//! - Invulnerable: An account guaranteed to be in the collator set.
31//!
32//! ## Implementation
33//!
34//! The final `Collators` are aggregated from two individual lists:
35//!
36//! 1. [`Invulnerables`]: a set of collators appointed by governance. These accounts will always be
37//!    collators.
38//! 2. [`CandidateList`]: these are *candidates to the collation task* and may or may not be elected
39//!    as a final collator.
40//!
41//! The current implementation resolves congestion of [`CandidateList`] through a simple auction
42//! mechanism. Candidates bid for the collator slots and at the end of the session, the auction ends
43//! and the top candidates are selected to become collators. The number of selected candidates is
44//! determined by the value of `DesiredCandidates`.
45//!
46//! Before the list reaches full capacity, candidates can register by placing the minimum bond
47//! through `register_as_candidate`. Then, if an account wants to participate in the collator slot
48//! auction, they have to replace an existing candidate by placing a greater deposit through
49//! `take_candidate_slot`. Existing candidates can increase their bids through `update_bond`.
50//!
51//! At any point, an account can take the place of another account in the candidate list if they put
52//! up a greater deposit than the target. While new joiners would like to deposit as little as
53//! possible to participate in the auction, the replacement threat incentivizes candidates to bid as
54//! close to their budget as possible in order to avoid being replaced.
55//!
56//! Candidates which are not on "winning" slots in the list can also decrease their deposits through
57//! `update_bond`, but candidates who are on top slots and try to decrease their deposits will fail
58//! in order to enforce auction mechanics and have meaningful bids.
59//!
60//! Candidates will not be allowed to get kicked or `leave_intent` if the total number of collators
61//! would fall below `MinEligibleCollators`. This is to ensure that some collators will always
62//! exist, i.e. someone is eligible to produce a block.
63//!
64//! When a new session starts, candidates with the highest deposits will be selected in order until
65//! the desired number of collators is reached. Candidates can increase or decrease their deposits
66//! between sessions in order to ensure they receive a slot in the collator list.
67//!
68//! ### Rewards
69//!
70//! The Collator Selection pallet maintains an on-chain account (the "Pot"). In each block, the
71//! collator who authored it receives:
72//!
73//! - Half the value of the Pot.
74//! - Half the value of the transaction fees within the block. The other half of the transaction
75//!   fees are deposited into the Pot.
76//!
77//! To initiate rewards, an ED needs to be transferred to the pot address.
78//!
79//! Note: Eventually the Pot distribution may be modified as discussed in [this
80//! issue](https://github.com/paritytech/statemint/issues/21#issuecomment-810481073).
81
82#![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	/// The in-code storage version.
126	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	/// A convertor from collators id. Since this pallet does not have stash/controller, this is
132	/// just identity.
133	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	/// Configure the pallet by specifying the parameters and types on which it depends.
141	#[pallet::config]
142	pub trait Config: frame_system::Config {
143		/// Overarching event type.
144		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
145
146		/// The currency mechanism.
147		type Currency: ReservableCurrency<Self::AccountId>;
148
149		/// Origin that can dictate updating parameters of this pallet.
150		type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
151
152		/// Account Identifier from which the internal Pot is generated.
153		#[pallet::constant]
154		type PotId: Get<PalletId>;
155
156		/// Maximum number of candidates that we should have.
157		///
158		/// This does not take into account the invulnerables.
159		#[pallet::constant]
160		type MaxCandidates: Get<u32>;
161
162		/// Minimum number eligible collators. Should always be greater than zero. This includes
163		/// Invulnerable collators. This ensures that there will always be one collator who can
164		/// produce a block.
165		#[pallet::constant]
166		type MinEligibleCollators: Get<u32>;
167
168		/// Maximum number of invulnerables.
169		#[pallet::constant]
170		type MaxInvulnerables: Get<u32>;
171
172		// Will be kicked if block is not produced in threshold.
173		#[pallet::constant]
174		type KickThreshold: Get<BlockNumberFor<Self>>;
175
176		/// A stable ID for a validator.
177		type ValidatorId: Member + Parameter;
178
179		/// A conversion from account ID to validator ID.
180		///
181		/// Its cost must be at most one storage read.
182		type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
183
184		/// Validate a user is registered
185		type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
186
187		/// The weight information of this pallet.
188		type WeightInfo: WeightInfo;
189	}
190
191	#[pallet::extra_constants]
192	impl<T: Config> Pallet<T> {
193		/// Gets this pallet's derived pot account.
194		fn pot_account() -> T::AccountId {
195			Self::account_id()
196		}
197	}
198
199	/// Basic information about a collation candidate.
200	#[derive(
201		PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
202	)]
203	pub struct CandidateInfo<AccountId, Balance> {
204		/// Account identifier.
205		pub who: AccountId,
206		/// Reserved deposit.
207		pub deposit: Balance,
208	}
209
210	#[pallet::pallet]
211	#[pallet::storage_version(STORAGE_VERSION)]
212	pub struct Pallet<T>(_);
213
214	/// The invulnerable, permissioned collators. This list must be sorted.
215	#[pallet::storage]
216	pub type Invulnerables<T: Config> =
217		StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
218
219	/// The (community, limited) collation candidates. `Candidates` and `Invulnerables` should be
220	/// mutually exclusive.
221	///
222	/// This list is sorted in ascending order by deposit and when the deposits are equal, the least
223	/// recently updated is considered greater.
224	#[pallet::storage]
225	pub type CandidateList<T: Config> = StorageValue<
226		_,
227		BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
228		ValueQuery,
229	>;
230
231	/// Last block authored by collator.
232	#[pallet::storage]
233	pub type LastAuthoredBlock<T: Config> =
234		StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
235
236	/// Desired number of candidates.
237	///
238	/// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct.
239	#[pallet::storage]
240	pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
241
242	/// Fixed amount to deposit to become a collator.
243	///
244	/// When a collator calls `leave_intent` they immediately receive the deposit back.
245	#[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		/// New Invulnerables were set.
288		NewInvulnerables { invulnerables: Vec<T::AccountId> },
289		/// A new Invulnerable was added.
290		InvulnerableAdded { account_id: T::AccountId },
291		/// An Invulnerable was removed.
292		InvulnerableRemoved { account_id: T::AccountId },
293		/// The number of desired candidates was set.
294		NewDesiredCandidates { desired_candidates: u32 },
295		/// The candidacy bond was set.
296		NewCandidacyBond { bond_amount: BalanceOf<T> },
297		/// A new candidate joined.
298		CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
299		/// Bond of a candidate updated.
300		CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
301		/// A candidate was removed.
302		CandidateRemoved { account_id: T::AccountId },
303		/// An account was replaced in the candidate list by another one.
304		CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
305		/// An account was unable to be added to the Invulnerables because they did not have keys
306		/// registered. Other Invulnerables may have been set.
307		InvalidInvulnerableSkipped { account_id: T::AccountId },
308	}
309
310	#[pallet::error]
311	pub enum Error<T> {
312		/// The pallet has too many candidates.
313		TooManyCandidates,
314		/// Leaving would result in too few candidates.
315		TooFewEligibleCollators,
316		/// Account is already a candidate.
317		AlreadyCandidate,
318		/// Account is not a candidate.
319		NotCandidate,
320		/// There are too many Invulnerables.
321		TooManyInvulnerables,
322		/// Account is already an Invulnerable.
323		AlreadyInvulnerable,
324		/// Account is not an Invulnerable.
325		NotInvulnerable,
326		/// Account has no associated validator ID.
327		NoAssociatedValidatorId,
328		/// Validator ID is not yet registered.
329		ValidatorNotRegistered,
330		/// Could not insert in the candidate list.
331		InsertToCandidateListFailed,
332		/// Could not remove from the candidate list.
333		RemoveFromCandidateListFailed,
334		/// New deposit amount would be below the minimum candidacy bond.
335		DepositTooLow,
336		/// Could not update the candidate list.
337		UpdateCandidateListFailed,
338		/// Deposit amount is too low to take the target's slot in the candidate list.
339		InsufficientBond,
340		/// The target account to be replaced in the candidate list is not a candidate.
341		TargetIsNotCandidate,
342		/// The updated deposit amount is equal to the amount already reserved.
343		IdenticalDeposit,
344		/// Cannot lower candidacy bond while occupying a future collator slot in the list.
345		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		/// Set the list of invulnerable (fixed) collators. These collators must do some
368		/// preparation, namely to have registered session keys.
369		///
370		/// The call will remove any accounts that have not registered keys from the set. That is,
371		/// it is non-atomic; the caller accepts all `AccountId`s passed in `new` _individually_ as
372		/// acceptable Invulnerables, and is not proposing a _set_ of new Invulnerables.
373		///
374		/// This call does not maintain mutual exclusivity of `Invulnerables` and `Candidates`. It
375		/// is recommended to use a batch of `add_invulnerable` and `remove_invulnerable` instead. A
376		/// `batch_all` can also be used to enforce atomicity. If any candidates are included in
377		/// `new`, they should be removed with `remove_invulnerable_candidate` after execution.
378		///
379		/// Must be called by the `UpdateOrigin`.
380		#[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			// don't wipe out the collator set
386			if new.is_empty() {
387				// Casting `u32` to `usize` should be safe on all machines running this.
388				ensure!(
389					CandidateList::<T>::decode_len().unwrap_or_default() >=
390						T::MinEligibleCollators::get() as usize,
391					Error::<T>::TooFewEligibleCollators
392				);
393			}
394
395			// Will need to check the length again when putting into a bounded vec, but this
396			// prevents the iterator from having too many elements.
397			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			// check if the invulnerables have associated validator keys before they are set
405			for account_id in &new {
406				// don't let one unprepared collator ruin things for everyone.
407				let validator_key = T::ValidatorIdOf::convert(account_id.clone());
408				match validator_key {
409					Some(key) => {
410						// key is not registered
411						if !T::ValidatorRegistration::is_registered(&key) {
412							Self::deposit_event(Event::InvalidInvulnerableSkipped {
413								account_id: account_id.clone(),
414							});
415							continue
416						}
417						// else condition passes; key is registered
418					},
419					// key does not exist
420					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			// should never fail since `new_with_keys` must be equal to or shorter than `new`
432			let mut bounded_invulnerables =
433				BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
434					.map_err(|_| Error::<T>::TooManyInvulnerables)?;
435
436			// Invulnerables must be sorted for removal.
437			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		/// Set the ideal number of non-invulnerable collators. If lowering this number, then the
448		/// number of running collators could be higher than this figure. Aside from that edge case,
449		/// there should be no other way to have more candidates than the desired number.
450		///
451		/// The origin for this call must be the `UpdateOrigin`.
452		#[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			// we trust origin calls, this is just a for more accurate benchmarking
460			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		/// Set the candidacy bond amount.
469		///
470		/// If the candidacy bond is increased by this call, all current candidates which have a
471		/// deposit lower than the new bond will be kicked from the list and get their deposits
472		/// back.
473		///
474		/// The origin for this call must be the `UpdateOrigin`.
475		#[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					// Closure below returns the number of candidates which were kicked because
494					// their deposits were lower than the new candidacy bond.
495					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		/// Register this account as a collator candidate. The account must (a) already have
518		/// registered session keys and (b) be able to reserve the `CandidacyBond`.
519		///
520		/// This call is not available to `Invulnerable` collators.
521		#[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			// ensure we are below limit.
527			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			// First authored block is current block plus kick threshold to handle session delay
543			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			// Safe to do unchecked add here because we ensure above that `length <
561			// T::MaxCandidates::get()`, and since `T::MaxCandidates` is `u32` it can be at most
562			// `u32::MAX`, therefore `length + 1` cannot overflow.
563			Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
564		}
565
566		/// Deregister `origin` as a collator candidate. Note that the collator can only leave on
567		/// session change. The `CandidacyBond` will be unreserved immediately.
568		///
569		/// This call will fail if the total number of candidates would drop below
570		/// `MinEligibleCollators`.
571		#[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			// Do remove their last authored block.
581			Self::try_remove_candidate(&who, true)?;
582
583			Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
584		}
585
586		/// Add a new account `who` to the list of `Invulnerables` collators. `who` must have
587		/// registered session keys. If `who` is a candidate, they will be removed.
588		///
589		/// The origin for this call must be the `UpdateOrigin`.
590		#[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			// ensure `who` has registered a validator key
602			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			// Error just means `who` wasn't a candidate, which is the state we want anyway. Don't
620			// remove their last authored block, as they are still a collator.
621			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		/// Remove an account `who` from the list of `Invulnerables` collators. `Invulnerables` must
640		/// be sorted.
641		///
642		/// The origin for this call must be the `UpdateOrigin`.
643		#[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		/// Update the candidacy bond of collator candidate `origin` to a new amount `new_deposit`.
665		///
666		/// Setting a `new_deposit` that is lower than the current deposit while `origin` is
667		/// occupying a top-`DesiredCandidates` slot is not allowed.
668		///
669		/// This call will fail if `origin` is not a collator candidate, the updated bond is lower
670		/// than the minimum candidacy bond, and/or the amount cannot be reserved.
671		#[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			// The function below will try to mutate the `CandidateList` entry for the caller to
680			// update their deposit to the new value of `new_deposit`. The return value is the
681			// position of the entry in the list, used for weight calculation.
682			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					// Remove the candidate from the list.
690					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						// Casting `u32` to `usize` should be safe on all machines running this.
696						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					// Update the deposit and insert the candidate in the correct spot in the list.
707					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		/// The caller `origin` replaces a candidate `target` in the collator candidate list by
727		/// reserving `deposit`. The amount `deposit` reserved by the caller must be greater than
728		/// the existing bond of the target it is trying to replace.
729		///
730		/// This call will fail if the caller is already a collator candidate or invulnerable, the
731		/// caller does not have registered session keys, the target is not a collator candidate,
732		/// and/or the `deposit` amount cannot be reserved.
733		#[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			// The closure below iterates through all elements of the candidate list to ensure that
754			// the caller isn't already a candidate and to find the target it's trying to replace in
755			// the list. The return value is a tuple of the position of the candidate to be replaced
756			// in the list along with its candidate information.
757			let target_info = CandidateList::<T>::try_mutate(
758				|candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
759					// Find the position in the list of the candidate that is being replaced.
760					let mut target_info_idx = None;
761					let mut new_info_idx = None;
762					for (idx, candidate_info) in candidates.iter().enumerate() {
763						// While iterating through the candidates trying to find the target,
764						// also ensure on the same pass that our caller isn't already a
765						// candidate.
766						ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
767						// If we find our target, update the position but do not stop the
768						// iteration since we're also checking that the caller isn't already a
769						// candidate.
770						if candidate_info.who == target {
771							target_info_idx = Some(idx);
772						}
773						// Find the spot where the new candidate would be inserted in the current
774						// version of the list.
775						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					// Remove the old candidate from the list.
783					let target_info = candidates.remove(target_info_idx);
784					ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
785
786					// We have removed one element before `new_info_idx`, so the position we have to
787					// insert to is reduced by 1.
788					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					// Insert the new candidate in the correct spot in the list.
793					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		/// Get a unique, inaccessible account ID from the `PotId`.
815		pub fn account_id() -> T::AccountId {
816			T::PotId::get().into_account_truncating()
817		}
818
819		/// Return the total number of accounts that are eligible collators (candidates and
820		/// invulnerables).
821		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		/// Removes a candidate if they exist and sends them back their deposit.
830		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		/// Assemble the current set of candidates and invulnerables into the next collator set.
852		///
853		/// This is done on the fly, as frequent as we are told to do so, as the session manager.
854		pub fn assemble_collators() -> Vec<T::AccountId> {
855			// Casting `u32` to `usize` should be safe on all machines running this.
856			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		/// Kicks out candidates that did not produce a block in the kick threshold and refunds
870		/// their deposits.
871		///
872		/// Return value is the number of candidates left in the list.
873		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						// They are invulnerable. No reason for them to be in `CandidateList` also.
888						// We don't even care about the min collators here, because an Account
889						// should not be a collator twice.
890						let _ = Self::try_remove_candidate(&c, false);
891						None
892					} else {
893						if Self::eligible_collators() <= min_collators || !is_lazy {
894							// Either this is a good collator (not lazy) or we are at the minimum
895							// that the system needs. They get to stay.
896							Some(c)
897						} else {
898							// This collator has not produced a block recently enough. Bye bye.
899							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		/// Ensure the correctness of the state of this pallet.
910		///
911		/// This should be valid before or after each state transition of this pallet.
912		///
913		/// # Invariants
914		///
915		/// ## `DesiredCandidates`
916		///
917		/// * The current desired candidate count should not exceed the candidate list capacity.
918		/// * The number of selected candidates together with the invulnerables must be greater than
919		///   or equal to the minimum number of eligible collators.
920		#[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	/// Keep track of number of authored blocks per authority, uncles are counted as well since
940	/// they're a valid proof of being online.
941	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			// assumes an ED will be sent to pot.
947			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			// `reward` is half of pot account minus ED, this should never fail.
952			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	/// Play the role of the session manager.
964	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			// The `expect` below is safe because the list is a `BoundedVec` with a max size of
973			// `T::MaxCandidates`, which is a `u32`. When `decode_len` returns `Some(len)`, `len`
974			// must be valid and at most `u32::MAX`, which must always be able to convert to `u32`.
975			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			// we don't care.
995		}
996		fn end_session(_: SessionIndex) {
997			// we don't care.
998		}
999	}
1000}
1001
1002/// [`TypedGet`] implementation to get the AccountId of the StakingPot.
1003pub 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}