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 WeightInfo = pallet_session_validator_management::weights::SubstrateWeight<Runtime>;
136//! type MainChainScriptsOrigin = EnsureRoot<Self::AccountId>;
137//!
138//! fn select_authorities(
139//! input: AuthoritySelectionInputs,
140//! sidechain_epoch: ScEpochNumber,
141//! ) -> Option<BoundedVec<CommitteeMemberOf<Self>, Self::MaxValidators>> {
142//! authority_selection_inherents::select_authorities::<CrossChainPublic, SessionKeys, MaxValidators>(
143//! Sidechain::genesis_utxo(),
144//! input,
145//! sidechain_epoch,
146//! )
147//! }
148//!
149//! fn current_epoch_number() -> ScEpochNumber {
150//! Sidechain::current_epoch_number()
151//! }
152//! }
153//! ```
154//!
155//! One value that needs to be decided upon by the chain builder is `MaxValidators` which dictates
156//! the maximum size of a committee. This value should be higher than the P + R of the D-Parameter
157//! used and should be adjusted accordingly before any D-Parameter changes that would exceed the
158//! previous value. In case a committee selected is bigger than `MaxValidators`, it will be truncated,
159//! potentially leading to a skewed seat allocation and threatening the security of the consensus.
160//!
161//! ## Genesis configuration
162//!
163//! Genesis config can be created programmatically:
164//!
165//! ```rust
166//! # use sp_session_validator_management::{ MainChainScripts, CommitteeMember };
167//! # use pallet_session_validator_management::{ Config, GenesisConfig };
168//! # fn create_genesis_config<T: Config>(cross_chain_pubkey_1: T::AuthorityId, session_keys_1: T::AuthorityKeys) -> GenesisConfig<T> {
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().unwrap(),
174//! }
175//! # }
176//! ```
177//!
178//! However, it is more typical for production chains to define their specs using Json. In that case
179//! an example configuration could look like this:
180//!
181//! ```json
182//! {
183//! "initialAuthorities": [
184//! {
185//! "Permissioned": {
186//! "id": "KW39r9CJjAVzmkf9zQ4YDb2hqfAVGdRqn53eRqyruqpxAP5YL",
187//! "keys": {
188//! "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
189//! "grandpa": "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
190//! }
191//! }
192//! }
193//! ],
194//! "mainChainScripts": {
195//! "committee_candidate_address": "addr_test1wrp8p2c5h7encl55gv26d5fpz9r99jxjcm0rxgny3993dxs2xy42p",
196//! "d_parameter_policy_id": "0x434dc797fd036b0b654c005551ec08f39d25fa7f0eecdf4b170d46cf",
197//! "permissioned_candidates_policy_id": "0xe1ce5d1b8b3e93a7493ecc11556790f915aabbc44a56b0b5145770b2"
198//! }
199//! }
200//! ```
201//!
202//! *Important*:
203//! Notice, that since the pallet's operation is necessary for block production, all main chain script
204//! values and at least one initial authority (block producer) must be provided by the genesis config.
205//!
206//!
207//! ## Updating pallet configuration
208//!
209//! ### MaxValidators
210//!
211//! The maximum number of committee seats. As this value is not typically expected to change, it is
212//! configured as part of the pallet's [Config]. This means that it can only be updated as part of a
213//! runtime upgrade. The chain builder should release a new runtime version with this value updated
214//! and the Partner Chain's governance mechanism should be used to apply it using [set_code].
215//!
216//! ### Main chain scripts
217//!
218//! The main chain scripts can change over time as the Partner Chain migrates to new versions of the
219//! Partner Chain smart contracts, either due to bug fixes or new features being released. This is
220//! necessary, because the script addresses are derived by hashing their Plutus code and are affected
221//! by any change made to it.
222//!
223//! The scripts are updated by invoking the [set_main_chain_scripts] extrinsic using the Partner Chain's
224//! governance mechanism.
225//!
226//! *Important*: Setting incorrect main chain script values will result in stalling block production
227//! indefinitely, requiring a network-wide roll-back. As such, main chain scripts update
228//! should be carried out with special care.
229//!
230//! [SessionManager]: pallet_session::SessionManager
231//! [set_code]: frame_system::Pallet::set_code
232
233#![cfg_attr(not(feature = "std"), no_std)]
234#![allow(clippy::type_complexity)]
235#![deny(missing_docs)]
236
237pub mod migrations;
238/// [`pallet_session`] integration.
239pub mod pallet_session_support;
240
241pub use pallet::*;
242
243#[cfg(test)]
244mod mock;
245
246#[cfg(feature = "runtime-benchmarks")]
247mod benchmarking;
248
249#[cfg(test)]
250mod tests;
251
252pub mod weights;
253
254use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
255use scale_info::TypeInfo;
256pub use sp_session_validator_management::CommitteeMember;
257pub use weights::WeightInfo;
258
259#[frame_support::pallet]
260pub mod pallet {
261 use super::*;
262 use frame_support::pallet_prelude::*;
263 use frame_support::traits::FindAuthor;
264 use frame_system::pallet_prelude::*;
265 use log::{info, warn};
266 use sidechain_domain::byte_string::SizedByteString;
267 use sidechain_domain::{MainchainAddress, PolicyId, ScEpochNumber};
268 use sp_core::blake2_256;
269 use sp_runtime::traits::{MaybeSerializeDeserialize, One, Zero};
270 use sp_session_validator_management::*;
271 use sp_std::collections::btree_set::BTreeSet;
272 use sp_std::vec::Vec;
273
274 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
275
276 #[pallet::pallet]
277 #[pallet::storage_version(STORAGE_VERSION)]
278 pub struct Pallet<T>(_);
279
280 #[pallet::config]
281 pub trait Config: frame_system::Config {
282 #[pallet::constant]
283 /// Maximum amount of validators.
284 type MaxValidators: Get<u32>;
285 /// Type identifying authorities.
286 type AuthorityId: Member
287 + Parameter
288 + MaybeSerializeDeserialize
289 + MaxEncodedLen
290 + Ord
291 + Into<Self::AccountId>;
292 /// Type of authority keys.
293 type AuthorityKeys: Parameter + Member + MaybeSerializeDeserialize + Ord + MaxEncodedLen;
294
295 /// Origin for governance calls
296 type MainChainScriptsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
297
298 /// Should select a committee for `sidechain_epoch` based on selection inputs `input`.
299 /// Should return [None] if selection was impossible for the given input.
300 fn select_authorities(
301 input: AuthoritySelectionInputs,
302 sidechain_epoch: ScEpochNumber,
303 ) -> Option<BoundedVec<CommitteeMemberOf<Self>, Self::MaxValidators>>;
304
305 /// Should return the current partner chain epoch.
306 fn current_epoch_number() -> ScEpochNumber;
307
308 /// Weight functions needed for pallet_session_validator_management.
309 type WeightInfo: WeightInfo;
310
311 /// Helper for creating mock data used by benchmarks
312 #[cfg(feature = "runtime-benchmarks")]
313 type BenchmarkHelper: benchmarking::BenchmarkHelper<Self>;
314 }
315
316 #[pallet::event]
317 pub enum Event<T: Config> {}
318
319 use frame_support::{BoundedVec, CloneNoBound};
320 use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
321 use scale_info::TypeInfo;
322
323 /// Committee member type used by the pallet
324 pub type CommitteeMemberOf<T> =
325 CommitteeMember<<T as Config>::AuthorityId, <T as Config>::AuthorityKeys>;
326
327 #[derive(CloneNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)]
328 #[scale_info(skip_type_params(MaxValidators))]
329 /// Committee info type used on-chain.
330 pub struct CommitteeInfo<AuthorityId: Clone, AuthorityKeys: Clone, MaxValidators> {
331 /// Epoch number the committee is selected for.
332 pub epoch: ScEpochNumber,
333 /// List of committee members.
334 pub committee: BoundedVec<CommitteeMember<AuthorityId, AuthorityKeys>, MaxValidators>,
335 }
336
337 /// Committee information type used by the pallet
338 pub type CommitteeInfoOf<T> = CommitteeInfo<
339 <T as Config>::AuthorityId,
340 <T as Config>::AuthorityKeys,
341 <T as Config>::MaxValidators,
342 >;
343
344 impl<AuthorityId: Clone, AuthorityKeys: Clone, MaxValidators>
345 CommitteeInfo<AuthorityId, AuthorityKeys, MaxValidators>
346 {
347 /// Returns committee info as a pair of epoch number and list of committee members
348 pub fn as_pair(self) -> (ScEpochNumber, Vec<CommitteeMember<AuthorityId, AuthorityKeys>>) {
349 (self.epoch, self.committee.to_vec())
350 }
351 }
352
353 impl<AuthorityId: Clone, AuthorityKeys: Clone, MaxValidators> Default
354 for CommitteeInfo<AuthorityId, AuthorityKeys, MaxValidators>
355 {
356 fn default() -> Self {
357 Self { epoch: ScEpochNumber::zero(), committee: BoundedVec::new() }
358 }
359 }
360
361 #[pallet::storage]
362 pub type ProvidedAccounts<T: Config> =
363 StorageValue<_, BoundedBTreeSet<T::AccountId, T::MaxValidators>, ValueQuery>;
364
365 #[pallet::storage]
366 pub type CurrentCommittee<T: Config> = StorageValue<_, CommitteeInfoOf<T>, ValueQuery>;
367
368 #[pallet::storage]
369 pub type NextCommittee<T: Config> = StorageValue<_, CommitteeInfoOf<T>, OptionQuery>;
370
371 /// Stores the stage of handling the inputs change. Used by session manager, to decide
372 /// if the session should be ended quickly, to speed up using the newly selected committee.
373 #[pallet::storage]
374 pub type CommitteeRotationStage<T: Config> =
375 StorageValue<_, CommitteeRotationStages, ValueQuery>;
376
377 #[pallet::storage]
378 pub type MainChainScriptsConfiguration<T: Config> =
379 StorageValue<_, MainChainScripts, ValueQuery>;
380
381 /// Stores the current version of `AuthorityKeys` type.
382 ///
383 /// This value is different from the pallet's storage version and is only used for versioning
384 /// `AuthorityKeys` which can change independently from other pallet storages during evolution
385 /// of a Partner Chain.
386 ///
387 /// This value should only be modified when the `AuthorityKeys` is changed, by scheduling
388 /// [AuthorityKeysMigration] during runtime upgrade.
389 ///
390 /// [AuthorityKeysMigration]: migrations::authority_keys::AuthorityKeysMigration
391 #[pallet::storage]
392 pub type AuthorityKeysVersion<T: Config> = StorageValue<_, u32, ValueQuery, GetDefault>;
393
394 #[pallet::error]
395 pub enum Error<T> {
396 /// [Pallet::set] has been called with epoch number that is not current epoch + 1
397 InvalidEpoch,
398 /// [Pallet::set] has been called a second time for the same next epoch
399 NextCommitteeAlreadySet,
400 }
401
402 #[pallet::genesis_config]
403 #[derive(frame_support::DefaultNoBound)]
404 pub struct GenesisConfig<T: Config> {
405 /// Initial committee members of the partner chain.
406 pub initial_authorities: Vec<CommitteeMemberOf<T>>,
407 /// Initial [MainChainScripts] of the partner chain.
408 pub main_chain_scripts: MainChainScripts,
409 }
410
411 #[pallet::genesis_build]
412 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
413 fn build(&self) {
414 let initial_authorities = BoundedVec::truncate_from(self.initial_authorities.clone());
415
416 let provided_accounts: BTreeSet<T::AccountId> =
417 initial_authorities.iter().map(|m| m.authority_id().into()).collect();
418 for account in &provided_accounts {
419 frame_system::Pallet::<T>::inc_providers(&account);
420 }
421 ProvidedAccounts::<T>::set(provided_accounts.try_into().unwrap());
422
423 let committee_info =
424 CommitteeInfo { epoch: ScEpochNumber::zero(), committee: initial_authorities };
425 CurrentCommittee::<T>::put(committee_info);
426 MainChainScriptsConfiguration::<T>::put(self.main_chain_scripts.clone());
427 }
428 }
429
430 #[pallet::hooks]
431 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
432 // Only reason for this hook is to set the genesis committee as the committee for first block's epoch.
433 fn on_initialize(block_nr: BlockNumberFor<T>) -> Weight {
434 if block_nr.is_one() {
435 CurrentCommittee::<T>::mutate(|committee| {
436 committee.epoch = T::current_epoch_number();
437 });
438 T::DbWeight::get().reads_writes(2, 1)
439 } else {
440 Weight::zero()
441 }
442 }
443 }
444
445 #[pallet::inherent]
446 impl<T: Config> ProvideInherent for Pallet<T> {
447 type Call = Call<T>;
448 type Error = InherentError;
449 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
450
451 /// Responsible for calling [Call::set] on each block by the block author, if the validator list changed
452 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
453 if NextCommittee::<T>::exists() {
454 None
455 } else {
456 let for_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
457 let (authority_selection_inputs, selection_inputs_hash) =
458 Self::inherent_data_to_authority_selection_inputs(data);
459 if let Some(validators) =
460 T::select_authorities(authority_selection_inputs, for_epoch_number)
461 {
462 Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
463 } else {
464 let current_committee = CurrentCommittee::<T>::get();
465 let current_committee_epoch = current_committee.epoch;
466 warn!(
467 "Committee for epoch {for_epoch_number} is the same as for epoch {current_committee_epoch}"
468 );
469 let validators = current_committee.committee;
470 Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
471 }
472 }
473 }
474
475 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
476 let (validators_param, for_epoch_number_param, call_selection_inputs_hash) = match call
477 {
478 Call::set { validators, for_epoch_number, selection_inputs_hash } => {
479 (validators, for_epoch_number, selection_inputs_hash)
480 },
481 _ => return Ok(()),
482 };
483
484 let (authority_selection_inputs, computed_selection_inputs_hash) =
485 Self::inherent_data_to_authority_selection_inputs(data);
486 let validators =
487 T::select_authorities(authority_selection_inputs, *for_epoch_number_param)
488 .unwrap_or_else(|| {
489 // Proposed block should keep the same committee if calculation of new one was impossible.
490 // This is code is executed before the committee rotation, so the NextCommittee should be used.
491 let committee_info = NextCommittee::<T>::get()
492 // Needed only for verification of the block no 1, before any `set` call is executed.
493 .unwrap_or_else(CurrentCommittee::<T>::get);
494 committee_info.committee
495 });
496
497 if *validators_param != validators {
498 if *call_selection_inputs_hash == computed_selection_inputs_hash {
499 return Err(InherentError::InvalidValidatorsMatchingHash(
500 computed_selection_inputs_hash,
501 ));
502 } else {
503 return Err(InherentError::InvalidValidatorsHashMismatch(
504 computed_selection_inputs_hash,
505 call_selection_inputs_hash.clone(),
506 ));
507 }
508 }
509
510 Ok(())
511 }
512
513 fn is_inherent(call: &Self::Call) -> bool {
514 matches!(call, Call::set { .. })
515 }
516
517 fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
518 if !NextCommittee::<T>::exists() {
519 Ok(Some(InherentError::CommitteeNeedsToBeStoredOneEpochInAdvance)) // change error
520 } else {
521 Ok(None)
522 }
523 }
524 }
525
526 #[pallet::call]
527 impl<T: Config> Pallet<T> {
528 /// 'for_epoch_number' parameter is needed only for validation purposes, because we need to make sure that
529 /// check_inherent uses the same epoch_number as was used to create inherent data.
530 /// Alternative approach would be to put epoch number inside InherentData. However, sidechain
531 /// epoch number is set in Runtime, thus, inherent data provider doesn't have to know about it.
532 /// On top of that, the latter approach is slightly more complicated to code.
533 #[pallet::call_index(0)]
534 #[pallet::weight((
535 T::WeightInfo::set(validators.len() as u32),
536 DispatchClass::Mandatory
537 ))]
538 pub fn set(
539 origin: OriginFor<T>,
540 validators: BoundedVec<CommitteeMemberOf<T>, T::MaxValidators>,
541 for_epoch_number: ScEpochNumber,
542 selection_inputs_hash: SizedByteString<32>,
543 ) -> DispatchResult {
544 ensure_none(origin)?;
545 let expected_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
546 ensure!(for_epoch_number == expected_epoch_number, Error::<T>::InvalidEpoch);
547 ensure!(!NextCommittee::<T>::exists(), Error::<T>::NextCommitteeAlreadySet);
548 let len = validators.len();
549 info!(
550 "💼 Storing committee of size {len} for epoch {for_epoch_number}, input data hash: {}",
551 selection_inputs_hash.to_hex_string()
552 );
553 NextCommittee::<T>::put(CommitteeInfo {
554 epoch: for_epoch_number,
555 committee: validators,
556 });
557 Ok(())
558 }
559
560 /// Changes the main chain scripts used for committee rotation.
561 ///
562 /// This extrinsic must be run either using `sudo` or some other chain governance mechanism.
563 #[pallet::call_index(1)]
564 #[pallet::weight(T::WeightInfo::set_main_chain_scripts())]
565 pub fn set_main_chain_scripts(
566 origin: OriginFor<T>,
567 committee_candidate_address: MainchainAddress,
568 d_parameter_policy_id: PolicyId,
569 permissioned_candidates_policy_id: PolicyId,
570 ) -> DispatchResult {
571 T::MainChainScriptsOrigin::ensure_origin(origin)?;
572 let new_scripts = MainChainScripts {
573 committee_candidate_address,
574 d_parameter_policy_id,
575 permissioned_candidates_policy_id,
576 };
577 MainChainScriptsConfiguration::<T>::put(new_scripts);
578 Ok(())
579 }
580 }
581
582 impl<T: Config> Pallet<T> {
583 /// Returns epoch number for which next committee hasn't been set yet.
584 pub fn get_next_unset_epoch_number() -> ScEpochNumber {
585 NextCommittee::<T>::get()
586 .map(|next_committee| next_committee.epoch + One::one())
587 .unwrap_or(CurrentCommittee::<T>::get().epoch + One::one())
588 }
589
590 /// Returns current committee member for an index
591 pub fn get_current_authority_at(index: usize) -> Option<CommitteeMemberOf<T>> {
592 let committee = CurrentCommittee::<T>::get().committee;
593 if committee.is_empty() {
594 return None;
595 }
596
597 committee.get(index).cloned()
598 }
599
600 /// Returns the committee member that is the author of the current block
601 ///
602 /// This function requires a `GetAuthor` type that should provide the index of the
603 /// block's author in the current committee through [FindAuthor].
604 pub fn find_current_authority<I: TryInto<usize>, GetAuthor: FindAuthor<I>>()
605 -> Option<CommitteeMemberOf<T>> {
606 let digest = frame_system::Pallet::<T>::digest();
607 let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
608 let author_index = GetAuthor::find_author(pre_runtime_digests)?;
609 Self::get_current_authority_at(author_index.try_into().ok()?)
610 }
611
612 /// Returns current committee from storage.
613 pub fn current_committee_storage() -> CommitteeInfoOf<T> {
614 CurrentCommittee::<T>::get()
615 }
616
617 /// Returns next committee from storage.
618 pub fn next_committee_storage()
619 -> Option<CommitteeInfo<T::AuthorityId, T::AuthorityKeys, T::MaxValidators>> {
620 NextCommittee::<T>::get()
621 }
622
623 /// Returns the `AuthorityId`s of next committee from storage.
624 ///
625 /// This function's result should be always defined after inherent call of 1st block of each epoch
626 pub fn next_committee() -> Option<BoundedVec<T::AuthorityId, T::MaxValidators>> {
627 Some(BoundedVec::truncate_from(
628 NextCommittee::<T>::get()?
629 .committee
630 .into_iter()
631 .map(|member| member.authority_id())
632 .collect::<Vec<T::AuthorityId>>(),
633 ))
634 }
635
636 fn inherent_data_to_authority_selection_inputs(
637 data: &InherentData,
638 ) -> (AuthoritySelectionInputs, SizedByteString<32>) {
639 let decoded_data = data
640 .get_data::<AuthoritySelectionInputs>(&INHERENT_IDENTIFIER)
641 .expect("Validator inherent data not correctly encoded")
642 .expect("Validator inherent data must be provided");
643 let data_hash = SizedByteString(blake2_256(&decoded_data.encode()));
644
645 (decoded_data, data_hash)
646 }
647
648 /// Calculates committee using configured `select_authorities` function
649 pub fn calculate_committee(
650 authority_selection_inputs: AuthoritySelectionInputs,
651 sidechain_epoch: ScEpochNumber,
652 ) -> Option<Vec<CommitteeMemberOf<T>>> {
653 T::select_authorities(authority_selection_inputs, sidechain_epoch).map(|c| c.to_vec())
654 }
655
656 /// If [NextCommittee] is defined, it moves its value to [CurrentCommittee] storage.
657 /// Returns the value taken from [NextCommittee].
658 pub fn rotate_committee_to_next_epoch() -> Option<Vec<CommitteeMemberOf<T>>> {
659 let next_committee = NextCommittee::<T>::take()?;
660
661 CurrentCommittee::<T>::put(next_committee.clone());
662
663 let validators = next_committee.committee.to_vec();
664 let len = validators.len();
665 info!(
666 "Committee rotated: Returning {len} validators, stored in epoch {}",
667 next_committee.epoch
668 );
669 Some(validators)
670 }
671
672 /// Returns main chain scripts.
673 pub fn get_main_chain_scripts() -> MainChainScripts {
674 MainChainScriptsConfiguration::<T>::get()
675 }
676 }
677}
678
679/// For session state machine
680#[derive(Encode, Decode, Default, Debug, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
681pub enum CommitteeRotationStages {
682 /// No action is required until the current committee becomes obsolete
683 #[default]
684 AwaitEpochChange,
685 /// Session ended because of epoch change
686 NewSessionDueEpochChange,
687 /// Session ended to accelerate use of validators queued in the previous block
688 AdditionalSession,
689}