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		type PotId: Get<PalletId>;
154
155		/// Maximum number of candidates that we should have.
156		///
157		/// This does not take into account the invulnerables.
158		type MaxCandidates: Get<u32>;
159
160		/// Minimum number eligible collators. Should always be greater than zero. This includes
161		/// Invulnerable collators. This ensures that there will always be one collator who can
162		/// produce a block.
163		type MinEligibleCollators: Get<u32>;
164
165		/// Maximum number of invulnerables.
166		type MaxInvulnerables: Get<u32>;
167
168		// Will be kicked if block is not produced in threshold.
169		type KickThreshold: Get<BlockNumberFor<Self>>;
170
171		/// A stable ID for a validator.
172		type ValidatorId: Member + Parameter;
173
174		/// A conversion from account ID to validator ID.
175		///
176		/// Its cost must be at most one storage read.
177		type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
178
179		/// Validate a user is registered
180		type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
181
182		/// The weight information of this pallet.
183		type WeightInfo: WeightInfo;
184	}
185
186	/// Basic information about a collation candidate.
187	#[derive(
188		PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
189	)]
190	pub struct CandidateInfo<AccountId, Balance> {
191		/// Account identifier.
192		pub who: AccountId,
193		/// Reserved deposit.
194		pub deposit: Balance,
195	}
196
197	#[pallet::pallet]
198	#[pallet::storage_version(STORAGE_VERSION)]
199	pub struct Pallet<T>(_);
200
201	/// The invulnerable, permissioned collators. This list must be sorted.
202	#[pallet::storage]
203	pub type Invulnerables<T: Config> =
204		StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
205
206	/// The (community, limited) collation candidates. `Candidates` and `Invulnerables` should be
207	/// mutually exclusive.
208	///
209	/// This list is sorted in ascending order by deposit and when the deposits are equal, the least
210	/// recently updated is considered greater.
211	#[pallet::storage]
212	pub type CandidateList<T: Config> = StorageValue<
213		_,
214		BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
215		ValueQuery,
216	>;
217
218	/// Last block authored by collator.
219	#[pallet::storage]
220	pub type LastAuthoredBlock<T: Config> =
221		StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
222
223	/// Desired number of candidates.
224	///
225	/// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct.
226	#[pallet::storage]
227	pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
228
229	/// Fixed amount to deposit to become a collator.
230	///
231	/// When a collator calls `leave_intent` they immediately receive the deposit back.
232	#[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		/// New Invulnerables were set.
275		NewInvulnerables { invulnerables: Vec<T::AccountId> },
276		/// A new Invulnerable was added.
277		InvulnerableAdded { account_id: T::AccountId },
278		/// An Invulnerable was removed.
279		InvulnerableRemoved { account_id: T::AccountId },
280		/// The number of desired candidates was set.
281		NewDesiredCandidates { desired_candidates: u32 },
282		/// The candidacy bond was set.
283		NewCandidacyBond { bond_amount: BalanceOf<T> },
284		/// A new candidate joined.
285		CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
286		/// Bond of a candidate updated.
287		CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
288		/// A candidate was removed.
289		CandidateRemoved { account_id: T::AccountId },
290		/// An account was replaced in the candidate list by another one.
291		CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
292		/// An account was unable to be added to the Invulnerables because they did not have keys
293		/// registered. Other Invulnerables may have been set.
294		InvalidInvulnerableSkipped { account_id: T::AccountId },
295	}
296
297	#[pallet::error]
298	pub enum Error<T> {
299		/// The pallet has too many candidates.
300		TooManyCandidates,
301		/// Leaving would result in too few candidates.
302		TooFewEligibleCollators,
303		/// Account is already a candidate.
304		AlreadyCandidate,
305		/// Account is not a candidate.
306		NotCandidate,
307		/// There are too many Invulnerables.
308		TooManyInvulnerables,
309		/// Account is already an Invulnerable.
310		AlreadyInvulnerable,
311		/// Account is not an Invulnerable.
312		NotInvulnerable,
313		/// Account has no associated validator ID.
314		NoAssociatedValidatorId,
315		/// Validator ID is not yet registered.
316		ValidatorNotRegistered,
317		/// Could not insert in the candidate list.
318		InsertToCandidateListFailed,
319		/// Could not remove from the candidate list.
320		RemoveFromCandidateListFailed,
321		/// New deposit amount would be below the minimum candidacy bond.
322		DepositTooLow,
323		/// Could not update the candidate list.
324		UpdateCandidateListFailed,
325		/// Deposit amount is too low to take the target's slot in the candidate list.
326		InsufficientBond,
327		/// The target account to be replaced in the candidate list is not a candidate.
328		TargetIsNotCandidate,
329		/// The updated deposit amount is equal to the amount already reserved.
330		IdenticalDeposit,
331		/// Cannot lower candidacy bond while occupying a future collator slot in the list.
332		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		/// Set the list of invulnerable (fixed) collators. These collators must do some
355		/// preparation, namely to have registered session keys.
356		///
357		/// The call will remove any accounts that have not registered keys from the set. That is,
358		/// it is non-atomic; the caller accepts all `AccountId`s passed in `new` _individually_ as
359		/// acceptable Invulnerables, and is not proposing a _set_ of new Invulnerables.
360		///
361		/// This call does not maintain mutual exclusivity of `Invulnerables` and `Candidates`. It
362		/// is recommended to use a batch of `add_invulnerable` and `remove_invulnerable` instead. A
363		/// `batch_all` can also be used to enforce atomicity. If any candidates are included in
364		/// `new`, they should be removed with `remove_invulnerable_candidate` after execution.
365		///
366		/// Must be called by the `UpdateOrigin`.
367		#[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			// don't wipe out the collator set
373			if new.is_empty() {
374				// Casting `u32` to `usize` should be safe on all machines running this.
375				ensure!(
376					CandidateList::<T>::decode_len().unwrap_or_default() >=
377						T::MinEligibleCollators::get() as usize,
378					Error::<T>::TooFewEligibleCollators
379				);
380			}
381
382			// Will need to check the length again when putting into a bounded vec, but this
383			// prevents the iterator from having too many elements.
384			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			// check if the invulnerables have associated validator keys before they are set
392			for account_id in &new {
393				// don't let one unprepared collator ruin things for everyone.
394				let validator_key = T::ValidatorIdOf::convert(account_id.clone());
395				match validator_key {
396					Some(key) => {
397						// key is not registered
398						if !T::ValidatorRegistration::is_registered(&key) {
399							Self::deposit_event(Event::InvalidInvulnerableSkipped {
400								account_id: account_id.clone(),
401							});
402							continue
403						}
404						// else condition passes; key is registered
405					},
406					// key does not exist
407					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			// should never fail since `new_with_keys` must be equal to or shorter than `new`
419			let mut bounded_invulnerables =
420				BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
421					.map_err(|_| Error::<T>::TooManyInvulnerables)?;
422
423			// Invulnerables must be sorted for removal.
424			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		/// Set the ideal number of non-invulnerable collators. If lowering this number, then the
435		/// number of running collators could be higher than this figure. Aside from that edge case,
436		/// there should be no other way to have more candidates than the desired number.
437		///
438		/// The origin for this call must be the `UpdateOrigin`.
439		#[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			// we trust origin calls, this is just a for more accurate benchmarking
447			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		/// Set the candidacy bond amount.
456		///
457		/// If the candidacy bond is increased by this call, all current candidates which have a
458		/// deposit lower than the new bond will be kicked from the list and get their deposits
459		/// back.
460		///
461		/// The origin for this call must be the `UpdateOrigin`.
462		#[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					// Closure below returns the number of candidates which were kicked because
481					// their deposits were lower than the new candidacy bond.
482					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		/// Register this account as a collator candidate. The account must (a) already have
505		/// registered session keys and (b) be able to reserve the `CandidacyBond`.
506		///
507		/// This call is not available to `Invulnerable` collators.
508		#[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			// ensure we are below limit.
514			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			// First authored block is current block plus kick threshold to handle session delay
530			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			// Safe to do unchecked add here because we ensure above that `length <
548			// T::MaxCandidates::get()`, and since `T::MaxCandidates` is `u32` it can be at most
549			// `u32::MAX`, therefore `length + 1` cannot overflow.
550			Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
551		}
552
553		/// Deregister `origin` as a collator candidate. Note that the collator can only leave on
554		/// session change. The `CandidacyBond` will be unreserved immediately.
555		///
556		/// This call will fail if the total number of candidates would drop below
557		/// `MinEligibleCollators`.
558		#[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			// Do remove their last authored block.
568			Self::try_remove_candidate(&who, true)?;
569
570			Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
571		}
572
573		/// Add a new account `who` to the list of `Invulnerables` collators. `who` must have
574		/// registered session keys. If `who` is a candidate, they will be removed.
575		///
576		/// The origin for this call must be the `UpdateOrigin`.
577		#[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			// ensure `who` has registered a validator key
589			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			// Error just means `who` wasn't a candidate, which is the state we want anyway. Don't
607			// remove their last authored block, as they are still a collator.
608			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		/// Remove an account `who` from the list of `Invulnerables` collators. `Invulnerables` must
627		/// be sorted.
628		///
629		/// The origin for this call must be the `UpdateOrigin`.
630		#[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		/// Update the candidacy bond of collator candidate `origin` to a new amount `new_deposit`.
652		///
653		/// Setting a `new_deposit` that is lower than the current deposit while `origin` is
654		/// occupying a top-`DesiredCandidates` slot is not allowed.
655		///
656		/// This call will fail if `origin` is not a collator candidate, the updated bond is lower
657		/// than the minimum candidacy bond, and/or the amount cannot be reserved.
658		#[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			// The function below will try to mutate the `CandidateList` entry for the caller to
667			// update their deposit to the new value of `new_deposit`. The return value is the
668			// position of the entry in the list, used for weight calculation.
669			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					// Remove the candidate from the list.
677					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						// Casting `u32` to `usize` should be safe on all machines running this.
683						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					// Update the deposit and insert the candidate in the correct spot in the list.
694					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		/// The caller `origin` replaces a candidate `target` in the collator candidate list by
714		/// reserving `deposit`. The amount `deposit` reserved by the caller must be greater than
715		/// the existing bond of the target it is trying to replace.
716		///
717		/// This call will fail if the caller is already a collator candidate or invulnerable, the
718		/// caller does not have registered session keys, the target is not a collator candidate,
719		/// and/or the `deposit` amount cannot be reserved.
720		#[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			// The closure below iterates through all elements of the candidate list to ensure that
741			// the caller isn't already a candidate and to find the target it's trying to replace in
742			// the list. The return value is a tuple of the position of the candidate to be replaced
743			// in the list along with its candidate information.
744			let target_info = CandidateList::<T>::try_mutate(
745				|candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
746					// Find the position in the list of the candidate that is being replaced.
747					let mut target_info_idx = None;
748					let mut new_info_idx = None;
749					for (idx, candidate_info) in candidates.iter().enumerate() {
750						// While iterating through the candidates trying to find the target,
751						// also ensure on the same pass that our caller isn't already a
752						// candidate.
753						ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
754						// If we find our target, update the position but do not stop the
755						// iteration since we're also checking that the caller isn't already a
756						// candidate.
757						if candidate_info.who == target {
758							target_info_idx = Some(idx);
759						}
760						// Find the spot where the new candidate would be inserted in the current
761						// version of the list.
762						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					// Remove the old candidate from the list.
770					let target_info = candidates.remove(target_info_idx);
771					ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
772
773					// We have removed one element before `new_info_idx`, so the position we have to
774					// insert to is reduced by 1.
775					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					// Insert the new candidate in the correct spot in the list.
780					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		/// Get a unique, inaccessible account ID from the `PotId`.
802		pub fn account_id() -> T::AccountId {
803			T::PotId::get().into_account_truncating()
804		}
805
806		/// Return the total number of accounts that are eligible collators (candidates and
807		/// invulnerables).
808		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		/// Removes a candidate if they exist and sends them back their deposit.
817		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		/// Assemble the current set of candidates and invulnerables into the next collator set.
839		///
840		/// This is done on the fly, as frequent as we are told to do so, as the session manager.
841		pub fn assemble_collators() -> Vec<T::AccountId> {
842			// Casting `u32` to `usize` should be safe on all machines running this.
843			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		/// Kicks out candidates that did not produce a block in the kick threshold and refunds
857		/// their deposits.
858		///
859		/// Return value is the number of candidates left in the list.
860		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						// They are invulnerable. No reason for them to be in `CandidateList` also.
875						// We don't even care about the min collators here, because an Account
876						// should not be a collator twice.
877						let _ = Self::try_remove_candidate(&c, false);
878						None
879					} else {
880						if Self::eligible_collators() <= min_collators || !is_lazy {
881							// Either this is a good collator (not lazy) or we are at the minimum
882							// that the system needs. They get to stay.
883							Some(c)
884						} else {
885							// This collator has not produced a block recently enough. Bye bye.
886							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		/// Ensure the correctness of the state of this pallet.
897		///
898		/// This should be valid before or after each state transition of this pallet.
899		///
900		/// # Invariants
901		///
902		/// ## `DesiredCandidates`
903		///
904		/// * The current desired candidate count should not exceed the candidate list capacity.
905		/// * The number of selected candidates together with the invulnerables must be greater than
906		///   or equal to the minimum number of eligible collators.
907		#[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	/// Keep track of number of authored blocks per authority, uncles are counted as well since
927	/// they're a valid proof of being online.
928	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			// assumes an ED will be sent to pot.
934			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			// `reward` is half of pot account minus ED, this should never fail.
939			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	/// Play the role of the session manager.
951	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			// The `expect` below is safe because the list is a `BoundedVec` with a max size of
960			// `T::MaxCandidates`, which is a `u32`. When `decode_len` returns `Some(len)`, `len`
961			// must be valid and at most `u32::MAX`, which must always be able to convert to `u32`.
962			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			// we don't care.
982		}
983		fn end_session(_: SessionIndex) {
984			// we don't care.
985		}
986	}
987}
988
989/// [`TypedGet`] implementation to get the AccountId of the StakingPot.
990pub 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}