pallet_session_validator_management/
lib.rs

1//! Pallet for setting the Partner Chain validators using inherent data
2//!
3//! # Purpose of the pallet
4//!
5//! This pallet provides a mechanism to rotate Partner Chain's block producing committees
6//! based on candidate registrations and chain configuration sourced from Cardano. It works
7//! by integrating with `pallet_session` as a [SessionManager] and [ShouldEndSession] to provide it
8//! with committee information and to rotate sessions. In addition to managing the sessions,
9//! the pallet automatically registers session keys of all active block producers, alleviating
10//! the need for manual key registration, and ensures that all necessary chain-local accounts
11//! exist.
12//!
13//! # Committee selection overview
14//!
15//! Committees are selected for sessions corresponding roughly to Partner Chain epochs, whose
16//! duration is configurable for each Partner Chain. Due to the way session rotation works in
17//! `pallet_session`, these sessions are delayed by *2 blocks* relative to their respective
18//! epoch.
19//!
20//! Committees are selected based on the following inputs sourced from Cardano:
21//! - `Registered candidates`:
22//!   Cardano SPOs who have registered themselves as willing to participate as block producers.
23//!   These candidates need to control an ADA stake pool to be eligible for selection to a
24//!   committee, and their chance at securing a seat is proportional to their pool's size.
25//!   This candidate group corresponds to a typical "trustless" Proof of Stake block producers.
26//! - `Permissioned candidates`:
27//!   A list of trusted block producers that do not need to register themselves or control any
28//!   ADA stake on Cardano to be eligible for a Partner Chain committee.
29//!   This candidate group serves a special role as trusted block producers during initial phase
30//!   of a Partner Chain's lifetime (when there may not be enough registered candidates to ensure
31//!   proper security and decentralization of the network), and are intended to be phased out as
32//!   the number of trustless participants grows.
33//! - `D-Parameter`:
34//!   A pair of two values `R` and `P`, controlling the number of committee seats alloted for
35//!   registered and permissioned candidates respectively, which means that a committee has R+P
36//!   seats overall. This parameter gives the Partner Chain the ability to bootstrap itself using
37//!   an initial pool of permissioned candidates running trusted nodes, and then gradually shift
38//!   to registered (trustless) candidates when proper decentralization is achieved
39//! - `randomness seed`:
40//!   All randomness when selecting the committee is seeded from data sourced from Cardano so that
41//!   it is tamper-proof and agreed upon by all nodes.
42//!
43//! The permissioned candidate list and the D-Parameter are controlled by the Partner Chain's
44//! governance authority and are crucial in ensuring the chain's security in initial phases of
45//! its existence
46//!
47//! # Observability parameters
48//!
49//! All input data used when selecting a committee of a Partner Chain is sourced from Cardano.
50//! To correctly identify it, each node needs access to the current values of:
51//! - `the registration validator address`, at which all registration UTXOs are located
52//! - `the D-Parameter minting policy`, whose tokens mark the UTXO containing D-Parameter value
53//! - `the permissioned candidate minting policy`, whose tokens mark the UTXO containing the
54//!   permissioned candidate list
55//!
56//! These values are stored in the pallet storage, ensuring that they're available for all nodes
57//! to use and agreed upon through the consensus mechanism, and can be updated using a governance
58//! level extrinsic [set_main_chain_scripts].
59//!
60//! # Usage
61//!
62//! ## Prerequisites
63//!
64//! This pallet's operation requires the appropriate inherent data provider and data source
65//! be present in the node. As this pallet is crucial for the operation of the chain itself,
66//! these must be present before at the chain start or before the pallet is migrated to, to
67//! avoid down time. See documentation of `sp_session_validator_management` for information
68//! on how to add the IDP to your node. A Db-Sync-based data source implementation is provided
69//! by the `partner_chains_db_sync_data_sources` crate.
70//!
71//! Aside from the node components, the pallet requires the Partner Chain smart contracts to
72//! have been initialized on Cardano and that at least one candidate - either a registered or
73//! permissioned one - exists. See `docs/user-guides/governance/governance.md` and
74//! `docs/user-guides/chain-builder.md` for more information about governance and how to set
75//! up the Partner Chain on Cardano.
76//!
77//! ## Adding into the runtime
78//!
79//! ### Defining key types
80//!
81//! As with a stock Substrate chain, a Partner Chain needs to define its session keys. What
82//! these keys are depends on the consensus mechanisms used by the chain. For a Partner Chain
83//! using Aura as its consensus with a Grandpa finality gadget, the session keys can be defined
84//! as following:
85//!
86//! ```rust, ignore
87//! sp_runtime::impl_opaque_keys! {
88//! 	#[derive(MaxEncodedLen, PartialOrd, Ord)]
89//! 	pub struct SessionKeys {
90//! 		pub aura: Aura
91//! 		pub grandpa: Grandpa,
92//! 	}
93//! }
94//! ```
95//!
96//! ### Adding the pallet
97//!
98//! The pallet should be added to the runtime _before_ `pallet_session`, but after the consensus
99//! pallets used by the chain:
100//!
101//! ```rust, ignore
102//! construct_runtime!(
103//! 	pub struct Runtime {
104//! 		System: frame_system,
105//! 		Timestamp: pallet_timestamp,
106//! 		Aura: pallet_aura,
107//! 		Grandpa: pallet_grandpa,
108//! 		Sidechain: pallet_sidechain,
109//! 		SessionCommitteeManagement: pallet_session_validator_management,
110//! 		Session: pallet_session exclude_parts { Call },
111//!         // ... other pallets
112//! 	}
113//! );
114//! ```
115//!
116//! *Important*:
117//! It is recommended that when `pallet_session` is wired into the runtime, its extrinsics are
118//! hidden, using `exclude_parts` like in the example above. This ensures that chain users can't
119//! manually register their keys in the pallet and so the registrations done on Cardano remain
120//! the sole source of truth about key ownership. Proper operation in presence of manually set
121//! user keys is not guaranteed by the toolkit and its behavior is left unspecified.
122//!
123//! ### Configuring the pallet
124//!
125//! Configuring the pallet is straightforward and mostly consists of passing to it types already
126//! defined by other crates and in previous steps:
127//!
128//! ```rust, ignore
129//! use sidechain_domain::cross_chain_app::Public as CrossChainPublic;
130//!
131//! impl pallet_session_validator_management::Config for Runtime {
132//! 	type MaxValidators = MaxValidators;
133//! 	type AuthorityId = CrossChainPublic;
134//! 	type AuthorityKeys = SessionKeys;
135//! 	type AuthoritySelectionInputs = authority_selection_inherents::AuthoritySelectionInputs;
136//! 	type ScEpochNumber = sidechain_domain::ScEpochNumber;
137//! 	type WeightInfo = pallet_session_validator_management::weights::SubstrateWeight<Runtime>;
138//! 	type CommitteeMember = authority_selection_inherents::CommitteeMember<CrossChainPublic, SessionKeys>;
139//! 	type MainChainScriptsOrigin = EnsureRoot<Self::AccountId>;
140//!
141//! 	fn select_authorities(
142//! 		input: AuthoritySelectionInputs,
143//! 		sidechain_epoch: ScEpochNumber,
144//! 	) -> Option<BoundedVec<Self::CommitteeMember, Self::MaxValidators>> {
145//! 		authority_selection_inherents::select_authorities::<CrossChainPublic, SessionKeys, MaxValidators>(
146//! 			Sidechain::genesis_utxo(),
147//! 			input,
148//! 			sidechain_epoch,
149//! 		)
150//! 	}
151//!
152//! 	fn current_epoch_number() -> ScEpochNumber {
153//! 		Sidechain::current_epoch_number()
154//! 	}
155//! }
156//! ```
157//!
158//! One value that needs to be decided upon by the chain builder is `MaxValidators` which dictates
159//! the maximum size of a committee. This value should be higher than the P + R of the D-Parameter
160//! used and should be adjusted accordingly before any D-Parameter changes that would exceed the
161//! previous value. In case a committee selected is bigger than `MaxValidators`, it will be truncated,
162//! potentially leading to a skewed seat allocation and threatening the security of the consensus.
163//!
164//! ## Genesis configuration
165//!
166//! Genesis config can be created programmatically:
167//!
168//! ```rust,ignore
169//! GenesisConfig {
170//! 	initial_authorities: vec![
171//!        CommitteeMember::permissioned(cross_chain_pubkey_1, session_keys_1),
172//!     ],
173//! 	main_chain_scripts: MainChainScripts::read_from_env()?,
174//! }
175//! ```
176//!
177//! However, it is more typical for production chains to define their specs using Json. In that case
178//! an example configuration could look like this:
179//!
180//! ```json
181//! {
182//!   "initialAuthorities": [
183//!     {
184//!       "Permissioned": {
185//!         "id": "KW39r9CJjAVzmkf9zQ4YDb2hqfAVGdRqn53eRqyruqpxAP5YL",
186//!         "keys": {
187//!           "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
188//!           "grandpa": "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
189//!         }
190//!       }
191//!     }
192//!   ],
193//!   "mainChainScripts": {
194//!     "committee_candidate_address": "addr_test1wrp8p2c5h7encl55gv26d5fpz9r99jxjcm0rxgny3993dxs2xy42p",
195//!     "d_parameter_policy_id": "0x434dc797fd036b0b654c005551ec08f39d25fa7f0eecdf4b170d46cf",
196//!     "permissioned_candidates_policy_id": "0xe1ce5d1b8b3e93a7493ecc11556790f915aabbc44a56b0b5145770b2"
197//!   }
198//! }
199//! ```
200//!
201//! *Important*:
202//! Notice, that since the pallet's operation is necessary for block production, all main chain script
203//! values and at least one initial authority (block producer) must be provided by the genesis config.
204//!
205//!
206//! ## Updating pallet configuration
207//!
208//! ### MaxValidators
209//!
210//! The maximum number of committee seats. As this value is not typically expected to change, it is
211//! configured as part of the pallet's [Config]. This means that it can only be updated as part of a
212//! runtime upgrade. The chain builder should release a new runtime version with this value updated
213//! and the Partner Chain's governance mechanism should be used to apply it using [set_code].
214//!
215//! ### Main chain scripts
216//!
217//! The main chain scripts can change over time as the Partner Chain migrates to new versions of the
218//! Partner Chain smart contracts, either due to bug fixes or new features being released. This is
219//! necessary, because the script addresses are derived by hashing their Plutus code and are affected
220//! by any change made to it.
221//!
222//! The scripts are updated by invoking the [set_main_chain_scripts] extrinsic using the Partner Chain's
223//! governance mechanism.
224//!
225//! *Important*: Setting incorrect main chain script values will result in stalling block production
226//!              indefinitely, requiring a network-wide roll-back. As such, main chain scripts update
227//!              should be carried out with special care.
228//!
229//! [SessionManager]: pallet_session::SessionManager
230//! [set_code]: frame_system::Pallet::set_code
231
232#![cfg_attr(not(feature = "std"), no_std)]
233#![allow(clippy::type_complexity)]
234#![deny(missing_docs)]
235
236pub mod migrations;
237/// [`pallet_session`] integration.
238pub mod pallet_session_support;
239
240pub use pallet::*;
241
242#[cfg(test)]
243mod mock;
244
245#[cfg(feature = "runtime-benchmarks")]
246mod benchmarking;
247
248#[cfg(test)]
249mod tests;
250
251pub mod weights;
252
253use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
254use scale_info::TypeInfo;
255pub use sp_session_validator_management::CommitteeMember;
256pub use weights::WeightInfo;
257
258#[frame_support::pallet]
259pub mod pallet {
260	use super::*;
261	use frame_support::pallet_prelude::*;
262	use frame_system::pallet_prelude::*;
263	use log::{info, warn};
264	use sidechain_domain::byte_string::SizedByteString;
265	use sidechain_domain::{MainchainAddress, PolicyId};
266	use sp_core::blake2_256;
267	use sp_runtime::traits::{MaybeSerializeDeserialize, One, Zero};
268	use sp_session_validator_management::*;
269	use sp_std::collections::btree_set::BTreeSet;
270	use sp_std::fmt::Display;
271	use sp_std::{ops::Add, vec::Vec};
272
273	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
274
275	#[pallet::pallet]
276	#[pallet::storage_version(STORAGE_VERSION)]
277	pub struct Pallet<T>(_);
278
279	#[pallet::config]
280	pub trait Config: frame_system::Config {
281		#[pallet::constant]
282		/// Maximum amount of validators.
283		type MaxValidators: Get<u32>;
284		/// Type identifying authorities.
285		type AuthorityId: Member
286			+ Parameter
287			+ MaybeSerializeDeserialize
288			+ MaxEncodedLen
289			+ Ord
290			+ Into<Self::AccountId>;
291		/// Type of authority keys.
292		type AuthorityKeys: Parameter + Member + MaybeSerializeDeserialize + Ord + MaxEncodedLen;
293		/// Type of input data used by `select_authorities` to select a committee.
294		type AuthoritySelectionInputs: Parameter;
295		/// Type of the epoch number used by the partner chain.
296		type ScEpochNumber: Parameter
297			+ MaxEncodedLen
298			+ Zero
299			+ Display
300			+ Add
301			+ One
302			+ Default
303			+ Ord
304			+ Copy
305			+ From<u64>
306			+ Into<u64>;
307
308		/// Type of committee members returned by `select_authorities`.
309		type CommitteeMember: Parameter
310			+ Member
311			+ MaybeSerializeDeserialize
312			+ MaxEncodedLen
313			+ CommitteeMember<AuthorityId = Self::AuthorityId, AuthorityKeys = Self::AuthorityKeys>;
314
315		/// Origin for governance calls
316		type MainChainScriptsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
317
318		/// Should select a committee for `sidechain_epoch` based on selection inputs `input`.
319		/// Should return [None] if selection was impossible for the given input.
320		fn select_authorities(
321			input: Self::AuthoritySelectionInputs,
322			sidechain_epoch: Self::ScEpochNumber,
323		) -> Option<BoundedVec<Self::CommitteeMember, Self::MaxValidators>>;
324
325		/// Should return the current partner chain epoch.
326		fn current_epoch_number() -> Self::ScEpochNumber;
327
328		/// Weight functions needed for pallet_session_validator_management.
329		type WeightInfo: WeightInfo;
330
331		/// Helper for creating mock data used by benchmarks
332		#[cfg(feature = "runtime-benchmarks")]
333		type BenchmarkHelper: benchmarking::BenchmarkHelper<Self>;
334	}
335
336	#[pallet::event]
337	pub enum Event<T: Config> {}
338
339	use frame_support::{BoundedVec, CloneNoBound};
340	use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
341	use scale_info::TypeInfo;
342
343	#[derive(CloneNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)]
344	#[scale_info(skip_type_params(MaxValidators))]
345	/// Committee info type used on-chain.
346	pub struct CommitteeInfo<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators> {
347		/// Epoch number the committee is selected for.
348		pub epoch: ScEpochNumber,
349		/// List of committee members.
350		pub committee: BoundedVec<CommitteeMember, MaxValidators>,
351	}
352
353	impl<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators>
354		CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
355	{
356		/// Returns committee info as a pair of epoch number and list of committee members
357		pub fn as_pair(self) -> (ScEpochNumber, Vec<CommitteeMember>) {
358			(self.epoch, self.committee.to_vec())
359		}
360	}
361
362	impl<ScEpochNumber, CommitteeMember, MaxValidators> Default
363		for CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
364	where
365		CommitteeMember: Clone,
366		ScEpochNumber: Clone + Zero,
367	{
368		fn default() -> Self {
369			Self { epoch: ScEpochNumber::zero(), committee: BoundedVec::new() }
370		}
371	}
372
373	#[pallet::storage]
374	pub type ProvidedAccounts<T: Config> =
375		StorageValue<_, BoundedBTreeSet<T::AccountId, T::MaxValidators>, ValueQuery>;
376
377	#[pallet::storage]
378	pub type CurrentCommittee<T: Config> = StorageValue<
379		_,
380		CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>,
381		ValueQuery,
382	>;
383
384	#[pallet::storage]
385	pub type NextCommittee<T: Config> = StorageValue<
386		_,
387		CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>,
388		OptionQuery,
389	>;
390
391	/// Stores the stage of handling the inputs change. Used by session manager, to decide
392	/// if the session should be ended quickly, to speed up using the newly selected committee.
393	#[pallet::storage]
394	pub type CommitteeRotationStage<T: Config> =
395		StorageValue<_, CommitteeRotationStages, ValueQuery>;
396
397	#[pallet::storage]
398	pub type MainChainScriptsConfiguration<T: Config> =
399		StorageValue<_, MainChainScripts, ValueQuery>;
400
401	#[pallet::error]
402	pub enum Error<T> {
403		/// [Pallet::set] has been called with epoch number that is not current epoch + 1
404		InvalidEpoch,
405		/// [Pallet::set] has been called a second time for the same next epoch
406		NextCommitteeAlreadySet,
407	}
408
409	#[pallet::genesis_config]
410	#[derive(frame_support::DefaultNoBound)]
411	pub struct GenesisConfig<T: Config> {
412		/// Initial committee members of the partner chain.
413		pub initial_authorities: Vec<T::CommitteeMember>,
414		/// Initial [MainChainScripts] of the partner chain.
415		pub main_chain_scripts: MainChainScripts,
416	}
417
418	#[pallet::genesis_build]
419	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
420		fn build(&self) {
421			let initial_authorities = BoundedVec::truncate_from(self.initial_authorities.clone());
422
423			let provided_accounts: BTreeSet<T::AccountId> =
424				initial_authorities.iter().map(|m| m.authority_id().into()).collect();
425			for account in &provided_accounts {
426				frame_system::Pallet::<T>::inc_providers(&account);
427			}
428			ProvidedAccounts::<T>::set(provided_accounts.try_into().unwrap());
429
430			let committee_info =
431				CommitteeInfo { epoch: T::ScEpochNumber::zero(), committee: initial_authorities };
432			CurrentCommittee::<T>::put(committee_info);
433			MainChainScriptsConfiguration::<T>::put(self.main_chain_scripts.clone());
434		}
435	}
436
437	#[pallet::hooks]
438	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
439		// Only reason for this hook is to set the genesis committee as the committee for first block's epoch.
440		fn on_initialize(block_nr: BlockNumberFor<T>) -> Weight {
441			if block_nr.is_one() {
442				CurrentCommittee::<T>::mutate(|committee| {
443					committee.epoch = T::current_epoch_number();
444				});
445				T::DbWeight::get().reads_writes(2, 1)
446			} else {
447				Weight::zero()
448			}
449		}
450	}
451
452	#[pallet::inherent]
453	impl<T: Config> ProvideInherent for Pallet<T> {
454		type Call = Call<T>;
455		type Error = InherentError;
456		const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
457
458		/// Responsible for calling [Call::set] on each block by the block author, if the validator list changed
459		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
460			if NextCommittee::<T>::exists() {
461				None
462			} else {
463				let for_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
464				let (authority_selection_inputs, selection_inputs_hash) =
465					Self::inherent_data_to_authority_selection_inputs(data);
466				if let Some(validators) =
467					T::select_authorities(authority_selection_inputs, for_epoch_number)
468				{
469					Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
470				} else {
471					let current_committee = CurrentCommittee::<T>::get();
472					let current_committee_epoch = current_committee.epoch;
473					warn!(
474						"Committee for epoch {for_epoch_number} is the same as for epoch {current_committee_epoch}"
475					);
476					let validators = current_committee.committee;
477					Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
478				}
479			}
480		}
481
482		fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
483			let (validators_param, for_epoch_number_param, call_selection_inputs_hash) = match call
484			{
485				Call::set { validators, for_epoch_number, selection_inputs_hash } => {
486					(validators, for_epoch_number, selection_inputs_hash)
487				},
488				_ => return Ok(()),
489			};
490
491			let (authority_selection_inputs, computed_selection_inputs_hash) =
492				Self::inherent_data_to_authority_selection_inputs(data);
493			let validators =
494				T::select_authorities(authority_selection_inputs, *for_epoch_number_param)
495					.unwrap_or_else(|| {
496						// Proposed block should keep the same committee if calculation of new one was impossible.
497						// This is code is executed before the committee rotation, so the NextCommittee should be used.
498						let committee_info = NextCommittee::<T>::get()
499							// Needed only for verification of the block no 1, before any `set` call is executed.
500							.unwrap_or_else(CurrentCommittee::<T>::get);
501						committee_info.committee
502					});
503
504			if *validators_param != validators {
505				if *call_selection_inputs_hash == computed_selection_inputs_hash {
506					return Err(InherentError::InvalidValidatorsMatchingHash(
507						computed_selection_inputs_hash,
508					));
509				} else {
510					return Err(InherentError::InvalidValidatorsHashMismatch(
511						computed_selection_inputs_hash,
512						call_selection_inputs_hash.clone(),
513					));
514				}
515			}
516
517			Ok(())
518		}
519
520		fn is_inherent(call: &Self::Call) -> bool {
521			matches!(call, Call::set { .. })
522		}
523
524		fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
525			if !NextCommittee::<T>::exists() {
526				Ok(Some(InherentError::CommitteeNeedsToBeStoredOneEpochInAdvance)) // change error
527			} else {
528				Ok(None)
529			}
530		}
531	}
532
533	#[pallet::call]
534	impl<T: Config> Pallet<T> {
535		/// 'for_epoch_number' parameter is needed only for validation purposes, because we need to make sure that
536		/// check_inherent uses the same epoch_number as was used to create inherent data.
537		/// Alternative approach would be to put epoch number inside InherentData. However, sidechain
538		/// epoch number is set in Runtime, thus, inherent data provider doesn't have to know about it.
539		/// On top of that, the latter approach is slightly more complicated to code.
540		#[pallet::call_index(0)]
541		#[pallet::weight((
542		T::WeightInfo::set(validators.len() as u32),
543		DispatchClass::Mandatory
544		))]
545		pub fn set(
546			origin: OriginFor<T>,
547			validators: BoundedVec<T::CommitteeMember, T::MaxValidators>,
548			for_epoch_number: T::ScEpochNumber,
549			selection_inputs_hash: SizedByteString<32>,
550		) -> DispatchResult {
551			ensure_none(origin)?;
552			let expected_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
553			ensure!(for_epoch_number == expected_epoch_number, Error::<T>::InvalidEpoch);
554			ensure!(!NextCommittee::<T>::exists(), Error::<T>::NextCommitteeAlreadySet);
555			let len = validators.len();
556			info!(
557				"💼 Storing committee of size {len} for epoch {for_epoch_number}, input data hash: {}",
558				selection_inputs_hash.to_hex_string()
559			);
560			NextCommittee::<T>::put(CommitteeInfo {
561				epoch: for_epoch_number,
562				committee: validators,
563			});
564			Ok(())
565		}
566
567		/// Changes the main chain scripts used for committee rotation.
568		///
569		/// This extrinsic must be run either using `sudo` or some other chain governance mechanism.
570		#[pallet::call_index(1)]
571		#[pallet::weight(T::WeightInfo::set_main_chain_scripts())]
572		pub fn set_main_chain_scripts(
573			origin: OriginFor<T>,
574			committee_candidate_address: MainchainAddress,
575			d_parameter_policy_id: PolicyId,
576			permissioned_candidates_policy_id: PolicyId,
577		) -> DispatchResult {
578			T::MainChainScriptsOrigin::ensure_origin(origin)?;
579			let new_scripts = MainChainScripts {
580				committee_candidate_address,
581				d_parameter_policy_id,
582				permissioned_candidates_policy_id,
583			};
584			MainChainScriptsConfiguration::<T>::put(new_scripts);
585			Ok(())
586		}
587	}
588
589	impl<T: Config> Pallet<T> {
590		/// Returns epoch number for which next committee hasn't been set yet.
591		pub fn get_next_unset_epoch_number() -> T::ScEpochNumber {
592			NextCommittee::<T>::get()
593				.map(|next_committee| next_committee.epoch + One::one())
594				.unwrap_or(CurrentCommittee::<T>::get().epoch + One::one())
595		}
596
597		/// Returns current committee member for an index, repeating them in a round-robin fashion if needed.
598		pub fn get_current_authority_round_robin(index: usize) -> Option<T::CommitteeMember> {
599			let committee = CurrentCommittee::<T>::get().committee;
600			if committee.is_empty() {
601				return None;
602			}
603
604			committee.get(index % committee.len() as usize).cloned()
605		}
606
607		/// Returns current committee from storage.
608		pub fn current_committee_storage()
609		-> CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators> {
610			CurrentCommittee::<T>::get()
611		}
612
613		/// Returns next committee from storage.
614		pub fn next_committee_storage()
615		-> Option<CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>> {
616			NextCommittee::<T>::get()
617		}
618
619		/// Returns the `AuthorityId`s of next committee from storage.
620		///
621		/// This function's result should be always defined after inherent call of 1st block of each epoch
622		pub fn next_committee() -> Option<BoundedVec<T::AuthorityId, T::MaxValidators>> {
623			Some(BoundedVec::truncate_from(
624				NextCommittee::<T>::get()?
625					.committee
626					.into_iter()
627					.map(|member| member.authority_id())
628					.collect::<Vec<T::AuthorityId>>(),
629			))
630		}
631
632		fn inherent_data_to_authority_selection_inputs(
633			data: &InherentData,
634		) -> (T::AuthoritySelectionInputs, SizedByteString<32>) {
635			let decoded_data = data
636				.get_data::<T::AuthoritySelectionInputs>(&INHERENT_IDENTIFIER)
637				.expect("Validator inherent data not correctly encoded")
638				.expect("Validator inherent data must be provided");
639			let data_hash = SizedByteString(blake2_256(&decoded_data.encode()));
640
641			(decoded_data, data_hash)
642		}
643
644		/// Calculates committee using configured `select_authorities` function
645		pub fn calculate_committee(
646			authority_selection_inputs: T::AuthoritySelectionInputs,
647			sidechain_epoch: T::ScEpochNumber,
648		) -> Option<Vec<T::CommitteeMember>> {
649			T::select_authorities(authority_selection_inputs, sidechain_epoch).map(|c| c.to_vec())
650		}
651
652		/// If [NextCommittee] is defined, it moves its value to [CurrentCommittee] storage.
653		/// Returns the value taken from [NextCommittee].
654		pub fn rotate_committee_to_next_epoch() -> Option<Vec<T::CommitteeMember>> {
655			let next_committee = NextCommittee::<T>::take()?;
656
657			CurrentCommittee::<T>::put(next_committee.clone());
658
659			let validators = next_committee.committee.to_vec();
660			let len = validators.len();
661			info!(
662				"Committee rotated: Returning {len} validators, stored in epoch {}",
663				next_committee.epoch
664			);
665			Some(validators)
666		}
667
668		/// Returns main chain scripts.
669		pub fn get_main_chain_scripts() -> MainChainScripts {
670			MainChainScriptsConfiguration::<T>::get()
671		}
672	}
673}
674
675/// For session state machine
676#[derive(Encode, Decode, Default, Debug, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
677pub enum CommitteeRotationStages {
678	/// No action is required until the current committee becomes obsolete
679	#[default]
680	AwaitEpochChange,
681	/// Session ended because of epoch change
682	NewSessionDueEpochChange,
683	/// Session ended to accelerate use of validators queued in the previous block
684	AdditionalSession,
685}