1#![cfg_attr(not(feature = "std"), no_std)]
97
98pub mod disabling;
99#[cfg(feature = "historical")]
100pub mod historical;
101#[cfg(feature = "runtime-benchmarks")]
102pub mod benchmarking;
103pub mod migrations;
104#[cfg(test)]
105mod mock;
106#[cfg(test)]
107mod tests;
108pub mod weights;
109
110extern crate alloc;
111
112use alloc::{boxed::Box, vec::Vec};
113use codec::{Decode, MaxEncodedLen};
114use core::{
115 marker::PhantomData,
116 ops::{Rem, Sub},
117};
118use disabling::DisablingStrategy;
119use subsoil::staking::{offence::OffenceSeverity, SessionIndex};
120use subsoil::runtime::{
121 traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero},
122 ConsensusEngineId, DispatchError, KeyTypeId, Permill, RuntimeAppPublic,
123};
124use topsoil_core::{
125 dispatch::DispatchResult,
126 ensure,
127 traits::{
128 fungible::{hold::Mutate as HoldMutate, Inspect, Mutate},
129 Defensive, EstimateNextNewSession, EstimateNextSessionRotation, FindAuthor, Get,
130 OneSessionHandler, ValidatorRegistration, ValidatorSet,
131 },
132 weights::Weight,
133 Parameter,
134};
135use topsoil_core::system::pallet_prelude::BlockNumberFor;
136
137pub use pallet::*;
138pub use weights::WeightInfo;
139
140#[cfg(any(feature = "try-runtime"))]
141use subsoil::runtime::TryRuntimeError;
142
143pub(crate) const LOG_TARGET: &str = "runtime::session";
144
145#[macro_export]
147macro_rules! log {
148 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
149 log::$level!(
150 target: crate::LOG_TARGET,
151 concat!("[{:?}] 💸 ", $patter), <topsoil_core::system::Pallet<T>>::block_number() $(, $values)*
152 )
153 };
154}
155
156pub trait ShouldEndSession<BlockNumber> {
158 fn should_end_session(now: BlockNumber) -> bool;
160}
161
162pub struct PeriodicSessions<Period, Offset>(PhantomData<(Period, Offset)>);
168
169impl<
170 BlockNumber: Rem<Output = BlockNumber> + Sub<Output = BlockNumber> + Zero + PartialOrd,
171 Period: Get<BlockNumber>,
172 Offset: Get<BlockNumber>,
173 > ShouldEndSession<BlockNumber> for PeriodicSessions<Period, Offset>
174{
175 fn should_end_session(now: BlockNumber) -> bool {
176 let offset = Offset::get();
177 now >= offset && ((now - offset) % Period::get()).is_zero()
178 }
179}
180
181impl<
182 BlockNumber: AtLeast32BitUnsigned + Clone,
183 Period: Get<BlockNumber>,
184 Offset: Get<BlockNumber>,
185 > EstimateNextSessionRotation<BlockNumber> for PeriodicSessions<Period, Offset>
186{
187 fn average_session_length() -> BlockNumber {
188 Period::get()
189 }
190
191 fn estimate_current_session_progress(now: BlockNumber) -> (Option<Permill>, Weight) {
192 let offset = Offset::get();
193 let period = Period::get();
194
195 let progress = if now >= offset {
199 let current = (now - offset) % period.clone() + One::one();
200 Some(Permill::from_rational(current, period))
201 } else {
202 Some(Permill::from_rational(now + One::one(), offset))
203 };
204
205 (progress, Zero::zero())
210 }
211
212 fn estimate_next_session_rotation(now: BlockNumber) -> (Option<BlockNumber>, Weight) {
213 let offset = Offset::get();
214 let period = Period::get();
215
216 let next_session = if now > offset {
217 let block_after_last_session = (now.clone() - offset) % period.clone();
218 if block_after_last_session > Zero::zero() {
219 now.saturating_add(period.saturating_sub(block_after_last_session))
220 } else {
221 now + period
226 }
227 } else {
228 offset
229 };
230
231 (Some(next_session), Zero::zero())
236 }
237}
238
239pub trait SessionManager<ValidatorId> {
241 fn new_session(new_index: SessionIndex) -> Option<Vec<ValidatorId>>;
255 fn new_session_genesis(new_index: SessionIndex) -> Option<Vec<ValidatorId>> {
260 Self::new_session(new_index)
261 }
262 fn end_session(end_index: SessionIndex);
267 fn start_session(start_index: SessionIndex);
271}
272
273impl<A> SessionManager<A> for () {
274 fn new_session(_: SessionIndex) -> Option<Vec<A>> {
275 None
276 }
277 fn start_session(_: SessionIndex) {}
278 fn end_session(_: SessionIndex) {}
279}
280
281pub trait SessionHandler<ValidatorId> {
283 const KEY_TYPE_IDS: &'static [KeyTypeId];
289
290 fn on_genesis_session<Ks: OpaqueKeys>(validators: &[(ValidatorId, Ks)]);
295
296 fn on_new_session<Ks: OpaqueKeys>(
306 changed: bool,
307 validators: &[(ValidatorId, Ks)],
308 queued_validators: &[(ValidatorId, Ks)],
309 );
310
311 fn on_before_session_ending() {}
316
317 fn on_disabled(validator_index: u32);
319}
320
321#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
322#[tuple_types_custom_trait_bound(OneSessionHandler<AId>)]
323impl<AId> SessionHandler<AId> for Tuple {
324 for_tuples!(
325 const KEY_TYPE_IDS: &'static [KeyTypeId] = &[ #( <Tuple::Key as RuntimeAppPublic>::ID ),* ];
326 );
327
328 fn on_genesis_session<Ks: OpaqueKeys>(validators: &[(AId, Ks)]) {
329 for_tuples!(
330 #(
331 let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
332 .filter_map(|k|
333 k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
334 )
335 );
336
337 Tuple::on_genesis_session(our_keys);
338 )*
339 )
340 }
341
342 fn on_new_session<Ks: OpaqueKeys>(
343 changed: bool,
344 validators: &[(AId, Ks)],
345 queued_validators: &[(AId, Ks)],
346 ) {
347 for_tuples!(
348 #(
349 let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
350 .filter_map(|k|
351 k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
352 ));
353 let queued_keys: Box<dyn Iterator<Item=_>> = Box::new(queued_validators.iter()
354 .filter_map(|k|
355 k.1.get::<Tuple::Key>(<Tuple::Key as RuntimeAppPublic>::ID).map(|k1| (&k.0, k1))
356 ));
357 Tuple::on_new_session(changed, our_keys, queued_keys);
358 )*
359 )
360 }
361
362 fn on_before_session_ending() {
363 for_tuples!( #( Tuple::on_before_session_ending(); )* )
364 }
365
366 fn on_disabled(i: u32) {
367 for_tuples!( #( Tuple::on_disabled(i); )* )
368 }
369}
370
371pub struct TestSessionHandler;
373impl<AId> SessionHandler<AId> for TestSessionHandler {
374 const KEY_TYPE_IDS: &'static [KeyTypeId] = &[subsoil::runtime::key_types::DUMMY];
375 fn on_genesis_session<Ks: OpaqueKeys>(_: &[(AId, Ks)]) {}
376 fn on_new_session<Ks: OpaqueKeys>(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {}
377 fn on_before_session_ending() {}
378 fn on_disabled(_: u32) {}
379}
380
381pub trait SessionInterface {
389 type ValidatorId: Clone;
391
392 type AccountId;
394
395 type Keys: OpaqueKeys + codec::Decode;
397
398 fn validators() -> Vec<Self::ValidatorId>;
400
401 fn prune_up_to(index: SessionIndex);
403
404 fn report_offence(offender: Self::ValidatorId, severity: OffenceSeverity);
408
409 fn set_keys(account: &Self::AccountId, keys: Self::Keys) -> DispatchResult;
418
419 fn purge_keys(account: &Self::AccountId) -> DispatchResult;
424
425 fn set_keys_weight() -> Weight;
427
428 fn purge_keys_weight() -> Weight;
430}
431
432#[topsoil_core::pallet]
433pub mod pallet {
434 use super::*;
435 use topsoil_core::pallet_prelude::*;
436 use topsoil_core::system::pallet_prelude::*;
437
438 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
440
441 #[pallet::pallet]
442 #[pallet::storage_version(STORAGE_VERSION)]
443 #[pallet::without_storage_info]
444 pub struct Pallet<T>(_);
445
446 #[pallet::config]
447 pub trait Config: topsoil_core::system::Config {
448 #[allow(deprecated)]
450 type RuntimeEvent: From<Event<Self>>
451 + IsType<<Self as topsoil_core::system::Config>::RuntimeEvent>;
452
453 type ValidatorId: Member
455 + Parameter
456 + MaybeSerializeDeserialize
457 + MaxEncodedLen
458 + TryFrom<Self::AccountId>;
459
460 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
468
469 type ShouldEndSession: ShouldEndSession<BlockNumberFor<Self>>;
471
472 type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
476
477 type SessionManager: SessionManager<Self::ValidatorId>;
479
480 type SessionHandler: SessionHandler<Self::ValidatorId>;
482
483 type Keys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize;
485
486 type DisablingStrategy: DisablingStrategy<Self>;
488
489 type WeightInfo: WeightInfo;
491
492 type Currency: Mutate<Self::AccountId>
494 + HoldMutate<Self::AccountId, Reason: From<HoldReason>>;
495
496 #[pallet::constant]
498 type KeyDeposit: Get<
499 <<Self as Config>::Currency as Inspect<<Self as topsoil_core::system::Config>::AccountId>>::Balance,
500 >;
501 }
502
503 #[pallet::genesis_config]
504 #[derive(topsoil_core::DefaultNoBound)]
505 pub struct GenesisConfig<T: Config> {
506 pub keys: Vec<(T::AccountId, T::ValidatorId, T::Keys)>,
510 pub non_authority_keys: Vec<(T::AccountId, T::ValidatorId, T::Keys)>,
514 }
515
516 #[pallet::genesis_build]
517 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
518 fn build(&self) {
519 if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() {
520 panic!("Number of keys in session handler and session keys does not match");
521 }
522
523 T::SessionHandler::KEY_TYPE_IDS
524 .iter()
525 .zip(T::Keys::key_ids())
526 .enumerate()
527 .for_each(|(i, (sk, kk))| {
528 if sk != kk {
529 panic!(
530 "Session handler and session key expect different key type at index: {}",
531 i,
532 );
533 }
534 });
535
536 for (account, val, keys) in
537 self.keys.iter().chain(self.non_authority_keys.iter()).cloned()
538 {
539 Pallet::<T>::inner_set_keys(&val, keys)
540 .expect("genesis config must not contain duplicates; qed");
541 if topsoil_core::system::Pallet::<T>::inc_consumers_without_limit(&account).is_err() {
542 topsoil_core::system::Pallet::<T>::inc_providers(&account);
547 }
548 }
549
550 let initial_validators_0 =
551 T::SessionManager::new_session_genesis(0).unwrap_or_else(|| {
552 topsoil_core::print(
553 "No initial validator provided by `SessionManager`, use \
554 session config keys to generate initial validator set.",
555 );
556 self.keys.iter().map(|x| x.1.clone()).collect()
557 });
558
559 let initial_validators_1 = T::SessionManager::new_session_genesis(1)
560 .unwrap_or_else(|| initial_validators_0.clone());
561
562 let queued_keys: Vec<_> = initial_validators_1
563 .into_iter()
564 .filter_map(|v| Pallet::<T>::load_keys(&v).map(|k| (v, k)))
565 .collect();
566
567 T::SessionHandler::on_genesis_session::<T::Keys>(&queued_keys);
569
570 Validators::<T>::put(initial_validators_0);
571 QueuedKeys::<T>::put(queued_keys);
572
573 T::SessionManager::start_session(0);
574 }
575 }
576
577 #[pallet::composite_enum]
579 pub enum HoldReason {
580 #[codec(index = 0)]
582 Keys,
583 }
584
585 #[pallet::storage]
587 pub type Validators<T: Config> = StorageValue<_, Vec<T::ValidatorId>, ValueQuery>;
588
589 #[pallet::storage]
591 pub type CurrentIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
592
593 #[pallet::storage]
596 pub type QueuedChanged<T> = StorageValue<_, bool, ValueQuery>;
597
598 #[pallet::storage]
601 pub type QueuedKeys<T: Config> = StorageValue<_, Vec<(T::ValidatorId, T::Keys)>, ValueQuery>;
602
603 #[pallet::storage]
609 pub type DisabledValidators<T> = StorageValue<_, Vec<(u32, OffenceSeverity)>, ValueQuery>;
610
611 #[pallet::storage]
613 pub type NextKeys<T: Config> =
614 StorageMap<_, Twox64Concat, T::ValidatorId, T::Keys, OptionQuery>;
615
616 #[pallet::storage]
618 pub type KeyOwner<T: Config> =
619 StorageMap<_, Twox64Concat, (KeyTypeId, Vec<u8>), T::ValidatorId, OptionQuery>;
620
621 #[pallet::storage]
626 pub type ExternallySetKeys<T: Config> =
627 StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>;
628
629 #[pallet::event]
630 #[pallet::generate_deposit(pub(super) fn deposit_event)]
631 pub enum Event<T: Config> {
632 NewSession { session_index: SessionIndex },
635 NewQueued,
638 ValidatorDisabled { validator: T::ValidatorId },
640 ValidatorReenabled { validator: T::ValidatorId },
642 }
643
644 #[pallet::error]
646 pub enum Error<T> {
647 InvalidProof,
649 NoAssociatedValidatorId,
651 DuplicatedKey,
653 NoKeys,
655 NoAccount,
657 }
658
659 #[pallet::hooks]
660 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
661 fn on_initialize(n: BlockNumberFor<T>) -> Weight {
664 if T::ShouldEndSession::should_end_session(n) {
665 Self::rotate_session();
666 T::BlockWeights::get().max_block
667 } else {
668 Weight::zero()
672 }
673 }
674
675 #[cfg(feature = "try-runtime")]
676 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
677 Self::do_try_state()
678 }
679 }
680
681 #[pallet::call]
682 impl<T: Config> Pallet<T> {
683 #[pallet::call_index(0)]
695 #[pallet::weight(T::WeightInfo::set_keys())]
696 pub fn set_keys(origin: OriginFor<T>, keys: T::Keys, proof: Vec<u8>) -> DispatchResult {
697 let who = ensure_signed(origin)?;
698 ensure!(
699 who.using_encoded(|who| keys.ownership_proof_is_valid(who, &proof)),
700 Error::<T>::InvalidProof,
701 );
702
703 Self::do_set_keys(&who, keys)?;
704 Ok(())
705 }
706
707 #[pallet::call_index(1)]
716 #[pallet::weight(T::WeightInfo::purge_keys())]
717 pub fn purge_keys(origin: OriginFor<T>) -> DispatchResult {
718 let who = ensure_signed(origin)?;
719 Self::do_purge_keys(&who)?;
720 Ok(())
721 }
722 }
723
724 #[cfg(feature = "runtime-benchmarks")]
725 impl<T: Config> Pallet<T> {
726 pub fn ensure_can_pay_key_deposit(who: &T::AccountId) -> Result<(), DispatchError> {
731 use topsoil_core::traits::tokens::{Fortitude, Preservation};
732 let deposit = T::KeyDeposit::get();
733 let has = T::Currency::reducible_balance(who, Preservation::Protect, Fortitude::Force);
734 if let Some(deficit) = deposit.checked_sub(&has) {
735 T::Currency::mint_into(who, deficit.max(T::Currency::minimum_balance()))
736 .map(|_inc| ())
737 } else {
738 Ok(())
739 }
740 }
741 }
742}
743
744impl<T: Config> Pallet<T> {
745 pub fn validators() -> Vec<T::ValidatorId> {
747 Validators::<T>::get()
748 }
749
750 pub fn current_index() -> SessionIndex {
752 CurrentIndex::<T>::get()
753 }
754
755 pub fn queued_keys() -> Vec<(T::ValidatorId, T::Keys)> {
757 QueuedKeys::<T>::get()
758 }
759
760 pub fn disabled_validators() -> Vec<u32> {
762 DisabledValidators::<T>::get().iter().map(|(i, _)| *i).collect()
763 }
764
765 pub fn rotate_session() {
769 let session_index = CurrentIndex::<T>::get();
770 let changed = QueuedChanged::<T>::get();
771
772 T::SessionHandler::on_before_session_ending();
774 T::SessionManager::end_session(session_index);
775 log!(trace, "ending_session {:?}", session_index);
776
777 let session_keys = QueuedKeys::<T>::get();
779 let validators =
780 session_keys.iter().map(|(validator, _)| validator.clone()).collect::<Vec<_>>();
781 Validators::<T>::put(&validators);
782
783 if changed {
784 log!(trace, "resetting disabled validators");
785 DisabledValidators::<T>::kill();
787 }
788
789 let session_index = session_index + 1;
791 CurrentIndex::<T>::put(session_index);
792 T::SessionManager::start_session(session_index);
793 log!(trace, "starting_session {:?}", session_index);
794
795 let maybe_next_validators = T::SessionManager::new_session(session_index + 1);
797 log!(
798 trace,
799 "planning_session {:?} with {:?} validators",
800 session_index + 1,
801 maybe_next_validators.as_ref().map(|v| v.len())
802 );
803 let (next_validators, next_identities_changed) =
804 if let Some(validators) = maybe_next_validators {
805 Self::deposit_event(Event::<T>::NewQueued);
809 (validators, true)
810 } else {
811 (Validators::<T>::get(), false)
812 };
813
814 let (queued_amalgamated, next_changed) = {
816 let mut changed = next_identities_changed;
819
820 let mut now_session_keys = session_keys.iter();
821 let mut check_next_changed = |keys: &T::Keys| {
822 if changed {
823 return;
824 }
825 if let Some((_, old_keys)) = now_session_keys.next() {
829 if old_keys != keys {
830 changed = true;
831 }
832 }
833 };
834 let queued_amalgamated =
835 next_validators
836 .into_iter()
837 .filter_map(|a| {
838 let k =
839 Self::load_keys(&a).or_else(|| {
840 log!(warn, "failed to load session key for {:?}, skipping for next session, maybe you need to set session keys for them?", a);
841 None
842 })?;
843 check_next_changed(&k);
844 Some((a, k))
845 })
846 .collect::<Vec<_>>();
847
848 (queued_amalgamated, changed)
849 };
850
851 QueuedKeys::<T>::put(queued_amalgamated.clone());
852 QueuedChanged::<T>::put(next_changed);
853
854 Self::deposit_event(Event::NewSession { session_index });
856
857 T::SessionHandler::on_new_session::<T::Keys>(changed, &session_keys, &queued_amalgamated);
859 }
860
861 pub fn upgrade_keys<Old, F>(upgrade: F)
877 where
878 Old: OpaqueKeys + Member + Decode,
879 F: Fn(T::ValidatorId, Old) -> T::Keys,
880 {
881 let old_ids = Old::key_ids();
882 let new_ids = T::Keys::key_ids();
883
884 NextKeys::<T>::translate::<Old, _>(|val, old_keys| {
886 for i in old_ids.iter() {
889 Self::clear_key_owner(*i, old_keys.get_raw(*i));
890 }
891
892 let new_keys = upgrade(val.clone(), old_keys);
893
894 for i in new_ids.iter() {
896 Self::put_key_owner(*i, new_keys.get_raw(*i), &val);
897 }
898
899 Some(new_keys)
900 });
901
902 let _ = QueuedKeys::<T>::translate::<Vec<(T::ValidatorId, Old)>, _>(|k| {
903 k.map(|k| {
904 k.into_iter()
905 .map(|(val, old_keys)| (val.clone(), upgrade(val, old_keys)))
906 .collect::<Vec<_>>()
907 })
908 });
909 }
910
911 fn do_set_keys(account: &T::AccountId, keys: T::Keys) -> DispatchResult {
916 let who = T::ValidatorIdOf::convert(account.clone())
917 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
918
919 ensure!(topsoil_core::system::Pallet::<T>::can_inc_consumer(account), Error::<T>::NoAccount);
920
921 let old_keys = Self::inner_set_keys(&who, keys)?;
922
923 let needs_local_setup =
927 old_keys.is_none() || ExternallySetKeys::<T>::take(account).is_some();
928 if needs_local_setup {
929 let deposit = T::KeyDeposit::get();
930 if !deposit.is_zero() {
931 T::Currency::hold(&HoldReason::Keys.into(), account, deposit)?;
932 }
933
934 let assertion = topsoil_core::system::Pallet::<T>::inc_consumers(account).is_ok();
935 debug_assert!(assertion, "can_inc_consumer() returned true; no change since; qed");
936 }
937
938 Ok(())
939 }
940
941 fn inner_set_keys(
948 who: &T::ValidatorId,
949 keys: T::Keys,
950 ) -> Result<Option<T::Keys>, DispatchError> {
951 let old_keys = Self::load_keys(who);
952
953 for id in T::Keys::key_ids() {
954 let key = keys.get_raw(*id);
955
956 ensure!(
958 Self::key_owner(*id, key).map_or(true, |owner| &owner == who),
959 Error::<T>::DuplicatedKey,
960 );
961 }
962
963 for id in T::Keys::key_ids() {
964 let key = keys.get_raw(*id);
965
966 if let Some(old) = old_keys.as_ref().map(|k| k.get_raw(*id)) {
967 if key == old {
968 continue;
969 }
970
971 Self::clear_key_owner(*id, old);
972 }
973
974 Self::put_key_owner(*id, key, who);
975 }
976
977 Self::put_keys(who, &keys);
978 Ok(old_keys)
979 }
980
981 fn do_purge_keys(account: &T::AccountId) -> DispatchResult {
982 let who = T::ValidatorIdOf::convert(account.clone())
983 .or_else(|| T::ValidatorId::try_from(account.clone()).ok())
987 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
988
989 let old_keys = Self::take_keys(&who).ok_or(Error::<T>::NoKeys)?;
990 for id in T::Keys::key_ids() {
991 let key_data = old_keys.get_raw(*id);
992 Self::clear_key_owner(*id, key_data);
993 }
994
995 let _ = T::Currency::release_all(
997 &HoldReason::Keys.into(),
998 account,
999 topsoil_core::traits::tokens::Precision::BestEffort,
1000 );
1001
1002 if ExternallySetKeys::<T>::take(account).is_none() {
1003 topsoil_core::system::Pallet::<T>::dec_consumers(account);
1005 }
1006
1007 Ok(())
1008 }
1009
1010 pub fn load_keys(v: &T::ValidatorId) -> Option<T::Keys> {
1011 NextKeys::<T>::get(v)
1012 }
1013
1014 fn take_keys(v: &T::ValidatorId) -> Option<T::Keys> {
1015 NextKeys::<T>::take(v)
1016 }
1017
1018 fn put_keys(v: &T::ValidatorId, keys: &T::Keys) {
1019 NextKeys::<T>::insert(v, keys);
1020 }
1021
1022 pub fn key_owner(id: KeyTypeId, key_data: &[u8]) -> Option<T::ValidatorId> {
1024 KeyOwner::<T>::get((id, key_data))
1025 }
1026
1027 fn put_key_owner(id: KeyTypeId, key_data: &[u8], v: &T::ValidatorId) {
1028 KeyOwner::<T>::insert((id, key_data), v)
1029 }
1030
1031 fn clear_key_owner(id: KeyTypeId, key_data: &[u8]) {
1032 KeyOwner::<T>::remove((id, key_data));
1033 }
1034
1035 pub fn disable_index_with_severity(i: u32, severity: OffenceSeverity) -> bool {
1041 if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 {
1042 return false;
1043 }
1044
1045 DisabledValidators::<T>::mutate(|disabled| {
1046 match disabled.binary_search_by_key(&i, |(index, _)| *index) {
1047 Ok(index) => {
1049 let current_severity = &mut disabled[index].1;
1050 if severity > *current_severity {
1051 log!(
1052 trace,
1053 "updating disablement severity of validator {:?} from {:?} to {:?}",
1054 i,
1055 *current_severity,
1056 severity
1057 );
1058 *current_severity = severity;
1059 }
1060 true
1061 },
1062 Err(index) => {
1064 log!(trace, "disabling validator {:?}", i);
1065 Self::deposit_event(Event::ValidatorDisabled {
1066 validator: Validators::<T>::get()[i as usize].clone(),
1067 });
1068 disabled.insert(index, (i, severity));
1069 T::SessionHandler::on_disabled(i);
1070 true
1071 },
1072 }
1073 })
1074 }
1075
1076 pub fn disable_index(i: u32) -> bool {
1079 let default_severity = OffenceSeverity::default();
1080 Self::disable_index_with_severity(i, default_severity)
1081 }
1082
1083 pub fn reenable_index(i: u32) -> bool {
1085 if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 {
1086 return false;
1087 }
1088
1089 DisabledValidators::<T>::mutate(|disabled| {
1090 if let Ok(index) = disabled.binary_search_by_key(&i, |(index, _)| *index) {
1091 log!(trace, "reenabling validator {:?}", i);
1092 Self::deposit_event(Event::ValidatorReenabled {
1093 validator: Validators::<T>::get()[i as usize].clone(),
1094 });
1095 disabled.remove(index);
1096 return true;
1097 }
1098 false
1099 })
1100 }
1101
1102 pub fn validator_id_to_index(id: &T::ValidatorId) -> Option<u32> {
1105 Validators::<T>::get().iter().position(|i| i == id).map(|i| i as u32)
1106 }
1107
1108 pub fn report_offence(validator: T::ValidatorId, severity: OffenceSeverity) {
1111 let decision =
1112 T::DisablingStrategy::decision(&validator, severity, &DisabledValidators::<T>::get());
1113 log!(
1114 debug,
1115 "reporting offence for {:?} with {:?}, decision: {:?}",
1116 validator,
1117 severity,
1118 decision
1119 );
1120
1121 if let Some(offender_idx) = decision.disable {
1123 Self::disable_index_with_severity(offender_idx, severity);
1124 }
1125
1126 if let Some(reenable_idx) = decision.reenable {
1128 Self::reenable_index(reenable_idx);
1129 }
1130 }
1131
1132 #[cfg(any(test, feature = "try-runtime"))]
1133 pub fn do_try_state() -> Result<(), subsoil::runtime::TryRuntimeError> {
1134 ensure!(
1136 DisabledValidators::<T>::get().windows(2).all(|pair| pair[0].0 <= pair[1].0),
1137 "DisabledValidators is not sorted"
1138 );
1139 Ok(())
1140 }
1141}
1142
1143impl<T: Config> ValidatorRegistration<T::ValidatorId> for Pallet<T> {
1144 fn is_registered(id: &T::ValidatorId) -> bool {
1145 Self::load_keys(id).is_some()
1146 }
1147}
1148
1149impl<T: Config> ValidatorSet<T::AccountId> for Pallet<T> {
1150 type ValidatorId = T::ValidatorId;
1151 type ValidatorIdOf = T::ValidatorIdOf;
1152
1153 fn session_index() -> subsoil::staking::SessionIndex {
1154 CurrentIndex::<T>::get()
1155 }
1156
1157 fn validators() -> Vec<Self::ValidatorId> {
1158 Validators::<T>::get()
1159 }
1160}
1161
1162impl<T: Config> EstimateNextNewSession<BlockNumberFor<T>> for Pallet<T> {
1163 fn average_session_length() -> BlockNumberFor<T> {
1164 T::NextSessionRotation::average_session_length()
1165 }
1166
1167 fn estimate_next_new_session(now: BlockNumberFor<T>) -> (Option<BlockNumberFor<T>>, Weight) {
1170 T::NextSessionRotation::estimate_next_session_rotation(now)
1171 }
1172}
1173
1174impl<T: Config> topsoil_core::traits::DisabledValidators for Pallet<T> {
1175 fn is_disabled(index: u32) -> bool {
1176 DisabledValidators::<T>::get().binary_search_by_key(&index, |(i, _)| *i).is_ok()
1177 }
1178
1179 fn disabled_validators() -> Vec<u32> {
1180 Self::disabled_validators()
1181 }
1182}
1183
1184#[cfg(feature = "historical")]
1185impl<T: Config + historical::Config> SessionInterface for Pallet<T> {
1186 type ValidatorId = T::ValidatorId;
1187 type AccountId = T::AccountId;
1188 type Keys = T::Keys;
1189
1190 fn validators() -> Vec<Self::ValidatorId> {
1191 Self::validators()
1192 }
1193
1194 fn prune_up_to(index: SessionIndex) {
1195 historical::Pallet::<T>::prune_up_to(index)
1196 }
1197
1198 fn report_offence(offender: Self::ValidatorId, severity: OffenceSeverity) {
1199 Self::report_offence(offender, severity)
1200 }
1201
1202 fn set_keys(account: &Self::AccountId, keys: Self::Keys) -> DispatchResult {
1203 let who = T::ValidatorIdOf::convert(account.clone())
1204 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
1205 let old_keys = Self::inner_set_keys(&who, keys)?;
1206 if old_keys.is_some() && !ExternallySetKeys::<T>::contains_key(account) {
1208 let _ = T::Currency::release_all(
1209 &HoldReason::Keys.into(),
1210 account,
1211 topsoil_core::traits::tokens::Precision::BestEffort,
1212 );
1213 topsoil_core::system::Pallet::<T>::dec_consumers(account);
1214 }
1215 ExternallySetKeys::<T>::insert(account, ());
1216 Ok(())
1217 }
1218
1219 fn purge_keys(account: &Self::AccountId) -> DispatchResult {
1220 let who = T::ValidatorIdOf::convert(account.clone())
1221 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
1222
1223 let old_keys = Self::take_keys(&who).ok_or(Error::<T>::NoKeys)?;
1224 for id in T::Keys::key_ids() {
1225 let key_data = old_keys.get_raw(*id);
1226 Self::clear_key_owner(*id, key_data);
1227 }
1228 let _ = T::Currency::release_all(
1229 &HoldReason::Keys.into(),
1230 account,
1231 topsoil_core::traits::tokens::Precision::BestEffort,
1232 );
1233 if ExternallySetKeys::<T>::take(account).is_none() {
1234 topsoil_core::system::Pallet::<T>::dec_consumers(account);
1235 }
1236 Ok(())
1237 }
1238
1239 fn set_keys_weight() -> Weight {
1240 T::WeightInfo::set_keys()
1241 }
1242
1243 fn purge_keys_weight() -> Weight {
1244 T::WeightInfo::purge_keys()
1245 }
1246}
1247
1248pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
1252
1253impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::ValidatorId>
1254 for FindAccountFromAuthorIndex<T, Inner>
1255{
1256 fn find_author<'a, I>(digests: I) -> Option<T::ValidatorId>
1257 where
1258 I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
1259 {
1260 let i = Inner::find_author(digests)?;
1261
1262 let validators = Validators::<T>::get();
1263 validators.get(i as usize).cloned()
1264 }
1265}