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		/// A stable ID for a validator.
260		type ValidatorId: Member
261			+ Parameter
262			+ MaybeSerializeDeserialize
263			+ MaxEncodedLen
264			+ Into<Self::AccountId>;
265
266		/// Indicator for when to end the session.
267		type ShouldEndSession: ShouldEndSession<BlockNumberFor<Self>>;
268
269		/// Something that can predict the next session rotation. This should typically come from
270		/// the same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort
271		/// estimate. It is helpful to implement [`EstimateNextNewSession`].
272		type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
273
274		/// Handler for managing new session.
275		type SessionManager: SessionManager<Self::ValidatorId, Self::Keys>;
276
277		/// Handler when a session has changed.
278		type SessionHandler: SessionHandler<Self::ValidatorId>;
279
280		/// The keys.
281		type Keys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize;
282	}
283
284	pub type ValidatorList<T> = Vec<<T as Config>::ValidatorId>;
285	pub type ValidatorAndKeysList<T> = Vec<(<T as Config>::ValidatorId, <T as Config>::Keys)>;
286
287	#[pallet::genesis_config]
288	#[derive(frame_support::DefaultNoBound)]
289	pub struct GenesisConfig<T: Config> {
290		pub initial_validators: ValidatorAndKeysList<T>,
291	}
292
293	#[pallet::genesis_build]
294	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
295		fn build(&self) {
296			if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() {
297				panic!("Number of keys in session handler and session keys does not match");
298			}
299
300			T::SessionHandler::KEY_TYPE_IDS
301				.iter()
302				.zip(T::Keys::key_ids())
303				.enumerate()
304				.for_each(|(i, (sk, kk))| {
305					if sk != kk {
306						panic!(
307							"Session handler and session key expect different key type at index: {}",
308							i,
309						);
310					}
311				});
312
313			let maybe_genesis_validators = Pallet::<T>::new_session_genesis(0);
314			let initial_validators = match &maybe_genesis_validators {
315				Some(validators) => validators,
316				None => {
317					frame_support::print(
318						"No initial validator provided by `SessionManager`, use \
319						session config keys to generate initial validator set.",
320					);
321					&self.initial_validators
322				},
323			};
324			Pallet::<T>::rotate_validators(initial_validators);
325			T::SessionHandler::on_genesis_session::<T::Keys>(initial_validators);
326			T::SessionManager::start_session(0);
327		}
328	}
329
330	#[pallet::storage]
331	// This storage is only needed to keep compatibility with Polkadot.js
332	pub type Validators<T: Config> = StorageValue<_, ValidatorList<T>, ValueQuery>;
333
334	#[pallet::storage]
335	pub type ValidatorsAndKeys<T: Config> = StorageValue<_, ValidatorAndKeysList<T>, ValueQuery>;
336
337	/// Current index of the session.
338	#[pallet::storage]
339	pub type CurrentIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
340
341	/// Indices of disabled validators.
342	///
343	/// The vec is always kept sorted so that we can find whether a given validator is
344	/// disabled using binary search. It gets cleared when `on_session_ending` returns
345	/// a new set of identities.
346	#[pallet::storage]
347	pub type DisabledValidators<T> = StorageValue<_, Vec<u32>, ValueQuery>;
348
349	#[pallet::event]
350	#[pallet::generate_deposit(pub(super) fn deposit_event)]
351	pub enum Event {
352		/// New session has happened. Note that the argument is the session index, not the
353		/// block number as the type might suggest.
354		NewSession { session_index: SessionIndex },
355	}
356
357	#[pallet::hooks]
358	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
359		/// Called when a block is initialized. Will rotate session if it is the last
360		/// block of the current session.
361		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
362			if T::ShouldEndSession::should_end_session(n) {
363				Self::rotate_session();
364				T::BlockWeights::get().max_block
365			} else {
366				// NOTE: the non-database part of the weight for `should_end_session(n)` is
367				// included as weight for empty block, the database part is expected to be in
368				// cache.
369				Weight::zero()
370			}
371		}
372	}
373}
374
375impl<T: Config> Pallet<T> {
376	/// Public function to access the current set of validators.
377	pub fn validators() -> Vec<T::ValidatorId> {
378		Validators::<T>::get()
379	}
380
381	pub fn validators_and_keys() -> Vec<(T::ValidatorId, T::Keys)> {
382		ValidatorsAndKeys::<T>::get()
383	}
384
385	/// Public function to access the current session index.
386	pub fn current_index() -> SessionIndex {
387		CurrentIndex::<T>::get()
388	}
389
390	/// Public function to access the disabled validators.
391	pub fn disabled_validators() -> Vec<u32> {
392		DisabledValidators::<T>::get()
393	}
394
395	/// Move on to next session. Register new validator set with session keys.
396	pub fn rotate_session() {
397		let session_index = <CurrentIndex<T>>::get();
398		log::trace!(target: "runtime::session", "rotating session {:?}", session_index);
399
400		T::SessionHandler::on_before_session_ending();
401		T::SessionManager::end_session(session_index);
402
403		let session_index = session_index + 1;
404		<CurrentIndex<T>>::put(session_index);
405
406		let (validators, changed) = if let Some(validators) = Self::new_session(session_index) {
407			Self::rotate_validators(&validators);
408			(validators, true)
409		} else {
410			(ValidatorsAndKeys::<T>::get(), false)
411		};
412
413		T::SessionManager::start_session(session_index);
414		Self::deposit_event(Event::NewSession { session_index });
415		// TODO if possible, remove queued_validators from SessionHandler (both Aura and Grandpa aren't using them anyway)
416		T::SessionHandler::on_new_session::<T::Keys>(changed, validators.as_ref(), &[]);
417	}
418
419	/// Disable the validator of index `i`, returns `false` if the validator was already disabled.
420	pub fn disable_index(i: u32) -> bool {
421		if i >= Validators::<T>::decode_len().unwrap_or(0) as u32 {
422			return false;
423		}
424
425		<DisabledValidators<T>>::mutate(|disabled| {
426			if let Err(index) = disabled.binary_search(&i) {
427				disabled.insert(index, i);
428				T::SessionHandler::on_disabled(i);
429				return true;
430			}
431
432			false
433		})
434	}
435
436	/// Disable the validator identified by `c`. (If using with the staking pallet,
437	/// this would be their *stash* account.)
438	///
439	/// Returns `false` either if the validator could not be found or it was already
440	/// disabled.
441	pub fn disable(c: &T::ValidatorId) -> bool {
442		ValidatorsAndKeys::<T>::get()
443			.iter()
444			.position(|(i, _)| i == c)
445			.map(|i| Self::disable_index(i as u32))
446			.unwrap_or(false)
447	}
448
449	pub fn acc_ids(validators: &[(T::ValidatorId, T::Keys)]) -> Vec<T::AccountId> {
450		validators.iter().map(|(v_id, _)| v_id.clone().into()).collect()
451	}
452	fn inc_provider(account: &T::AccountId) {
453		frame_system::Pallet::<T>::inc_providers(account);
454	}
455
456	fn dec_provider(account: &T::AccountId) -> Result<DecRefStatus, DispatchError> {
457		frame_system::Pallet::<T>::dec_providers(account)
458	}
459
460	fn change_account_providers(new_ids: &[T::AccountId], old_ids: &[T::AccountId]) {
461		let new_ids = BTreeSet::from_iter(new_ids);
462		let old_ids = BTreeSet::from_iter(old_ids);
463		let to_inc = new_ids.difference(&old_ids);
464		let to_dec = old_ids.difference(&new_ids);
465		for account in to_inc {
466			Self::inc_provider(account);
467		}
468		for account in to_dec {
469			Self::dec_provider(account).expect(
470				"We always match dec_providers with corresponding inc_providers, thus it cannot fail",
471			);
472		}
473	}
474
475	fn rotate_validators(new_validators: &ValidatorAndKeysList<T>) {
476		ValidatorsAndKeys::<T>::put(new_validators);
477		#[cfg(feature = "polkadot-js-compat")]
478		{
479			let validator_ids: Vec<_> = new_validators.iter().cloned().map(|v| v.0).collect();
480			// This storage is not used for chain operation but is required by
481			// Polkadot.js to show block producers in the explorer
482			Validators::<T>::put(validator_ids);
483		}
484		Self::change_account_providers(
485			&Self::acc_ids(new_validators),
486			&Self::acc_ids(&ValidatorsAndKeys::<T>::get()),
487		);
488		<DisabledValidators<T>>::take();
489	}
490
491	pub fn new_session(index: SessionIndex) -> Option<ValidatorAndKeysList<T>> {
492		let validators = T::SessionManager::new_session(index)?;
493		Some(validators)
494	}
495
496	pub fn new_session_genesis(index: SessionIndex) -> Option<ValidatorAndKeysList<T>> {
497		let validators = T::SessionManager::new_session_genesis(index)?;
498		Some(validators)
499	}
500}
501
502impl<T: Config> ValidatorRegistration<T::ValidatorId> for Pallet<T> {
503	fn is_registered(id: &T::ValidatorId) -> bool {
504		ValidatorsAndKeys::<T>::get().iter().any(|(vid, _)| vid == id)
505	}
506}
507
508impl<T: Config> EstimateNextNewSession<BlockNumberFor<T>> for Pallet<T> {
509	fn average_session_length() -> BlockNumberFor<T> {
510		T::NextSessionRotation::average_session_length()
511	}
512
513	/// This session pallet always calls new_session and next_session at the same time, hence we
514	/// do a simple proxy and pass the function to next rotation.
515	fn estimate_next_new_session(now: BlockNumberFor<T>) -> (Option<BlockNumberFor<T>>, Weight) {
516		T::NextSessionRotation::estimate_next_session_rotation(now)
517	}
518}
519
520impl<T: Config> frame_support::traits::DisabledValidators for Pallet<T> {
521	fn is_disabled(index: u32) -> bool {
522		DisabledValidators::<T>::get().binary_search(&index).is_ok()
523	}
524
525	fn disabled_validators() -> Vec<u32> {
526		DisabledValidators::<T>::get()
527	}
528}
529
530/// Wraps the author-scraping logic for consensus engines that can recover
531/// the canonical index of an author. This then transforms it into the
532/// registering account-ID of that session key index.
533pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
534
535impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::ValidatorId>
536	for FindAccountFromAuthorIndex<T, Inner>
537{
538	fn find_author<'a, I>(digests: I) -> Option<T::ValidatorId>
539	where
540		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
541	{
542		let i = Inner::find_author(digests)?;
543
544		let validators = ValidatorsAndKeys::<T>::get();
545		validators.get(i as usize).cloned().map(|validator_and_key| validator_and_key.0)
546	}
547}