referrerpolicy=no-referrer-when-downgrade

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		#[allow(deprecated)]
145		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
146
147		/// The currency mechanism.
148		type Currency: ReservableCurrency<Self::AccountId>;
149
150		/// Origin that can dictate updating parameters of this pallet.
151		type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
152
153		/// Account Identifier from which the internal Pot is generated.
154		#[pallet::constant]
155		type PotId: Get<PalletId>;
156
157		/// Maximum number of candidates that we should have.
158		///
159		/// This does not take into account the invulnerables.
160		#[pallet::constant]
161		type MaxCandidates: Get<u32>;
162
163		/// Minimum number eligible collators. Should always be greater than zero. This includes
164		/// Invulnerable collators. This ensures that there will always be one collator who can
165		/// produce a block.
166		#[pallet::constant]
167		type MinEligibleCollators: Get<u32>;
168
169		/// Maximum number of invulnerables.
170		#[pallet::constant]
171		type MaxInvulnerables: Get<u32>;
172
173		// Will be kicked if block is not produced in threshold.
174		#[pallet::constant]
175		type KickThreshold: Get<BlockNumberFor<Self>>;
176
177		/// A stable ID for a validator.
178		type ValidatorId: Member + Parameter;
179
180		/// A conversion from account ID to validator ID.
181		///
182		/// Its cost must be at most one storage read.
183		type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
184
185		/// Validate a user is registered
186		type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
187
188		/// The weight information of this pallet.
189		type WeightInfo: WeightInfo;
190	}
191
192	#[pallet::extra_constants]
193	impl<T: Config> Pallet<T> {
194		/// Gets this pallet's derived pot account.
195		fn pot_account() -> T::AccountId {
196			Self::account_id()
197		}
198	}
199
200	/// Basic information about a collation candidate.
201	#[derive(
202		PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
203	)]
204	pub struct CandidateInfo<AccountId, Balance> {
205		/// Account identifier.
206		pub who: AccountId,
207		/// Reserved deposit.
208		pub deposit: Balance,
209	}
210
211	#[pallet::pallet]
212	#[pallet::storage_version(STORAGE_VERSION)]
213	pub struct Pallet<T>(_);
214
215	/// The invulnerable, permissioned collators. This list must be sorted.
216	#[pallet::storage]
217	pub type Invulnerables<T: Config> =
218		StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
219
220	/// The (community, limited) collation candidates. `Candidates` and `Invulnerables` should be
221	/// mutually exclusive.
222	///
223	/// This list is sorted in ascending order by deposit and when the deposits are equal, the least
224	/// recently updated is considered greater.
225	#[pallet::storage]
226	pub type CandidateList<T: Config> = StorageValue<
227		_,
228		BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
229		ValueQuery,
230	>;
231
232	/// Last block authored by collator.
233	#[pallet::storage]
234	pub type LastAuthoredBlock<T: Config> =
235		StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
236
237	/// Desired number of candidates.
238	///
239	/// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct.
240	#[pallet::storage]
241	pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
242
243	/// Fixed amount to deposit to become a collator.
244	///
245	/// When a collator calls `leave_intent` they immediately receive the deposit back.
246	#[pallet::storage]
247	pub type CandidacyBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
248
249	#[pallet::genesis_config]
250	#[derive(DefaultNoBound)]
251	pub struct GenesisConfig<T: Config> {
252		pub invulnerables: Vec<T::AccountId>,
253		pub candidacy_bond: BalanceOf<T>,
254		pub desired_candidates: u32,
255	}
256
257	#[pallet::genesis_build]
258	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
259		fn build(&self) {
260			let duplicate_invulnerables = self
261				.invulnerables
262				.iter()
263				.collect::<alloc::collections::btree_set::BTreeSet<_>>();
264			assert!(
265				duplicate_invulnerables.len() == self.invulnerables.len(),
266				"duplicate invulnerables in genesis."
267			);
268
269			let mut bounded_invulnerables =
270				BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())
271					.expect("genesis invulnerables are more than T::MaxInvulnerables");
272			assert!(
273				T::MaxCandidates::get() >= self.desired_candidates,
274				"genesis desired_candidates are more than T::MaxCandidates",
275			);
276
277			bounded_invulnerables.sort();
278
279			DesiredCandidates::<T>::put(self.desired_candidates);
280			CandidacyBond::<T>::put(self.candidacy_bond);
281			Invulnerables::<T>::put(bounded_invulnerables);
282		}
283	}
284
285	#[pallet::event]
286	#[pallet::generate_deposit(pub(super) fn deposit_event)]
287	pub enum Event<T: Config> {
288		/// New Invulnerables were set.
289		NewInvulnerables { invulnerables: Vec<T::AccountId> },
290		/// A new Invulnerable was added.
291		InvulnerableAdded { account_id: T::AccountId },
292		/// An Invulnerable was removed.
293		InvulnerableRemoved { account_id: T::AccountId },
294		/// The number of desired candidates was set.
295		NewDesiredCandidates { desired_candidates: u32 },
296		/// The candidacy bond was set.
297		NewCandidacyBond { bond_amount: BalanceOf<T> },
298		/// A new candidate joined.
299		CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
300		/// Bond of a candidate updated.
301		CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
302		/// A candidate was removed.
303		CandidateRemoved { account_id: T::AccountId },
304		/// An account was replaced in the candidate list by another one.
305		CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
306		/// An account was unable to be added to the Invulnerables because they did not have keys
307		/// registered. Other Invulnerables may have been set.
308		InvalidInvulnerableSkipped { account_id: T::AccountId },
309	}
310
311	#[pallet::error]
312	pub enum Error<T> {
313		/// The pallet has too many candidates.
314		TooManyCandidates,
315		/// Leaving would result in too few candidates.
316		TooFewEligibleCollators,
317		/// Account is already a candidate.
318		AlreadyCandidate,
319		/// Account is not a candidate.
320		NotCandidate,
321		/// There are too many Invulnerables.
322		TooManyInvulnerables,
323		/// Account is already an Invulnerable.
324		AlreadyInvulnerable,
325		/// Account is not an Invulnerable.
326		NotInvulnerable,
327		/// Account has no associated validator ID.
328		NoAssociatedValidatorId,
329		/// Validator ID is not yet registered.
330		ValidatorNotRegistered,
331		/// Could not insert in the candidate list.
332		InsertToCandidateListFailed,
333		/// Could not remove from the candidate list.
334		RemoveFromCandidateListFailed,
335		/// New deposit amount would be below the minimum candidacy bond.
336		DepositTooLow,
337		/// Could not update the candidate list.
338		UpdateCandidateListFailed,
339		/// Deposit amount is too low to take the target's slot in the candidate list.
340		InsufficientBond,
341		/// The target account to be replaced in the candidate list is not a candidate.
342		TargetIsNotCandidate,
343		/// The updated deposit amount is equal to the amount already reserved.
344		IdenticalDeposit,
345		/// Cannot lower candidacy bond while occupying a future collator slot in the list.
346		InvalidUnreserve,
347	}
348
349	#[pallet::hooks]
350	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
351		fn integrity_test() {
352			assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator");
353			assert!(
354				T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get()) >=
355					T::MinEligibleCollators::get(),
356				"invulnerables and candidates must be able to satisfy collator demand"
357			);
358		}
359
360		#[cfg(feature = "try-runtime")]
361		fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
362			Self::do_try_state()
363		}
364	}
365
366	#[pallet::call]
367	impl<T: Config> Pallet<T> {
368		/// Set the list of invulnerable (fixed) collators. These collators must do some
369		/// preparation, namely to have registered session keys.
370		///
371		/// The call will remove any accounts that have not registered keys from the set. That is,
372		/// it is non-atomic; the caller accepts all `AccountId`s passed in `new` _individually_ as
373		/// acceptable Invulnerables, and is not proposing a _set_ of new Invulnerables.
374		///
375		/// This call does not maintain mutual exclusivity of `Invulnerables` and `Candidates`. It
376		/// is recommended to use a batch of `add_invulnerable` and `remove_invulnerable` instead. A
377		/// `batch_all` can also be used to enforce atomicity. If any candidates are included in
378		/// `new`, they should be removed with `remove_invulnerable_candidate` after execution.
379		///
380		/// Must be called by the `UpdateOrigin`.
381		#[pallet::call_index(0)]
382		#[pallet::weight(T::WeightInfo::set_invulnerables(new.len() as u32))]
383		pub fn set_invulnerables(origin: OriginFor<T>, new: Vec<T::AccountId>) -> DispatchResult {
384			T::UpdateOrigin::ensure_origin(origin)?;
385
386			// don't wipe out the collator set
387			if new.is_empty() {
388				// Casting `u32` to `usize` should be safe on all machines running this.
389				ensure!(
390					CandidateList::<T>::decode_len().unwrap_or_default() >=
391						T::MinEligibleCollators::get() as usize,
392					Error::<T>::TooFewEligibleCollators
393				);
394			}
395
396			// Will need to check the length again when putting into a bounded vec, but this
397			// prevents the iterator from having too many elements.
398			ensure!(
399				new.len() as u32 <= T::MaxInvulnerables::get(),
400				Error::<T>::TooManyInvulnerables
401			);
402
403			let mut new_with_keys = Vec::new();
404
405			// check if the invulnerables have associated validator keys before they are set
406			for account_id in &new {
407				// don't let one unprepared collator ruin things for everyone.
408				let validator_key = T::ValidatorIdOf::convert(account_id.clone());
409				match validator_key {
410					Some(key) => {
411						// key is not registered
412						if !T::ValidatorRegistration::is_registered(&key) {
413							Self::deposit_event(Event::InvalidInvulnerableSkipped {
414								account_id: account_id.clone(),
415							});
416							continue
417						}
418						// else condition passes; key is registered
419					},
420					// key does not exist
421					None => {
422						Self::deposit_event(Event::InvalidInvulnerableSkipped {
423							account_id: account_id.clone(),
424						});
425						continue
426					},
427				}
428
429				new_with_keys.push(account_id.clone());
430			}
431
432			// should never fail since `new_with_keys` must be equal to or shorter than `new`
433			let mut bounded_invulnerables =
434				BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
435					.map_err(|_| Error::<T>::TooManyInvulnerables)?;
436
437			// Invulnerables must be sorted for removal.
438			bounded_invulnerables.sort();
439
440			Invulnerables::<T>::put(&bounded_invulnerables);
441			Self::deposit_event(Event::NewInvulnerables {
442				invulnerables: bounded_invulnerables.to_vec(),
443			});
444
445			Ok(())
446		}
447
448		/// Set the ideal number of non-invulnerable collators. If lowering this number, then the
449		/// number of running collators could be higher than this figure. Aside from that edge case,
450		/// there should be no other way to have more candidates than the desired number.
451		///
452		/// The origin for this call must be the `UpdateOrigin`.
453		#[pallet::call_index(1)]
454		#[pallet::weight(T::WeightInfo::set_desired_candidates())]
455		pub fn set_desired_candidates(
456			origin: OriginFor<T>,
457			max: u32,
458		) -> DispatchResultWithPostInfo {
459			T::UpdateOrigin::ensure_origin(origin)?;
460			// we trust origin calls, this is just a for more accurate benchmarking
461			if max > T::MaxCandidates::get() {
462				log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");
463			}
464			DesiredCandidates::<T>::put(max);
465			Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max });
466			Ok(().into())
467		}
468
469		/// Set the candidacy bond amount.
470		///
471		/// If the candidacy bond is increased by this call, all current candidates which have a
472		/// deposit lower than the new bond will be kicked from the list and get their deposits
473		/// back.
474		///
475		/// The origin for this call must be the `UpdateOrigin`.
476		#[pallet::call_index(2)]
477		#[pallet::weight(T::WeightInfo::set_candidacy_bond(
478			T::MaxCandidates::get(),
479			T::MaxCandidates::get()
480		))]
481		pub fn set_candidacy_bond(
482			origin: OriginFor<T>,
483			bond: BalanceOf<T>,
484		) -> DispatchResultWithPostInfo {
485			T::UpdateOrigin::ensure_origin(origin)?;
486			let bond_increased = CandidacyBond::<T>::mutate(|old_bond| -> bool {
487				let bond_increased = *old_bond < bond;
488				*old_bond = bond;
489				bond_increased
490			});
491			let initial_len = CandidateList::<T>::decode_len().unwrap_or_default();
492			let kicked = (bond_increased && initial_len > 0)
493				.then(|| {
494					// Closure below returns the number of candidates which were kicked because
495					// their deposits were lower than the new candidacy bond.
496					CandidateList::<T>::mutate(|candidates| -> usize {
497						let first_safe_candidate = candidates
498							.iter()
499							.position(|candidate| candidate.deposit >= bond)
500							.unwrap_or(initial_len);
501						let kicked_candidates = candidates.drain(..first_safe_candidate);
502						for candidate in kicked_candidates {
503							T::Currency::unreserve(&candidate.who, candidate.deposit);
504							LastAuthoredBlock::<T>::remove(candidate.who);
505						}
506						first_safe_candidate
507					})
508				})
509				.unwrap_or_default();
510			Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond });
511			Ok(Some(T::WeightInfo::set_candidacy_bond(
512				bond_increased.then(|| initial_len as u32).unwrap_or_default(),
513				kicked as u32,
514			))
515			.into())
516		}
517
518		/// Register this account as a collator candidate. The account must (a) already have
519		/// registered session keys and (b) be able to reserve the `CandidacyBond`.
520		///
521		/// This call is not available to `Invulnerable` collators.
522		#[pallet::call_index(3)]
523		#[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))]
524		pub fn register_as_candidate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
525			let who = ensure_signed(origin)?;
526
527			// ensure we are below limit.
528			let length: u32 = CandidateList::<T>::decode_len()
529				.unwrap_or_default()
530				.try_into()
531				.unwrap_or_default();
532			ensure!(length < T::MaxCandidates::get(), Error::<T>::TooManyCandidates);
533			ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
534
535			let validator_key = T::ValidatorIdOf::convert(who.clone())
536				.ok_or(Error::<T>::NoAssociatedValidatorId)?;
537			ensure!(
538				T::ValidatorRegistration::is_registered(&validator_key),
539				Error::<T>::ValidatorNotRegistered
540			);
541
542			let deposit = CandidacyBond::<T>::get();
543			// First authored block is current block plus kick threshold to handle session delay
544			CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
545				ensure!(
546					!candidates.iter().any(|candidate_info| candidate_info.who == who),
547					Error::<T>::AlreadyCandidate
548				);
549				T::Currency::reserve(&who, deposit)?;
550				LastAuthoredBlock::<T>::insert(
551					who.clone(),
552					frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
553				);
554				candidates
555					.try_insert(0, CandidateInfo { who: who.clone(), deposit })
556					.map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
557				Ok(())
558			})?;
559
560			Self::deposit_event(Event::CandidateAdded { account_id: who, deposit });
561			// Safe to do unchecked add here because we ensure above that `length <
562			// T::MaxCandidates::get()`, and since `T::MaxCandidates` is `u32` it can be at most
563			// `u32::MAX`, therefore `length + 1` cannot overflow.
564			Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
565		}
566
567		/// Deregister `origin` as a collator candidate. Note that the collator can only leave on
568		/// session change. The `CandidacyBond` will be unreserved immediately.
569		///
570		/// This call will fail if the total number of candidates would drop below
571		/// `MinEligibleCollators`.
572		#[pallet::call_index(4)]
573		#[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
574		pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
575			let who = ensure_signed(origin)?;
576			ensure!(
577				Self::eligible_collators() > T::MinEligibleCollators::get(),
578				Error::<T>::TooFewEligibleCollators
579			);
580			let length = CandidateList::<T>::decode_len().unwrap_or_default();
581			// Do remove their last authored block.
582			Self::try_remove_candidate(&who, true)?;
583
584			Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
585		}
586
587		/// Add a new account `who` to the list of `Invulnerables` collators. `who` must have
588		/// registered session keys. If `who` is a candidate, they will be removed.
589		///
590		/// The origin for this call must be the `UpdateOrigin`.
591		#[pallet::call_index(5)]
592		#[pallet::weight(T::WeightInfo::add_invulnerable(
593			T::MaxInvulnerables::get().saturating_sub(1),
594			T::MaxCandidates::get()
595		))]
596		pub fn add_invulnerable(
597			origin: OriginFor<T>,
598			who: T::AccountId,
599		) -> DispatchResultWithPostInfo {
600			T::UpdateOrigin::ensure_origin(origin)?;
601
602			// ensure `who` has registered a validator key
603			let validator_key = T::ValidatorIdOf::convert(who.clone())
604				.ok_or(Error::<T>::NoAssociatedValidatorId)?;
605			ensure!(
606				T::ValidatorRegistration::is_registered(&validator_key),
607				Error::<T>::ValidatorNotRegistered
608			);
609
610			Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
611				match invulnerables.binary_search(&who) {
612					Ok(_) => return Err(Error::<T>::AlreadyInvulnerable)?,
613					Err(pos) => invulnerables
614						.try_insert(pos, who.clone())
615						.map_err(|_| Error::<T>::TooManyInvulnerables)?,
616				}
617				Ok(())
618			})?;
619
620			// Error just means `who` wasn't a candidate, which is the state we want anyway. Don't
621			// remove their last authored block, as they are still a collator.
622			let _ = Self::try_remove_candidate(&who, false);
623
624			Self::deposit_event(Event::InvulnerableAdded { account_id: who });
625
626			let weight_used = T::WeightInfo::add_invulnerable(
627				Invulnerables::<T>::decode_len()
628					.unwrap_or_default()
629					.try_into()
630					.unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)),
631				CandidateList::<T>::decode_len()
632					.unwrap_or_default()
633					.try_into()
634					.unwrap_or(T::MaxCandidates::get()),
635			);
636
637			Ok(Some(weight_used).into())
638		}
639
640		/// Remove an account `who` from the list of `Invulnerables` collators. `Invulnerables` must
641		/// be sorted.
642		///
643		/// The origin for this call must be the `UpdateOrigin`.
644		#[pallet::call_index(6)]
645		#[pallet::weight(T::WeightInfo::remove_invulnerable(T::MaxInvulnerables::get()))]
646		pub fn remove_invulnerable(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
647			T::UpdateOrigin::ensure_origin(origin)?;
648
649			ensure!(
650				Self::eligible_collators() > T::MinEligibleCollators::get(),
651				Error::<T>::TooFewEligibleCollators
652			);
653
654			Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
655				let pos =
656					invulnerables.binary_search(&who).map_err(|_| Error::<T>::NotInvulnerable)?;
657				invulnerables.remove(pos);
658				Ok(())
659			})?;
660
661			Self::deposit_event(Event::InvulnerableRemoved { account_id: who });
662			Ok(())
663		}
664
665		/// Update the candidacy bond of collator candidate `origin` to a new amount `new_deposit`.
666		///
667		/// Setting a `new_deposit` that is lower than the current deposit while `origin` is
668		/// occupying a top-`DesiredCandidates` slot is not allowed.
669		///
670		/// This call will fail if `origin` is not a collator candidate, the updated bond is lower
671		/// than the minimum candidacy bond, and/or the amount cannot be reserved.
672		#[pallet::call_index(7)]
673		#[pallet::weight(T::WeightInfo::update_bond(T::MaxCandidates::get()))]
674		pub fn update_bond(
675			origin: OriginFor<T>,
676			new_deposit: BalanceOf<T>,
677		) -> DispatchResultWithPostInfo {
678			let who = ensure_signed(origin)?;
679			ensure!(new_deposit >= CandidacyBond::<T>::get(), Error::<T>::DepositTooLow);
680			// The function below will try to mutate the `CandidateList` entry for the caller to
681			// update their deposit to the new value of `new_deposit`. The return value is the
682			// position of the entry in the list, used for weight calculation.
683			let length =
684				CandidateList::<T>::try_mutate(|candidates| -> Result<usize, DispatchError> {
685					let idx = candidates
686						.iter()
687						.position(|candidate_info| candidate_info.who == who)
688						.ok_or_else(|| Error::<T>::NotCandidate)?;
689					let candidate_count = candidates.len();
690					// Remove the candidate from the list.
691					let mut info = candidates.remove(idx);
692					let old_deposit = info.deposit;
693					if new_deposit > old_deposit {
694						T::Currency::reserve(&who, new_deposit - old_deposit)?;
695					} else if new_deposit < old_deposit {
696						// Casting `u32` to `usize` should be safe on all machines running this.
697						ensure!(
698							idx.saturating_add(DesiredCandidates::<T>::get() as usize) <
699								candidate_count,
700							Error::<T>::InvalidUnreserve
701						);
702						T::Currency::unreserve(&who, old_deposit - new_deposit);
703					} else {
704						return Err(Error::<T>::IdenticalDeposit.into())
705					}
706
707					// Update the deposit and insert the candidate in the correct spot in the list.
708					info.deposit = new_deposit;
709					let new_pos = candidates
710						.iter()
711						.position(|candidate| candidate.deposit >= new_deposit)
712						.unwrap_or_else(|| candidates.len());
713					candidates
714						.try_insert(new_pos, info)
715						.map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
716
717					Ok(candidate_count)
718				})?;
719
720			Self::deposit_event(Event::CandidateBondUpdated {
721				account_id: who,
722				deposit: new_deposit,
723			});
724			Ok(Some(T::WeightInfo::update_bond(length as u32)).into())
725		}
726
727		/// The caller `origin` replaces a candidate `target` in the collator candidate list by
728		/// reserving `deposit`. The amount `deposit` reserved by the caller must be greater than
729		/// the existing bond of the target it is trying to replace.
730		///
731		/// This call will fail if the caller is already a collator candidate or invulnerable, the
732		/// caller does not have registered session keys, the target is not a collator candidate,
733		/// and/or the `deposit` amount cannot be reserved.
734		#[pallet::call_index(8)]
735		#[pallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))]
736		pub fn take_candidate_slot(
737			origin: OriginFor<T>,
738			deposit: BalanceOf<T>,
739			target: T::AccountId,
740		) -> DispatchResultWithPostInfo {
741			let who = ensure_signed(origin)?;
742
743			ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
744			ensure!(deposit >= CandidacyBond::<T>::get(), Error::<T>::InsufficientBond);
745
746			let validator_key = T::ValidatorIdOf::convert(who.clone())
747				.ok_or(Error::<T>::NoAssociatedValidatorId)?;
748			ensure!(
749				T::ValidatorRegistration::is_registered(&validator_key),
750				Error::<T>::ValidatorNotRegistered
751			);
752
753			let length = CandidateList::<T>::decode_len().unwrap_or_default();
754			// The closure below iterates through all elements of the candidate list to ensure that
755			// the caller isn't already a candidate and to find the target it's trying to replace in
756			// the list. The return value is a tuple of the position of the candidate to be replaced
757			// in the list along with its candidate information.
758			let target_info = CandidateList::<T>::try_mutate(
759				|candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
760					// Find the position in the list of the candidate that is being replaced.
761					let mut target_info_idx = None;
762					let mut new_info_idx = None;
763					for (idx, candidate_info) in candidates.iter().enumerate() {
764						// While iterating through the candidates trying to find the target,
765						// also ensure on the same pass that our caller isn't already a
766						// candidate.
767						ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
768						// If we find our target, update the position but do not stop the
769						// iteration since we're also checking that the caller isn't already a
770						// candidate.
771						if candidate_info.who == target {
772							target_info_idx = Some(idx);
773						}
774						// Find the spot where the new candidate would be inserted in the current
775						// version of the list.
776						if new_info_idx.is_none() && candidate_info.deposit >= deposit {
777							new_info_idx = Some(idx);
778						}
779					}
780					let target_info_idx =
781						target_info_idx.ok_or(Error::<T>::TargetIsNotCandidate)?;
782
783					// Remove the old candidate from the list.
784					let target_info = candidates.remove(target_info_idx);
785					ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
786
787					// We have removed one element before `new_info_idx`, so the position we have to
788					// insert to is reduced by 1.
789					let new_pos = new_info_idx
790						.map(|i| i.saturating_sub(1))
791						.unwrap_or_else(|| candidates.len());
792					let new_info = CandidateInfo { who: who.clone(), deposit };
793					// Insert the new candidate in the correct spot in the list.
794					candidates
795						.try_insert(new_pos, new_info)
796						.expect("candidate count previously decremented; qed");
797
798					Ok(target_info)
799				},
800			)?;
801			T::Currency::reserve(&who, deposit)?;
802			T::Currency::unreserve(&target_info.who, target_info.deposit);
803			LastAuthoredBlock::<T>::remove(target_info.who.clone());
804			LastAuthoredBlock::<T>::insert(
805				who.clone(),
806				frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
807			);
808
809			Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit });
810			Ok(Some(T::WeightInfo::take_candidate_slot(length as u32)).into())
811		}
812	}
813
814	impl<T: Config> Pallet<T> {
815		/// Get a unique, inaccessible account ID from the `PotId`.
816		pub fn account_id() -> T::AccountId {
817			T::PotId::get().into_account_truncating()
818		}
819
820		/// Return the total number of accounts that are eligible collators (candidates and
821		/// invulnerables).
822		fn eligible_collators() -> u32 {
823			CandidateList::<T>::decode_len()
824				.unwrap_or_default()
825				.saturating_add(Invulnerables::<T>::decode_len().unwrap_or_default())
826				.try_into()
827				.unwrap_or(u32::MAX)
828		}
829
830		/// Removes a candidate if they exist and sends them back their deposit.
831		fn try_remove_candidate(
832			who: &T::AccountId,
833			remove_last_authored: bool,
834		) -> Result<(), DispatchError> {
835			CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
836				let idx = candidates
837					.iter()
838					.position(|candidate_info| candidate_info.who == *who)
839					.ok_or(Error::<T>::NotCandidate)?;
840				let deposit = candidates[idx].deposit;
841				T::Currency::unreserve(who, deposit);
842				candidates.remove(idx);
843				if remove_last_authored {
844					LastAuthoredBlock::<T>::remove(who.clone())
845				};
846				Ok(())
847			})?;
848			Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() });
849			Ok(())
850		}
851
852		/// Assemble the current set of candidates and invulnerables into the next collator set.
853		///
854		/// This is done on the fly, as frequent as we are told to do so, as the session manager.
855		pub fn assemble_collators() -> Vec<T::AccountId> {
856			// Casting `u32` to `usize` should be safe on all machines running this.
857			let desired_candidates = DesiredCandidates::<T>::get() as usize;
858			let mut collators = Invulnerables::<T>::get().to_vec();
859			collators.extend(
860				CandidateList::<T>::get()
861					.iter()
862					.rev()
863					.cloned()
864					.take(desired_candidates)
865					.map(|candidate_info| candidate_info.who),
866			);
867			collators
868		}
869
870		/// Kicks out candidates that did not produce a block in the kick threshold and refunds
871		/// their deposits.
872		///
873		/// Return value is the number of candidates left in the list.
874		pub fn kick_stale_candidates(candidates: impl IntoIterator<Item = T::AccountId>) -> u32 {
875			let now = frame_system::Pallet::<T>::block_number();
876			let kick_threshold = T::KickThreshold::get();
877			let min_collators = T::MinEligibleCollators::get();
878			candidates
879				.into_iter()
880				.filter_map(|c| {
881					let last_block = LastAuthoredBlock::<T>::get(c.clone());
882					let since_last = now.saturating_sub(last_block);
883
884					let is_invulnerable = Invulnerables::<T>::get().contains(&c);
885					let is_lazy = since_last >= kick_threshold;
886
887					if is_invulnerable {
888						// They are invulnerable. No reason for them to be in `CandidateList` also.
889						// We don't even care about the min collators here, because an Account
890						// should not be a collator twice.
891						let _ = Self::try_remove_candidate(&c, false);
892						None
893					} else {
894						if Self::eligible_collators() <= min_collators || !is_lazy {
895							// Either this is a good collator (not lazy) or we are at the minimum
896							// that the system needs. They get to stay.
897							Some(c)
898						} else {
899							// This collator has not produced a block recently enough. Bye bye.
900							let _ = Self::try_remove_candidate(&c, true);
901							None
902						}
903					}
904				})
905				.count()
906				.try_into()
907				.expect("filter_map operation can't result in a bounded vec larger than its original; qed")
908		}
909
910		/// Ensure the correctness of the state of this pallet.
911		///
912		/// This should be valid before or after each state transition of this pallet.
913		///
914		/// # Invariants
915		///
916		/// ## `DesiredCandidates`
917		///
918		/// * The current desired candidate count should not exceed the candidate list capacity.
919		/// * The number of selected candidates together with the invulnerables must be greater than
920		///   or equal to the minimum number of eligible collators.
921		#[cfg(any(test, feature = "try-runtime"))]
922		pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
923			let desired_candidates = DesiredCandidates::<T>::get();
924
925			frame_support::ensure!(
926				desired_candidates <= T::MaxCandidates::get(),
927				"Shouldn't demand more candidates than the pallet config allows."
928			);
929
930			frame_support::ensure!(
931				desired_candidates.saturating_add(T::MaxInvulnerables::get()) >=
932					T::MinEligibleCollators::get(),
933				"Invulnerable set together with desired candidates should be able to meet the collator quota."
934			);
935
936			Ok(())
937		}
938	}
939
940	/// Keep track of number of authored blocks per authority, uncles are counted as well since
941	/// they're a valid proof of being online.
942	impl<T: Config + pallet_authorship::Config>
943		pallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pallet<T>
944	{
945		fn note_author(author: T::AccountId) {
946			let pot = Self::account_id();
947			// assumes an ED will be sent to pot.
948			let reward = T::Currency::free_balance(&pot)
949				.checked_sub(&T::Currency::minimum_balance())
950				.unwrap_or_else(Zero::zero)
951				.div(2u32.into());
952			// `reward` is half of pot account minus ED, this should never fail.
953			let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);
954			debug_assert!(_success.is_ok());
955			LastAuthoredBlock::<T>::insert(author, frame_system::Pallet::<T>::block_number());
956
957			frame_system::Pallet::<T>::register_extra_weight_unchecked(
958				T::WeightInfo::note_author(),
959				DispatchClass::Mandatory,
960			);
961		}
962	}
963
964	/// Play the role of the session manager.
965	impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {
966		fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {
967			log::info!(
968				"assembling new collators for new session {} at #{:?}",
969				index,
970				<frame_system::Pallet<T>>::block_number(),
971			);
972
973			// The `expect` below is safe because the list is a `BoundedVec` with a max size of
974			// `T::MaxCandidates`, which is a `u32`. When `decode_len` returns `Some(len)`, `len`
975			// must be valid and at most `u32::MAX`, which must always be able to convert to `u32`.
976			let candidates_len_before: u32 = CandidateList::<T>::decode_len()
977				.unwrap_or_default()
978				.try_into()
979				.expect("length is at most `T::MaxCandidates`, so it must fit in `u32`; qed");
980			let active_candidates_count = Self::kick_stale_candidates(
981				CandidateList::<T>::get()
982					.iter()
983					.map(|candidate_info| candidate_info.who.clone()),
984			);
985			let removed = candidates_len_before.saturating_sub(active_candidates_count);
986			let result = Self::assemble_collators();
987
988			frame_system::Pallet::<T>::register_extra_weight_unchecked(
989				T::WeightInfo::new_session(removed, candidates_len_before),
990				DispatchClass::Mandatory,
991			);
992			Some(result)
993		}
994		fn start_session(_: SessionIndex) {
995			// we don't care.
996		}
997		fn end_session(_: SessionIndex) {
998			// we don't care.
999		}
1000	}
1001}
1002
1003/// [`TypedGet`] implementation to get the AccountId of the StakingPot.
1004pub struct StakingPotAccountId<R>(PhantomData<R>);
1005impl<R> TypedGet for StakingPotAccountId<R>
1006where
1007	R: crate::Config,
1008{
1009	type Type = <R as frame_system::Config>::AccountId;
1010	fn get() -> Self::Type {
1011		<crate::Pallet<R>>::account_id()
1012	}
1013}