diff --git a/pallets/duniter-wot/src/lib.rs b/pallets/duniter-wot/src/lib.rs index d9214f14fcc5a2bf445329ed257ad2536232b528..8927b0952f236effb9a2c36c061b62c5d39bb0b8 100644 --- a/pallets/duniter-wot/src/lib.rs +++ b/pallets/duniter-wot/src/lib.rs @@ -35,6 +35,7 @@ use traits::*; use frame_support::pallet_prelude::*; use pallet_certification::traits::SetNextIssuableOn; use pallet_identity::{IdtyEvent, IdtyStatus}; +use pallet_membership::MembershipRemovalReason; use sp_runtime::traits::IsMember; type IdtyIndex = u32; @@ -176,7 +177,7 @@ where // implement cert call checks impl<T: Config<I>, I: 'static> pallet_certification::traits::CheckCertAllowed<IdtyIndex> for Pallet<T, I> -// TODO add the following where clause once checks can be done on pallet instance +// TODO (#136) add the following where clause once checks can be done on pallet instance // where // T: pallet_membership::Config<I>, { @@ -201,9 +202,10 @@ impl<T: Config<I>, I: 'static> pallet_certification::traits::CheckCertAllowed<Id // ensure receiver identity is confirmed and not revoked if let Some(receiver_data) = pallet_identity::Pallet::<T>::identity(receiver) { match receiver_data.status { - IdtyStatus::Unvalidated | IdtyStatus::Member | IdtyStatus::NotMember => {} // able to receive cert + // able to receive cert + IdtyStatus::Unvalidated | IdtyStatus::Member | IdtyStatus::NotMember => {} IdtyStatus::Unconfirmed => return Err(Error::<T, I>::CertToUnconfirmed.into()), - IdtyStatus::Revoked => return Err(Error::<T, I>::CertToRevoked.into()), // TODO more self-explanatory error message + IdtyStatus::Revoked => return Err(Error::<T, I>::CertToRevoked.into()), }; } else { return Err(Error::<T, I>::IdtyNotFound.into()); @@ -283,19 +285,33 @@ impl<T: Config<I>, I: 'static> pallet_identity::traits::OnIdtyChange<T> for Pall } // TODO distinguish if identity is revoked (keep it) and if identity is removed (also remove certs) IdtyEvent::Removed { status } => { - if *status != IdtyStatus::Revoked { - if let Err(e) = - <pallet_certification::Pallet<T, I>>::remove_all_certs_received_by( - frame_system::Origin::<T>::Root.into(), - idty_index, - ) - { + // try remove membership in any case + <pallet_membership::Pallet<T, I>>::do_remove_membership( + idty_index, + MembershipRemovalReason::Revoked, + ); + + // only remove certs if identity is unvalidated + match status { + IdtyStatus::Unconfirmed | IdtyStatus::Unvalidated => { + if let Err(e) = + <pallet_certification::Pallet<T, I>>::remove_all_certs_received_by( + frame_system::Origin::<T>::Root.into(), + idty_index, + ) + { + sp_std::if_std! { + println!("fail to remove certs received by some idty: {:?}", e) + } + } + } + IdtyStatus::Revoked => {} + IdtyStatus::Member | IdtyStatus::NotMember => { sp_std::if_std! { - println!("fail to remove certs received by some idty: {:?}", e) + println!("removed non-revoked identity: {:?}", idty_index); } } } - // TODO make sure a non-revoked identity can not be removed } } } @@ -331,12 +347,10 @@ impl<T: Config<I>, I: 'static> pallet_certification::traits::OnRemovedCert<IdtyI && pallet_membership::Pallet::<T, I>::is_member(&receiver) { // expire receiver membership - // it gives them a bit of time to get back enough certs - if let Err(e) = <pallet_membership::Pallet<T, I>>::force_expire_membership(receiver) { - sp_std::if_std! { - println!("fail to expire membership: {:?}", e) - } - } + <pallet_membership::Pallet<T, I>>::do_remove_membership( + receiver, + MembershipRemovalReason::NotEnoughCerts, + ) } } } diff --git a/pallets/duniter-wot/src/mock.rs b/pallets/duniter-wot/src/mock.rs index 05f44d7bf88a7c86dd3376002a9d8b72bfe9fce9..06d26386841923b4c1369ffb2ee691c9caf5afa1 100644 --- a/pallets/duniter-wot/src/mock.rs +++ b/pallets/duniter-wot/src/mock.rs @@ -135,7 +135,7 @@ impl pallet_identity::Config for Test { type AccountLinker = (); type Signer = UintAuthorityId; type Signature = TestSignature; - type OnIdtyChange = DuniterWot; + type OnIdtyChange = (DuniterWot, SmithSubWot); type RemoveIdentityConsumers = (); type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/duniter-wot/src/tests.rs b/pallets/duniter-wot/src/tests.rs index 3950835ae783efd8ae43fefd0fde03fa7a665163..f973097c62deb4fd653c2029d35a5fed6e49a7ef 100644 --- a/pallets/duniter-wot/src/tests.rs +++ b/pallets/duniter-wot/src/tests.rs @@ -108,7 +108,7 @@ fn test_smith_certs_expirations_should_expire_smith_membership() { System::assert_has_event(RuntimeEvent::SmithMembership( pallet_membership::Event::MembershipRemoved { member: 1, - reason: MembershipRemovalReason::Expired, + reason: MembershipRemovalReason::NotEnoughCerts, }, )); }); @@ -139,9 +139,9 @@ fn test_smith_member_cant_change_its_idty_address() { }); } -/// members of the smith subwot can not remove their identity +/// members of the smith subwot can revoke their identity #[test] -fn test_smith_member_cant_revoke_its_idty() { +fn test_smith_member_can_revoke_its_idty() { new_test_ext(5, 3).execute_with(|| { run_to_block(2); @@ -151,15 +151,26 @@ fn test_smith_member_cant_revoke_its_idty() { }; // Identity 3 can't change it's address - assert_noop!( - Identity::revoke_identity( - RuntimeOrigin::signed(3), - 3, - 3, - TestSignature(3, (REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()) - ), - pallet_duniter_wot::Error::<Test, Instance2>::NotAllowedToRemoveIdty - ); + assert_ok!(Identity::revoke_identity( + RuntimeOrigin::signed(3), + 3, + 3, + TestSignature(3, (REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()) + )); + // membership should be removed + System::assert_has_event(RuntimeEvent::Membership( + pallet_membership::Event::MembershipRemoved { + member: 3, + reason: pallet_membership::MembershipRemovalReason::Revoked, + }, + )); + // smith membership should be removed as well + System::assert_has_event(RuntimeEvent::SmithMembership( + pallet_membership::Event::MembershipRemoved { + member: 3, + reason: pallet_membership::MembershipRemovalReason::Revoked, + }, + )); }); } @@ -278,26 +289,37 @@ fn test_revoke_idty() { new_test_ext(5, 1).execute_with(|| { run_to_block(2); - // Alice identity can not be revoked because she's smith - assert_noop!( - Identity::revoke_identity( - RuntimeOrigin::signed(1), - 1, + // Alice identity can be revoked + assert_ok!(Identity::revoke_identity( + RuntimeOrigin::signed(1), + 1, + 1, + TestSignature( 1, - TestSignature( - 1, - ( - REVOCATION_PAYLOAD_PREFIX, - RevocationPayload { - idty_index: 1u32, - genesis_hash: System::block_hash(0), - } - ) - .encode() + ( + REVOCATION_PAYLOAD_PREFIX, + RevocationPayload { + idty_index: 1u32, + genesis_hash: System::block_hash(0), + } ) - ), - pallet_duniter_wot::Error::<Test, Instance2>::NotAllowedToRemoveIdty - ); + .encode() + ) + )); + // her membership should be removed + System::assert_has_event(RuntimeEvent::Membership( + pallet_membership::Event::MembershipRemoved { + member: 1, + reason: pallet_membership::MembershipRemovalReason::Revoked, + }, + )); + // and her smith membership should be removed as well + System::assert_has_event(RuntimeEvent::SmithMembership( + pallet_membership::Event::MembershipRemoved { + member: 1, + reason: pallet_membership::MembershipRemovalReason::Revoked, + }, + )); // Anyone should be able to submit Bob revocation certificate assert_ok!(Identity::revoke_identity( @@ -364,8 +386,8 @@ fn test_idty_membership_expire() { status: IdtyStatus::NotMember, }) ); - // TODO check that identity is added to auto-revoke list (currently IdentityChangeSchedule) - // and check the revocation event is there + // check that identity is added to auto-revoke list (currently IdentityChangeSchedule) + assert_eq!(Identity::next_scheduled(14), vec!(3)); run_to_block(14); assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); // Charlie's identity should be auto-revoked at block #11 (8 + 3) @@ -473,7 +495,7 @@ fn test_certification_expire() { System::assert_has_event(RuntimeEvent::SmithMembership( pallet_membership::Event::MembershipRemoved { member: 1, - reason: MembershipRemovalReason::Expired, + reason: MembershipRemovalReason::NotEnoughCerts, }, )); @@ -497,7 +519,7 @@ fn test_certification_expire() { System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipRemoved { member: 1, - reason: MembershipRemovalReason::Expired, + reason: MembershipRemovalReason::NotEnoughCerts, }, )); @@ -579,7 +601,7 @@ fn test_cert_can_not_be_issued() { System::assert_has_event(RuntimeEvent::SmithMembership( pallet_membership::Event::MembershipRemoved { member: 1, - reason: MembershipRemovalReason::Expired, + reason: MembershipRemovalReason::NotEnoughCerts, }, )); @@ -627,7 +649,7 @@ fn test_cert_can_not_be_issued() { System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipRemoved { member: 1, - reason: MembershipRemovalReason::Expired, + reason: MembershipRemovalReason::NotEnoughCerts, }, // pending membership expires at 23 )); diff --git a/pallets/identity/src/lib.rs b/pallets/identity/src/lib.rs index 0de04edab3e642fc90605826c895f016d0cff937..ddf3d0121758633a40f67db42fa8436c7c3f7d82 100644 --- a/pallets/identity/src/lib.rs +++ b/pallets/identity/src/lib.rs @@ -739,6 +739,12 @@ pub mod pallet { ); Self::deposit_event(Event::IdtyRevoked { idty_index, reason }); + T::OnIdtyChange::on_idty_change( + idty_index, + &IdtyEvent::Removed { + status: IdtyStatus::Revoked, + }, + ); return T::WeightInfo::do_remove_identity(); // TODO benchmark this // return T::WeightInfo::do_revoke_identity(); diff --git a/pallets/membership/src/lib.rs b/pallets/membership/src/lib.rs index 53292e3f7f77807df79a3d47222b21de4d8dd386..493e836d2442940a4855014ca4e21f59f152d6a2 100644 --- a/pallets/membership/src/lib.rs +++ b/pallets/membership/src/lib.rs @@ -56,8 +56,14 @@ impl<IdtyId, AccountId> SetupBenchmark<IdtyId, AccountId> for () { #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum MembershipRemovalReason { + // reach end of life Expired, + // was explicitly revoked Revoked, + // received certs count passed below threshold + NotEnoughCerts, + // system reasons (consumers, authority members, or root) + System, } #[frame_support::pallet] @@ -155,7 +161,6 @@ pub mod pallet { MembershipRemoved { member: T::IdtyId, reason: MembershipRemovalReason, - // TODO maybe add auto-revocation delay? }, } @@ -199,7 +204,7 @@ pub mod pallet { let idty_id = Self::get_idty_id(origin)?; Self::check_allowed_to_claim(idty_id)?; - Self::do_claim_membership(idty_id); + Self::do_add_membership(idty_id); Ok(().into()) } @@ -231,7 +236,7 @@ pub mod pallet { let idty_id = Self::get_idty_id(origin)?; // Apply phase - Self::do_revoke_membership(idty_id); + Self::do_remove_membership(idty_id, MembershipRemovalReason::Revoked); Ok(().into()) } @@ -240,25 +245,6 @@ pub mod pallet { // INTERNAL FUNCTIONS // impl<T: Config<I>, I: 'static> Pallet<T, I> { - /// force expire membership - pub fn force_expire_membership(idty_id: T::IdtyId) -> DispatchResultWithPostInfo { - Self::do_expire_membership(idty_id); - Ok(().into()) - } - - /// try to claim membership if not already done - pub fn try_claim_membership(idty_id: T::IdtyId) { - if Self::check_allowed_to_claim(idty_id).is_ok() { - Self::do_claim_membership(idty_id); - } - // else { should not try to claim membership if not allowed} - } - - /// force revoke membership - pub fn force_revoke_membership(idty_id: T::IdtyId) { - Self::do_revoke_membership(idty_id); - } - /// unschedule membership expiry fn unschedule_membership_expiry(idty_id: T::IdtyId, block_number: T::BlockNumber) { let mut scheduled = MembershipsExpireOn::<T, I>::get(block_number); @@ -288,38 +274,25 @@ pub mod pallet { Ok(()) } - /// perform membership claim - fn do_claim_membership(idty_id: T::IdtyId) { + /// perform membership addition + fn do_add_membership(idty_id: T::IdtyId) { Self::insert_membership_and_schedule_expiry(idty_id); T::OnEvent::on_event(&sp_membership::Event::MembershipAdded(idty_id)); } - /// perform membership revokation - fn do_revoke_membership(idty_id: T::IdtyId) { + /// perform membership removal + pub fn do_remove_membership(idty_id: T::IdtyId, reason: MembershipRemovalReason) { if let Some(membership_data) = Membership::<T, I>::take(idty_id) { Self::unschedule_membership_expiry(idty_id, membership_data.expire_on); Self::deposit_event(Event::MembershipRemoved { member: idty_id, - reason: MembershipRemovalReason::Revoked, + reason, }); T::OnEvent::on_event(&sp_membership::Event::MembershipRemoved(idty_id)); } } - /// perform membership expiration - fn do_expire_membership(idty_id: T::IdtyId) { - if Membership::<T, I>::take(idty_id).is_some() { - Self::deposit_event(Event::MembershipRemoved { - member: idty_id, - reason: MembershipRemovalReason::Expired, - }); - T::OnEvent::on_event(&sp_membership::Event::MembershipRemoved(idty_id)); - } // else should not happen - } - /// check the origin and get identity id if valid - // TODO avoid the concept of idty_id which does not tell the status of the identity - // maybe call functions using the origin instead and let the conversion happen in an other pallet fn get_idty_id(origin: OriginFor<T>) -> Result<T::IdtyId, DispatchError> { if let Ok(RawOrigin::Signed(account_id)) = origin.into() { T::IdtyIdOf::convert(account_id).ok_or_else(|| Error::<T, I>::IdtyIdNotFound.into()) @@ -327,20 +300,21 @@ pub mod pallet { Err(BadOrigin.into()) } } + /// perform the membership expiry scheduled at given block pub fn expire_memberships(block_number: T::BlockNumber) -> Weight { let mut expired_idty_count = 0u32; for idty_id in MembershipsExpireOn::<T, I>::take(block_number) { // remove membership (take) - Self::do_expire_membership(idty_id); + Self::do_remove_membership(idty_id, MembershipRemovalReason::Expired); expired_idty_count = 0; } T::WeightInfo::expire_memberships(expired_idty_count) } /// check if identity is member - pub(super) fn do_is_member(idty_id: &T::IdtyId) -> bool { + pub(super) fn is_member(idty_id: &T::IdtyId) -> bool { Membership::<T, I>::contains_key(idty_id) } } @@ -350,7 +324,7 @@ pub mod pallet { impl<T: Config<I>, I: 'static> sp_runtime::traits::IsMember<T::IdtyId> for Pallet<T, I> { fn is_member(idty_id: &T::IdtyId) -> bool { - Self::do_is_member(idty_id) + Self::is_member(idty_id) } } diff --git a/runtime/common/src/handlers.rs b/runtime/common/src/handlers.rs index f7f297055c4a192653d0693424e6e85b125306a9..0c8fd0b8e090994c8661fb5c080514e04cd41c1a 100644 --- a/runtime/common/src/handlers.rs +++ b/runtime/common/src/handlers.rs @@ -114,6 +114,7 @@ impl< } // authority member removal handler +// TODO refac smith wot pub struct OnRemovedAuthorityMemberHandler<Runtime>(core::marker::PhantomData<Runtime>); impl<Runtime> pallet_authority_members::traits::OnRemovedMember<IdtyIndex> for OnRemovedAuthorityMemberHandler<Runtime> @@ -122,11 +123,15 @@ where { fn on_removed_member(idty_index: IdtyIndex) { // TODO investigate why we should remove smith membership when removing authority member - pallet_membership::Pallet::<Runtime, Instance2>::force_revoke_membership(idty_index); + pallet_membership::Pallet::<Runtime, Instance2>::do_remove_membership( + idty_index, + MembershipRemovalReason::System, + ); } } // identity removal handler +// TODO check if it is really useful pub struct RemoveIdentityConsumersImpl<Runtime>(core::marker::PhantomData<Runtime>); impl<Runtime> pallet_identity::traits::RemoveIdentityConsumers<IdtyIndex> for RemoveIdentityConsumersImpl<Runtime> @@ -139,10 +144,16 @@ where fn remove_idty_consumers(idty_index: IdtyIndex) -> Weight { // Remove smith member if pallet_membership::Pallet::<Runtime, Instance2>::is_member(&idty_index) { - pallet_membership::Pallet::<Runtime, Instance2>::force_revoke_membership(idty_index); + pallet_membership::Pallet::<Runtime, Instance2>::do_remove_membership( + idty_index, + MembershipRemovalReason::System, + ); } // Remove "classic" member - pallet_membership::Pallet::<Runtime, Instance1>::force_revoke_membership(idty_index); + pallet_membership::Pallet::<Runtime, Instance1>::do_remove_membership( + idty_index, + MembershipRemovalReason::System, + ); Weight::zero() } diff --git a/runtime/gdev/tests/fixme_tests.rs b/runtime/gdev/tests/fixme_tests.rs index f0a283762d55a3a5890ebe2a6b4609394f233f6a..9bbdd6a2bbfa34d93b8f256877b6c144e071e5e5 100644 --- a/runtime/gdev/tests/fixme_tests.rs +++ b/runtime/gdev/tests/fixme_tests.rs @@ -41,9 +41,10 @@ fn can_still_issue_cert_when_membership_lost() { ExtBuilder::new(1, 3, 4).build().execute_with(|| { run_to_block(1); - // expire Bob membership (could happen for negative distance evaluation for example) - assert_ok!(Membership::force_expire_membership( + // expire Bob membership + assert_ok!(Membership::do_remove_membership( 2, // Bob + MembershipRemovalReason::System )); System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipRemoved { diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs index 7bacdb04786508a5cc0832edd12ab966386ad722..5d74b29d7543f04ac5e880d9df2dc158ce8bd8b7 100644 --- a/runtime/gdev/tests/integration_tests.rs +++ b/runtime/gdev/tests/integration_tests.rs @@ -572,7 +572,10 @@ fn test_ud_claimed_membership_on_and_off() { run_to_block(13); // alice identity expires - assert_ok!(Membership::force_expire_membership(1)); + assert_ok!(Membership::do_remove_membership( + 1, + MembershipRemovalReason::System + )); System::assert_has_event(RuntimeEvent::UniversalDividend( pallet_universal_dividend::Event::UdsAutoPaidAtRemoval { count: 1,