pallet_partner_chains_session/
lib.rs

1// This file was copied from substrate/frame/session/src/lib.rs in polkadot-sdk repository and heavily modified.
2// Modifications can be seen by comparing the two files.
3
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
6
7// Additional modifications by Input Output Global, Inc.
8// Copyright (C) 2024, Input Output Global, Inc.
9
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14// 	http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22//! # Session Pallet
23//!
24//! The Session pallet allows validators to manage their session keys, provides a function for
25//! changing the session length, and handles session rotation.
26//!
27//! - [`Config`]
28//! - [`Call`]
29//! - [`Pallet`]
30//!
31//! ## Overview
32//!
33//! ### Terminology
34//! <!-- Original author of paragraph: @gavofyork -->
35//!
36//! - **Session:** A session is a period of time that has a constant set of validators. Validators
37//!   can only join or exit the validator set at a session change. It is measured in block numbers.
38//!   The block where a session is ended is determined by the `ShouldEndSession` trait. When the
39//!   session is ending, a new validator set can be chosen by `OnSessionEnding` implementations.
40//!
41//! - **Session key:** A session key is actually several keys kept together that provide the various
42//!   signing functions required by network authorities/validators in pursuit of their duties.
43//! - **Validator ID:** Every account has an associated validator ID. For some simple staking
44//!   systems, this may just be the same as the account ID. For staking systems using a
45//!   stash/controller model, the validator ID would be the stash account ID of the controller.
46//!
47//! - **Session key configuration process:** Session keys are set using `set_keys` for use not in
48//!   the next session, but the session after next. They are stored in `NextKeys`, a mapping between
49//!   the caller's `ValidatorId` and the session keys provided. `set_keys` allows users to set their
50//!   session key prior to being selected as validator. It is a public call since it uses
51//!   `ensure_signed`, which checks that the origin is a signed account. As such, the account ID of
52//!   the origin stored in `NextKeys` may not necessarily be associated with a block author or a
53//!   validator. The session keys of accounts are removed once their account balance is zero.
54//!
55//! - **Session length:** This pallet does not assume anything about the length of each session.
56//!   Rather, it relies on an implementation of `ShouldEndSession` to dictate a new session's start.
57//!   This pallet provides the `PeriodicSessions` struct for simple periodic sessions.
58//!
59//! - **Session rotation configuration:** Configure as either a 'normal' (rewardable session where
60//!   rewards are applied) or 'exceptional' (slashable) session rotation.
61//!
62//! - **Session rotation process:** At the beginning of each block, the `on_initialize` function
63//!   queries the provided implementation of `ShouldEndSession`. If the session is to end the newly
64//!   activated validator IDs and session keys are taken from storage and passed to the
65//!   `SessionHandler`. The validator set supplied by `SessionManager::new_session` and the
66//!   corresponding session keys, which may have been registered via `set_keys` during the previous
67//!   session, are written to storage where they will wait one session before being passed to the
68//!   `SessionHandler` themselves.
69//!
70//! ### Goals
71//!
72//! The Session pallet is designed to make the following possible:
73//!
74//! - Set session keys of the validator set for upcoming sessions.
75//! - Control the length of sessions.
76//! - Configure and switch between either normal or exceptional session rotations.
77//!
78//! ## Interface
79//!
80//!
81//! ### Public Functions
82//!
83//! - `rotate_session` - Change to the next session. Register the new authority set.
84//! - `disable_index` - Disable a validator by index.
85//! - `disable` - Disable a validator by Validator ID
86//!
87#![cfg_attr(not(feature = "std"), no_std)]
88
89extern crate alloc;
90
91use alloc::collections::BTreeSet;
92use frame_support::{
93	ConsensusEngineId,
94	traits::{
95		EstimateNextNewSession, EstimateNextSessionRotation, FindAuthor, OneSessionHandler,
96		ValidatorRegistration,
97	},
98	weights::Weight,
99};
100use frame_system::DecRefStatus;
101use frame_system::pallet_prelude::BlockNumberFor;
102pub use pallet::*;
103use sp_runtime::{DispatchError, KeyTypeId, RuntimeAppPublic, traits::OpaqueKeys};
104pub use sp_staking::SessionIndex;
105use sp_std::prelude::*;
106
107#[cfg(feature = "pallet-session-compat")]
108pub mod pallet_session_compat;
109
110/// Decides whether the session should be ended.
111pub trait ShouldEndSession<BlockNumber> {
112	/// Return `true` if the session should be ended.
113	fn should_end_session(now: BlockNumber) -> bool;
114}
115
116/// A trait for managing creation of new validator set.
117pub trait SessionManager<ValidatorId, Keys> {
118	/// Plan a new session, and optionally provide the new validator set.
119	///
120	/// Even if the validator-set is the same as before, if any underlying economic conditions have
121	/// changed (i.e. stake-weights), the new validator set must be returned. This is necessary for
122	/// consensus engines making use of the session pallet to issue a validator-set change so
123	/// misbehavior can be provably associated with the new economic conditions as opposed to the
124	/// old. The returned validator set, if any, will not be applied until `new_index`. `new_index`
125	/// is strictly greater than from previous call.
126	///
127	/// The first session start at index 0.
128	///
129	/// `new_session(session)` is guaranteed to be called before `end_session(session-1)`. In other
130	/// words, a new session must always be planned before an ongoing one can be finished.
131	fn new_session(new_index: SessionIndex) -> Option<Vec<(ValidatorId, Keys)>>;
132	/// Same as `new_session`, but it this should only be called at genesis.
133	///
134	/// The session manager might decide to treat this in a different way. Default impl is simply
135	/// using [`new_session`](Self::new_session).
136	fn new_session_genesis(new_index: SessionIndex) -> Option<Vec<(ValidatorId, Keys)>> {
137		Self::new_session(new_index)
138	}
139	/// End the session.
140	///
141	/// Because the session pallet can queue validator set the ending session can be lower than the
142	/// last new session index.
143	fn end_session(end_index: SessionIndex);
144	/// Start an already planned session.
145	///
146	/// The session start to be used for validation.
147	fn start_session(start_index: SessionIndex);
148}
149
150impl<A, B> SessionManager<A, B> for () {
151	fn new_session(_: SessionIndex) -> Option<Vec<(A, B)>> {
152		None
153	}
154	fn start_session(_: SessionIndex) {}
155	fn end_session(_: SessionIndex) {}
156}
157
158/// Handler for session life cycle events.
159pub trait SessionHandler<ValidatorId> {
160	/// All the key type ids this session handler can process.
161	///
162	/// The order must be the same as it expects them in
163	/// [`on_new_session`](Self::on_new_session<Ks>) and
164	/// [`on_genesis_session`](Self::on_genesis_session<Ks>).
165	const KEY_TYPE_IDS: &'static [KeyTypeId];
166
167	/// The given validator set will be used for the genesis session.
168	/// It is guaranteed that the given validator set will also be used
169	/// for the second session, therefore the first call to `on_new_session`
170	/// should provide the same validator set.
171	fn on_genesis_session<Ks: OpaqueKeys>(validators: &[(ValidatorId, Ks)]);
172
173	/// Session set has changed; act appropriately. Note that this can be called
174	/// before initialization of your pallet.
175	///
176	/// `changed` is true whenever any of the session keys or underlying economic
177	/// identities or weightings behind those keys has changed.
178	fn on_new_session<Ks: OpaqueKeys>(
179		changed: bool,
180		validators: &[(ValidatorId, Ks)],
181		queued_validators: &[(ValidatorId, Ks)],
182	);
183
184	/// A notification for end of the session.
185	///
186	/// Note it is triggered before any [`SessionManager::end_session`] handlers,
187	/// so we can still affect the validator set.
188	fn on_before_session_ending() {}
189
190	/// A validator got disabled. Act accordingly until a new session begins.
191	fn on_disabled(validator_index: u32);
192}
193
194#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
195#[tuple_types_custom_trait_bound(OneSessionHandler<AId>)]
196impl<AId> SessionHandler<AId> for Tuple {
197	for_tuples!(
198		const KEY_TYPE_IDS: &'static [KeyTypeId] = &[ #( <Tuple::Key as RuntimeAppPublic>::ID ),* ];
199	);
200
201	fn on_genesis_session<Ks: OpaqueKeys>(validators: &[(AId, Ks)]) {
202		for_tuples!(
203			#(
204				let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
205					.filter_map(|k|
206						k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
207					)
208				);
209
210				Tuple::on_genesis_session(our_keys);
211			)*
212		)
213	}
214
215	fn on_new_session<Ks: OpaqueKeys>(
216		changed: bool,
217		validators: &[(AId, Ks)],
218		queued_validators: &[(AId, Ks)],
219	) {
220		for_tuples!(
221			#(
222				let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
223					.filter_map(|k|
224						k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
225					));
226				let queued_keys: Box<dyn Iterator<Item=_>> = Box::new(queued_validators.iter()
227					.filter_map(|k|
228						k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
229					));
230				Tuple::on_new_session(changed, our_keys, queued_keys);
231			)*
232		)
233	}
234
235	fn on_before_session_ending() {
236		for_tuples!( #( Tuple::on_before_session_ending(); )* )
237	}
238
239	fn on_disabled(i: u32) {
240		for_tuples!( #( Tuple::on_disabled(i); )* )
241	}
242}
243
244#[frame_support::pallet]
245pub mod pallet {
246	use super::*;
247	use frame_support::pallet_prelude::*;
248
249	/// The current storage version.
250	const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
251
252	#[pallet::pallet]
253	#[pallet::storage_version(STORAGE_VERSION)]
254	#[pallet::without_storage_info]
255	pub struct Pallet<T>(_);
256
257	#[pallet::config]
258	pub trait Config: frame_system::Config {
259		/// The overarching event type.
260		type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
261
262		/// A stable ID for a validator.
263		type ValidatorId: Member
264			+ Parameter
265			+ MaybeSerializeDeserialize
266			+ MaxEncodedLen
267			+ Into<Self::AccountId>;
268
269		/// Indicator for when to end the session.
270		type ShouldEndSession: ShouldEndSession<BlockNumberFor<Self>>;
271
272		/// Something that can predict the next session rotation. This should typically come from
273		/// the same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort
274		/// estimate. It is helpful to implement [`EstimateNextNewSession`].
275		type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
276
277		/// Handler for managing new session.
278		type SessionManager: SessionManager<Self::ValidatorId, Self::Keys>;
279
280		/// Handler when a session has changed.
281		type SessionHandler: SessionHandler<Self::ValidatorId>;
282
283		/// The keys.
284		type Keys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize;
285	}
286
287	pub type ValidatorList<T> = Vec<<T as Config>::ValidatorId>;
288	pub type ValidatorAndKeysList<T> = Vec<(<T as Config>::ValidatorId, <T as Config>::Keys)>;
289
290	#[pallet::genesis_config]
291	#[derive(frame_support::DefaultNoBound)]
292	pub struct GenesisConfig<T: Config> {
293		pub initial_validators: ValidatorAndKeysList<T>,
294	}
295
296	#[pallet::genesis_build]
297	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
298		fn build(&self) {
299			if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() {
300				panic!("Number of keys in session handler and session keys does not match");
301			}
302
303			T::SessionHandler::KEY_TYPE_IDS
304				.iter()
305				.zip(T::Keys::key_ids())
306				.enumerate()
307				.for_each(|(i, (sk, kk))| {
308					if sk != kk {
309						panic!(
310							"Session handler and session key expect different key type at index: {}",
311							i,
312						);
313					}
314				});
315
316			let maybe_genesis_validators = Pallet::<T>::new_session_genesis(0);
317			let initial_validators = match &maybe_genesis_validators {
318				Some(validators) => validators,
319				None => {
320					frame_support::print(
321						"No initial validator provided by `SessionManager`, use \
322						session config keys to generate initial validator set.",
323					);
324					&self.initial_validators
325				},
326			};
327			Pallet::<T>::rotate_validators(initial_validators);
328			T::SessionHandler::on_genesis_session::<T::Keys>(initial_validators);
329			T::SessionManager::start_session(0);
330		}
331	}
332
333	#[pallet::storage]
334	// This storage is only needed to keep compatibility with Polkadot.js
335	pub type Validators<T: Config> = StorageValue<_, ValidatorList<T>, ValueQuery>;
336
337	#[pallet::storage]
338	pub type ValidatorsAndKeys<T: Config> = StorageValue<_, ValidatorAndKeysList<T>, ValueQuery>;
339
340	/// Current index of the session.
341	#[pallet::storage]
342	pub type CurrentIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
343
344	/// Indices of disabled validators.
345	///
346	/// The vec is always kept sorted so that we can find whether a given validator is
347	/// disabled using binary search. It gets cleared when `on_session_ending` returns
348	/// a new set of identities.
349	#[pallet::storage]
350	pub type DisabledValidators<T> = StorageValue<_, Vec<u32>, ValueQuery>;
351
352	#[pallet::event]
353	#[pallet::generate_deposit(pub(super) fn deposit_event)]
354	pub enum Event {
355		/// New session has happened. Note that the argument is the session index, not the
356		/// block number as the type might suggest.
357		NewSession { session_index: SessionIndex },
358	}
359
360	#[pallet::hooks]
361	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
362		/// Called when a block is initialized. Will rotate session if it is the last
363		/// block of the current session.
364		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
365			if T::ShouldEndSession::should_end_session(n) {
366				Self::rotate_session();
367				T::BlockWeights::get().max_block
368			} else {
369				// NOTE: the non-database part of the weight for `should_end_session(n)` is
370				// included as weight for empty block, the database part is expected to be in
371				// cache.
372				Weight::zero()
373			}
374		}
375	}
376}
377
378impl<T: Config> Pallet<T> {
379	/// Public function to access the current set of validators.
380	pub fn validators() -> Vec<T::ValidatorId> {
381		Validators::<T>::get()
382	}
383
384	pub fn validators_and_keys() -> Vec<(T::ValidatorId, T::Keys)> {
385		ValidatorsAndKeys::<T>::get()
386	}
387
388	/// Public function to access the current session index.
389	pub fn current_index() -> SessionIndex {
390		CurrentIndex::<T>::get()
391	}
392
393	/// Public function to access the disabled validators.
394	pub fn disabled_validators() -> Vec<u32> {
395		DisabledValidators::<T>::get()
396	}
397
398	/// Move on to next session. Register new validator set with session keys.
399	pub fn rotate_session() {
400		let session_index = <CurrentIndex<T>>::get();
401		log::trace!(target: "runtime::session", "rotating session {:?}", session_index);
402
403		T::SessionHandler::on_before_session_ending();
404		T::SessionManager::end_session(session_index);
405
406		let session_index = session_index + 1;
407		<CurrentIndex<T>>::put(session_index);
408
409		let (validators, changed) = if let Some(validators) = Self::new_session(session_index) {
410			Self::rotate_validators(&validators);
411			(validators, true)
412		} else {
413			(ValidatorsAndKeys::<T>::get(), false)
414		};
415
416		T::SessionManager::start_session(session_index);
417		Self::deposit_event(Event::NewSession { session_index });
418		// TODO if possible, remove queued_validators from SessionHandler (both Aura and Grandpa aren't using them anyway)
419		T::SessionHandler::on_new_session::<T::Keys>(changed, validators.as_ref(), &[]);
420	}
421
422	/// Disable the validator of index `i`, returns `false` if the validator was already disabled.
423	pub fn disable_index(i: u32) -> bool {
424		if i >= Validators::<T>::decode_len().unwrap_or(0) as u32 {
425			return false;
426		}
427
428		<DisabledValidators<T>>::mutate(|disabled| {
429			if let Err(index) = disabled.binary_search(&i) {
430				disabled.insert(index, i);
431				T::SessionHandler::on_disabled(i);
432				return true;
433			}
434
435			false
436		})
437	}
438
439	/// Disable the validator identified by `c`. (If using with the staking pallet,
440	/// this would be their *stash* account.)
441	///
442	/// Returns `false` either if the validator could not be found or it was already
443	/// disabled.
444	pub fn disable(c: &T::ValidatorId) -> bool {
445		ValidatorsAndKeys::<T>::get()
446			.iter()
447			.position(|(i, _)| i == c)
448			.map(|i| Self::disable_index(i as u32))
449			.unwrap_or(false)
450	}
451
452	pub fn acc_ids(validators: &[(T::ValidatorId, T::Keys)]) -> Vec<T::AccountId> {
453		validators.iter().map(|(v_id, _)| v_id.clone().into()).collect()
454	}
455	fn inc_provider(account: &T::AccountId) {
456		frame_system::Pallet::<T>::inc_providers(account);
457	}
458
459	fn dec_provider(account: &T::AccountId) -> Result<DecRefStatus, DispatchError> {
460		frame_system::Pallet::<T>::dec_providers(account)
461	}
462
463	fn change_account_providers(new_ids: &[T::AccountId], old_ids: &[T::AccountId]) {
464		let new_ids = BTreeSet::from_iter(new_ids);
465		let old_ids = BTreeSet::from_iter(old_ids);
466		let to_inc = new_ids.difference(&old_ids);
467		let to_dec = old_ids.difference(&new_ids);
468		for account in to_inc {
469			Self::inc_provider(account);
470		}
471		for account in to_dec {
472			Self::dec_provider(account).expect(
473				"We always match dec_providers with corresponding inc_providers, thus it cannot fail",
474			);
475		}
476	}
477
478	fn rotate_validators(new_validators: &ValidatorAndKeysList<T>) {
479		ValidatorsAndKeys::<T>::put(new_validators);
480		#[cfg(feature = "polkadot-js-compat")]
481		{
482			let validator_ids: Vec<_> = new_validators.iter().cloned().map(|v| v.0).collect();
483			// This storage is not used for chain operation but is required by
484			// Polkadot.js to show block producers in the explorer
485			Validators::<T>::put(validator_ids);
486		}
487		Self::change_account_providers(
488			&Self::acc_ids(new_validators),
489			&Self::acc_ids(&ValidatorsAndKeys::<T>::get()),
490		);
491		<DisabledValidators<T>>::take();
492	}
493
494	pub fn new_session(index: SessionIndex) -> Option<ValidatorAndKeysList<T>> {
495		let validators = T::SessionManager::new_session(index)?;
496		Some(validators)
497	}
498
499	pub fn new_session_genesis(index: SessionIndex) -> Option<ValidatorAndKeysList<T>> {
500		let validators = T::SessionManager::new_session_genesis(index)?;
501		Some(validators)
502	}
503}
504
505impl<T: Config> ValidatorRegistration<T::ValidatorId> for Pallet<T> {
506	fn is_registered(id: &T::ValidatorId) -> bool {
507		ValidatorsAndKeys::<T>::get().iter().any(|(vid, _)| vid == id)
508	}
509}
510
511impl<T: Config> EstimateNextNewSession<BlockNumberFor<T>> for Pallet<T> {
512	fn average_session_length() -> BlockNumberFor<T> {
513		T::NextSessionRotation::average_session_length()
514	}
515
516	/// This session pallet always calls new_session and next_session at the same time, hence we
517	/// do a simple proxy and pass the function to next rotation.
518	fn estimate_next_new_session(now: BlockNumberFor<T>) -> (Option<BlockNumberFor<T>>, Weight) {
519		T::NextSessionRotation::estimate_next_session_rotation(now)
520	}
521}
522
523impl<T: Config> frame_support::traits::DisabledValidators for Pallet<T> {
524	fn is_disabled(index: u32) -> bool {
525		DisabledValidators::<T>::get().binary_search(&index).is_ok()
526	}
527
528	fn disabled_validators() -> Vec<u32> {
529		DisabledValidators::<T>::get()
530	}
531}
532
533/// Wraps the author-scraping logic for consensus engines that can recover
534/// the canonical index of an author. This then transforms it into the
535/// registering account-ID of that session key index.
536pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
537
538impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::ValidatorId>
539	for FindAccountFromAuthorIndex<T, Inner>
540{
541	fn find_author<'a, I>(digests: I) -> Option<T::ValidatorId>
542	where
543		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
544	{
545		let i = Inner::find_author(digests)?;
546
547		let validators = ValidatorsAndKeys::<T>::get();
548		validators.get(i as usize).cloned().map(|validator_and_key| validator_and_key.0)
549	}
550}