pallet_session_validator_management/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
4#![allow(clippy::type_complexity)]
5#![deny(missing_docs)]
6
7pub mod migrations;
8#[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 #[pallet::constant]
50 type MaxValidators: Get<u32>;
52 type AuthorityId: Member
54 + Parameter
55 + MaybeSerializeDeserialize
56 + MaxEncodedLen
57 + Ord
58 + Into<Self::AccountId>;
59 type AuthorityKeys: Parameter + Member + MaybeSerializeDeserialize + Ord + MaxEncodedLen;
61 type AuthoritySelectionInputs: Parameter;
63 type ScEpochNumber: Parameter
65 + MaxEncodedLen
66 + Zero
67 + Display
68 + Add
69 + One
70 + Default
71 + Ord
72 + Copy
73 + From<u64>
74 + Into<u64>;
75
76 type CommitteeMember: Parameter
78 + Member
79 + MaybeSerializeDeserialize
80 + MaxEncodedLen
81 + CommitteeMember<AuthorityId = Self::AuthorityId, AuthorityKeys = Self::AuthorityKeys>;
82
83 type MainChainScriptsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
85
86 fn select_authorities(
89 input: Self::AuthoritySelectionInputs,
90 sidechain_epoch: Self::ScEpochNumber,
91 ) -> Option<BoundedVec<Self::CommitteeMember, Self::MaxValidators>>;
92
93 fn current_epoch_number() -> Self::ScEpochNumber;
95
96 type WeightInfo: WeightInfo;
98 }
99
100 #[pallet::event]
101 pub enum Event<T: Config> {}
102
103 use frame_support::{BoundedVec, CloneNoBound};
104 use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
105 use scale_info::TypeInfo;
106
107 #[derive(CloneNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)]
108 #[scale_info(skip_type_params(MaxValidators))]
109 pub struct CommitteeInfo<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators> {
111 pub epoch: ScEpochNumber,
113 pub committee: BoundedVec<CommitteeMember, MaxValidators>,
115 }
116
117 impl<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators>
118 CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
119 {
120 pub fn as_pair(self) -> (ScEpochNumber, Vec<CommitteeMember>) {
122 (self.epoch, self.committee.to_vec())
123 }
124 }
125
126 impl<ScEpochNumber, CommitteeMember, MaxValidators> Default
127 for CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
128 where
129 CommitteeMember: Clone,
130 ScEpochNumber: Clone + Zero,
131 {
132 fn default() -> Self {
133 Self { epoch: ScEpochNumber::zero(), committee: BoundedVec::new() }
134 }
135 }
136
137 #[pallet::storage]
138 pub type CurrentCommittee<T: Config> = StorageValue<
139 _,
140 CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>,
141 ValueQuery,
142 >;
143
144 #[pallet::storage]
145 pub type NextCommittee<T: Config> = StorageValue<
146 _,
147 CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>,
148 OptionQuery,
149 >;
150
151 #[pallet::storage]
152 pub type MainChainScriptsConfiguration<T: Config> =
153 StorageValue<_, MainChainScripts, ValueQuery>;
154
155 #[pallet::error]
156 pub enum Error<T> {
157 InvalidEpoch,
159 NextCommitteeAlreadySet,
161 }
162
163 #[pallet::genesis_config]
164 #[derive(frame_support::DefaultNoBound)]
165 pub struct GenesisConfig<T: Config> {
166 pub initial_authorities: Vec<T::CommitteeMember>,
168 pub main_chain_scripts: MainChainScripts,
170 }
171
172 #[pallet::genesis_build]
173 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
174 fn build(&self) {
175 let initial_authorities = BoundedVec::truncate_from(self.initial_authorities.clone());
176 let committee_info =
177 CommitteeInfo { epoch: T::ScEpochNumber::zero(), committee: initial_authorities };
178 CurrentCommittee::<T>::put(committee_info);
179 MainChainScriptsConfiguration::<T>::put(self.main_chain_scripts.clone());
180 }
181 }
182
183 #[pallet::hooks]
184 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
185 fn on_initialize(block_nr: BlockNumberFor<T>) -> Weight {
192 if block_nr.is_one() {
193 CurrentCommittee::<T>::mutate(|committee| {
194 committee.epoch = T::current_epoch_number();
195 });
196 T::DbWeight::get().reads_writes(2, 1)
197 } else {
198 Weight::zero()
199 }
200 }
201 }
202
203 #[pallet::inherent]
204 impl<T: Config> ProvideInherent for Pallet<T> {
205 type Call = Call<T>;
206 type Error = InherentError;
207 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
208
209 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
211 if NextCommittee::<T>::exists() {
212 None
213 } else {
214 let for_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
215 let (authority_selection_inputs, selection_inputs_hash) =
216 Self::inherent_data_to_authority_selection_inputs(data);
217 if let Some(validators) =
218 T::select_authorities(authority_selection_inputs, for_epoch_number)
219 {
220 Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
221 } else {
222 let current_committee = CurrentCommittee::<T>::get();
223 let current_committee_epoch = current_committee.epoch;
224 warn!(
225 "Committee for epoch {for_epoch_number} is the same as for epoch {current_committee_epoch}"
226 );
227 let validators = current_committee.committee;
228 Some(Call::set { validators, for_epoch_number, selection_inputs_hash })
229 }
230 }
231 }
232
233 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
235 let (validators_param, for_epoch_number_param, call_selection_inputs_hash) = match call
236 {
237 Call::set { validators, for_epoch_number, selection_inputs_hash } => {
238 (validators, for_epoch_number, selection_inputs_hash)
239 },
240 _ => return Ok(()),
241 };
242
243 let (authority_selection_inputs, computed_selection_inputs_hash) =
244 Self::inherent_data_to_authority_selection_inputs(data);
245 let validators =
246 T::select_authorities(authority_selection_inputs, *for_epoch_number_param)
247 .unwrap_or_else(|| {
248 let committee_info = NextCommittee::<T>::get()
251 .unwrap_or_else(CurrentCommittee::<T>::get);
253 committee_info.committee
254 });
255
256 if *validators_param != validators {
257 if *call_selection_inputs_hash == computed_selection_inputs_hash {
258 return Err(InherentError::InvalidValidatorsMatchingHash(
259 computed_selection_inputs_hash,
260 ));
261 } else {
262 return Err(InherentError::InvalidValidatorsHashMismatch(
263 computed_selection_inputs_hash,
264 call_selection_inputs_hash.clone(),
265 ));
266 }
267 }
268
269 Ok(())
270 }
271
272 fn is_inherent(call: &Self::Call) -> bool {
273 matches!(call, Call::set { .. })
274 }
275
276 fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
277 if !NextCommittee::<T>::exists() {
278 Ok(Some(InherentError::CommitteeNeedsToBeStoredOneEpochInAdvance)) } else {
280 Ok(None)
281 }
282 }
283 }
284
285 #[pallet::call]
286 impl<T: Config> Pallet<T> {
287 #[pallet::call_index(0)]
293 #[pallet::weight((
294 T::WeightInfo::set(validators.len() as u32),
295 DispatchClass::Mandatory
296 ))]
297 pub fn set(
298 origin: OriginFor<T>,
299 validators: BoundedVec<T::CommitteeMember, T::MaxValidators>,
300 for_epoch_number: T::ScEpochNumber,
301 selection_inputs_hash: SizedByteString<32>,
302 ) -> DispatchResult {
303 ensure_none(origin)?;
304 let expected_epoch_number = CurrentCommittee::<T>::get().epoch + One::one();
305 ensure!(for_epoch_number == expected_epoch_number, Error::<T>::InvalidEpoch);
306 ensure!(!NextCommittee::<T>::exists(), Error::<T>::NextCommitteeAlreadySet);
307 let len = validators.len();
308 info!(
309 "💼 Storing committee of size {len} for epoch {for_epoch_number}, input data hash: {}",
310 selection_inputs_hash.to_hex_string()
311 );
312 NextCommittee::<T>::put(CommitteeInfo {
313 epoch: for_epoch_number,
314 committee: validators,
315 });
316 Ok(())
317 }
318
319 #[pallet::call_index(1)]
323 #[pallet::weight(T::WeightInfo::set(1))]
324 pub fn set_main_chain_scripts(
325 origin: OriginFor<T>,
326 committee_candidate_address: MainchainAddress,
327 d_parameter_policy_id: PolicyId,
328 permissioned_candidates_policy_id: PolicyId,
329 ) -> DispatchResult {
330 T::MainChainScriptsOrigin::ensure_origin(origin)?;
331 let new_scripts = MainChainScripts {
332 committee_candidate_address,
333 d_parameter_policy_id,
334 permissioned_candidates_policy_id,
335 };
336 MainChainScriptsConfiguration::<T>::put(new_scripts);
337 Ok(())
338 }
339 }
340
341 impl<T: Config> Pallet<T> {
342 pub fn get_next_unset_epoch_number() -> T::ScEpochNumber {
344 NextCommittee::<T>::get()
345 .map(|next_committee| next_committee.epoch + One::one())
346 .unwrap_or(CurrentCommittee::<T>::get().epoch + One::one())
347 }
348
349 pub fn get_current_authority_round_robin(index: usize) -> Option<T::CommitteeMember> {
351 let committee = CurrentCommittee::<T>::get().committee;
352 if committee.is_empty() {
353 return None;
354 }
355
356 committee.get(index % committee.len() as usize).cloned()
357 }
358
359 pub fn current_committee_storage()
361 -> CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators> {
362 CurrentCommittee::<T>::get()
363 }
364
365 pub fn next_committee_storage()
367 -> Option<CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>> {
368 NextCommittee::<T>::get()
369 }
370
371 pub fn next_committee() -> Option<BoundedVec<T::AuthorityId, T::MaxValidators>> {
375 Some(BoundedVec::truncate_from(
376 NextCommittee::<T>::get()?
377 .committee
378 .into_iter()
379 .map(|member| member.authority_id())
380 .collect::<Vec<T::AuthorityId>>(),
381 ))
382 }
383
384 fn inherent_data_to_authority_selection_inputs(
385 data: &InherentData,
386 ) -> (T::AuthoritySelectionInputs, SizedByteString<32>) {
387 let decoded_data = data
388 .get_data::<T::AuthoritySelectionInputs>(&INHERENT_IDENTIFIER)
389 .expect("Validator inherent data not correctly encoded")
390 .expect("Validator inherent data must be provided");
391 let data_hash = SizedByteString(blake2_256(&decoded_data.encode()));
392
393 (decoded_data, data_hash)
394 }
395
396 pub fn calculate_committee(
398 authority_selection_inputs: T::AuthoritySelectionInputs,
399 sidechain_epoch: T::ScEpochNumber,
400 ) -> Option<Vec<T::CommitteeMember>> {
401 T::select_authorities(authority_selection_inputs, sidechain_epoch).map(|c| c.to_vec())
402 }
403
404 pub fn rotate_committee_to_next_epoch() -> Option<Vec<T::CommitteeMember>> {
407 let next_committee = NextCommittee::<T>::take()?;
408
409 CurrentCommittee::<T>::put(next_committee.clone());
410
411 let validators = next_committee.committee.to_vec();
412 let len = validators.len();
413 info!(
414 "Committee rotated: Returning {len} validators, stored in epoch {}",
415 next_committee.epoch
416 );
417 Some(validators)
418 }
419
420 pub fn get_main_chain_scripts() -> MainChainScripts {
422 MainChainScriptsConfiguration::<T>::get()
423 }
424 }
425}