pallet_session_validator_management/
lib.rs

1//!  Pallet for setting the Partner Chain validators using inherent data
2
3#![cfg_attr(not(feature = "std"), no_std)]
4#![allow(clippy::type_complexity)]
5#![deny(missing_docs)]
6
7pub mod migrations;
8/// [`pallet_session`] integration.
9#[cfg(feature = "pallet-session-compat")]
10pub mod pallet_session_support;
11#[cfg(feature = "pallet-session-compat")]
12pub mod session_manager;
13
14pub use pallet::*;
15
16#[cfg(test)]
17mod mock;
18
19#[cfg(test)]
20mod tests;
21
22pub mod weights;
23
24pub use sp_session_validator_management::CommitteeMember;
25pub use weights::WeightInfo;
26
27#[frame_support::pallet]
28pub mod pallet {
29	use super::*;
30	use frame_support::pallet_prelude::*;
31	use frame_system::pallet_prelude::*;
32	use log::{info, warn};
33	use sidechain_domain::byte_string::SizedByteString;
34	use sidechain_domain::{MainchainAddress, PolicyId};
35	use sp_core::blake2_256;
36	use sp_runtime::traits::{MaybeSerializeDeserialize, One, Zero};
37	use sp_session_validator_management::*;
38	use sp_std::fmt::Display;
39	use sp_std::{ops::Add, vec, vec::Vec};
40
41	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
42
43	#[pallet::pallet]
44	#[pallet::storage_version(STORAGE_VERSION)]
45	pub struct Pallet<T>(_);
46
47	#[pallet::config]
48	pub trait Config: frame_system::Config {
49		/// The ubiquitous event type.
50		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
51		#[pallet::constant]
52		/// Maximum amount of validators.
53		type MaxValidators: Get<u32>;
54		/// Type identifying authorities.
55		type AuthorityId: Member
56			+ Parameter
57			+ MaybeSerializeDeserialize
58			+ MaxEncodedLen
59			+ Ord
60			+ Into<Self::AccountId>;
61		/// Type of authority keys.
62		type AuthorityKeys: Parameter + Member + MaybeSerializeDeserialize + Ord + MaxEncodedLen;
63		/// Type of input data used by `select_authorities` to select a committee.
64		type AuthoritySelectionInputs: Parameter;
65		/// Type of the epoch number used by the partner chain.
66		type ScEpochNumber: Parameter
67			+ MaxEncodedLen
68			+ Zero
69			+ Display
70			+ Add
71			+ One
72			+ Default
73			+ Ord
74			+ Copy
75			+ From<u64>
76			+ Into<u64>;
77
78		/// Type of committee members returned by `select_authorities`.
79		type CommitteeMember: Parameter
80			+ Member
81			+ MaybeSerializeDeserialize
82			+ MaxEncodedLen
83			+ CommitteeMember<AuthorityId = Self::AuthorityId, AuthorityKeys = Self::AuthorityKeys>;
84
85		/// Origin for governance calls
86		type MainChainScriptsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
87
88		/// Should select a committee for `sidechain_epoch` based on selection inputs `input`.
89		/// Should return [None] if selection was impossible for the given input.
90		fn select_authorities(
91			input: Self::AuthoritySelectionInputs,
92			sidechain_epoch: Self::ScEpochNumber,
93		) -> Option<BoundedVec<Self::CommitteeMember, Self::MaxValidators>>;
94
95		/// Should return the current partner chain epoch.
96		fn current_epoch_number() -> Self::ScEpochNumber;
97
98		/// Weight functions needed for pallet_session_validator_management.
99		type WeightInfo: WeightInfo;
100	}
101
102	#[pallet::event]
103	pub enum Event<T: Config> {}
104
105	use frame_support::{BoundedVec, CloneNoBound};
106	use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
107	use scale_info::TypeInfo;
108
109	#[derive(CloneNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)]
110	#[scale_info(skip_type_params(MaxValidators))]
111	/// Committee info type used on-chain.
112	pub struct CommitteeInfo<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators> {
113		/// Epoch number the committee is selected for.
114		pub epoch: ScEpochNumber,
115		/// List of committee members.
116		pub committee: BoundedVec<CommitteeMember, MaxValidators>,
117	}
118
119	impl<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators>
120		CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
121	{
122		/// Returns committee info as a pair of epoch number and list of committee members
123		pub fn as_pair(self) -> (ScEpochNumber, Vec<CommitteeMember>) {
124			(self.epoch, self.committee.to_vec())
125		}
126	}
127
128	impl<ScEpochNumber, CommitteeMember, MaxValidators> Default
129		for CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
130	where
131		CommitteeMember: Clone,
132		ScEpochNumber: Clone + Zero,
133	{
134		fn default() -> Self {
135			Self { epoch: ScEpochNumber::zero(), committee: BoundedVec::new() }
136		}
137	}
138
139	#[pallet::storage]
140	pub type CurrentCommittee<T: Config> = StorageValue<
141		_,
142		CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>,
143		ValueQuery,
144	>;
145
146	#[pallet::storage]
147	pub type NextCommittee<T: Config> = StorageValue<
148		_,
149		CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>,
150		OptionQuery,
151	>;
152
153	#[pallet::storage]
154	pub type MainChainScriptsConfiguration<T: Config> =
155		StorageValue<_, MainChainScripts, ValueQuery>;
156
157	#[pallet::error]
158	pub enum Error<T> {
159		/// [Pallet::set] has been called with epoch number that is not current epoch + 1
160		InvalidEpoch,
161		/// [Pallet::set] has been called a second time for the same next epoch
162		NextCommitteeAlreadySet,
163	}
164
165	#[pallet::genesis_config]
166	#[derive(frame_support::DefaultNoBound)]
167	pub struct GenesisConfig<T: Config> {
168		/// Initial committee members of the partner chain.
169		pub initial_authorities: Vec<T::CommitteeMember>,
170		/// Initial [MainChainScripts] of the partner chain.
171		pub main_chain_scripts: MainChainScripts,
172	}
173
174	#[pallet::genesis_build]
175	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
176		fn build(&self) {
177			let initial_authorities = BoundedVec::truncate_from(self.initial_authorities.clone());
178			let committee_info =
179				CommitteeInfo { epoch: T::ScEpochNumber::zero(), committee: initial_authorities };
180			CurrentCommittee::<T>::put(committee_info);
181			MainChainScriptsConfiguration::<T>::put(self.main_chain_scripts.clone());
182		}
183	}
184
185	#[pallet::hooks]
186	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
187		// Only reason for this hook is to set the genesis committee as the committee for first block's epoch.
188		// If it wouldn't be set, the should_end_session() function would return true at the 2nd block,
189		// thus denying handover phase to genesis committee, which would break the chain. With this hook,
190		// should_end_session() returns true at 1st block and changes committee to the same one, thus allowing
191		// handover phase to happen. After having proper chain initialization procedure this probably won't be needed anymore.
192		// Note: If chain is started during handover phase, it will wait until new epoch to produce the first block.
193		fn on_initialize(block_nr: BlockNumberFor<T>) -> Weight {
194			if block_nr.is_one() {
195				CurrentCommittee::<T>::mutate(|committee| {
196					committee.epoch = T::current_epoch_number();
197				});
198				T::DbWeight::get().reads_writes(2, 1)
199			} else {
200				Weight::zero()
201			}
202		}
203	}
204
205	#[pallet::inherent]
206	impl<T: Config> ProvideInherent for Pallet<T> {
207		type Call = Call<T>;
208		type Error = InherentError;
209		const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
210
211		/// Responsible for calling [Call:set] on each block by the block author, if the validator list changed
212		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
213			if NextCommittee::<T>::exists() {
214				None
215			} else {
216				let for_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
217				let (authority_selection_inputs, selection_inputs_hash) =
218					Self::inherent_data_to_authority_selection_inputs(data);
219				if let Some(validators) =
220					T::select_authorities(authority_selection_inputs, for_epoch_number)
221				{
222					Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
223				} else {
224					let current_committee = CurrentCommittee::<T>::get();
225					let current_committee_epoch = current_committee.epoch;
226					warn!(
227						"Committee for epoch {for_epoch_number} is the same as for epoch {current_committee_epoch}"
228					);
229					let validators = current_committee.committee;
230					Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
231				}
232			}
233		}
234
235		// TODO make this call run by every full node, so it can be relied upon for ensuring that the block is correct
236		fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
237			let (validators_param, for_epoch_number_param, call_selection_inputs_hash) = match call
238			{
239				Call::set { validators, for_epoch_number, selection_inputs_hash } => {
240					(validators, for_epoch_number, selection_inputs_hash)
241				},
242				_ => return Ok(()),
243			};
244
245			let (authority_selection_inputs, computed_selection_inputs_hash) =
246				Self::inherent_data_to_authority_selection_inputs(data);
247			let validators =
248				T::select_authorities(authority_selection_inputs, *for_epoch_number_param)
249					.unwrap_or_else(|| {
250						// Proposed block should keep the same committee if calculation of new one was impossible.
251						// This is code is executed before the committee rotation, so the NextCommittee should be used.
252						let committee_info = NextCommittee::<T>::get()
253							// Needed only for verification of the block no 1, before any `set` call is executed.
254							.unwrap_or_else(CurrentCommittee::<T>::get);
255						committee_info.committee
256					});
257
258			if *validators_param != validators {
259				if *call_selection_inputs_hash == computed_selection_inputs_hash {
260					return Err(InherentError::InvalidValidatorsMatchingHash(
261						computed_selection_inputs_hash,
262					));
263				} else {
264					return Err(InherentError::InvalidValidatorsHashMismatch(
265						computed_selection_inputs_hash,
266						call_selection_inputs_hash.clone(),
267					));
268				}
269			}
270
271			Ok(())
272		}
273
274		fn is_inherent(call: &Self::Call) -> bool {
275			matches!(call, Call::set { .. })
276		}
277
278		fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
279			if !NextCommittee::<T>::exists() {
280				Ok(Some(InherentError::CommitteeNeedsToBeStoredOneEpochInAdvance)) // change error
281			} else {
282				Ok(None)
283			}
284		}
285	}
286
287	#[pallet::call]
288	impl<T: Config> Pallet<T> {
289		/// 'for_epoch_number' parameter is needed only for validation purposes, because we need to make sure that
290		/// check_inherent uses the same epoch_number as was used to create inherent data.
291		/// Alternative approach would be to put epoch number inside InherentData. However, sidechain
292		/// epoch number is set in Runtime, thus, inherent data provider doesn't have to know about it.
293		/// On top of that, the latter approach is slightly more complicated to code.
294		#[pallet::call_index(0)]
295		#[pallet::weight((
296		T::WeightInfo::set(validators.len() as u32),
297		DispatchClass::Mandatory
298		))]
299		pub fn set(
300			origin: OriginFor<T>,
301			validators: BoundedVec<T::CommitteeMember, T::MaxValidators>,
302			for_epoch_number: T::ScEpochNumber,
303			selection_inputs_hash: SizedByteString<32>,
304		) -> DispatchResult {
305			ensure_none(origin)?;
306			let expected_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
307			ensure!(for_epoch_number == expected_epoch_number, Error::<T>::InvalidEpoch);
308			ensure!(!NextCommittee::<T>::exists(), Error::<T>::NextCommitteeAlreadySet);
309			let len = validators.len();
310			info!(
311				"💼 Storing committee of size {len} for epoch {for_epoch_number}, input data hash: {}",
312				selection_inputs_hash.to_hex_string()
313			);
314			NextCommittee::<T>::put(CommitteeInfo {
315				epoch: for_epoch_number,
316				committee: validators,
317			});
318			Ok(())
319		}
320
321		/// Changes the main chain scripts used for committee rotation.
322		///
323		/// This extrinsic must be run either using `sudo` or some other chain governance mechanism.
324		#[pallet::call_index(1)]
325		#[pallet::weight(T::WeightInfo::set(1))]
326		pub fn set_main_chain_scripts(
327			origin: OriginFor<T>,
328			committee_candidate_address: MainchainAddress,
329			d_parameter_policy_id: PolicyId,
330			permissioned_candidates_policy_id: PolicyId,
331		) -> DispatchResult {
332			T::MainChainScriptsOrigin::ensure_origin(origin)?;
333			let new_scripts = MainChainScripts {
334				committee_candidate_address,
335				d_parameter_policy_id,
336				permissioned_candidates_policy_id,
337			};
338			MainChainScriptsConfiguration::<T>::put(new_scripts);
339			Ok(())
340		}
341	}
342
343	impl<T: Config> Pallet<T> {
344		/// Returns epoch number for which next committee hasn't been set yet.
345		pub fn get_next_unset_epoch_number() -> T::ScEpochNumber {
346			NextCommittee::<T>::get()
347				.map(|next_committee| next_committee.epoch + One::one())
348				.unwrap_or(CurrentCommittee::<T>::get().epoch + One::one())
349		}
350
351		/// Returns current committee member for an index, repeating them in a round-robin fashion if needed.
352		pub fn get_current_authority_round_robin(index: usize) -> Option<T::CommitteeMember> {
353			let committee = CurrentCommittee::<T>::get().committee;
354			if committee.is_empty() {
355				return None;
356			}
357
358			committee.get(index % committee.len() as usize).cloned()
359		}
360
361		/// Returns current committee from storage.
362		pub fn current_committee_storage()
363		-> CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators> {
364			CurrentCommittee::<T>::get()
365		}
366
367		/// Returns next committee from storage.
368		pub fn next_committee_storage()
369		-> Option<CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>> {
370			NextCommittee::<T>::get()
371		}
372
373		/// Returns the `AuthorityId`s of next committee from storage.
374		///
375		/// This function's result should be always defined after inherent call of 1st block of each epoch
376		pub fn next_committee() -> Option<BoundedVec<T::AuthorityId, T::MaxValidators>> {
377			Some(BoundedVec::truncate_from(
378				NextCommittee::<T>::get()?
379					.committee
380					.into_iter()
381					.map(|member| member.authority_id())
382					.collect::<Vec<T::AuthorityId>>(),
383			))
384		}
385
386		fn inherent_data_to_authority_selection_inputs(
387			data: &InherentData,
388		) -> (T::AuthoritySelectionInputs, SizedByteString<32>) {
389			let decoded_data = data
390				.get_data::<T::AuthoritySelectionInputs>(&INHERENT_IDENTIFIER)
391				.expect("Validator inherent data not correctly encoded")
392				.expect("Validator inherent data must be provided");
393			let data_hash = SizedByteString(blake2_256(&decoded_data.encode()));
394
395			(decoded_data, data_hash)
396		}
397
398		/// Calculates committee using configured `select_authorities` function
399		pub fn calculate_committee(
400			authority_selection_inputs: T::AuthoritySelectionInputs,
401			sidechain_epoch: T::ScEpochNumber,
402		) -> Option<Vec<T::CommitteeMember>> {
403			T::select_authorities(authority_selection_inputs, sidechain_epoch).map(|c| c.to_vec())
404		}
405
406		/// If [NextCommittee] is defined, it moves its value to [CurrentCommittee] storage.
407		/// Returns the value taken from [NextCommittee].
408		pub fn rotate_committee_to_next_epoch() -> Option<Vec<T::CommitteeMember>> {
409			let next_committee = NextCommittee::<T>::take()?;
410
411			CurrentCommittee::<T>::put(next_committee.clone());
412
413			let validators = next_committee.committee.to_vec();
414			let len = validators.len();
415			info!(
416				"Committee rotated: Returning {len} validators, stored in epoch {}",
417				next_committee.epoch
418			);
419			Some(validators)
420		}
421
422		/// Returns main chain scripts.
423		pub fn get_main_chain_scripts() -> MainChainScripts {
424			MainChainScriptsConfiguration::<T>::get()
425		}
426	}
427}