From 7ed0dd2aa2e055b434bb6b584d8ebd16ed30e01b Mon Sep 17 00:00:00 2001 From: librelois <c@elo.tf> Date: Sat, 22 Jan 2022 11:55:10 +0100 Subject: [PATCH] =?UTF-8?q?feat(authority-members):=C2=A0add=20param=20Max?= =?UTF-8?q?KeysLife?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pallets/authority-members/Cargo.toml | 1 + pallets/authority-members/src/lib.rs | 51 ++++++++++++++------ pallets/authority-members/src/mock.rs | 3 +- pallets/authority-members/src/tests.rs | 66 ++++++++++++++++++++++++++ pallets/authority-members/src/types.rs | 10 ++-- runtime/common/src/pallets_config.rs | 26 +++++----- 6 files changed, 120 insertions(+), 37 deletions(-) diff --git a/pallets/authority-members/Cargo.toml b/pallets/authority-members/Cargo.toml index c6deae911..26b80e4cb 100644 --- a/pallets/authority-members/Cargo.toml +++ b/pallets/authority-members/Cargo.toml @@ -57,6 +57,7 @@ branch = 'duniter-monthly-2022-01' [dependencies.pallet-session] default-features = false +features = ["historical"] git = 'https://github.com/librelois/substrate.git' branch = 'duniter-monthly-2022-01' diff --git a/pallets/authority-members/src/lib.rs b/pallets/authority-members/src/lib.rs index fd9f76a8f..2fbb814bf 100644 --- a/pallets/authority-members/src/lib.rs +++ b/pallets/authority-members/src/lib.rs @@ -66,9 +66,11 @@ pub mod pallet { type IsMember: IsMember<Self::MemberId>; type OnRemovedMember: OnRemovedMember<Self::MemberId>; type OwnerKeyOf: Convert<Self::MemberId, Option<Self::AccountId>>; - type MemberId: Copy + MaybeSerializeDeserialize + Parameter + Ord; + #[pallet::constant] + type MaxKeysLife: Get<SessionIndex>; #[pallet::constant] type MaxOfflineSessions: Get<SessionIndex>; + type MemberId: Copy + MaybeSerializeDeserialize + Parameter + Ord; type RefreshValidatorIdOrigin: EnsureOrigin<Self::Origin>; type RemoveMemberOrigin: EnsureOrigin<Self::Origin>; } @@ -93,7 +95,10 @@ pub mod pallet { impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { fn build(&self) { for (member_id, (validator_id, _is_online)) in &self.initial_authorities { - Members::<T>::insert(member_id, MemberData::new_genesis(validator_id.clone())); + Members::<T>::insert( + member_id, + MemberData::new_genesis(T::MaxKeysLife::get(), validator_id.clone()), + ); } let mut members_ids = self .initial_authorities @@ -110,7 +115,8 @@ pub mod pallet { .collect::<Vec<T::MemberId>>(); members_ids.sort(); - OnlineAuthorities::<T>::put(members_ids); + OnlineAuthorities::<T>::put(members_ids.clone()); + MustRotateKeysBefore::<T>::insert(T::MaxKeysLife::get(), members_ids); } } @@ -138,6 +144,11 @@ pub mod pallet { pub type MembersExpireOn<T: Config> = StorageMap<_, Blake2_128Concat, SessionIndex, Vec<T::MemberId>, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn must_rotate_keys_before)] + pub type MustRotateKeysBefore<T: Config> = + StorageMap<_, Blake2_128Concat, SessionIndex, Vec<T::MemberId>, ValueQuery>; + // HOOKS // // EVENTS // @@ -271,16 +282,22 @@ pub mod pallet { } .dispatch_bypass_filter(origin)?; - let expire_on_session = pallet_session::Pallet::<T>::current_index() - .saturating_add(T::MaxOfflineSessions::get()); + let current_session_index = pallet_session::Pallet::<T>::current_index(); + let expire_on_session = + current_session_index.saturating_add(T::MaxOfflineSessions::get()); + let must_rotate_keys_before = + current_session_index.saturating_add(T::MaxKeysLife::get()); Members::<T>::mutate_exists(member_id, |member_data_opt| { let mut member_data = member_data_opt.get_or_insert(MemberData { expire_on_session, + must_rotate_keys_before, validator_id: validator_id.clone(), }); + member_data.must_rotate_keys_before = must_rotate_keys_before; member_data.validator_id = validator_id; }); + MustRotateKeysBefore::<T>::append(must_rotate_keys_before, member_id); Ok(().into()) } @@ -299,17 +316,14 @@ pub mod pallet { return Err(Error::<T>::NotMember.into()); } - let expire_on_session = pallet_session::Pallet::<T>::current_index() - .saturating_add(T::MaxOfflineSessions::get()); - Members::<T>::mutate(member_id, |member_data_opt| { - let validator_id_clone = validator_id.clone(); - member_data_opt - .get_or_insert(MemberData::new(validator_id, expire_on_session)) - .validator_id = validator_id_clone; - }); - - Ok(().into()) + if let Some(member_data) = member_data_opt { + member_data.validator_id = validator_id; + Ok(().into()) + } else { + Err(Error::<T>::MemberNotFound.into()) + } + }) } #[pallet::weight(0)] pub fn remove_member( @@ -360,6 +374,13 @@ pub mod pallet { } } } + for member_id in MustRotateKeysBefore::<T>::take(current_session_index) { + if let Some(member_data) = Members::<T>::get(member_id) { + if member_data.must_rotate_keys_before == current_session_index { + Self::do_remove_member(member_id); + } + } + } } fn insert_in(member_id: T::MemberId) -> bool { let not_already_inserted = IncomingAuthorities::<T>::mutate(|members_ids| { diff --git a/pallets/authority-members/src/mock.rs b/pallets/authority-members/src/mock.rs index 5da294071..326b26066 100644 --- a/pallets/authority-members/src/mock.rs +++ b/pallets/authority-members/src/mock.rs @@ -149,8 +149,9 @@ impl IsMember<u64> for TestIsSmithMember { impl pallet_authority_members::Config for Test { type Event = Event; - type KeysWrapper = MockSessionKeys; + type KeysWrapper = MockSessionKeys; type IsMember = TestIsSmithMember; + type MaxKeysLife = ConstU32<5>; type MaxOfflineSessions = ConstU32<2>; type MemberId = u64; type OnRemovedMember = (); diff --git a/pallets/authority-members/src/tests.rs b/pallets/authority-members/src/tests.rs index dcd20ebf1..6f01e8b17 100644 --- a/pallets/authority-members/src/tests.rs +++ b/pallets/authority-members/src/tests.rs @@ -34,6 +34,7 @@ fn test_genesis_build() { AuthorityMembers::member(3), Some(MemberData { expire_on_session: 0, + must_rotate_keys_before: 5, validator_id: 3, }) ); @@ -41,6 +42,7 @@ fn test_genesis_build() { AuthorityMembers::member(6), Some(MemberData { expire_on_session: 0, + must_rotate_keys_before: 5, validator_id: 6, }) ); @@ -48,6 +50,7 @@ fn test_genesis_build() { AuthorityMembers::member(9), Some(MemberData { expire_on_session: 0, + must_rotate_keys_before: 5, validator_id: 9, }) ); @@ -68,6 +71,63 @@ fn test_new_session_shoud_not_change_authorities_set() { }); } +#[test] +fn test_max_keys_life_rule() { + new_test_ext(3).execute_with(|| { + run_to_block(11); + + assert_eq!(Session::current_index(), 2); + assert_eq!(Session::validators(), vec![3, 6, 9]); + + // Member 3 and 6 rotate their sessions keys + assert_ok!(AuthorityMembers::set_session_keys( + Origin::signed(3), + 3, + UintAuthorityId(3).into() + ),); + assert_ok!(AuthorityMembers::set_session_keys( + Origin::signed(6), + 6, + UintAuthorityId(6).into() + ),); + + // Verify state + assert_eq!( + AuthorityMembers::member(3), + Some(MemberData { + expire_on_session: 0, + must_rotate_keys_before: 7, + validator_id: 3, + }) + ); + assert_eq!( + AuthorityMembers::member(6), + Some(MemberData { + expire_on_session: 0, + must_rotate_keys_before: 7, + validator_id: 6, + }) + ); + + // Member 9 should expire at session 5 + run_to_block(26); + assert_eq!(Session::current_index(), 5); + assert_eq!(AuthorityMembers::online(), vec![3, 6]); + assert_eq!(AuthorityMembers::member(9), None); + + // Member 9 should be "deprogrammed" but still in the authorities set for 1 session + assert_eq!(Session::queued_keys().len(), 2); + assert_eq!(Session::queued_keys()[0].0, 3); + assert_eq!(Session::queued_keys()[1].0, 6); + assert_eq!(Session::validators(), vec![3, 6, 9]); + + // Member 9 should be **effectively** out at session 6 + run_to_block(31); + assert_eq!(Session::current_index(), 6); + assert_eq!(Session::validators(), vec![3, 6]); + }); +} + #[test] fn test_go_offline() { new_test_ext(3).execute_with(|| { @@ -84,6 +144,7 @@ fn test_go_offline() { AuthorityMembers::member(9), Some(MemberData { expire_on_session: 0, + must_rotate_keys_before: 5, validator_id: 9, }) ); @@ -94,6 +155,7 @@ fn test_go_offline() { AuthorityMembers::member(9), Some(MemberData { expire_on_session: 4, + must_rotate_keys_before: 5, validator_id: 9, }) ); @@ -133,6 +195,7 @@ fn test_go_online() { AuthorityMembers::member(12), Some(MemberData { expire_on_session: 2, + must_rotate_keys_before: 5, validator_id: 12, }) ); @@ -148,6 +211,7 @@ fn test_go_online() { AuthorityMembers::member(12), Some(MemberData { expire_on_session: 2, + must_rotate_keys_before: 5, validator_id: 12, }) ); @@ -196,6 +260,7 @@ fn test_go_online_then_go_offline_in_same_session() { AuthorityMembers::member(12), Some(MemberData { expire_on_session: 2, + must_rotate_keys_before: 5, validator_id: 12, }) ); @@ -223,6 +288,7 @@ fn test_go_offline_then_go_online_in_same_session() { AuthorityMembers::member(9), Some(MemberData { expire_on_session: 0, + must_rotate_keys_before: 5, validator_id: 9, }) ); diff --git a/pallets/authority-members/src/types.rs b/pallets/authority-members/src/types.rs index e22484a07..5e5fd7007 100644 --- a/pallets/authority-members/src/types.rs +++ b/pallets/authority-members/src/types.rs @@ -26,19 +26,15 @@ use sp_staking::SessionIndex; #[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] pub struct MemberData<ValidatorId: Decode + Encode + TypeInfo> { pub expire_on_session: SessionIndex, + pub must_rotate_keys_before: SessionIndex, pub validator_id: ValidatorId, } impl<ValidatorId: Decode + Encode + TypeInfo> MemberData<ValidatorId> { - pub fn new(validator_id: ValidatorId, expire_on_session: SessionIndex) -> Self { - MemberData { - expire_on_session, - validator_id, - } - } - pub fn new_genesis(validator_id: ValidatorId) -> Self { + pub fn new_genesis(must_rotate_keys_before: SessionIndex, validator_id: ValidatorId) -> Self { MemberData { expire_on_session: 0, + must_rotate_keys_before, validator_id, } } diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index b5fff7fab..226d575d8 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -176,13 +176,24 @@ macro_rules! pallets_config { impl pallet_authority_discovery::Config for Runtime { type MaxAuthorities = MaxAuthorities; } + impl pallet_authority_members::Config for Runtime { + type Event = Event; + type KeysWrapper = opaque::SessionKeysWrapper; + type IsMember = SmithsMembership; + type OnRemovedMember = OnRemovedAUthorityMemberHandler<Runtime>; + type OwnerKeyOf = OwnerKeyOfImpl<Runtime>; + type MemberId = IdtyIndex; + type MaxKeysLife = frame_support::pallet_prelude::ConstU32<1_000>; + type MaxOfflineSessions = frame_support::pallet_prelude::ConstU32<100>; + type RefreshValidatorIdOrigin = EnsureRoot<Self::AccountId>; + type RemoveMemberOrigin = EnsureRoot<Self::AccountId>; + } impl pallet_authorship::Config for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex<Self, Babe>; type UncleGenerations = UncleGenerations; type FilterUncle = (); type EventHandler = ImOnline; } - impl pallet_im_online::Config for Runtime { type AuthorityId = ImOnlineId; type Event = Event; @@ -215,7 +226,6 @@ macro_rules! pallets_config { type FullIdentification = ValidatorFullIdentification; type FullIdentificationOf = FullIdentificationOfImpl; } - impl pallet_grandpa::Config for Runtime { type Event = Event; type Call = Call; @@ -237,18 +247,6 @@ macro_rules! pallets_config { type MaxAuthorities = MaxAuthorities; } - impl pallet_authority_members::Config for Runtime { - type Event = Event; - type KeysWrapper = opaque::SessionKeysWrapper; - type IsMember = SmithsMembership; - type OnRemovedMember = OnRemovedAUthorityMemberHandler<Runtime>; - type OwnerKeyOf = OwnerKeyOfImpl<Runtime>; - type MemberId = IdtyIndex; - type MaxOfflineSessions = frame_support::pallet_prelude::ConstU32<100>; - type RefreshValidatorIdOrigin = EnsureRoot<Self::AccountId>; - type RemoveMemberOrigin = EnsureRoot<Self::AccountId>; - } - // UTILITIES // impl pallet_utility::Config for Runtime { -- GitLab