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 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
51 #[pallet::constant]
52 type MaxValidators: Get<u32>;
54 type AuthorityId: Member
56 + Parameter
57 + MaybeSerializeDeserialize
58 + MaxEncodedLen
59 + Ord
60 + Into<Self::AccountId>;
61 type AuthorityKeys: Parameter + Member + MaybeSerializeDeserialize + Ord + MaxEncodedLen;
63 type AuthoritySelectionInputs: Parameter;
65 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 CommitteeMember: Parameter
80 + Member
81 + MaybeSerializeDeserialize
82 + MaxEncodedLen
83 + CommitteeMember<AuthorityId = Self::AuthorityId, AuthorityKeys = Self::AuthorityKeys>;
84
85 type MainChainScriptsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
87
88 fn select_authorities(
91 input: Self::AuthoritySelectionInputs,
92 sidechain_epoch: Self::ScEpochNumber,
93 ) -> Option<BoundedVec<Self::CommitteeMember, Self::MaxValidators>>;
94
95 fn current_epoch_number() -> Self::ScEpochNumber;
97
98 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 pub struct CommitteeInfo<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators> {
113 pub epoch: ScEpochNumber,
115 pub committee: BoundedVec<CommitteeMember, MaxValidators>,
117 }
118
119 impl<ScEpochNumber: Clone, CommitteeMember: Clone, MaxValidators>
120 CommitteeInfo<ScEpochNumber, CommitteeMember, MaxValidators>
121 {
122 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 InvalidEpoch,
161 NextCommitteeAlreadySet,
163 }
164
165 #[pallet::genesis_config]
166 #[derive(frame_support::DefaultNoBound)]
167 pub struct GenesisConfig<T: Config> {
168 pub initial_authorities: Vec<T::CommitteeMember>,
170 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 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 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 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 let committee_info = NextCommittee::<T>::get()
253 .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)) } else {
282 Ok(None)
283 }
284 }
285 }
286
287 #[pallet::call]
288 impl<T: Config> Pallet<T> {
289 #[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 #[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 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 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 pub fn current_committee_storage()
363 -> CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators> {
364 CurrentCommittee::<T>::get()
365 }
366
367 pub fn next_committee_storage()
369 -> Option<CommitteeInfo<T::ScEpochNumber, T::CommitteeMember, T::MaxValidators>> {
370 NextCommittee::<T>::get()
371 }
372
373 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 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 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 pub fn get_main_chain_scripts() -> MainChainScripts {
424 MainChainScriptsConfiguration::<T>::get()
425 }
426 }
427}