Skip to main content

plant_session/
lib.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! # Session Pallet
8//!
9//! The Session pallet allows validators to manage their session keys, provides a function for
10//! changing the session length, and handles session rotation.
11//!
12//! - [`Config`]
13//! - [`Call`]
14//! - [`Pallet`]
15//!
16//! ## Overview
17//!
18//! ### Terminology
19//! <!-- Original author of paragraph: @gavofyork -->
20//!
21//! - **Session:** A session is a period of time that has a constant set of validators. Validators
22//!   can only join or exit the validator set at a session change. It is measured in block numbers.
23//!   The block where a session is ended is determined by the `ShouldEndSession` trait. When the
24//!   session is ending, a new validator set can be chosen by `OnSessionEnding` implementations.
25//!
26//! - **Session key:** A session key is actually several keys kept together that provide the various
27//!   signing functions required by network authorities/validators in pursuit of their duties.
28//! - **Validator ID:** Every account has an associated validator ID. For some simple staking
29//!   systems, this may just be the same as the account ID. For staking systems using a
30//!   stash/controller model, the validator ID would be the stash account ID of the controller.
31//!
32//! - **Session key configuration process:** Session keys are set using `set_keys` for use not in
33//!   the next session, but the session after next. They are stored in `NextKeys`, a mapping between
34//!   the caller's `ValidatorId` and the session keys provided. `set_keys` allows users to set their
35//!   session key prior to being selected as validator. It is a public call since it uses
36//!   `ensure_signed`, which checks that the origin is a signed account. As such, the account ID of
37//!   the origin stored in `NextKeys` may not necessarily be associated with a block author or a
38//!   validator. The session keys of accounts are removed once their account balance is zero.
39//!
40//! - **Session length:** This pallet does not assume anything about the length of each session.
41//!   Rather, it relies on an implementation of `ShouldEndSession` to dictate a new session's start.
42//!   This pallet provides the `PeriodicSessions` struct for simple periodic sessions.
43//!
44//! - **Session rotation configuration:** Configure as either a 'normal' (rewardable session where
45//!   rewards are applied) or 'exceptional' (slashable) session rotation.
46//!
47//! - **Session rotation process:** At the beginning of each block, the `on_initialize` function
48//!   queries the provided implementation of `ShouldEndSession`. If the session is to end the newly
49//!   activated validator IDs and session keys are taken from storage and passed to the
50//!   `SessionHandler`. The validator set supplied by `SessionManager::new_session` and the
51//!   corresponding session keys, which may have been registered via `set_keys` during the previous
52//!   session, are written to storage where they will wait one session before being passed to the
53//!   `SessionHandler` themselves.
54//!
55//! ### Goals
56//!
57//! The Session pallet is designed to make the following possible:
58//!
59//! - Set session keys of the validator set for upcoming sessions.
60//! - Control the length of sessions.
61//! - Configure and switch between either normal or exceptional session rotations.
62//!
63//! ## Interface
64//!
65//! ### Dispatchable Functions
66//!
67//! - `set_keys` - Set a validator's session keys for upcoming sessions.
68//!
69//! ### Public Functions
70//!
71//! - `rotate_session` - Change to the next session. Register the new authority set. Queue changes
72//!   for next session rotation.
73//! - `disable_index` - Disable a validator by index.
74//! - `disable` - Disable a validator by Validator ID
75//!
76//! ## Usage
77//!
78//! ### Example from the FRAME
79//!
80//! The [Staking pallet](../plant_staking/index.html) uses the Session pallet to get the validator
81//! set.
82//!
83//! ```
84//! use plant_session as session;
85//!
86//! fn validators<T: plant_session::Config>() -> Vec<<T as plant_session::Config>::ValidatorId> {
87//! 	plant_session::Validators::<T>::get()
88//! }
89//! # fn main(){}
90//! ```
91//!
92//! ## Related Pallets
93//!
94//! - [Staking](../plant_staking/index.html)
95
96#![cfg_attr(not(feature = "std"), no_std)]
97
98pub mod disabling;
99#[cfg(feature = "historical")]
100pub mod historical;
101#[cfg(feature = "runtime-benchmarks")]
102pub mod benchmarking;
103pub mod migrations;
104#[cfg(test)]
105mod mock;
106#[cfg(test)]
107mod tests;
108pub mod weights;
109
110extern crate alloc;
111
112use alloc::{boxed::Box, vec::Vec};
113use codec::{Decode, MaxEncodedLen};
114use core::{
115	marker::PhantomData,
116	ops::{Rem, Sub},
117};
118use disabling::DisablingStrategy;
119use subsoil::staking::{offence::OffenceSeverity, SessionIndex};
120use subsoil::runtime::{
121	traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero},
122	ConsensusEngineId, DispatchError, KeyTypeId, Permill, RuntimeAppPublic,
123};
124use topsoil_core::{
125	dispatch::DispatchResult,
126	ensure,
127	traits::{
128		fungible::{hold::Mutate as HoldMutate, Inspect, Mutate},
129		Defensive, EstimateNextNewSession, EstimateNextSessionRotation, FindAuthor, Get,
130		OneSessionHandler, ValidatorRegistration, ValidatorSet,
131	},
132	weights::Weight,
133	Parameter,
134};
135use topsoil_core::system::pallet_prelude::BlockNumberFor;
136
137pub use pallet::*;
138pub use weights::WeightInfo;
139
140#[cfg(any(feature = "try-runtime"))]
141use subsoil::runtime::TryRuntimeError;
142
143pub(crate) const LOG_TARGET: &str = "runtime::session";
144
145// syntactic sugar for logging.
146#[macro_export]
147macro_rules! log {
148	($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
149		log::$level!(
150			target: crate::LOG_TARGET,
151			concat!("[{:?}] 💸 ", $patter), <topsoil_core::system::Pallet<T>>::block_number() $(, $values)*
152		)
153	};
154}
155
156/// Decides whether the session should be ended.
157pub trait ShouldEndSession<BlockNumber> {
158	/// Return `true` if the session should be ended.
159	fn should_end_session(now: BlockNumber) -> bool;
160}
161
162/// Ends the session after a fixed period of blocks.
163///
164/// The first session will have length of `Offset`, and
165/// the following sessions will have length of `Period`.
166/// This may prove nonsensical if `Offset` >= `Period`.
167pub struct PeriodicSessions<Period, Offset>(PhantomData<(Period, Offset)>);
168
169impl<
170		BlockNumber: Rem<Output = BlockNumber> + Sub<Output = BlockNumber> + Zero + PartialOrd,
171		Period: Get<BlockNumber>,
172		Offset: Get<BlockNumber>,
173	> ShouldEndSession<BlockNumber> for PeriodicSessions<Period, Offset>
174{
175	fn should_end_session(now: BlockNumber) -> bool {
176		let offset = Offset::get();
177		now >= offset && ((now - offset) % Period::get()).is_zero()
178	}
179}
180
181impl<
182		BlockNumber: AtLeast32BitUnsigned + Clone,
183		Period: Get<BlockNumber>,
184		Offset: Get<BlockNumber>,
185	> EstimateNextSessionRotation<BlockNumber> for PeriodicSessions<Period, Offset>
186{
187	fn average_session_length() -> BlockNumber {
188		Period::get()
189	}
190
191	fn estimate_current_session_progress(now: BlockNumber) -> (Option<Permill>, Weight) {
192		let offset = Offset::get();
193		let period = Period::get();
194
195		// NOTE: we add one since we assume that the current block has already elapsed,
196		// i.e. when evaluating the last block in the session the progress should be 100%
197		// (0% is never returned).
198		let progress = if now >= offset {
199			let current = (now - offset) % period.clone() + One::one();
200			Some(Permill::from_rational(current, period))
201		} else {
202			Some(Permill::from_rational(now + One::one(), offset))
203		};
204
205		// Weight note: `estimate_current_session_progress` has no storage reads and trivial
206		// computational overhead. There should be no risk to the chain having this weight value be
207		// zero for now. However, this value of zero was not properly calculated, and so it would be
208		// reasonable to come back here and properly calculate the weight of this function.
209		(progress, Zero::zero())
210	}
211
212	fn estimate_next_session_rotation(now: BlockNumber) -> (Option<BlockNumber>, Weight) {
213		let offset = Offset::get();
214		let period = Period::get();
215
216		let next_session = if now > offset {
217			let block_after_last_session = (now.clone() - offset) % period.clone();
218			if block_after_last_session > Zero::zero() {
219				now.saturating_add(period.saturating_sub(block_after_last_session))
220			} else {
221				// this branch happens when the session is already rotated or will rotate in this
222				// block (depending on being called before or after `session::on_initialize`). Here,
223				// we assume the latter, namely that this is called after `session::on_initialize`,
224				// and thus we add period to it as well.
225				now + period
226			}
227		} else {
228			offset
229		};
230
231		// Weight note: `estimate_next_session_rotation` has no storage reads and trivial
232		// computational overhead. There should be no risk to the chain having this weight value be
233		// zero for now. However, this value of zero was not properly calculated, and so it would be
234		// reasonable to come back here and properly calculate the weight of this function.
235		(Some(next_session), Zero::zero())
236	}
237}
238
239/// A trait for managing creation of new validator set.
240pub trait SessionManager<ValidatorId> {
241	/// Plan a new session, and optionally provide the new validator set.
242	///
243	/// Even if the validator-set is the same as before, if any underlying economic conditions have
244	/// changed (i.e. stake-weights), the new validator set must be returned. This is necessary for
245	/// consensus engines making use of the session pallet to issue a validator-set change so
246	/// misbehavior can be provably associated with the new economic conditions as opposed to the
247	/// old. The returned validator set, if any, will not be applied until `new_index`. `new_index`
248	/// is strictly greater than from previous call.
249	///
250	/// The first session start at index 0.
251	///
252	/// `new_session(session)` is guaranteed to be called before `end_session(session-1)`. In other
253	/// words, a new session must always be planned before an ongoing one can be finished.
254	fn new_session(new_index: SessionIndex) -> Option<Vec<ValidatorId>>;
255	/// Same as `new_session`, but it this should only be called at genesis.
256	///
257	/// The session manager might decide to treat this in a different way. Default impl is simply
258	/// using [`new_session`](Self::new_session).
259	fn new_session_genesis(new_index: SessionIndex) -> Option<Vec<ValidatorId>> {
260		Self::new_session(new_index)
261	}
262	/// End the session.
263	///
264	/// Because the session pallet can queue validator set the ending session can be lower than the
265	/// last new session index.
266	fn end_session(end_index: SessionIndex);
267	/// Start an already planned session.
268	///
269	/// The session start to be used for validation.
270	fn start_session(start_index: SessionIndex);
271}
272
273impl<A> SessionManager<A> for () {
274	fn new_session(_: SessionIndex) -> Option<Vec<A>> {
275		None
276	}
277	fn start_session(_: SessionIndex) {}
278	fn end_session(_: SessionIndex) {}
279}
280
281/// Handler for session life cycle events.
282pub trait SessionHandler<ValidatorId> {
283	/// All the key type ids this session handler can process.
284	///
285	/// The order must be the same as it expects them in
286	/// [`on_new_session`](Self::on_new_session<Ks>) and
287	/// [`on_genesis_session`](Self::on_genesis_session<Ks>).
288	const KEY_TYPE_IDS: &'static [KeyTypeId];
289
290	/// The given validator set will be used for the genesis session.
291	/// It is guaranteed that the given validator set will also be used
292	/// for the second session, therefore the first call to `on_new_session`
293	/// should provide the same validator set.
294	fn on_genesis_session<Ks: OpaqueKeys>(validators: &[(ValidatorId, Ks)]);
295
296	/// Session set has changed; act appropriately. Note that this can be called
297	/// before initialization of your pallet.
298	///
299	/// `changed` is true whenever any of the session keys or underlying economic
300	/// identities or weightings behind `validators` keys has changed. `queued_validators`
301	/// could change without `validators` changing. Example of possible sequent calls:
302	///     Session N: on_new_session(false, unchanged_validators, unchanged_queued_validators)
303	///     Session N + 1: on_new_session(false, unchanged_validators, new_queued_validators)
304	/// 	Session N + 2: on_new_session(true, new_queued_validators, new_queued_validators)
305	fn on_new_session<Ks: OpaqueKeys>(
306		changed: bool,
307		validators: &[(ValidatorId, Ks)],
308		queued_validators: &[(ValidatorId, Ks)],
309	);
310
311	/// A notification for end of the session.
312	///
313	/// Note it is triggered before any [`SessionManager::end_session`] handlers,
314	/// so we can still affect the validator set.
315	fn on_before_session_ending() {}
316
317	/// A validator got disabled. Act accordingly until a new session begins.
318	fn on_disabled(validator_index: u32);
319}
320
321#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
322#[tuple_types_custom_trait_bound(OneSessionHandler<AId>)]
323impl<AId> SessionHandler<AId> for Tuple {
324	for_tuples!(
325		const KEY_TYPE_IDS: &'static [KeyTypeId] = &[ #( <Tuple::Key as RuntimeAppPublic>::ID ),* ];
326	);
327
328	fn on_genesis_session<Ks: OpaqueKeys>(validators: &[(AId, Ks)]) {
329		for_tuples!(
330			#(
331				let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
332					.filter_map(|k|
333						k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
334					)
335				);
336
337				Tuple::on_genesis_session(our_keys);
338			)*
339		)
340	}
341
342	fn on_new_session<Ks: OpaqueKeys>(
343		changed: bool,
344		validators: &[(AId, Ks)],
345		queued_validators: &[(AId, Ks)],
346	) {
347		for_tuples!(
348			#(
349				let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
350					.filter_map(|k|
351						k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
352					));
353				let queued_keys: Box<dyn Iterator<Item=_>> = Box::new(queued_validators.iter()
354					.filter_map(|k|
355						k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
356					));
357				Tuple::on_new_session(changed, our_keys, queued_keys);
358			)*
359		)
360	}
361
362	fn on_before_session_ending() {
363		for_tuples!( #( Tuple::on_before_session_ending(); )* )
364	}
365
366	fn on_disabled(i: u32) {
367		for_tuples!( #( Tuple::on_disabled(i); )* )
368	}
369}
370
371/// `SessionHandler` for tests that use `UintAuthorityId` as `Keys`.
372pub struct TestSessionHandler;
373impl<AId> SessionHandler<AId> for TestSessionHandler {
374	const KEY_TYPE_IDS: &'static [KeyTypeId] = &[subsoil::runtime::key_types::DUMMY];
375	fn on_genesis_session<Ks: OpaqueKeys>(_: &[(AId, Ks)]) {}
376	fn on_new_session<Ks: OpaqueKeys>(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {}
377	fn on_before_session_ending() {}
378	fn on_disabled(_: u32) {}
379}
380
381/// Interface to the session pallet for session management.
382///
383/// This trait provides a complete interface for managing sessions from external contexts,
384/// such as other pallets or runtime components. It combines session key management with
385/// validator operations and historical session data pruning.
386///
387/// Implemented by `Pallet<T>` when `T: Config + historical::Config`.
388pub trait SessionInterface {
389	/// The validator id type of the session pallet.
390	type ValidatorId: Clone;
391
392	/// The account id type.
393	type AccountId;
394
395	/// The session keys type.
396	type Keys: OpaqueKeys + codec::Decode;
397
398	/// Get the current set of validators.
399	fn validators() -> Vec<Self::ValidatorId>;
400
401	/// Prune historical session data up to the given session index.
402	fn prune_up_to(index: SessionIndex);
403
404	/// Report an offence for a validator.
405	///
406	/// This is used to disable validators directly on the RC until the next validator set.
407	fn report_offence(offender: Self::ValidatorId, severity: OffenceSeverity);
408
409	/// Set session keys for an account.
410	///
411	/// This method is intended for privileged callers (e.g., other pallets receiving validated
412	/// requests via XCM). It bypasses deposit holds and consumer reference tracking, so the
413	/// account does not need to be "live" or have balance on this chain.
414	///
415	/// This method does not validate ownership proof. Callers must verify that the keys belong to
416	/// the account before calling this method.
417	fn set_keys(account: &Self::AccountId, keys: Self::Keys) -> DispatchResult;
418
419	/// Purge session keys for an account.
420	///
421	/// This method is intended for privileged callers (e.g., other pallets receiving validated
422	/// requests via XCM). It bypasses deposit release and consumer reference decrement.
423	fn purge_keys(account: &Self::AccountId) -> DispatchResult;
424
425	/// Weight for setting session keys.
426	fn set_keys_weight() -> Weight;
427
428	/// Weight for purging session keys.
429	fn purge_keys_weight() -> Weight;
430}
431
432#[topsoil_core::pallet]
433pub mod pallet {
434	use super::*;
435	use topsoil_core::pallet_prelude::*;
436	use topsoil_core::system::pallet_prelude::*;
437
438	/// The in-code storage version.
439	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
440
441	#[pallet::pallet]
442	#[pallet::storage_version(STORAGE_VERSION)]
443	#[pallet::without_storage_info]
444	pub struct Pallet<T>(_);
445
446	#[pallet::config]
447	pub trait Config: topsoil_core::system::Config {
448		/// The overarching event type.
449		#[allow(deprecated)]
450		type RuntimeEvent: From<Event<Self>>
451			+ IsType<<Self as topsoil_core::system::Config>::RuntimeEvent>;
452
453		/// A stable ID for a validator.
454		type ValidatorId: Member
455			+ Parameter
456			+ MaybeSerializeDeserialize
457			+ MaxEncodedLen
458			+ TryFrom<Self::AccountId>;
459
460		/// A conversion from account ID to validator ID.
461		///
462		/// It is also a means to check that an account id is eligible to set session keys, through
463		/// being associated with a validator id. To disable this check, use
464		/// [`subsoil::runtime::traits::ConvertInto`].
465		///
466		/// Its cost must be at most one storage read.
467		type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
468
469		/// Indicator for when to end the session.
470		type ShouldEndSession: ShouldEndSession<BlockNumberFor<Self>>;
471
472		/// Something that can predict the next session rotation. This should typically come from
473		/// the same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort
474		/// estimate. It is helpful to implement [`EstimateNextNewSession`].
475		type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
476
477		/// Handler for managing new session.
478		type SessionManager: SessionManager<Self::ValidatorId>;
479
480		/// Handler when a session has changed.
481		type SessionHandler: SessionHandler<Self::ValidatorId>;
482
483		/// The keys.
484		type Keys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize;
485
486		/// `DisablingStragegy` controls how validators are disabled
487		type DisablingStrategy: DisablingStrategy<Self>;
488
489		/// Weight information for extrinsics in this pallet.
490		type WeightInfo: WeightInfo;
491
492		/// The currency type for placing holds when setting keys.
493		type Currency: Mutate<Self::AccountId>
494			+ HoldMutate<Self::AccountId, Reason: From<HoldReason>>;
495
496		/// The amount to be held when setting keys.
497		#[pallet::constant]
498		type KeyDeposit: Get<
499			<<Self as Config>::Currency as Inspect<<Self as topsoil_core::system::Config>::AccountId>>::Balance,
500		>;
501	}
502
503	#[pallet::genesis_config]
504	#[derive(topsoil_core::DefaultNoBound)]
505	pub struct GenesisConfig<T: Config> {
506		/// Initial list of validator at genesis representing by their `(AccountId, ValidatorId,
507		/// Keys)`. These keys will be considered authorities for the first two sessions and they
508		/// will be valid at least until session 2
509		pub keys: Vec<(T::AccountId, T::ValidatorId, T::Keys)>,
510		/// List of (AccountId, ValidatorId, Keys) that will be registered at genesis, but not as
511		/// active validators. These keys are set, together with `keys`, as authority candidates
512		/// for future sessions (enactable from session 2 onwards)
513		pub non_authority_keys: Vec<(T::AccountId, T::ValidatorId, T::Keys)>,
514	}
515
516	#[pallet::genesis_build]
517	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
518		fn build(&self) {
519			if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() {
520				panic!("Number of keys in session handler and session keys does not match");
521			}
522
523			T::SessionHandler::KEY_TYPE_IDS
524				.iter()
525				.zip(T::Keys::key_ids())
526				.enumerate()
527				.for_each(|(i, (sk, kk))| {
528					if sk != kk {
529						panic!(
530							"Session handler and session key expect different key type at index: {}",
531							i,
532						);
533					}
534				});
535
536			for (account, val, keys) in
537				self.keys.iter().chain(self.non_authority_keys.iter()).cloned()
538			{
539				Pallet::<T>::inner_set_keys(&val, keys)
540					.expect("genesis config must not contain duplicates; qed");
541				if topsoil_core::system::Pallet::<T>::inc_consumers_without_limit(&account).is_err() {
542					// This will leak a provider reference, however it only happens once (at
543					// genesis) so it's really not a big deal and we assume that the user wants to
544					// do this since it's the only way a non-endowed account can contain a session
545					// key.
546					topsoil_core::system::Pallet::<T>::inc_providers(&account);
547				}
548			}
549
550			let initial_validators_0 =
551				T::SessionManager::new_session_genesis(0).unwrap_or_else(|| {
552					topsoil_core::print(
553						"No initial validator provided by `SessionManager`, use \
554						session config keys to generate initial validator set.",
555					);
556					self.keys.iter().map(|x| x.1.clone()).collect()
557				});
558
559			let initial_validators_1 = T::SessionManager::new_session_genesis(1)
560				.unwrap_or_else(|| initial_validators_0.clone());
561
562			let queued_keys: Vec<_> = initial_validators_1
563				.into_iter()
564				.filter_map(|v| Pallet::<T>::load_keys(&v).map(|k| (v, k)))
565				.collect();
566
567			// Tell everyone about the genesis session keys
568			T::SessionHandler::on_genesis_session::<T::Keys>(&queued_keys);
569
570			Validators::<T>::put(initial_validators_0);
571			QueuedKeys::<T>::put(queued_keys);
572
573			T::SessionManager::start_session(0);
574		}
575	}
576
577	/// A reason for the pallet placing a hold on funds.
578	#[pallet::composite_enum]
579	pub enum HoldReason {
580		// Funds are held when settings keys
581		#[codec(index = 0)]
582		Keys,
583	}
584
585	/// The current set of validators.
586	#[pallet::storage]
587	pub type Validators<T: Config> = StorageValue<_, Vec<T::ValidatorId>, ValueQuery>;
588
589	/// Current index of the session.
590	#[pallet::storage]
591	pub type CurrentIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
592
593	/// True if the underlying economic identities or weighting behind the validators
594	/// has changed in the queued validator set.
595	#[pallet::storage]
596	pub type QueuedChanged<T> = StorageValue<_, bool, ValueQuery>;
597
598	/// The queued keys for the next session. When the next session begins, these keys
599	/// will be used to determine the validator's session keys.
600	#[pallet::storage]
601	pub type QueuedKeys<T: Config> = StorageValue<_, Vec<(T::ValidatorId, T::Keys)>, ValueQuery>;
602
603	/// Indices of disabled validators.
604	///
605	/// The vec is always kept sorted so that we can find whether a given validator is
606	/// disabled using binary search. It gets cleared when `on_session_ending` returns
607	/// a new set of identities.
608	#[pallet::storage]
609	pub type DisabledValidators<T> = StorageValue<_, Vec<(u32, OffenceSeverity)>, ValueQuery>;
610
611	/// The next session keys for a validator.
612	#[pallet::storage]
613	pub type NextKeys<T: Config> =
614		StorageMap<_, Twox64Concat, T::ValidatorId, T::Keys, OptionQuery>;
615
616	/// The owner of a key. The key is the `KeyTypeId` + the encoded key.
617	#[pallet::storage]
618	pub type KeyOwner<T: Config> =
619		StorageMap<_, Twox64Concat, (KeyTypeId, Vec<u8>), T::ValidatorId, OptionQuery>;
620
621	/// Accounts whose keys were set via `SessionInterface` (external path) without
622	/// incrementing the consumer reference or placing a key deposit. `do_purge_keys`
623	/// only decrements consumers for accounts that were registered through the local
624	/// session pallet.
625	#[pallet::storage]
626	pub type ExternallySetKeys<T: Config> =
627		StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>;
628
629	#[pallet::event]
630	#[pallet::generate_deposit(pub(super) fn deposit_event)]
631	pub enum Event<T: Config> {
632		/// New session has happened. Note that the argument is the session index, not the
633		/// block number as the type might suggest.
634		NewSession { session_index: SessionIndex },
635		/// The `NewSession` event in the current block also implies a new validator set to be
636		/// queued.
637		NewQueued,
638		/// Validator has been disabled.
639		ValidatorDisabled { validator: T::ValidatorId },
640		/// Validator has been re-enabled.
641		ValidatorReenabled { validator: T::ValidatorId },
642	}
643
644	/// Error for the session pallet.
645	#[pallet::error]
646	pub enum Error<T> {
647		/// Invalid ownership proof.
648		InvalidProof,
649		/// No associated validator ID for account.
650		NoAssociatedValidatorId,
651		/// Registered duplicate key.
652		DuplicatedKey,
653		/// No keys are associated with this account.
654		NoKeys,
655		/// Key setting account is not live, so it's impossible to associate keys.
656		NoAccount,
657	}
658
659	#[pallet::hooks]
660	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
661		/// Called when a block is initialized. Will rotate session if it is the last
662		/// block of the current session.
663		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
664			if T::ShouldEndSession::should_end_session(n) {
665				Self::rotate_session();
666				T::BlockWeights::get().max_block
667			} else {
668				// NOTE: the non-database part of the weight for `should_end_session(n)` is
669				// included as weight for empty block, the database part is expected to be in
670				// cache.
671				Weight::zero()
672			}
673		}
674
675		#[cfg(feature = "try-runtime")]
676		fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
677			Self::do_try_state()
678		}
679	}
680
681	#[pallet::call]
682	impl<T: Config> Pallet<T> {
683		/// Sets the session key(s) of the function caller to `keys`.
684		///
685		/// Allows an account to set its session key prior to becoming a validator.
686		/// This doesn't take effect until the next session.
687		///
688		/// - `origin`: The dispatch origin of this function must be signed.
689		/// - `keys`: The new session keys to set. These are the public keys of all sessions keys
690		///   setup in the runtime.
691		/// - `proof`: The proof that `origin` has access to the private keys of `keys`. See
692		///   [`impl_opaque_keys`](subsoil::runtime::impl_opaque_keys) for more information about the
693		///   proof format.
694		#[pallet::call_index(0)]
695		#[pallet::weight(T::WeightInfo::set_keys())]
696		pub fn set_keys(origin: OriginFor<T>, keys: T::Keys, proof: Vec<u8>) -> DispatchResult {
697			let who = ensure_signed(origin)?;
698			ensure!(
699				who.using_encoded(|who| keys.ownership_proof_is_valid(who, &proof)),
700				Error::<T>::InvalidProof,
701			);
702
703			Self::do_set_keys(&who, keys)?;
704			Ok(())
705		}
706
707		/// Removes any session key(s) of the function caller.
708		///
709		/// This doesn't take effect until the next session.
710		///
711		/// The dispatch origin of this function must be Signed and the account must be either be
712		/// convertible to a validator ID using the chain's typical addressing system (this usually
713		/// means being a controller account) or directly convertible into a validator ID (which
714		/// usually means being a stash account).
715		#[pallet::call_index(1)]
716		#[pallet::weight(T::WeightInfo::purge_keys())]
717		pub fn purge_keys(origin: OriginFor<T>) -> DispatchResult {
718			let who = ensure_signed(origin)?;
719			Self::do_purge_keys(&who)?;
720			Ok(())
721		}
722	}
723
724	#[cfg(feature = "runtime-benchmarks")]
725	impl<T: Config> Pallet<T> {
726		/// Mint enough funds into `who`, such that they can pay the session key setting deposit.
727		///
728		/// Meant to be used if any pallet's benchmarking code wishes to set session keys, and wants
729		/// to make sure it will succeed.
730		pub fn ensure_can_pay_key_deposit(who: &T::AccountId) -> Result<(), DispatchError> {
731			use topsoil_core::traits::tokens::{Fortitude, Preservation};
732			let deposit = T::KeyDeposit::get();
733			let has = T::Currency::reducible_balance(who, Preservation::Protect, Fortitude::Force);
734			if let Some(deficit) = deposit.checked_sub(&has) {
735				T::Currency::mint_into(who, deficit.max(T::Currency::minimum_balance()))
736					.map(|_inc| ())
737			} else {
738				Ok(())
739			}
740		}
741	}
742}
743
744impl<T: Config> Pallet<T> {
745	/// Public function to access the current set of validators.
746	pub fn validators() -> Vec<T::ValidatorId> {
747		Validators::<T>::get()
748	}
749
750	/// Public function to access the current session index.
751	pub fn current_index() -> SessionIndex {
752		CurrentIndex::<T>::get()
753	}
754
755	/// Public function to access the queued keys.
756	pub fn queued_keys() -> Vec<(T::ValidatorId, T::Keys)> {
757		QueuedKeys::<T>::get()
758	}
759
760	/// Public function to access the disabled validators.
761	pub fn disabled_validators() -> Vec<u32> {
762		DisabledValidators::<T>::get().iter().map(|(i, _)| *i).collect()
763	}
764
765	/// Move on to next session. Register new validator set and session keys. Changes to the
766	/// validator set have a session of delay to take effect. This allows for equivocation
767	/// punishment after a fork.
768	pub fn rotate_session() {
769		let session_index = CurrentIndex::<T>::get();
770		let changed = QueuedChanged::<T>::get();
771
772		// Inform the session handlers that a session is going to end.
773		T::SessionHandler::on_before_session_ending();
774		T::SessionManager::end_session(session_index);
775		log!(trace, "ending_session {:?}", session_index);
776
777		// Get queued session keys and validators.
778		let session_keys = QueuedKeys::<T>::get();
779		let validators =
780			session_keys.iter().map(|(validator, _)| validator.clone()).collect::<Vec<_>>();
781		Validators::<T>::put(&validators);
782
783		if changed {
784			log!(trace, "resetting disabled validators");
785			// reset disabled validators if active set was changed
786			DisabledValidators::<T>::kill();
787		}
788
789		// Increment session index.
790		let session_index = session_index + 1;
791		CurrentIndex::<T>::put(session_index);
792		T::SessionManager::start_session(session_index);
793		log!(trace, "starting_session {:?}", session_index);
794
795		// Get next validator set.
796		let maybe_next_validators = T::SessionManager::new_session(session_index + 1);
797		log!(
798			trace,
799			"planning_session {:?} with {:?} validators",
800			session_index + 1,
801			maybe_next_validators.as_ref().map(|v| v.len())
802		);
803		let (next_validators, next_identities_changed) =
804			if let Some(validators) = maybe_next_validators {
805				// NOTE: as per the documentation on `OnSessionEnding`, we consider
806				// the validator set as having changed even if the validators are the
807				// same as before, as underlying economic conditions may have changed.
808				Self::deposit_event(Event::<T>::NewQueued);
809				(validators, true)
810			} else {
811				(Validators::<T>::get(), false)
812			};
813
814		// Queue next session keys.
815		let (queued_amalgamated, next_changed) = {
816			// until we are certain there has been a change, iterate the prior
817			// validators along with the current and check for changes
818			let mut changed = next_identities_changed;
819
820			let mut now_session_keys = session_keys.iter();
821			let mut check_next_changed = |keys: &T::Keys| {
822				if changed {
823					return;
824				}
825				// since a new validator set always leads to `changed` starting
826				// as true, we can ensure that `now_session_keys` and `next_validators`
827				// have the same length. this function is called once per iteration.
828				if let Some((_, old_keys)) = now_session_keys.next() {
829					if old_keys != keys {
830						changed = true;
831					}
832				}
833			};
834			let queued_amalgamated =
835				next_validators
836					.into_iter()
837					.filter_map(|a| {
838						let k =
839							Self::load_keys(&a).or_else(|| {
840								log!(warn, "failed to load session key for {:?}, skipping for next session, maybe you need to set session keys for them?", a);
841								None
842							})?;
843						check_next_changed(&k);
844						Some((a, k))
845					})
846					.collect::<Vec<_>>();
847
848			(queued_amalgamated, changed)
849		};
850
851		QueuedKeys::<T>::put(queued_amalgamated.clone());
852		QueuedChanged::<T>::put(next_changed);
853
854		// Record that this happened.
855		Self::deposit_event(Event::NewSession { session_index });
856
857		// Tell everyone about the new session keys.
858		T::SessionHandler::on_new_session::<T::Keys>(changed, &session_keys, &queued_amalgamated);
859	}
860
861	/// Upgrade the key type from some old type to a new type. Supports adding
862	/// and removing key types.
863	///
864	/// This function should be used with extreme care and only during an
865	/// `on_runtime_upgrade` block. Misuse of this function can put your blockchain
866	/// into an unrecoverable state.
867	///
868	/// Care should be taken that the raw versions of the
869	/// added keys are unique for every `ValidatorId, KeyTypeId` combination.
870	/// This is an invariant that the session pallet typically maintains internally.
871	///
872	/// As the actual values of the keys are typically not known at runtime upgrade,
873	/// it's recommended to initialize the keys to a (unique) dummy value with the expectation
874	/// that all validators should invoke `set_keys` before those keys are actually
875	/// required.
876	pub fn upgrade_keys<Old, F>(upgrade: F)
877	where
878		Old: OpaqueKeys + Member + Decode,
879		F: Fn(T::ValidatorId, Old) -> T::Keys,
880	{
881		let old_ids = Old::key_ids();
882		let new_ids = T::Keys::key_ids();
883
884		// Translate NextKeys, and key ownership relations at the same time.
885		NextKeys::<T>::translate::<Old, _>(|val, old_keys| {
886			// Clear all key ownership relations. Typically the overlap should
887			// stay the same, but no guarantees by the upgrade function.
888			for i in old_ids.iter() {
889				Self::clear_key_owner(*i, old_keys.get_raw(*i));
890			}
891
892			let new_keys = upgrade(val.clone(), old_keys);
893
894			// And now set the new ones.
895			for i in new_ids.iter() {
896				Self::put_key_owner(*i, new_keys.get_raw(*i), &val);
897			}
898
899			Some(new_keys)
900		});
901
902		let _ = QueuedKeys::<T>::translate::<Vec<(T::ValidatorId, Old)>, _>(|k| {
903			k.map(|k| {
904				k.into_iter()
905					.map(|(val, old_keys)| (val.clone(), upgrade(val, old_keys)))
906					.collect::<Vec<_>>()
907			})
908		});
909	}
910
911	/// Perform the set_key operation, checking for duplicates. Does not set `Changed`.
912	///
913	/// This ensures that the reference counter in system is incremented appropriately and as such
914	/// must accept an account ID, rather than a validator ID.
915	fn do_set_keys(account: &T::AccountId, keys: T::Keys) -> DispatchResult {
916		let who = T::ValidatorIdOf::convert(account.clone())
917			.ok_or(Error::<T>::NoAssociatedValidatorId)?;
918
919		ensure!(topsoil_core::system::Pallet::<T>::can_inc_consumer(account), Error::<T>::NoAccount);
920
921		let old_keys = Self::inner_set_keys(&who, keys)?;
922
923		// Place deposit and increment consumer if this is a new local registration,
924		// or if transitioning from external to local management.
925		// We also clear `ExternallySetKeys` if set.
926		let needs_local_setup =
927			old_keys.is_none() || ExternallySetKeys::<T>::take(account).is_some();
928		if needs_local_setup {
929			let deposit = T::KeyDeposit::get();
930			if !deposit.is_zero() {
931				T::Currency::hold(&HoldReason::Keys.into(), account, deposit)?;
932			}
933
934			let assertion = topsoil_core::system::Pallet::<T>::inc_consumers(account).is_ok();
935			debug_assert!(assertion, "can_inc_consumer() returned true; no change since; qed");
936		}
937
938		Ok(())
939	}
940
941	/// Perform the set_key operation, checking for duplicates. Does not set `Changed`.
942	///
943	/// The old keys for this validator are returned, or `None` if there were none.
944	///
945	/// This does not ensure that the reference counter in system is incremented appropriately, it
946	/// must be done by the caller or the keys will be leaked in storage.
947	fn inner_set_keys(
948		who: &T::ValidatorId,
949		keys: T::Keys,
950	) -> Result<Option<T::Keys>, DispatchError> {
951		let old_keys = Self::load_keys(who);
952
953		for id in T::Keys::key_ids() {
954			let key = keys.get_raw(*id);
955
956			// ensure keys are without duplication.
957			ensure!(
958				Self::key_owner(*id, key).map_or(true, |owner| &owner == who),
959				Error::<T>::DuplicatedKey,
960			);
961		}
962
963		for id in T::Keys::key_ids() {
964			let key = keys.get_raw(*id);
965
966			if let Some(old) = old_keys.as_ref().map(|k| k.get_raw(*id)) {
967				if key == old {
968					continue;
969				}
970
971				Self::clear_key_owner(*id, old);
972			}
973
974			Self::put_key_owner(*id, key, who);
975		}
976
977		Self::put_keys(who, &keys);
978		Ok(old_keys)
979	}
980
981	fn do_purge_keys(account: &T::AccountId) -> DispatchResult {
982		let who = T::ValidatorIdOf::convert(account.clone())
983			// `purge_keys` may not have a controller-stash pair any more. If so then we expect the
984			// stash account to be passed in directly and convert that to a `ValidatorId` using the
985			// `TryFrom` trait if supported.
986			.or_else(|| T::ValidatorId::try_from(account.clone()).ok())
987			.ok_or(Error::<T>::NoAssociatedValidatorId)?;
988
989		let old_keys = Self::take_keys(&who).ok_or(Error::<T>::NoKeys)?;
990		for id in T::Keys::key_ids() {
991			let key_data = old_keys.get_raw(*id);
992			Self::clear_key_owner(*id, key_data);
993		}
994
995		// Use release_all to handle the case where the exact amount might not be available
996		let _ = T::Currency::release_all(
997			&HoldReason::Keys.into(),
998			account,
999			topsoil_core::traits::tokens::Precision::BestEffort,
1000		);
1001
1002		if ExternallySetKeys::<T>::take(account).is_none() {
1003			// Consumer was incremented locally via `do_set_keys`, so decrement it.
1004			topsoil_core::system::Pallet::<T>::dec_consumers(account);
1005		}
1006
1007		Ok(())
1008	}
1009
1010	pub fn load_keys(v: &T::ValidatorId) -> Option<T::Keys> {
1011		NextKeys::<T>::get(v)
1012	}
1013
1014	fn take_keys(v: &T::ValidatorId) -> Option<T::Keys> {
1015		NextKeys::<T>::take(v)
1016	}
1017
1018	fn put_keys(v: &T::ValidatorId, keys: &T::Keys) {
1019		NextKeys::<T>::insert(v, keys);
1020	}
1021
1022	/// Query the owner of a session key by returning the owner's validator ID.
1023	pub fn key_owner(id: KeyTypeId, key_data: &[u8]) -> Option<T::ValidatorId> {
1024		KeyOwner::<T>::get((id, key_data))
1025	}
1026
1027	fn put_key_owner(id: KeyTypeId, key_data: &[u8], v: &T::ValidatorId) {
1028		KeyOwner::<T>::insert((id, key_data), v)
1029	}
1030
1031	fn clear_key_owner(id: KeyTypeId, key_data: &[u8]) {
1032		KeyOwner::<T>::remove((id, key_data));
1033	}
1034
1035	/// Disable the validator of index `i` with a specified severity,
1036	/// returns `false` if the validator is not found.
1037	///
1038	/// Note: If validator is already disabled, the severity will
1039	/// be updated if the new one is higher.
1040	pub fn disable_index_with_severity(i: u32, severity: OffenceSeverity) -> bool {
1041		if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 {
1042			return false;
1043		}
1044
1045		DisabledValidators::<T>::mutate(|disabled| {
1046			match disabled.binary_search_by_key(&i, |(index, _)| *index) {
1047				// Validator is already disabled, update severity if the new one is higher
1048				Ok(index) => {
1049					let current_severity = &mut disabled[index].1;
1050					if severity > *current_severity {
1051						log!(
1052							trace,
1053							"updating disablement severity of validator {:?} from {:?} to {:?}",
1054							i,
1055							*current_severity,
1056							severity
1057						);
1058						*current_severity = severity;
1059					}
1060					true
1061				},
1062				// Validator is not disabled, add to `DisabledValidators` and disable it
1063				Err(index) => {
1064					log!(trace, "disabling validator {:?}", i);
1065					Self::deposit_event(Event::ValidatorDisabled {
1066						validator: Validators::<T>::get()[i as usize].clone(),
1067					});
1068					disabled.insert(index, (i, severity));
1069					T::SessionHandler::on_disabled(i);
1070					true
1071				},
1072			}
1073		})
1074	}
1075
1076	/// Disable the validator of index `i` with a default severity (defaults to most severe),
1077	/// returns `false` if the validator is not found.
1078	pub fn disable_index(i: u32) -> bool {
1079		let default_severity = OffenceSeverity::default();
1080		Self::disable_index_with_severity(i, default_severity)
1081	}
1082
1083	/// Re-enable the validator of index `i`, returns `false` if the validator was not disabled.
1084	pub fn reenable_index(i: u32) -> bool {
1085		if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 {
1086			return false;
1087		}
1088
1089		DisabledValidators::<T>::mutate(|disabled| {
1090			if let Ok(index) = disabled.binary_search_by_key(&i, |(index, _)| *index) {
1091				log!(trace, "reenabling validator {:?}", i);
1092				Self::deposit_event(Event::ValidatorReenabled {
1093					validator: Validators::<T>::get()[i as usize].clone(),
1094				});
1095				disabled.remove(index);
1096				return true;
1097			}
1098			false
1099		})
1100	}
1101
1102	/// Convert a validator ID to an index.
1103	/// (If using with the staking pallet, this would be their *stash* account.)
1104	pub fn validator_id_to_index(id: &T::ValidatorId) -> Option<u32> {
1105		Validators::<T>::get().iter().position(|i| i == id).map(|i| i as u32)
1106	}
1107
1108	/// Report an offence for the given validator and let disabling strategy decide
1109	/// what changes to disabled validators should be made.
1110	pub fn report_offence(validator: T::ValidatorId, severity: OffenceSeverity) {
1111		let decision =
1112			T::DisablingStrategy::decision(&validator, severity, &DisabledValidators::<T>::get());
1113		log!(
1114			debug,
1115			"reporting offence for {:?} with {:?}, decision: {:?}",
1116			validator,
1117			severity,
1118			decision
1119		);
1120
1121		// Disable
1122		if let Some(offender_idx) = decision.disable {
1123			Self::disable_index_with_severity(offender_idx, severity);
1124		}
1125
1126		// Re-enable
1127		if let Some(reenable_idx) = decision.reenable {
1128			Self::reenable_index(reenable_idx);
1129		}
1130	}
1131
1132	#[cfg(any(test, feature = "try-runtime"))]
1133	pub fn do_try_state() -> Result<(), subsoil::runtime::TryRuntimeError> {
1134		// Ensure that the validators are sorted
1135		ensure!(
1136			DisabledValidators::<T>::get().windows(2).all(|pair| pair[0].0 <= pair[1].0),
1137			"DisabledValidators is not sorted"
1138		);
1139		Ok(())
1140	}
1141}
1142
1143impl<T: Config> ValidatorRegistration<T::ValidatorId> for Pallet<T> {
1144	fn is_registered(id: &T::ValidatorId) -> bool {
1145		Self::load_keys(id).is_some()
1146	}
1147}
1148
1149impl<T: Config> ValidatorSet<T::AccountId> for Pallet<T> {
1150	type ValidatorId = T::ValidatorId;
1151	type ValidatorIdOf = T::ValidatorIdOf;
1152
1153	fn session_index() -> subsoil::staking::SessionIndex {
1154		CurrentIndex::<T>::get()
1155	}
1156
1157	fn validators() -> Vec<Self::ValidatorId> {
1158		Validators::<T>::get()
1159	}
1160}
1161
1162impl<T: Config> EstimateNextNewSession<BlockNumberFor<T>> for Pallet<T> {
1163	fn average_session_length() -> BlockNumberFor<T> {
1164		T::NextSessionRotation::average_session_length()
1165	}
1166
1167	/// This session pallet always calls new_session and next_session at the same time, hence we
1168	/// do a simple proxy and pass the function to next rotation.
1169	fn estimate_next_new_session(now: BlockNumberFor<T>) -> (Option<BlockNumberFor<T>>, Weight) {
1170		T::NextSessionRotation::estimate_next_session_rotation(now)
1171	}
1172}
1173
1174impl<T: Config> topsoil_core::traits::DisabledValidators for Pallet<T> {
1175	fn is_disabled(index: u32) -> bool {
1176		DisabledValidators::<T>::get().binary_search_by_key(&index, |(i, _)| *i).is_ok()
1177	}
1178
1179	fn disabled_validators() -> Vec<u32> {
1180		Self::disabled_validators()
1181	}
1182}
1183
1184#[cfg(feature = "historical")]
1185impl<T: Config + historical::Config> SessionInterface for Pallet<T> {
1186	type ValidatorId = T::ValidatorId;
1187	type AccountId = T::AccountId;
1188	type Keys = T::Keys;
1189
1190	fn validators() -> Vec<Self::ValidatorId> {
1191		Self::validators()
1192	}
1193
1194	fn prune_up_to(index: SessionIndex) {
1195		historical::Pallet::<T>::prune_up_to(index)
1196	}
1197
1198	fn report_offence(offender: Self::ValidatorId, severity: OffenceSeverity) {
1199		Self::report_offence(offender, severity)
1200	}
1201
1202	fn set_keys(account: &Self::AccountId, keys: Self::Keys) -> DispatchResult {
1203		let who = T::ValidatorIdOf::convert(account.clone())
1204			.ok_or(Error::<T>::NoAssociatedValidatorId)?;
1205		let old_keys = Self::inner_set_keys(&who, keys)?;
1206		// Transitioning from local to external: clean up deposit and consumer ref.
1207		if old_keys.is_some() && !ExternallySetKeys::<T>::contains_key(account) {
1208			let _ = T::Currency::release_all(
1209				&HoldReason::Keys.into(),
1210				account,
1211				topsoil_core::traits::tokens::Precision::BestEffort,
1212			);
1213			topsoil_core::system::Pallet::<T>::dec_consumers(account);
1214		}
1215		ExternallySetKeys::<T>::insert(account, ());
1216		Ok(())
1217	}
1218
1219	fn purge_keys(account: &Self::AccountId) -> DispatchResult {
1220		let who = T::ValidatorIdOf::convert(account.clone())
1221			.ok_or(Error::<T>::NoAssociatedValidatorId)?;
1222
1223		let old_keys = Self::take_keys(&who).ok_or(Error::<T>::NoKeys)?;
1224		for id in T::Keys::key_ids() {
1225			let key_data = old_keys.get_raw(*id);
1226			Self::clear_key_owner(*id, key_data);
1227		}
1228		let _ = T::Currency::release_all(
1229			&HoldReason::Keys.into(),
1230			account,
1231			topsoil_core::traits::tokens::Precision::BestEffort,
1232		);
1233		if ExternallySetKeys::<T>::take(account).is_none() {
1234			topsoil_core::system::Pallet::<T>::dec_consumers(account);
1235		}
1236		Ok(())
1237	}
1238
1239	fn set_keys_weight() -> Weight {
1240		T::WeightInfo::set_keys()
1241	}
1242
1243	fn purge_keys_weight() -> Weight {
1244		T::WeightInfo::purge_keys()
1245	}
1246}
1247
1248/// Wraps the author-scraping logic for consensus engines that can recover
1249/// the canonical index of an author. This then transforms it into the
1250/// registering account-ID of that session key index.
1251pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
1252
1253impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::ValidatorId>
1254	for FindAccountFromAuthorIndex<T, Inner>
1255{
1256	fn find_author<'a, I>(digests: I) -> Option<T::ValidatorId>
1257	where
1258		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
1259	{
1260		let i = Inner::find_author(digests)?;
1261
1262		let validators = Validators::<T>::get();
1263		validators.get(i as usize).cloned()
1264	}
1265}