diff --git a/docs/api/runtime-calls.md b/docs/api/runtime-calls.md index 033203a40f859cd2c557e50e9e8ef5d2fbebadcc..3c96e8358dfda38ca1dc6434860180de1e675926 100644 --- a/docs/api/runtime-calls.md +++ b/docs/api/runtime-calls.md @@ -13,7 +13,7 @@ through on-chain governance mechanisms. ## User calls -There are **79** user calls from **22** pallets. +There are **77** user calls from **21** pallets. ### Account - 1 @@ -831,51 +831,6 @@ payload_sig: T::Signature Link an account to an identity -### Membership - 42 - -#### claim_membership - 1 - -<details><summary><code>claim_membership()</code></summary> - -Taking 0.0213 % of a block. - -```rust -``` -</details> - - -claim membership -it must fullfill the requirements (certs, distance) -TODO #159 for main wot claim_membership is called automatically when distance is evaluated positively -for smith wot, it means joining the authority members - -#### renew_membership - 2 - -<details><summary><code>renew_membership()</code></summary> - -Taking 0.0164 % of a block. - -```rust -``` -</details> - - -extend the validity period of an active membership - -#### revoke_membership - 3 - -<details><summary><code>revoke_membership()</code></summary> - -Taking 0.0586 % of a block. - -```rust -``` -</details> - - -revoke an active membership -(only available for sub wot, automatic for main wot) - ### Certification - 43 #### add_cert - 0 @@ -932,20 +887,37 @@ remove all certifications received by an identity (only root) <details><summary><code>request_distance_evaluation()</code></summary> -Taking 0.0187 % of a block. +Taking 0.06 % of a block. + +```rust +``` +</details> + + +Request caller identity to be evaluated +positive evaluation will result in claim/renew membership +negative evaluation will result in slash for caller + +#### request_distance_evaluation_for - 4 + +<details><summary><code>request_distance_evaluation_for(target)</code></summary> + +Taking 0.0805 % of a block. ```rust +target: T::IdtyIndex ``` </details> -Request an identity to be evaluated +Request target identity to be evaluated +only possible for unvalidated identity #### update_evaluation - 1 <details><summary><code>update_evaluation(computation_result)</code></summary> -Taking 0.0195 % of a block. +Taking 0.0914 % of a block. ```rust computation_result: ComputationResult @@ -954,12 +926,13 @@ computation_result: ComputationResult (Inherent) Push an evaluation result to the pool +this is called internally by validators (= inherent) #### force_update_evaluation - 2 <details><summary><code>force_update_evaluation(evaluator, computation_result)</code></summary> -Taking 0.0122 % of a block. +Taking 0.0759 % of a block. ```rust evaluator: <T as frame_system::Config>::AccountId @@ -968,28 +941,21 @@ computation_result: ComputationResult </details> -Push an evaluation result to the pool +Force push an evaluation result to the pool -#### force_set_distance_status - 3 +#### force_valid_distance_status - 3 -<details><summary><code>force_set_distance_status(identity, status)</code></summary> +<details><summary><code>force_valid_distance_status(identity)</code></summary> -Taking 0.011 % of a block. +Taking 0.074 % of a block. ```rust identity: <T as pallet_identity::Config>::IdtyIndex -status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)> ``` </details> -Set the distance evaluation status of an identity - -Removes the status if `status` is `None`. - -* `status.0` is the account for whom the price will be unreserved or slashed - when the evaluation completes. -* `status.1` is the status of the evaluation. +Force set the distance evaluation status of an identity ### AtomicSwap - 50 diff --git a/docs/api/runtime-errors.md b/docs/api/runtime-errors.md index ebb10eb1a66022ba5bb640d7dc65cbf89a34d0b5..03b5c31fb96e2ecb7fcf408e945b1e79464d17a2 100644 --- a/docs/api/runtime-errors.md +++ b/docs/api/runtime-errors.md @@ -1,6 +1,6 @@ # Runtime errors -There are **178** errors from **35** pallets. +There are **185** errors from **35** pallets. <ul> <li>System - 0 @@ -775,8 +775,8 @@ Distance evaluation has not been requested <li> <details> <summary> -<code>IdtyNotAllowedToRequestMembership</code> - 5</summary> -Identity is not allowed to request membership. +<code>IdtyNotAllowedToClaimMembership</code> - 5</summary> +Identity is not allowed to claim membership. </details> </li> <li> @@ -849,6 +849,20 @@ Cannot issue a certification to a revoked identity Issuer or receiver not found. </details> </li> +<li> +<details> +<summary> +<code>NotEnoughCertsReceivedToRequestDistanceEvaluation</code> - 16</summary> +Not enough certs received to request distance evaluation. +</details> +</li> +<li> +<details> +<summary> +<code>MembershipRenewalPeriodNotRespected</code> - 17</summary> +Membership can only be renewed after an antispam delay +</details> +</li> </ul> </li> <li>Identity - 41 @@ -1011,6 +1025,13 @@ Membership already acquired. Membership not found. </details> </li> +<li> +<details> +<summary> +<code>AlreadyMember</code> - 3</summary> +Already member, can not claim membership +</details> +</li> </ul> </li> <li>Certification - 43 @@ -1085,31 +1106,59 @@ No author for this block. <li> <details> <summary> -<code>NoIdentity</code> - 4</summary> +<code>CallerHasNoIdentity</code> - 4</summary> Caller has no identity. </details> </li> <li> <details> <summary> -<code>QueueFull</code> - 5</summary> +<code>CallerIdentityNotFound</code> - 5</summary> +Caller identity not found. +</details> +</li> +<li> +<details> +<summary> +<code>CallerNotMember</code> - 6</summary> +Caller not member. +</details> +</li> +<li> +<details> +<summary> +<code>TargetIdentityNotFound</code> - 7</summary> +Target identity not found. +</details> +</li> +<li> +<details> +<summary> +<code>QueueFull</code> - 8</summary> Evaluation queue is full. </details> </li> <li> <details> <summary> -<code>TooManyEvaluators</code> - 6</summary> +<code>TooManyEvaluators</code> - 9</summary> Too many evaluators in the current evaluation pool. </details> </li> <li> <details> <summary> -<code>WrongResultLength</code> - 7</summary> +<code>WrongResultLength</code> - 10</summary> Evaluation result has a wrong length. </details> </li> +<li> +<details> +<summary> +<code>DistanceRequestOnlyAllowedForUnvalidated</code> - 11</summary> +Targeted distance evaluation request is only possible for an unvalidated identity +</details> +</li> </ul> </li> <li>AtomicSwap - 50 diff --git a/docs/api/runtime-events.md b/docs/api/runtime-events.md index 9aa80926367bb4a66c4f20f066063c6975b15769..c495670a16aafa3fe9b3201bb93b2ea3adc843b2 100644 --- a/docs/api/runtime-events.md +++ b/docs/api/runtime-events.md @@ -1,6 +1,6 @@ # Runtime events -There are **127** events from **35** pallets. +There are **128** events from **35** pallets. <ul> <li>System - 0 @@ -1286,7 +1286,20 @@ expire_on: BlockNumberFor<T> <li> <details> <summary> -<code>MembershipRemoved(member, reason)</code> - 1</summary> +<code>MembershipRenewed(member, expire_on)</code> - 1</summary> +A membership was renewed. + +```rust +member: T::IdtyId +expire_on: BlockNumberFor<T> +``` + +</details> +</li> +<li> +<details> +<summary> +<code>MembershipRemoved(member, reason)</code> - 2</summary> A membership was removed. ```rust @@ -1360,11 +1373,11 @@ who: T::AccountId <li> <details> <summary> -<code>EvaluationUpdated(evaluator)</code> - 1</summary> -A distance evaluation was updated. +<code>EvaluatedValid(idty_index)</code> - 1</summary> +Distance rule was found valid ```rust -evaluator: T::AccountId +idty_index: T::IdtyIndex ``` </details> @@ -1372,12 +1385,11 @@ evaluator: T::AccountId <li> <details> <summary> -<code>EvaluationStatusForced(idty_index, status)</code> - 2</summary> -A distance status was forced. +<code>EvaluatedInvalid(idty_index)</code> - 2</summary> +Distance rule was found invalid ```rust idty_index: T::IdtyIndex -status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)> ``` </details> diff --git a/end2end-tests/cucumber-features/identity_creation.feature b/end2end-tests/cucumber-features/identity_creation.feature index f8bffeacc3017112a3b02485a693d5c56ec3f893..7de97f182dd43c222dd6e737b53693ccabd4ccc1 100644 --- a/end2end-tests/cucumber-features/identity_creation.feature +++ b/end2end-tests/cucumber-features/identity_creation.feature @@ -29,6 +29,4 @@ Feature: Identity creation Then dave should have distance result in 1 session When alice runs distance oracle When 30 blocks later - Then dave should have distance ok - When dave claims membership Then dave identity should be member diff --git a/end2end-tests/cucumber-genesis/default.json b/end2end-tests/cucumber-genesis/default.json index 245e91e312e06afca31bf3a5023a5e423c8d2e31..cfd66ba939a20409e5c577a657138dd211f69ec7 100644 --- a/end2end-tests/cucumber-genesis/default.json +++ b/end2end-tests/cucumber-genesis/default.json @@ -53,7 +53,7 @@ "idty_confirm_period": 40, "idty_creation_period": 50, "membership_period": 1000, - "pending_membership_period": 500, + "membership_renewal_period": 500, "ud_creation_period": 60000, "ud_reeval_period": 600000, "smith_cert_max_by_issuer": 8, @@ -64,9 +64,15 @@ "wot_min_cert_for_membership": 2 }, "clique_smiths": [ - { "name": "Alice" }, - { "name": "Bob" }, - { "name": "Charlie" } + { + "name": "Alice" + }, + { + "name": "Bob" + }, + { + "name": "Charlie" + } ], "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "technical_committee": [ diff --git a/end2end-tests/cucumber-genesis/wot.json b/end2end-tests/cucumber-genesis/wot.json index 6b1fad67065eb3323c083cc7e0e80ae8ce6098ef..813fc6fc13b2a9082f716734835b6b83f1aa27ad 100644 --- a/end2end-tests/cucumber-genesis/wot.json +++ b/end2end-tests/cucumber-genesis/wot.json @@ -62,7 +62,7 @@ "idty_confirm_period": 40, "idty_creation_period": 50, "membership_period": 1000, - "pending_membership_period": 500, + "membership_renewal_period": 500, "ud_creation_period": 60000, "ud_reeval_period": 600000, "smith_cert_max_by_issuer": 8, @@ -73,9 +73,15 @@ "wot_min_cert_for_membership": 2 }, "clique_smiths": [ - { "name": "Alice" }, - { "name": "Bob" }, - { "name": "Charlie" } + { + "name": "Alice" + }, + { + "name": "Bob" + }, + { + "name": "Charlie" + } ], "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "technical_committee": [ diff --git a/end2end-tests/tests/common/membership.rs b/end2end-tests/tests/common/membership.rs index d800f3387f756240b64645bda3c2859391b706db..c7b3fa56c291bbd82f08fe3519bd38b721ad5e33 100644 --- a/end2end-tests/tests/common/membership.rs +++ b/end2end-tests/tests/common/membership.rs @@ -14,27 +14,4 @@ // You should have received a copy of the GNU Affero General Public License // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. -use super::*; -use sp_keyring::AccountKeyring; -use subxt::tx::PairSigner; - -pub async fn claim_membership(client: &Client, from: AccountKeyring) -> Result<()> { - let from = PairSigner::new(from.pair()); - - let _events = create_block_with_extrinsic( - client, - client - .tx() - .create_signed( - &gdev::tx().membership().claim_membership(), - &from, - BaseExtrinsicParamsBuilder::new(), - ) - .await - .unwrap(), - ) - .await - .unwrap(); - - Ok(()) -} +// no more membership calls diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs index 7832a4a3d89048a033d61245d2a99cbaa508ea13..48d7c30be44ce6ef8a1c7bfba5cc4a487f4d83c3 100644 --- a/end2end-tests/tests/cucumber_tests.rs +++ b/end2end-tests/tests/cucumber_tests.rs @@ -333,14 +333,6 @@ async fn confirm_identity(world: &mut DuniterWorld, from: String, pseudo: String common::identity::confirm_identity(world.client(), from, pseudo).await } -#[allow(clippy::needless_pass_by_ref_mut)] -#[when(regex = r#"([a-zA-Z]+) claims membership"#)] -async fn claim_membership(world: &mut DuniterWorld, from: String) -> Result<()> { - // input names to keyrings - let from = AccountKeyring::from_str(&from).expect("unknown from"); - common::membership::claim_membership(world.client(), from).await -} - #[allow(clippy::needless_pass_by_ref_mut)] #[when(regex = r#"([a-zA-Z]+) requests distance evaluation"#)] async fn request_distance_evaluation(world: &mut DuniterWorld, who: String) -> Result<()> { @@ -541,33 +533,6 @@ async fn should_have_distance_result_in_sessions( Err(anyhow::anyhow!("no evaluation in given pool").into()) } -#[allow(clippy::needless_pass_by_ref_mut)] -#[then(regex = r"([a-zA-Z]+) should have distance ok")] -async fn should_have_distance_ok(world: &mut DuniterWorld, who: String) -> Result<()> { - let who = AccountKeyring::from_str(&who).unwrap().to_account_id(); - - let idty_id = world - .read(&gdev::storage().identity().identity_index_of(&who.into())) - .await - .await? - .unwrap(); - - match world - .read(&gdev::storage().distance().identity_distance_status(idty_id)) - .await - .await? - { - Some((_, gdev::runtime_types::pallet_distance::types::DistanceStatus::Valid)) => Ok(()), - Some((_, gdev::runtime_types::pallet_distance::types::DistanceStatus::Invalid)) => { - Err(anyhow::anyhow!("invalid distance status").into()) - } - Some((_, gdev::runtime_types::pallet_distance::types::DistanceStatus::Pending)) => { - Err(anyhow::anyhow!("pending distance status").into()) - } - None => Err(anyhow::anyhow!("no distance status").into()), - } -} - use gdev::runtime_types::pallet_identity::types::IdtyStatus; // status from string diff --git a/node/src/chain_spec/gdev.rs b/node/src/chain_spec/gdev.rs index e927d483bc777d7b21c647ca7d07e3303ed464da..a66be70d21a9446bfd6833e250dab394db805260 100644 --- a/node/src/chain_spec/gdev.rs +++ b/node/src/chain_spec/gdev.rs @@ -87,7 +87,7 @@ fn get_parameters(parameters_from_file: &Option<GenesisParameters>) -> CommonPar identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(), identity_idty_creation_period: parameters_from_file.idty_creation_period, membership_membership_period: parameters_from_file.membership_period, - membership_pending_membership_period: parameters_from_file.pending_membership_period, + membership_membership_renewal_period: parameters_from_file.membership_renewal_period, cert_max_by_issuer: parameters_from_file.cert_max_by_issuer, cert_min_received_cert_to_be_able_to_issue_cert: parameters_from_file .cert_min_received_cert_to_issue_cert, @@ -373,6 +373,7 @@ fn get_local_chain_parameters() -> Option<GenesisParameters> { let babe_epoch_duration = get_env("DUNITER_BABE_EPOCH_DURATION", 30) as u64; let cert_validity_period = get_env("DUNITER_CERT_VALIDITY_PERIOD", 1_000); let membership_period = get_env("DUNITER_MEMBERSHIP_PERIOD", 1_000); + let membership_renewal_period = get_env("DUNITER_MEMBERSHIP_RENEWAL_PERIOD", 1_000); let ud_creation_period = get_env("DUNITER_UD_CREATION_PERIOD", 60_000); let ud_reeval_period = get_env("DUNITER_UD_REEEVAL_PERIOD", 1_200_000); Some(GenesisParameters { @@ -384,7 +385,7 @@ fn get_local_chain_parameters() -> Option<GenesisParameters> { idty_confirm_period: 40, idty_creation_period: 50, membership_period, - pending_membership_period: 500, + membership_renewal_period, ud_creation_period, ud_reeval_period, smith_cert_max_by_issuer: 8, diff --git a/node/src/chain_spec/gen_genesis_data.rs b/node/src/chain_spec/gen_genesis_data.rs index 56481245eb257f0fadc84ce85b7969d83b8c6520..2565f3d4a273fdb7999e5be179372fdfc0ac18d4 100644 --- a/node/src/chain_spec/gen_genesis_data.rs +++ b/node/src/chain_spec/gen_genesis_data.rs @@ -553,6 +553,15 @@ where G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32 ) } + if common_parameters.membership_membership_renewal_period / DAYS + != G1_DUNITER_V1_MSVALIDITY / DUNITER_V1_DAYS + { + warn!( + "parameter `membership_renewal_period` ({} days) is different from Ğ1's ({} days)", + common_parameters.membership_membership_renewal_period as f32 / DAYS as f32, + G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32 + ) + } if common_parameters.cert_cert_period / DAYS != G1_DUNITER_V1_SIGPERIOD / DUNITER_V1_DAYS { warn!( "parameter `cert_period` ({} days) is different from Ğ1's ({} days)", @@ -807,8 +816,8 @@ fn dump_genesis_info(info: GenesisInfo) { get_best_unit_and_diviser_for_blocks(p.identity_idty_creation_period); let (membership_membership_period, membership_membership_period_unit) = get_best_unit_and_diviser_for_blocks(p.membership_membership_period); - let (membership_pending_membership_period, membership_pending_membership_period_unit) = - get_best_unit_and_diviser_for_blocks(p.membership_pending_membership_period); + let (membership_membership_renewal_period, membership_membership_renewal_period_unit) = + get_best_unit_and_diviser_for_blocks(p.membership_membership_renewal_period); let (cert_cert_period, cert_cert_period_unit) = get_best_unit_and_diviser_for_blocks(p.cert_cert_period); let (cert_max_by_issuer, cert_max_by_issuer_unit) = @@ -857,7 +866,7 @@ fn dump_genesis_info(info: GenesisInfo) { - identity.change_owner_key_period: {} {} - identity.idty_creation_period: {} {} - membership.membership_period: {} {} - - membership.pending_membership_period: {} {} + - membership.membership_renewal_period: {} {} - cert.cert_period: {} {} - cert.max_by_issuer: {} {} - cert.min_received_cert_to_be_able_to_issue_cert: {} {} @@ -907,8 +916,8 @@ fn dump_genesis_info(info: GenesisInfo) { identity_idty_creation_period_unit, membership_membership_period, membership_membership_period_unit, - membership_pending_membership_period, - membership_pending_membership_period_unit, + membership_membership_renewal_period, + membership_membership_renewal_period_unit, cert_cert_period, cert_cert_period_unit, cert_max_by_issuer, @@ -1980,7 +1989,7 @@ pub struct CommonParameters { pub identity_change_owner_key_period: u32, pub identity_idty_creation_period: u32, pub membership_membership_period: u32, - pub membership_pending_membership_period: u32, + pub membership_membership_renewal_period: u32, pub cert_cert_period: u32, pub cert_max_by_issuer: u32, pub cert_min_received_cert_to_be_able_to_issue_cert: u32, diff --git a/node/src/chain_spec/gtest.rs b/node/src/chain_spec/gtest.rs index d01ee0ccea092ce251aa1f631b40fd6ac56625a2..e7814f6d0fb4e1e72f37e4610601ec5441e700da 100644 --- a/node/src/chain_spec/gtest.rs +++ b/node/src/chain_spec/gtest.rs @@ -91,7 +91,7 @@ fn get_parameters(_: &Option<GenesisParameters>) -> CommonParameters { identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(), identity_idty_creation_period: parameters::IdtyCreationPeriod::get(), membership_membership_period: parameters::MembershipPeriod::get(), - membership_pending_membership_period: parameters::PendingMembershipPeriod::get(), + membership_membership_renewal_period: parameters::MembershipRenewalPeriod::get(), cert_max_by_issuer: parameters::MaxByIssuer::get(), cert_min_received_cert_to_be_able_to_issue_cert: parameters::MinReceivedCertToBeAbleToIssueCert::get(), diff --git a/pallets/distance/src/benchmarking.rs b/pallets/distance/src/benchmarking.rs index 33ba9419b1f9aeb7b4837948bf47e1ce166742a0..c762bde19753caa3c506c546772f495a98d620f9 100644 --- a/pallets/distance/src/benchmarking.rs +++ b/pallets/distance/src/benchmarking.rs @@ -50,16 +50,36 @@ benchmarks! { T: pallet_balances::Config, T::Balance: From<u64>, T::BlockNumber: From<u32>, } + + // request distance evaluation request_distance_evaluation { - let idty = T::IdtyIndex::one(); - let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key; - let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); - let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); + let idty = T::IdtyIndex::one(); + let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key; + let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); + let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); }: _<T::RuntimeOrigin>(caller_origin.clone()) verify { - assert!(IdentityDistanceStatus::<T>::get(idty) == Some((caller.clone(), DistanceStatus::Pending)), "Request not added"); + assert!(PendingEvaluationRequest::<T>::get(idty) == Some(caller.clone()), "Request not added"); assert_has_event::<T>(Event::<T>::EvaluationRequested { idty_index: idty, who: caller }.into()); } + + // request distance evaluation for + request_distance_evaluation_for { + let idty = T::IdtyIndex::one(); + let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key; + let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); + let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); + let target = 2u32; + // set target status since targeted distance evaluation only allowed for unvalidated + pallet_identity::Identities::<T>::mutate(target, + |idty_val| idty_val.as_mut().unwrap().status = pallet_identity::IdtyStatus::Unvalidated); + }: _<T::RuntimeOrigin>(caller_origin.clone(), target) + verify { + assert!(PendingEvaluationRequest::<T>::get(target) == Some(caller.clone()), "Request not added"); + assert_has_event::<T>(Event::<T>::EvaluationRequested { idty_index: target, who: caller }.into()); + } + + // update evaluation update_evaluation { let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryPlain( sp_consensus_babe::digests::SecondaryPlainPreDigest { authority_index: 0u32, slot: Default::default() }); @@ -71,27 +91,25 @@ benchmarks! { let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); let i in 1 .. MAX_EVALUATIONS_PER_SESSION => populate_pool::<T>(i)?; }: _<T::RuntimeOrigin>(RawOrigin::None.into(), ComputationResult{distances: vec![Perbill::one(); i as usize]}) - verify { - assert_has_event::<T>(Event::<T>::EvaluationUpdated { evaluator: caller }.into()); - } + + // force update evaluation force_update_evaluation { let idty = T::IdtyIndex::one(); let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key; let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); let i in 1 .. MAX_EVALUATIONS_PER_SESSION => populate_pool::<T>(i)?; }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), caller.clone(), ComputationResult{distances: vec![Perbill::one(); i as usize]}) + + // force valid distance status + force_valid_distance_status { + let idty = T::IdtyIndex::one(); + let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key; + }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), idty) verify { - assert_has_event::<T>(Event::<T>::EvaluationUpdated { evaluator: caller }.into()); - } - force_set_distance_status { - let idty = T::IdtyIndex::one(); - let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key; - let status = Some((caller.clone(), DistanceStatus::Valid)); - }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), idty, status.clone()) - verify { - assert!(IdentityDistanceStatus::<T>::get(idty) == Some((caller, DistanceStatus::Valid)), "Status not set"); - assert_has_event::<T>(Event::<T>::EvaluationStatusForced { idty_index: idty, status }.into()); + assert_has_event::<T>(Event::<T>::EvaluatedValid { idty_index: idty }.into()); } + + // on finalize on_finalize { DidUpdate::<T>::set(true); }: { Pallet::<T>::on_finalize(Default::default()); } diff --git a/pallets/distance/src/lib.rs b/pallets/distance/src/lib.rs index d7d131cd8f00e99d7bcef9b4470ae512a1caa07e..b7ee0e21420bc71d7c316f19d40c7d73f3293fab 100644 --- a/pallets/distance/src/lib.rs +++ b/pallets/distance/src/lib.rs @@ -17,7 +17,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod median; -mod traits; +pub mod traits; mod types; mod weights; @@ -26,6 +26,8 @@ pub mod benchmarking; #[cfg(test)] mod mock; +#[cfg(test)] +mod tests; pub use pallet::*; pub use traits::*; @@ -37,6 +39,7 @@ use pallet_authority_members::SessionIndex; use sp_distance::{InherentError, INHERENT_IDENTIFIER}; use sp_inherents::{InherentData, InherentIdentifier}; use sp_std::convert::TryInto; +use sp_std::prelude::*; type IdtyIndex = u32; @@ -66,6 +69,7 @@ pub mod pallet { + pallet_identity::Config<IdtyIndex = IdtyIndex> + pallet_session::Config { + /// Currency type used in this pallet (used for reserve/slash) type Currency: ReservableCurrency<Self::AccountId>; /// Amount reserved during evaluation #[pallet::constant] @@ -79,12 +83,14 @@ pub mod pallet { /// Minimum ratio of accessible referees #[pallet::constant] type MinAccessibleReferees: Get<Perbill>; - /// Number of session to keep a positive evaluation result - type ResultExpiration: Get<u32>; /// The overarching event type. type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// Type representing the weight of this pallet type WeightInfo: WeightInfo; + /// Handler for successful distance evaluation + type OnValidDistanceStatus: OnValidDistanceStatus<Self>; + /// Trait to check that distance evaluation request is allowed + type CheckRequestDistanceEvaluation: CheckRequestDistanceEvaluation<Self>; } // STORAGE // @@ -128,35 +134,20 @@ pub mod pallet { pub type EvaluationBlock<T: Config> = StorageValue<_, <T as frame_system::Config>::Hash, ValueQuery>; - /// Distance evaluation status by identity + /// Pending evaluation requesters /// - /// * `.0` is the account who requested an evaluation and reserved the price, + /// account who requested an evaluation and reserved the price, /// for whom the price will be unreserved or slashed when the evaluation completes. - /// * `.1` is the status of the evaluation. #[pallet::storage] - #[pallet::getter(fn identity_distance_status)] - pub type IdentityDistanceStatus<T: Config> = StorageMap< + #[pallet::getter(fn pending_evaluation_request)] + pub type PendingEvaluationRequest<T: Config> = StorageMap< _, Twox64Concat, <T as pallet_identity::Config>::IdtyIndex, - (<T as frame_system::Config>::AccountId, DistanceStatus), + <T as frame_system::Config>::AccountId, OptionQuery, >; - /// Identities by distance status expiration session index - #[pallet::storage] - #[pallet::getter(fn distance_status_expire_on)] - pub type DistanceStatusExpireOn<T: Config> = StorageMap< - _, - Twox64Concat, - u32, - BoundedVec< - <T as pallet_identity::Config>::IdtyIndex, - ConstU32<MAX_EVALUATIONS_PER_SESSION>, - >, - ValueQuery, - >; - /// Did evaluation get updated in this block? #[pallet::storage] pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>; @@ -176,13 +167,10 @@ pub mod pallet { idty_index: T::IdtyIndex, who: T::AccountId, }, - /// A distance evaluation was updated. - EvaluationUpdated { evaluator: T::AccountId }, - /// A distance status was forced. - EvaluationStatusForced { - idty_index: T::IdtyIndex, - status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>, - }, + /// Distance rule was found valid + EvaluatedValid { idty_index: T::IdtyIndex }, + /// Distance rule was found invalid + EvaluatedInvalid { idty_index: T::IdtyIndex }, } // ERRORS // @@ -198,13 +186,21 @@ pub mod pallet { /// No author for this block. NoAuthor, /// Caller has no identity. - NoIdentity, + CallerHasNoIdentity, + /// Caller identity not found. + CallerIdentityNotFound, + /// Caller not member. + CallerNotMember, + /// Target identity not found. + TargetIdentityNotFound, /// Evaluation queue is full. QueueFull, /// Too many evaluators in the current evaluation pool. TooManyEvaluators, /// Evaluation result has a wrong length. WrongResultLength, + /// Targeted distance evaluation request is only possible for an unvalidated identity + DistanceRequestOnlyAllowedForUnvalidated, } #[pallet::hooks] @@ -228,32 +224,45 @@ pub mod pallet { #[pallet::call] impl<T: Config> Pallet<T> { - /// Request an identity to be evaluated + /// Request caller identity to be evaluated + /// positive evaluation will result in claim/renew membership + /// negative evaluation will result in slash for caller #[pallet::call_index(0)] #[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation())] pub fn request_distance_evaluation(origin: OriginFor<T>) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let idty = - pallet_identity::IdentityIndexOf::<T>::get(&who).ok_or(Error::<T>::NoIdentity)?; + let idty = Self::check_request_distance_evaluation_self(&who)?; - ensure!( - IdentityDistanceStatus::<T>::get(idty) - != Some((who.clone(), DistanceStatus::Pending)), - Error::<T>::AlreadyInEvaluation - ); + Pallet::<T>::do_request_distance_evaluation(&who, idty)?; + Ok(().into()) + } - Pallet::<T>::do_request_distance_evaluation(who, idty)?; + /// Request target identity to be evaluated + /// only possible for unvalidated identity + #[pallet::call_index(4)] + #[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation_for())] + pub fn request_distance_evaluation_for( + origin: OriginFor<T>, + target: T::IdtyIndex, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + Self::check_request_distance_evaluation_for(&who, target)?; + + Pallet::<T>::do_request_distance_evaluation(&who, target)?; Ok(().into()) } /// (Inherent) Push an evaluation result to the pool + /// this is called internally by validators (= inherent) #[pallet::call_index(1)] #[pallet::weight(<T as pallet::Config>::WeightInfo::update_evaluation(MAX_EVALUATIONS_PER_SESSION))] pub fn update_evaluation( origin: OriginFor<T>, computation_result: ComputationResult, ) -> DispatchResult { + // no origin = inherent ensure_none(origin)?; ensure!( !DidUpdate::<T>::exists(), @@ -267,7 +276,8 @@ pub mod pallet { Ok(()) } - /// Push an evaluation result to the pool + /// Force push an evaluation result to the pool + // (it is convenient to have this call in end2end tests) #[pallet::call_index(2)] #[pallet::weight(<T as pallet::Config>::WeightInfo::force_update_evaluation(MAX_EVALUATIONS_PER_SESSION))] pub fn force_update_evaluation( @@ -280,60 +290,21 @@ pub mod pallet { Pallet::<T>::do_update_evaluation(evaluator, computation_result) } - /// Set the distance evaluation status of an identity - /// - /// Removes the status if `status` is `None`. - /// - /// * `status.0` is the account for whom the price will be unreserved or slashed - /// when the evaluation completes. - /// * `status.1` is the status of the evaluation. + /// Force set the distance evaluation status of an identity + // (it is convenient to have this in test network) #[pallet::call_index(3)] - #[pallet::weight(<T as pallet::Config>::WeightInfo::force_set_distance_status())] - pub fn force_set_distance_status( + #[pallet::weight(<T as pallet::Config>::WeightInfo::force_valid_distance_status())] + pub fn force_valid_distance_status( origin: OriginFor<T>, identity: <T as pallet_identity::Config>::IdtyIndex, - status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>, ) -> DispatchResult { ensure_root(origin)?; - IdentityDistanceStatus::<T>::set(identity, status.clone()); - DistanceStatusExpireOn::<T>::mutate( - pallet_session::CurrentIndex::<T>::get() + T::ResultExpiration::get(), - move |identities| { - identities - .try_push(identity) - .map_err(|_| Error::<T>::TooManyEvaluationsInBlock) - }, - )?; - Self::deposit_event(Event::EvaluationStatusForced { - idty_index: identity, - status, - }); + Self::do_valid_distance_status(identity); Ok(()) } } - // BENCHMARK FUNCTIONS // - - impl<T: Config> Pallet<T> { - /// Force the distance status using IdtyIndex and AccountId - /// only to prepare identity for benchmarking. - pub fn set_distance_status( - identity: <T as pallet_identity::Config>::IdtyIndex, - status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>, - ) -> DispatchResult { - IdentityDistanceStatus::<T>::set(identity, status); - DistanceStatusExpireOn::<T>::mutate( - pallet_session::CurrentIndex::<T>::get() + T::ResultExpiration::get(), - move |identities| { - identities - .try_push(identity) - .map_err(|_| Error::<T>::TooManyEvaluationsInBlock.into()) - }, - ) - } - } - // INTERNAL FUNCTIONS // impl<T: Config> Pallet<T> { @@ -398,55 +369,110 @@ pub mod pallet { } } + /// check that request distance evaluation is allowed + fn check_request_distance_evaluation_self( + who: &T::AccountId, + ) -> Result<<T as pallet_identity::Config>::IdtyIndex, DispatchError> { + // caller has an identity + let idty_index = pallet_identity::IdentityIndexOf::<T>::get(who) + .ok_or(Error::<T>::CallerHasNoIdentity)?; + Self::check_request_distance_evaluation_common(idty_index)?; + Ok(idty_index) + } + + /// check that targeted request distance evaluation is allowed + fn check_request_distance_evaluation_for( + who: &T::AccountId, + target: <T as pallet_identity::Config>::IdtyIndex, + ) -> Result<(), DispatchError> { + // caller has an identity + let caller_idty_index = pallet_identity::IdentityIndexOf::<T>::get(who) + .ok_or(Error::<T>::CallerHasNoIdentity)?; + let caller_idty = pallet_identity::Identities::<T>::get(caller_idty_index) + .ok_or(Error::<T>::CallerIdentityNotFound)?; + // caller is member + ensure!( + caller_idty.status == pallet_identity::IdtyStatus::Member, + Error::<T>::CallerNotMember + ); + // target has an identity + let target_idty = pallet_identity::Identities::<T>::get(target) + .ok_or(Error::<T>::TargetIdentityNotFound)?; + // target is unvalidated + ensure!( + target_idty.status == pallet_identity::IdtyStatus::Unvalidated, + Error::<T>::DistanceRequestOnlyAllowedForUnvalidated + ); + Self::check_request_distance_evaluation_common(target)?; + Ok(()) + } + + // common checks between check_request_distance_evaluation _self and _for + fn check_request_distance_evaluation_common( + target: <T as pallet_identity::Config>::IdtyIndex, + ) -> Result<(), DispatchError> { + // no pending evaluation request + ensure!( + PendingEvaluationRequest::<T>::get(target).is_none(), + Error::<T>::AlreadyInEvaluation + ); + // external validation + // target has received enough certifications + T::CheckRequestDistanceEvaluation::check_request_distance_evaluation(target) + } + + /// request distance evaluation in current pool fn do_request_distance_evaluation( - who: T::AccountId, + who: &T::AccountId, idty_index: <T as pallet_identity::Config>::IdtyIndex, ) -> Result<(), DispatchError> { Pallet::<T>::mutate_current_pool( - pallet_session::CurrentIndex::<T>::get(), + pallet_session::CurrentIndex::<T>::get(), // TODO look |current_pool| { + // TODO not needed if called in extrinsics only + // since extrinsics are transactional by default ensure!( current_pool.evaluations.len() < (MAX_EVALUATIONS_PER_SESSION as usize), Error::<T>::QueueFull ); - T::Currency::reserve(&who, <T as Config>::EvaluationPrice::get())?; + T::Currency::reserve(who, <T as Config>::EvaluationPrice::get())?; current_pool .evaluations .try_push((idty_index, median::MedianAcc::new())) .map_err(|_| Error::<T>::QueueFull)?; - IdentityDistanceStatus::<T>::insert( - idty_index, - (&who, DistanceStatus::Pending), - ); + PendingEvaluationRequest::<T>::insert(idty_index, who); - DistanceStatusExpireOn::<T>::mutate( - pallet_session::CurrentIndex::<T>::get() + T::ResultExpiration::get(), - move |identities| identities.try_push(idty_index).ok(), - ); - Self::deposit_event(Event::EvaluationRequested { idty_index, who }); + Self::deposit_event(Event::EvaluationRequested { + idty_index, + who: who.clone(), + }); Ok(()) }, ) } + /// update distance evaluation in next pool fn do_update_evaluation( evaluator: <T as frame_system::Config>::AccountId, computation_result: ComputationResult, ) -> DispatchResult { Pallet::<T>::mutate_next_pool(pallet_session::CurrentIndex::<T>::get(), |result_pool| { + // evaluation must be provided for all identities (no more, no less) ensure!( computation_result.distances.len() == result_pool.evaluations.len(), Error::<T>::WrongResultLength ); + // insert the evaluator if not already there if result_pool .evaluators .try_insert(evaluator.clone()) .map_err(|_| Error::<T>::TooManyEvaluators)? { + // update the median accumulator with the new result for (distance_value, (_identity, median_acc)) in computation_result .distances .into_iter() @@ -454,23 +480,28 @@ pub mod pallet { { median_acc.push(distance_value); } - - Self::deposit_event(Event::EvaluationUpdated { evaluator }); Ok(()) } else { + // one author can only submit one evaluation Err(Error::<T>::TooManyEvaluationsByAuthor.into()) } }) } + + /// Set the distance status using IdtyIndex and AccountId + pub fn do_valid_distance_status(idty: <T as pallet_identity::Config>::IdtyIndex) { + // callback + T::OnValidDistanceStatus::on_valid_distance_status(idty); + // deposit event + Self::deposit_event(Event::EvaluatedValid { idty_index: idty }); + } } impl<T: Config> pallet_authority_members::OnNewSession for Pallet<T> { fn on_new_session(index: SessionIndex) { + // set evaluation block EvaluationBlock::<T>::set(frame_system::Pallet::<T>::parent_hash()); - // Make results expire - DistanceStatusExpireOn::<T>::remove(index); - // Apply the results from the current pool (which was previous session's result pool) // We take the results so the pool is left empty for the new session. #[allow(clippy::type_complexity)] @@ -479,35 +510,48 @@ pub mod pallet { <T as pallet_identity::Config>::IdtyIndex, > = Pallet::<T>::take_current_pool(index); for (idty, median_acc) in current_pool.evaluations.into_iter() { + // distance result + let mut distance_result: Option<bool> = None; + + // get result of the computation if let Some(median_result) = median_acc.get_median() { let median = match median_result { MedianResult::One(m) => m, MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1) }; - IdentityDistanceStatus::<T>::mutate(idty, |entry| { - if let Some((account_id, status)) = entry.as_mut() { - if median >= T::MinAccessibleReferees::get() { - T::Currency::unreserve( - account_id, - <T as Config>::EvaluationPrice::get(), - ); - *status = DistanceStatus::Valid; - } else { - T::Currency::slash_reserved( - account_id, - <T as Config>::EvaluationPrice::get(), - ); - *status = DistanceStatus::Invalid; - } + // update distance result + distance_result = Some(median >= T::MinAccessibleReferees::get()); + } + + // take requester and perform unreserve or slash + if let Some(requester) = PendingEvaluationRequest::<T>::take(idty) { + match distance_result { + None => { + // no result, unreserve + T::Currency::unreserve( + &requester, + <T as Config>::EvaluationPrice::get(), + ); } - }); - } else if let Some((account_id, _status)) = IdentityDistanceStatus::<T>::take(idty) - { - <T as Config>::Currency::unreserve( - &account_id, - <T as Config>::EvaluationPrice::get(), - ); + Some(true) => { + // positive result, unreserve and apply + T::Currency::unreserve( + &requester, + <T as Config>::EvaluationPrice::get(), + ); + Self::do_valid_distance_status(idty); + } + Some(false) => { + // negative result, slash and deposit event + T::Currency::slash_reserved( + &requester, + <T as Config>::EvaluationPrice::get(), + ); + Self::deposit_event(Event::EvaluatedInvalid { idty_index: idty }); + } + } } + // if evaluation happened without request, it's ok to do nothing } } } diff --git a/pallets/distance/src/mock.rs b/pallets/distance/src/mock.rs index ef477a8c8fb954bf324b2b0936b092492ff05831..6861b3c43ad8a817ba9b04f521203a21d9f2bccd 100644 --- a/pallets/distance/src/mock.rs +++ b/pallets/distance/src/mock.rs @@ -19,7 +19,7 @@ use crate::{self as pallet_distance}; use core::marker::PhantomData; use frame_support::{ parameter_types, - traits::{Everything, GenesisBuild}, + traits::{Everything, GenesisBuild, OnFinalize, OnInitialize}, }; use frame_system as system; use pallet_balances::AccountData; @@ -256,9 +256,10 @@ impl pallet_distance::Config for Test { type EvaluationPrice = frame_support::traits::ConstU64<1000>; type MaxRefereeDistance = frame_support::traits::ConstU32<5>; type MinAccessibleReferees = MinAccessibleReferees; - type ResultExpiration = frame_support::traits::ConstU32<720>; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); + type OnValidDistanceStatus = (); + type CheckRequestDistanceEvaluation = (); } // Build genesis storage according to the mock runtime. @@ -290,3 +291,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities { sp_io::TestExternalities::new(t) } + +pub fn run_to_block(n: u64) { + while System::block_number() < n { + Session::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::reset_events(); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Session::on_initialize(System::block_number()); + } +} diff --git a/pallets/distance/src/tests.rs b/pallets/distance/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..b1be3c04a488d49496992b8166235d9229554261 --- /dev/null +++ b/pallets/distance/src/tests.rs @@ -0,0 +1,41 @@ +// Copyright 2023 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. + +use crate::mock::*; +use crate::*; +use frame_support::assert_ok; +use frame_support::traits::Currency; + +#[test] +fn test_request_distance_evaluation() { + new_test_ext().execute_with(|| { + run_to_block(1); + // give enough for reserve + Balances::make_free_balance_be(&1, 10_000); + + // call request + assert_ok!(Distance::request_distance_evaluation( + RuntimeOrigin::signed(1) + )); + System::assert_has_event(RuntimeEvent::Distance(Event::EvaluationRequested { + idty_index: 1, + who: 1, + })); + + // currency was reserved + assert_eq!(Balances::reserved_balance(1), 1000); + }); +} diff --git a/pallets/distance/src/traits.rs b/pallets/distance/src/traits.rs index de9d532d64f4ce64a16fd20fab28455523f18e69..6849d9c250c29421bffb67f4d52c0cf626271a59 100644 --- a/pallets/distance/src/traits.rs +++ b/pallets/distance/src/traits.rs @@ -14,8 +14,24 @@ // You should have received a copy of the GNU Affero General Public License // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. -pub trait HandleNegativeEvaluation<T: crate::Config> { - /// Do something with the reserved amount on the account, - /// when distance evaluation result is negative. - fn handle_negative_evaluation(account_id: T::AccountId); +use crate::*; +use frame_support::pallet_prelude::*; + +pub trait OnValidDistanceStatus<T: Config> { + /// Handler for valid distance evaluation + fn on_valid_distance_status(idty_index: T::IdtyIndex); +} + +impl<T: Config> OnValidDistanceStatus<T> for () { + fn on_valid_distance_status(_idty_index: T::IdtyIndex) {} +} + +pub trait CheckRequestDistanceEvaluation<T: Config> { + fn check_request_distance_evaluation(idty_index: T::IdtyIndex) -> Result<(), DispatchError>; +} + +impl<T: Config> CheckRequestDistanceEvaluation<T> for () { + fn check_request_distance_evaluation(_idty_index: T::IdtyIndex) -> Result<(), DispatchError> { + Ok(()) + } } diff --git a/pallets/distance/src/weights.rs b/pallets/distance/src/weights.rs index b0808361a6b8d1810b51c159390fe3d8d640c268..0e8f34c45944c330c446ce4ac047c395f157702e 100644 --- a/pallets/distance/src/weights.rs +++ b/pallets/distance/src/weights.rs @@ -20,102 +20,82 @@ use frame_support::weights::{constants::RocksDbWeight, Weight}; pub trait WeightInfo { fn request_distance_evaluation() -> Weight; + fn request_distance_evaluation_for() -> Weight; fn update_evaluation(i: u32) -> Weight; fn force_update_evaluation(i: u32) -> Weight; - fn force_set_distance_status() -> Weight; + fn force_valid_distance_status() -> Weight; fn on_finalize() -> Weight; } // Insecure weights implementation, use it for tests only! impl WeightInfo for () { - /// Storage: Identity IdentityIndexOf (r:1 w:0) - /// Proof Skipped: Identity IdentityIndexOf (max_values: None, max_size: None, mode: Measured) - /// Storage: Distance IdentityDistanceStatus (r:1 w:1) - /// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Distance EvaluationPool2 (r:1 w:1) - /// Proof Skipped: Distance EvaluationPool2 (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(121), added: 2596, mode: MaxEncodedLen) - /// Storage: Distance DistanceStatusExpireOn (r:1 w:1) - /// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured) fn request_distance_evaluation() -> Weight { // Proof Size summary in bytes: - // Measured: `935` - // Estimated: `4400` - // Minimum execution time: 28_469_000 picoseconds. - Weight::from_parts(30_905_000, 0) - .saturating_add(Weight::from_parts(0, 4400)) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(4)) + // Measured: `1280` + // Estimated: `4745` + // Minimum execution time: 876_053_000 picoseconds. + Weight::from_parts(898_445_000, 0) + .saturating_add(Weight::from_parts(0, 4745)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + + fn request_distance_evaluation_for() -> Weight { + // Proof Size summary in bytes: + // Measured: `1485` + // Estimated: `7425` + // Minimum execution time: 1_118_982_000 picoseconds. + Weight::from_parts(1_292_782_000, 0) + .saturating_add(Weight::from_parts(0, 7425)) + .saturating_add(RocksDbWeight::get().reads(10)) + .saturating_add(RocksDbWeight::get().writes(3)) } - /// Storage: Distance DidUpdate (r:1 w:1) - /// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Authorship Author (r:1 w:1) - /// Proof: Authorship Author (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) - /// Storage: System Digest (r:1 w:0) - /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Distance EvaluationPool0 (r:1 w:1) - /// Proof Skipped: Distance EvaluationPool0 (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `i` is `[1, 600]`. + fn update_evaluation(i: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `744 + i * (10 ±0)` - // Estimated: `2228 + i * (10 ±0)` - // Minimum execution time: 13_870_000 picoseconds. - Weight::from_parts(17_116_748, 0) - .saturating_add(Weight::from_parts(0, 2228)) - // Standard Error: 684 - .saturating_add(Weight::from_parts(128_989, 0).saturating_mul(i.into())) - .saturating_add(RocksDbWeight::get().reads(5)) + // Measured: `773 + i * (10 ±0)` + // Estimated: `2256 + i * (10 ±0)` + // Minimum execution time: 463_878_000 picoseconds. + Weight::from_parts(743_823_548, 0) + .saturating_add(Weight::from_parts(0, 2256)) + // Standard Error: 292_144 + .saturating_add(Weight::from_parts(1_326_639, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into())) } - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Distance EvaluationPool0 (r:1 w:1) - /// Proof Skipped: Distance EvaluationPool0 (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `i` is `[1, 600]`. + fn force_update_evaluation(i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `612 + i * (10 ±0)` - // Estimated: `2096 + i * (10 ±0)` - // Minimum execution time: 8_392_000 picoseconds. - Weight::from_parts(10_825_908, 0) - .saturating_add(Weight::from_parts(0, 2096)) - // Standard Error: 326 - .saturating_add(Weight::from_parts(123_200, 0).saturating_mul(i.into())) + // Estimated: `2095 + i * (10 ±0)` + // Minimum execution time: 208_812_000 picoseconds. + Weight::from_parts(257_150_521, 0) + .saturating_add(Weight::from_parts(0, 2095)) + // Standard Error: 53_366 + .saturating_add(Weight::from_parts(1_841_329, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into())) } - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Distance DistanceStatusExpireOn (r:1 w:1) - /// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured) - /// Storage: Distance IdentityDistanceStatus (r:0 w:1) - /// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured) - fn force_set_distance_status() -> Weight { + + fn force_valid_distance_status() -> Weight { // Proof Size summary in bytes: - // Measured: `586` - // Estimated: `4051` - // Minimum execution time: 8_099_000 picoseconds. - Weight::from_parts(8_786_000, 0) - .saturating_add(Weight::from_parts(0, 4051)) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Measured: `1181` + // Estimated: `7121` + // Minimum execution time: 873_892_000 picoseconds. + Weight::from_parts(1_081_510_000, 0) + .saturating_add(Weight::from_parts(0, 7121)) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(5)) } - /// Storage: Distance DidUpdate (r:1 w:1) - /// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured) + fn on_finalize() -> Weight { // Proof Size summary in bytes: // Measured: `170` // Estimated: `1655` - // Minimum execution time: 3_904_000 picoseconds. - Weight::from_parts(4_132_000, 0) + // Minimum execution time: 93_595_000 picoseconds. + Weight::from_parts(109_467_000, 0) .saturating_add(Weight::from_parts(0, 1655)) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) diff --git a/pallets/duniter-test-parameters/src/lib.rs b/pallets/duniter-test-parameters/src/lib.rs index 4da26974ba7202c4dce8476d415e965d54276878..55ca1c612e69e681ef2f76bb877b92f209cca87a 100644 --- a/pallets/duniter-test-parameters/src/lib.rs +++ b/pallets/duniter-test-parameters/src/lib.rs @@ -45,7 +45,7 @@ pub mod types { pub idty_confirm_period: BlockNumber, pub idty_creation_period: BlockNumber, pub membership_period: BlockNumber, - pub pending_membership_period: BlockNumber, + pub membership_renewal_period: BlockNumber, pub ud_creation_period: PeriodCount, pub ud_reeval_period: PeriodCount, pub smith_cert_max_by_issuer: CertCount, diff --git a/pallets/duniter-wot/src/lib.rs b/pallets/duniter-wot/src/lib.rs index eb127c0cc0979c1064910b791c89d70df89f44b5..079467a3f8f2d3c3c5c139bfa666d9b46668f428 100644 --- a/pallets/duniter-wot/src/lib.rs +++ b/pallets/duniter-wot/src/lib.rs @@ -30,8 +30,6 @@ mod benchmarking;*/ pub use pallet::*; -use traits::*; - use frame_support::pallet_prelude::*; use pallet_certification::traits::SetNextIssuableOn; use pallet_identity::{IdtyEvent, IdtyStatus}; @@ -61,8 +59,6 @@ pub mod pallet { + pallet_identity::Config<IdtyIndex = IdtyIndex> + pallet_membership::Config<IdtyId = IdtyIndex> { - /// Distance evaluation provider - type IsDistanceOk: IsDistanceOk<IdtyIndex>; #[pallet::constant] type FirstIssuableOn: Get<Self::BlockNumber>; #[pallet::constant] @@ -97,8 +93,8 @@ pub mod pallet { DistanceEvaluationPending, /// Distance evaluation has not been requested DistanceEvaluationNotRequested, - /// Identity is not allowed to request membership. - IdtyNotAllowedToRequestMembership, + /// Identity is not allowed to claim membership. + IdtyNotAllowedToClaimMembership, /// Identity not allowed to renew membership. IdtyNotAllowedToRenewMembership, /// Identity creation period not respected. @@ -119,6 +115,10 @@ pub mod pallet { CertToRevoked, /// Issuer or receiver not found. IdtyNotFound, + /// Not enough certs received to request distance evaluation. + NotEnoughCertsReceivedToRequestDistanceEvaluation, + /// Membership can only be renewed after an antispam delay + MembershipRenewalPeriodNotRespected, } } @@ -191,26 +191,34 @@ impl<T: Config> pallet_certification::traits::CheckCertAllowed<IdtyIndex> for Pa impl<T: Config> sp_membership::traits::CheckMembershipCallAllowed<IdtyIndex> for Pallet<T> { // membership claim is only possible when enough certs are received (both wots) and distance is ok fn check_idty_allowed_to_claim_membership(idty_index: &IdtyIndex) -> Result<(), DispatchError> { + // check identity status + let idty_value = + pallet_identity::Pallet::<T>::identity(idty_index).ok_or(Error::<T>::IdtyNotFound)?; + ensure!( + idty_value.status == IdtyStatus::Unvalidated + || idty_value.status == IdtyStatus::NotMember, + Error::<T>::IdtyNotAllowedToClaimMembership + ); + // check received certifications + // TODO deduplicate let idty_cert_meta = pallet_certification::Pallet::<T>::idty_cert_meta(idty_index); ensure!( idty_cert_meta.received_count >= T::MinCertForMembership::get(), Error::<T>::NotEnoughCertsToClaimMembership ); - T::IsDistanceOk::is_distance_ok(idty_index)?; Ok(()) } // membership renewal is only possible when identity is member (otherwise it should claim again) fn check_idty_allowed_to_renew_membership(idty_index: &IdtyIndex) -> Result<(), DispatchError> { - if let Some(idty_value) = pallet_identity::Pallet::<T>::identity(idty_index) { - ensure!( - idty_value.status == IdtyStatus::Member, - Error::<T>::IdtyNotAllowedToRenewMembership - ); - T::IsDistanceOk::is_distance_ok(idty_index)?; - } else { - return Err(Error::<T>::IdtyNotFound.into()); - } + // check identity status + let idty_value = + pallet_identity::Pallet::<T>::identity(idty_index).ok_or(Error::<T>::IdtyNotFound)?; + ensure!( + idty_value.status == IdtyStatus::Member, + Error::<T>::IdtyNotAllowedToRenewMembership + ); + // no need to check certification count since loosing certifications make membership expire Ok(()) } } @@ -321,3 +329,86 @@ impl<T: Config> pallet_certification::traits::OnRemovedCert<IdtyIndex> for Palle } } } + +/// valid distance status handler +impl<T: Config + pallet_distance::Config> pallet_distance::traits::OnValidDistanceStatus<T> + for Pallet<T> +{ + fn on_valid_distance_status(idty_index: IdtyIndex) { + if let Some(identity) = pallet_identity::Identities::<T>::get(idty_index) { + match identity.status { + IdtyStatus::Unconfirmed | IdtyStatus::Revoked => { + // IdtyStatus::Unconfirmed + // distance evaluation request should never happen for unconfirmed identity + // IdtyStatus::Revoked + // the identity can have been revoked during distance evaluation by the oracle + } + + IdtyStatus::Unvalidated | IdtyStatus::NotMember => { + // IdtyStatus::Unvalidated + // normal scenario for first entry + // IdtyStatus::NotMember + // normal scenario for re-entry + // the following can fail if a certification expired during distance evaluation + // otherwise it should succeed + let _ = pallet_membership::Pallet::<T>::try_add_membership(idty_index); + // sp_std::if_std! { + // if let Err(e) = r { + // print!("failed to claim identity when distance status was found ok: "); + // println!("{:?}", idty_index); + // println!("reason: {:?}", e); + // } + // } + } + IdtyStatus::Member => { + // IdtyStatus::Member + // normal scenario for renewal + // should succeed + let _ = pallet_membership::Pallet::<T>::try_renew_membership(idty_index); + // sp_std::if_std! { + // if let Err(e) = r { + // print!("failed to renew identity when distance status was found ok: "); + // println!("{:?}", idty_index); + // println!("reason: {:?}", e); + // } + // } + } + } + } else { + // identity was removed before distance status was found + // so it's ok to do nothing + sp_std::if_std! { + println!("identity was removed before distance status was found: {:?}", idty_index); + } + } + } +} + +/// distance evaluation request allowed check +impl<T: Config + pallet_distance::Config> pallet_distance::traits::CheckRequestDistanceEvaluation<T> + for Pallet<T> +{ + fn check_request_distance_evaluation(idty_index: IdtyIndex) -> Result<(), DispatchError> { + // check membership renewal antispam + let maybe_membership_data = pallet_membership::Pallet::<T>::membership(idty_index); + if let Some(membership_data) = maybe_membership_data { + // if membership data exists, this is for a renewal, apply antispam + ensure!( + // current_block > expiration block - membership period + renewal period + membership_data.expire_on + + <T as pallet_membership::Config>::MembershipRenewalPeriod::get() + < frame_system::Pallet::<T>::block_number() + + <T as pallet_membership::Config>::MembershipPeriod::get(), + Error::<T>::MembershipRenewalPeriodNotRespected + ); + }; + // check cert count + // TODO deduplicate + let idty_cert_meta = pallet_certification::Pallet::<T>::idty_cert_meta(idty_index); + ensure!( + idty_cert_meta.received_count >= T::MinCertForMembership::get(), + Error::<T>::NotEnoughCertsReceivedToRequestDistanceEvaluation + ); + Ok(()) + } +} diff --git a/pallets/duniter-wot/src/mock.rs b/pallets/duniter-wot/src/mock.rs index c043ef76448f72bcf18013fd168b8d9c232c1bee..d2679e1b92185cd66f0f979401b2bcc0d2063b26 100644 --- a/pallets/duniter-wot/src/mock.rs +++ b/pallets/duniter-wot/src/mock.rs @@ -48,7 +48,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, DuniterWot: pallet_duniter_wot::{Pallet}, Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>}, - Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>}, + Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>}, Cert: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>}, } ); @@ -97,7 +97,6 @@ impl pallet_duniter_wot::Config for Test { type MinCertForMembership = MinCertForMembership; type MinCertForCreateIdtyRight = MinCertForCreateIdtyRight; type FirstIssuableOn = FirstIssuableOn; - type IsDistanceOk = crate::traits::DistanceAlwaysOk; } // Identity @@ -139,6 +138,7 @@ impl pallet_identity::Config for Test { // Membership parameter_types! { pub const MembershipPeriod: u64 = 8; + pub const MembershipRenewalPeriod: u64 = 2; } impl pallet_membership::Config for Test { @@ -147,6 +147,7 @@ impl pallet_membership::Config for Test { type IdtyIdOf = IdentityIndexOf<Self>; type AccountIdOf = (); type MembershipPeriod = MembershipPeriod; + type MembershipRenewalPeriod = MembershipRenewalPeriod; type OnEvent = DuniterWot; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/duniter-wot/src/tests.rs b/pallets/duniter-wot/src/tests.rs index 2b86ee97f6dd1258126a035a3ca166a95b13eb69..fc4f4df0ed1b2543a1028fbb7ca50bd7a9a5e648 100644 --- a/pallets/duniter-wot/src/tests.rs +++ b/pallets/duniter-wot/src/tests.rs @@ -123,7 +123,7 @@ fn test_new_idty_validation() { // Ferdie should be able to claim membership run_to_block(5); - assert_ok!(Membership::claim_membership(RuntimeOrigin::signed(6)),); + assert_ok!(Membership::try_add_membership(6)); System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipAdded { member: 6, @@ -242,9 +242,9 @@ fn test_idty_membership_expire() { run_to_block(4); // Alice renews her membership - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); + assert_ok!(Membership::try_renew_membership(1)); // Bob renews his membership - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); + assert_ok!(Membership::try_renew_membership(2)); run_to_block(5); // renew certifications so that Alice can still issue cert at block 22 @@ -253,7 +253,7 @@ fn test_idty_membership_expire() { // Charlie's membership should expire at block #8 run_to_block(8); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); + assert_ok!(Membership::try_renew_membership(1)); assert!(Membership::membership(3).is_none()); System::assert_has_event(RuntimeEvent::Membership( @@ -276,7 +276,7 @@ fn test_idty_membership_expire() { // 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))); + assert_ok!(Membership::try_renew_membership(1)); // Charlie's identity should be auto-revoked at block #11 (8 + 3) System::assert_has_event(RuntimeEvent::Identity( pallet_identity::Event::IdtyRevoked { @@ -356,15 +356,15 @@ fn test_certification_expire() { assert_ok!(Cert::add_cert(RuntimeOrigin::signed(3), 3, 2)); // --- BLOCK 7 --- run_to_block(7); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); + assert_ok!(Membership::try_renew_membership(1)); + assert_ok!(Membership::try_renew_membership(2)); + assert_ok!(Membership::try_renew_membership(3)); // --- BLOCK 14 --- run_to_block(14); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); + assert_ok!(Membership::try_renew_membership(1)); + assert_ok!(Membership::try_renew_membership(2)); + assert_ok!(Membership::try_renew_membership(3)); // normal cert Bob → Alice expires at block 20 run_to_block(20); @@ -387,18 +387,18 @@ fn test_certification_expire() { // --- BLOCK 21 --- // Bob and Charlie can renew their membership run_to_block(21); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); + assert_ok!(Membership::try_renew_membership(2)); + assert_ok!(Membership::try_renew_membership(3)); // Alice can not renew her membership which does not exist assert_noop!( - Membership::renew_membership(RuntimeOrigin::signed(1)), + Membership::try_renew_membership(1), pallet_membership::Error::<Test>::MembershipNotFound ); // Alice can not claim her membership because she does not have enough certifications assert_noop!( - Membership::claim_membership(RuntimeOrigin::signed(1)), + Membership::try_add_membership(1), pallet_duniter_wot::Error::<Test>::NotEnoughCertsToClaimMembership ); @@ -436,16 +436,16 @@ fn test_cert_can_not_be_issued() { assert_ok!(Cert::add_cert(RuntimeOrigin::signed(4), 4, 3)); // +20 // --- BLOCK 7 --- run_to_block(7); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); // + 8 - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); // + 8 - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); // + 8 - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(4))); // + 8 + assert_ok!(Membership::try_renew_membership(1)); // + 8 + assert_ok!(Membership::try_renew_membership(2)); // + 8 + assert_ok!(Membership::try_renew_membership(3)); // + 8 + assert_ok!(Membership::try_renew_membership(4)); // + 8 run_to_block(14); - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); // + 8 - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); // + 8 - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); // + 8 - assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(4))); // + 8 + assert_ok!(Membership::try_renew_membership(1)); // + 8 + assert_ok!(Membership::try_renew_membership(2)); // + 8 + assert_ok!(Membership::try_renew_membership(3)); // + 8 + assert_ok!(Membership::try_renew_membership(4)); // + 8 run_to_block(20); // println!("{:?}", System::events()); diff --git a/pallets/duniter-wot/src/traits.rs b/pallets/duniter-wot/src/traits.rs index 6934ce454bdf2c1cbc318158ba8647671804dca7..f3474822b34656f1c99a9b12d34a548316c87941 100644 --- a/pallets/duniter-wot/src/traits.rs +++ b/pallets/duniter-wot/src/traits.rs @@ -13,17 +13,3 @@ // // You should have received a copy of the GNU Affero General Public License // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. - -use crate::DispatchError; - -pub trait IsDistanceOk<IdtyId> { - fn is_distance_ok(idty_id: &IdtyId) -> Result<(), DispatchError>; -} - -pub struct DistanceAlwaysOk; - -impl<IdtyId> IsDistanceOk<IdtyId> for DistanceAlwaysOk { - fn is_distance_ok(_idty_id: &IdtyId) -> Result<(), DispatchError> { - Ok(()) - } -} diff --git a/pallets/membership/src/benchmarking.rs b/pallets/membership/src/benchmarking.rs index 1bdf5edf6c75666c3c2fc1acd901d2b3ad642733..84216da894acd5049049f33b48ec40bf6d756317 100644 --- a/pallets/membership/src/benchmarking.rs +++ b/pallets/membership/src/benchmarking.rs @@ -20,17 +20,15 @@ use super::*; use frame_benchmarking::benchmarks; use frame_system::pallet_prelude::BlockNumberFor; -use frame_system::RawOrigin; -use sp_runtime::traits::{Convert, One}; #[cfg(test)] use maplit::btreemap; use crate::Pallet; -fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { - frame_system::Pallet::<T>::assert_has_event(generic_event.into()); -} +// fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { +// frame_system::Pallet::<T>::assert_has_event(generic_event.into()); +// } benchmarks! { where_clause { @@ -39,39 +37,7 @@ benchmarks! { <T as frame_system::Config>::BlockNumber: From<u32>, } - // claim membership - claim_membership { - let idty: T::IdtyId = 3.into(); - Membership::<T>::take(idty); - let caller: T::AccountId = T::AccountIdOf::convert(idty).unwrap(); - let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); - T::BenchmarkSetupHandler::force_status_ok(&idty, &caller); - }: _<T::RuntimeOrigin>(caller_origin) - verify { - assert_has_event::<T>(Event::<T>::MembershipAdded{member: idty, expire_on: BlockNumberFor::<T>::one() + T::MembershipPeriod::get()}.into()); - } - - // renew membership - renew_membership { - let idty: T::IdtyId = 3.into(); - let caller: T::AccountId = T::AccountIdOf::convert(idty).unwrap(); - let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); - T::BenchmarkSetupHandler::force_status_ok(&idty, &caller); - }: _<T::RuntimeOrigin>(caller_origin) - verify { - assert_has_event::<T>(Event::<T>::MembershipAdded{member: idty, expire_on: BlockNumberFor::<T>::one() + T::MembershipPeriod::get()}.into()); - } - - // revoke membership - revoke_membership { - let idty: T::IdtyId = 3.into(); - let caller: T::AccountId = T::AccountIdOf::convert(idty).unwrap(); - let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); - frame_system::pallet::Pallet::<T>::set_block_number(10_000_000.into()); // Arbitrarily high, to be in the worst case of wot instance. - }: _<T::RuntimeOrigin>(caller_origin) - verify { - assert_has_event::<T>(Event::<T>::MembershipRemoved{member: idty, reason: MembershipRemovalReason::Revoked}.into()); - } + // TODO membership add and renewal should be included to distance on_new_session as worst case scenario // Base weight of an empty initialize on_initialize { @@ -98,7 +64,7 @@ benchmarks! { impl_benchmark_test_suite!( Pallet, - crate::mock::new_test_ext(crate::mock::DefaultMembershipConfig { + crate::mock::new_test_ext(crate::mock::MembershipConfig { memberships: btreemap![ 3 => MembershipData { expire_on: 3, diff --git a/pallets/membership/src/lib.rs b/pallets/membership/src/lib.rs index cba39e37ee5abb5fc058fcdea588e74f7dced747..38f1c491c231635abb6bb6c1777b432275cd9882 100644 --- a/pallets/membership/src/lib.rs +++ b/pallets/membership/src/lib.rs @@ -32,9 +32,7 @@ pub use pallet::*; pub use weights::WeightInfo; use frame_support::dispatch::Weight; -use frame_support::error::BadOrigin; use frame_support::pallet_prelude::*; -use frame_system::RawOrigin; use sp_membership::traits::*; use sp_membership::MembershipData; use sp_runtime::traits::Zero; @@ -44,13 +42,13 @@ use std::collections::BTreeMap; #[cfg(feature = "runtime-benchmarks")] pub trait SetupBenchmark<IdtyId, AccountId> { - fn force_status_ok(idty_index: &IdtyId, account: &AccountId); + fn force_valid_distance_status(idty_index: &IdtyId); fn add_cert(_issuer: &IdtyId, _receiver: &IdtyId); } #[cfg(feature = "runtime-benchmarks")] impl<IdtyId, AccountId> SetupBenchmark<IdtyId, AccountId> for () { - fn force_status_ok(_idty_id: &IdtyId, _account: &AccountId) {} + fn force_valid_distance_status(_idty_id: &IdtyId) {} fn add_cert(_issuer: &IdtyId, _receiver: &IdtyId) {} } @@ -94,8 +92,12 @@ pub mod pallet { /// Something that gives the AccountId of an IdtyId type AccountIdOf: Convert<Self::IdtyId, Option<Self::AccountId>>; #[pallet::constant] - /// Maximum life span of a non-renewable membership (in number of blocks) + /// Maximum life span of a single membership (in number of blocks) + // TODO this could be renamed "validity" or "duration" type MembershipPeriod: Get<Self::BlockNumber>; + /// Minimum delay to wait before renewing membership + // i.e. asking for distance evaluation + type MembershipRenewalPeriod: Get<Self::BlockNumber>; /// On event handler type OnEvent: OnEvent<Self::IdtyId>; /// Because this pallet emits events, it depends on the runtime's definition of an event. @@ -156,6 +158,11 @@ pub mod pallet { member: T::IdtyId, expire_on: BlockNumberFor<T>, }, + /// A membership was renewed. + MembershipRenewed { + member: T::IdtyId, + expire_on: BlockNumberFor<T>, + }, /// A membership was removed. MembershipRemoved { member: T::IdtyId, @@ -173,6 +180,8 @@ pub mod pallet { MembershipAlreadyAcquired, /// Membership not found. MembershipNotFound, + /// Already member, can not claim membership + AlreadyMember, } // HOOKS // @@ -188,61 +197,13 @@ pub mod pallet { } } - // CALLS // - - #[pallet::call] - impl<T: Config> Pallet<T> { - /// claim membership - /// it must fullfill the requirements (certs, distance) - /// TODO #159 for main wot claim_membership is called automatically when distance is evaluated positively - /// for smith wot, it means joining the authority members - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::claim_membership())] - pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo { - // get identity - let idty_id = Self::get_idty_id(origin)?; - - Self::check_allowed_to_claim(idty_id)?; - Self::do_add_membership(idty_id); - Ok(().into()) - } - - /// extend the validity period of an active membership - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::renew_membership())] - pub fn renew_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo { - // Verify phase - let idty_id = Self::get_idty_id(origin)?; - let membership_data = - Membership::<T>::get(idty_id).ok_or(Error::<T>::MembershipNotFound)?; - - T::CheckMembershipCallAllowed::check_idty_allowed_to_renew_membership(&idty_id)?; - - // apply phase - Self::unschedule_membership_expiry(idty_id, membership_data.expire_on); - Self::insert_membership_and_schedule_expiry(idty_id); - T::OnEvent::on_event(&sp_membership::Event::MembershipRenewed(idty_id)); - - Ok(().into()) - } - - /// revoke an active membership - /// (only available for sub wot, automatic for main wot) - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::revoke_membership())] - pub fn revoke_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo { - // Verify phase - let idty_id = Self::get_idty_id(origin)?; - - // Apply phase - Self::do_remove_membership(idty_id, MembershipRemovalReason::Revoked); - - Ok(().into()) - } - } + // // CALLS // + // #[pallet::call] + // impl<T: Config> Pallet<T> { + // // no calls for membership pallet + // } // INTERNAL FUNCTIONS // - impl<T: Config> Pallet<T> { /// unschedule membership expiry fn unschedule_membership_expiry(idty_id: T::IdtyId, block_number: T::BlockNumber) { @@ -254,31 +215,78 @@ pub mod pallet { } } /// schedule membership expiry - fn insert_membership_and_schedule_expiry(idty_id: T::IdtyId) { + fn insert_membership_and_schedule_expiry(idty_id: T::IdtyId) -> T::BlockNumber { let block_number = frame_system::pallet::Pallet::<T>::block_number(); let expire_on = block_number + T::MembershipPeriod::get(); Membership::<T>::insert(idty_id, MembershipData { expire_on }); MembershipsExpireOn::<T>::append(expire_on, idty_id); - Self::deposit_event(Event::MembershipAdded { - member: idty_id, - expire_on, - }); + expire_on } /// check that membership can be claimed pub fn check_allowed_to_claim(idty_id: T::IdtyId) -> Result<(), DispatchError> { + // no-op is error + ensure!( + Membership::<T>::get(idty_id).is_none(), + Error::<T>::AlreadyMember + ); + // enough certifications and distance rule for example T::CheckMembershipCallAllowed::check_idty_allowed_to_claim_membership(&idty_id)?; Ok(()) } + /// check that membership can be renewed + pub fn check_allowed_to_renew( + idty_id: T::IdtyId, + ) -> Result<MembershipData<T::BlockNumber>, DispatchError> { + let membership_data = + Membership::<T>::get(idty_id).ok_or(Error::<T>::MembershipNotFound)?; + + // enough certifications and distance rule for example + T::CheckMembershipCallAllowed::check_idty_allowed_to_renew_membership(&idty_id)?; + Ok(membership_data) + } + + /// try claim membership + pub fn try_add_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> { + Self::check_allowed_to_claim(idty_id)?; + Self::do_add_membership(idty_id); + Ok(()) + } + + /// try renew membership + pub fn try_renew_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> { + let membership_data = Self::check_allowed_to_renew(idty_id)?; + Self::do_renew_membership(idty_id, membership_data); + Ok(()) + } + /// perform membership addition fn do_add_membership(idty_id: T::IdtyId) { - Self::insert_membership_and_schedule_expiry(idty_id); + let expire_on = Self::insert_membership_and_schedule_expiry(idty_id); + Self::deposit_event(Event::MembershipAdded { + member: idty_id, + expire_on, + }); T::OnEvent::on_event(&sp_membership::Event::MembershipAdded(idty_id)); } + /// perform membership renewal + fn do_renew_membership( + idty_id: T::IdtyId, + membership_data: MembershipData<T::BlockNumber>, + ) { + Self::unschedule_membership_expiry(idty_id, membership_data.expire_on); + let expire_on = Self::insert_membership_and_schedule_expiry(idty_id); + Self::deposit_event(Event::MembershipRenewed { + member: idty_id, + expire_on, + }); + T::OnEvent::on_event(&sp_membership::Event::MembershipRenewed(idty_id)); + } + /// perform membership removal pub fn do_remove_membership(idty_id: T::IdtyId, reason: MembershipRemovalReason) { if let Some(membership_data) = Membership::<T>::take(idty_id) { @@ -291,15 +299,6 @@ pub mod pallet { } } - /// check the origin and get identity id if valid - 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>::IdtyIdNotFound.into()) - } else { - 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; diff --git a/pallets/membership/src/mock.rs b/pallets/membership/src/mock.rs index 5173ff716833722a3269e9c65bc5a04e721fbaab..a13325a0c29ce3e32fa7d16cfc5af80de48f06e4 100644 --- a/pallets/membership/src/mock.rs +++ b/pallets/membership/src/mock.rs @@ -41,7 +41,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, - DefaultMembership: pallet_membership::{Pallet, Call, Event<T>, Storage, Config<T>}, + Membership: pallet_membership::{Pallet, Event<T>, Storage, Config<T>}, } ); @@ -79,7 +79,7 @@ impl system::Config for Test { parameter_types! { pub const MembershipPeriod: BlockNumber = 5; - pub const PendingMembershipPeriod: BlockNumber = 3; + pub const MembershipRenewalPeriod: BlockNumber = 2; } impl pallet_membership::Config for Test { @@ -88,6 +88,7 @@ impl pallet_membership::Config for Test { type IdtyIdOf = ConvertInto; type AccountIdOf = ConvertInto; type MembershipPeriod = MembershipPeriod; + type MembershipRenewalPeriod = MembershipRenewalPeriod; type OnEvent = (); type RuntimeEvent = RuntimeEvent; type WeightInfo = (); @@ -99,7 +100,7 @@ impl pallet_membership::Config for Test { pub fn new_test_ext(gen_conf: pallet_membership::GenesisConfig<Test>) -> sp_io::TestExternalities { GenesisConfig { system: SystemConfig::default(), - default_membership: gen_conf, + membership: gen_conf, } .build_storage() .unwrap() @@ -108,11 +109,11 @@ pub fn new_test_ext(gen_conf: pallet_membership::GenesisConfig<Test>) -> sp_io:: pub fn run_to_block(n: u64) { while System::block_number() < n { - DefaultMembership::on_finalize(System::block_number()); + Membership::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::reset_events(); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); - DefaultMembership::on_initialize(System::block_number()); + Membership::on_initialize(System::block_number()); } } diff --git a/pallets/membership/src/tests.rs b/pallets/membership/src/tests.rs index 53da60336bb6cefa94a5897836a38871929f7503..4f22c32135af2cd04f215261307b0ad5278dfba2 100644 --- a/pallets/membership/src/tests.rs +++ b/pallets/membership/src/tests.rs @@ -22,11 +22,8 @@ use maplit::btreemap; use sp_membership::traits::*; use sp_membership::MembershipData; -// alias -type RtEvent = RuntimeEvent; - -fn default_gen_conf() -> DefaultMembershipConfig { - DefaultMembershipConfig { +fn default_gen_conf() -> MembershipConfig { + MembershipConfig { memberships: btreemap![ 0 => MembershipData { expire_on: 3, @@ -41,10 +38,10 @@ fn test_genesis_build() { run_to_block(1); // Verify state assert_eq!( - DefaultMembership::membership(0), + Membership::membership(0), Some(MembershipData { expire_on: 3 }) ); - assert_eq!(DefaultMembership::members_count(), 1); + assert_eq!(Membership::members_count(), 1); }); } @@ -55,42 +52,39 @@ fn test_membership_expiration() { new_test_ext(default_gen_conf()).execute_with(|| { // Membership 0 should not expired on block #2 run_to_block(2); - assert!(DefaultMembership::is_member(&0)); + assert!(Membership::is_member(&0)); // Membership 0 should expire on block #3 run_to_block(3); - assert!(!DefaultMembership::is_member(&0)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved { + assert!(!Membership::is_member(&0)); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved { member: 0, reason: MembershipRemovalReason::Expired, })); }); } -/// test membership renewal -// there is no limit for membership renewal outside wot rules (number of certs, distance rule) +/// test membership renewal (triggered automatically after distance evaluation) #[test] fn test_membership_renewal() { new_test_ext(default_gen_conf()).execute_with(|| { // membership still valid at block 2 run_to_block(2); - assert!(DefaultMembership::is_member(&0)); + assert!(Membership::is_member(&0)); // Membership 0 can be renewed - assert_ok!(DefaultMembership::renew_membership(RuntimeOrigin::signed( - 0 - ),)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipAdded { + assert_ok!(Membership::try_renew_membership(0)); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRenewed { member: 0, expire_on: 2 + <Test as crate::Config>::MembershipPeriod::get(), })); // membership should not expire at block 3 to 6 because it has been renewed run_to_block(3); - assert!(DefaultMembership::is_member(&0)); + assert!(Membership::is_member(&0)); run_to_block(6); - assert!(DefaultMembership::is_member(&0)); + assert!(Membership::is_member(&0)); // membership should expire at block 7 (2+5) run_to_block(7); - assert!(!DefaultMembership::is_member(&0)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved { + assert!(!Membership::is_member(&0)); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved { member: 0, reason: MembershipRemovalReason::Expired, })); @@ -102,14 +96,14 @@ fn test_membership_renewal() { fn test_membership_renewal_nope() { new_test_ext(default_gen_conf()).execute_with(|| { run_to_block(2); - assert!(!DefaultMembership::is_member(&1)); + assert!(!Membership::is_member(&1)); // Membership 1 can not be renewed assert_noop!( - DefaultMembership::renew_membership(RuntimeOrigin::signed(1)), + Membership::try_renew_membership(1), Error::<Test>::MembershipNotFound, ); run_to_block(3); - assert!(!DefaultMembership::is_member(&1)); + assert!(!Membership::is_member(&1)); }); } @@ -119,21 +113,17 @@ fn test_membership_revocation() { new_test_ext(default_gen_conf()).execute_with(|| { run_to_block(1); // Membership 0 can be revocable on block #1 - assert_ok!(DefaultMembership::revoke_membership(RuntimeOrigin::signed( - 0 - ),)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved { + Membership::do_remove_membership(0, MembershipRemovalReason::Revoked); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved { member: 0, reason: MembershipRemovalReason::Revoked, })); - assert_eq!(DefaultMembership::membership(0), None); + assert_eq!(Membership::membership(0), None); // Membership 0 can re-claim membership run_to_block(5); - assert_ok!(DefaultMembership::claim_membership(RuntimeOrigin::signed( - 0 - ),)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipAdded { + assert_ok!(Membership::try_add_membership(0)); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipAdded { member: 0, expire_on: 5 + <Test as crate::Config>::MembershipPeriod::get(), })); @@ -149,28 +139,24 @@ fn test_membership_workflow() { new_test_ext(Default::default()).execute_with(|| { // - Then, idty 0 claim membership run_to_block(2); - assert_ok!(DefaultMembership::claim_membership(RuntimeOrigin::signed( - 0 - ),)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipAdded { + assert_ok!(Membership::try_add_membership(0)); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipAdded { member: 0, expire_on: 2 + <Test as crate::Config>::MembershipPeriod::get(), })); // - Then, idty 0 claim renewal, should success run_to_block(2); - assert_ok!(DefaultMembership::renew_membership(RuntimeOrigin::signed( - 0 - ),)); + assert_ok!(Membership::try_renew_membership(0)); // idty 0 should still be member until membership period ended run_to_block(6); // 2 + 5 - 1 - assert!(DefaultMembership::is_member(&0)); + assert!(Membership::is_member(&0)); // - Then, idty 0 should expire after membership period run_to_block(7); // 2 + 5 - assert!(!DefaultMembership::is_member(&0)); - System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved { + assert!(!Membership::is_member(&0)); + System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved { member: 0, reason: MembershipRemovalReason::Expired, })); diff --git a/pallets/universal-dividend/src/lib.rs b/pallets/universal-dividend/src/lib.rs index 25de8fb027cd88c57244465d9a0292b9c937f591..be744570ea9d9e85ec20262738c2e7149eadb01d 100644 --- a/pallets/universal-dividend/src/lib.rs +++ b/pallets/universal-dividend/src/lib.rs @@ -99,6 +99,8 @@ pub mod pallet { #[pallet::type_value] pub fn DefaultForCurrentUdIndex() -> UdIndex { + // FIXME seems off by 1 + // or rename to "next" ud index instead of "current" ud index 1 } @@ -110,6 +112,9 @@ pub mod pallet { #[cfg(test)] #[pallet::storage] + // UD should be linked to idtyid instead of accountid + // if it is convenient in test, why not have it in runtime also? + // storing it in idty_value.data is strange pub type TestMembers<T: Config> = StorageMap< _, Blake2_128Concat, @@ -263,6 +268,7 @@ pub mod pallet { // Increment ud index let ud_index = CurrentUdIndex::<T>::mutate(|next_ud_index| { + // FIXME seems off by 1 due to default to 1 core::mem::replace(next_ud_index, next_ud_index.saturating_add(1)) }); diff --git a/resources/gdev.yaml b/resources/gdev.yaml index a901457535966732d744522220a1b26ce5dfe1ee..fcfd98034cb67c96d53cdc9991f43184fdffd02d 100644 --- a/resources/gdev.yaml +++ b/resources/gdev.yaml @@ -27,8 +27,8 @@ parameters: cert_validity_period: 2102400 # Validity duration of a membership. 1051200 blocks = 73 days. membership_period: 1051200 - # Validity duration of a pending membership. 172800 blocks = 12 days. - pending_membership_period: 172800 + # Period to wait before membership renewal. 1051200 blocks = 1 days. + membership_renewal_period: 14400 # Delay a new member must observe before being able to emit a certification wot_first_cert_issuable_on: 0 # Number of required received certs to become a member @@ -68,4 +68,5 @@ sudo_key: "5CfodrEFe64MJtWvfhTHYBuUySr4WXLv2B41mZFucTXSGMFA" treasury_funder_address: "5E6q47RRGZU15LjUiBTm2DZjpqFKAjRNafYS8YV8AzTQZtLG" # The technical committee members, to act as sudo -technical_committee: ["Pini", "moul", "HugoTrentesaux", "tuxmain", "1000i100", "vit", "cgeek"] \ No newline at end of file +technical_committee: + ["Pini", "moul", "HugoTrentesaux", "tuxmain", "1000i100", "vit", "cgeek"] diff --git a/resources/metadata.scale b/resources/metadata.scale index 15ad56874e147a9b94c30468250afd196b8504ec..5908806517d61badcdd60a81e867552b7a21ea01 100644 Binary files a/resources/metadata.scale and b/resources/metadata.scale differ diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index 6bae43eb554b952e97e8f7e23b226f4002bf0b70..d0ce866c0d4bf0bde2f48c5155b05ecdc9f1bdd6 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -453,7 +453,6 @@ macro_rules! pallets_config { impl pallet_duniter_wot::Config for Runtime { type FirstIssuableOn = WotFirstCertIssuableOn; - type IsDistanceOk = common_runtime::providers::MainWotIsDistanceOk<Runtime>; type MinCertForMembership = WotMinCertForMembership; type MinCertForCreateIdtyRight = WotMinCertForCreateIdtyRight; } @@ -488,6 +487,7 @@ macro_rules! pallets_config { type IdtyIdOf = common_runtime::providers::IdentityIndexOf<Self>; type AccountIdOf = common_runtime::providers::IdentityAccountIdProvider<Self>; type MembershipPeriod = MembershipPeriod; + type MembershipRenewalPeriod = MembershipRenewalPeriod; type OnEvent = (OnMembershipEventHandler<Wot, Runtime>, Wot); type RuntimeEvent = RuntimeEvent; type WeightInfo = common_runtime::weights::pallet_membership::WeightInfo<Runtime>; @@ -516,9 +516,10 @@ macro_rules! pallets_config { type EvaluationPrice = frame_support::traits::ConstU64<1000>; type MaxRefereeDistance = frame_support::traits::ConstU32<5>; type MinAccessibleReferees = MinAccessibleReferees; - type ResultExpiration = frame_support::traits::ConstU32<720>; type RuntimeEvent = RuntimeEvent; type WeightInfo = common_runtime::weights::pallet_distance::WeightInfo<Runtime>; + type OnValidDistanceStatus = Wot; + type CheckRequestDistanceEvaluation = Wot; } // SMITH-MEMBERS diff --git a/runtime/common/src/providers.rs b/runtime/common/src/providers.rs index ca631988e74cfd3b19417be348605c4bd58d5574..504cf92f8db91313aa780b7e8c5b2f927bc35863 100644 --- a/runtime/common/src/providers.rs +++ b/runtime/common/src/providers.rs @@ -17,7 +17,6 @@ use crate::{entities::IdtyData, AccountId, IdtyIndex}; use core::marker::PhantomData; use pallet_universal_dividend::FirstEligibleUd; -use sp_runtime::DispatchError; pub struct IdentityAccountIdProvider<Runtime>(PhantomData<Runtime>); @@ -71,31 +70,6 @@ where } } -pub struct MainWotIsDistanceOk<T>(PhantomData<T>); - -impl<T> pallet_duniter_wot::traits::IsDistanceOk<<T as pallet_identity::Config>::IdtyIndex> - for MainWotIsDistanceOk<T> -where - T: pallet_distance::Config + pallet_duniter_wot::Config, -{ - fn is_distance_ok( - idty_id: &<T as pallet_identity::Config>::IdtyIndex, - ) -> Result<(), DispatchError> { - match pallet_distance::Pallet::<T>::identity_distance_status(idty_id) { - Some((_, status)) => match status { - pallet_distance::DistanceStatus::Valid => Ok(()), - pallet_distance::DistanceStatus::Invalid => { - Err(pallet_duniter_wot::Error::<T>::DistanceIsInvalid.into()) - } - pallet_distance::DistanceStatus::Pending => { - Err(pallet_duniter_wot::Error::<T>::DistanceEvaluationPending.into()) - } - }, - None => Err(pallet_duniter_wot::Error::<T>::DistanceEvaluationNotRequested.into()), - } - } -} - pub struct IsWoTMemberProvider<T>(PhantomData<T>); impl<T: pallet_smith_members::Config> sp_runtime::traits::IsMember<<T as pallet_membership::Config>::IdtyId> @@ -121,14 +95,8 @@ macro_rules! impl_benchmark_setup_handler { T: pallet_certification::Config, <T as pallet_certification::Config>::IdtyIndex: From<u32>, { - fn force_status_ok( - idty_id: &IdtyIndex, - account: &<T as frame_system::Config>::AccountId, - ) -> () { - let _ = pallet_distance::Pallet::<T>::set_distance_status( - *idty_id, - Some((account.clone(), pallet_distance::DistanceStatus::Valid)), - ); + fn force_valid_distance_status(idty_id: &IdtyIndex) -> () { + let _ = pallet_distance::Pallet::<T>::do_valid_distance_status(*idty_id); } fn add_cert(issuer: &IdtyIndex, receiver: &IdtyIndex) { let _ = pallet_certification::Pallet::<T>::do_add_cert_checked( diff --git a/runtime/common/src/weights/pallet_distance.rs b/runtime/common/src/weights/pallet_distance.rs index 27aa944753e7c172da0a2c05b5493358dcd3008c..c1c3c8c557f2b26e50632388bc65d06780c999fd 100644 --- a/runtime/common/src/weights/pallet_distance.rs +++ b/runtime/common/src/weights/pallet_distance.rs @@ -17,19 +17,19 @@ //! Autogenerated weights for `pallet_distance` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-01-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-20, STEPS: `8`, REPEAT: `4`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bgallois-ms7d43`, CPU: `12th Gen Intel(R) Core(TM) i3-12100F` +//! HOSTNAME: `squirrel`, CPU: `Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/duniter +// ./target/debug/duniter // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=* +// --steps=8 +// --repeat=4 +// --pallet=pallet-distance // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -50,25 +50,57 @@ pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> { /// Storage: Identity IdentityIndexOf (r:1 w:0) /// Proof Skipped: Identity IdentityIndexOf (max_values: None, max_size: None, mode: Measured) - /// Storage: Distance IdentityDistanceStatus (r:1 w:1) - /// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured) + /// Storage: Distance PendingEvaluationRequest (r:1 w:1) + /// Proof Skipped: Distance PendingEvaluationRequest (max_values: None, max_size: None, mode: Measured) + /// Storage: Distance ValidEvaluationResult (r:1 w:0) + /// Proof Skipped: Distance ValidEvaluationResult (max_values: None, max_size: None, mode: Measured) + /// Storage: Cert StorageIdtyCertMeta (r:1 w:0) + /// Proof Skipped: Cert StorageIdtyCertMeta (max_values: None, max_size: None, mode: Measured) + /// Storage: Parameters ParametersStorage (r:1 w:0) + /// Proof Skipped: Parameters ParametersStorage (max_values: Some(1), max_size: None, mode: Measured) /// Storage: Session CurrentIndex (r:1 w:0) /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) /// Storage: Distance EvaluationPool2 (r:1 w:1) /// Proof Skipped: Distance EvaluationPool2 (max_values: Some(1), max_size: None, mode: Measured) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(126), added: 2601, mode: MaxEncodedLen) - /// Storage: Distance DistanceStatusExpireOn (r:1 w:1) - /// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured) fn request_distance_evaluation() -> Weight { // Proof Size summary in bytes: - // Measured: `939` - // Estimated: `4404` - // Minimum execution time: 33_043_000 picoseconds. - Weight::from_parts(33_977_000, 0) - .saturating_add(Weight::from_parts(0, 4404)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `1280` + // Estimated: `4745` + // Minimum execution time: 876_053_000 picoseconds. + Weight::from_parts(898_445_000, 0) + .saturating_add(Weight::from_parts(0, 4745)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: Identity IdentityIndexOf (r:1 w:0) + /// Proof Skipped: Identity IdentityIndexOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Identity Identities (r:2 w:0) + /// Proof Skipped: Identity Identities (max_values: None, max_size: None, mode: Measured) + /// Storage: Distance PendingEvaluationRequest (r:1 w:1) + /// Proof Skipped: Distance PendingEvaluationRequest (max_values: None, max_size: None, mode: Measured) + /// Storage: Distance ValidEvaluationResult (r:1 w:0) + /// Proof Skipped: Distance ValidEvaluationResult (max_values: None, max_size: None, mode: Measured) + /// Storage: Cert StorageIdtyCertMeta (r:1 w:0) + /// Proof Skipped: Cert StorageIdtyCertMeta (max_values: None, max_size: None, mode: Measured) + /// Storage: Parameters ParametersStorage (r:1 w:0) + /// Proof Skipped: Parameters ParametersStorage (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Session CurrentIndex (r:1 w:0) + /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Distance EvaluationPool2 (r:1 w:1) + /// Proof Skipped: Distance EvaluationPool2 (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(126), added: 2601, mode: MaxEncodedLen) + fn request_distance_evaluation_for() -> Weight { + // Proof Size summary in bytes: + // Measured: `1485` + // Estimated: `7425` + // Minimum execution time: 1_118_982_000 picoseconds. + Weight::from_parts(1_292_782_000, 0) + .saturating_add(Weight::from_parts(0, 7425)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Distance DidUpdate (r:1 w:1) /// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured) @@ -85,13 +117,13 @@ impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> { /// The range of component `i` is `[1, 600]`. fn update_evaluation(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `772 + i * (10 ±0)` + // Measured: `773 + i * (10 ±0)` // Estimated: `2256 + i * (10 ±0)` - // Minimum execution time: 22_135_000 picoseconds. - Weight::from_parts(27_043_883, 0) + // Minimum execution time: 463_878_000 picoseconds. + Weight::from_parts(743_823_548, 0) .saturating_add(Weight::from_parts(0, 2256)) - // Standard Error: 1_421 - .saturating_add(Weight::from_parts(125_435, 0).saturating_mul(i.into())) + // Standard Error: 292_144 + .saturating_add(Weight::from_parts(1_326_639, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into())) @@ -103,32 +135,40 @@ impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> { /// The range of component `i` is `[1, 600]`. fn force_update_evaluation(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `611 + i * (10 ±0)` + // Measured: `612 + i * (10 ±0)` // Estimated: `2095 + i * (10 ±0)` - // Minimum execution time: 13_033_000 picoseconds. - Weight::from_parts(15_741_933, 0) + // Minimum execution time: 208_812_000 picoseconds. + Weight::from_parts(257_150_521, 0) .saturating_add(Weight::from_parts(0, 2095)) - // Standard Error: 693 - .saturating_add(Weight::from_parts(121_989, 0).saturating_mul(i.into())) + // Standard Error: 53_366 + .saturating_add(Weight::from_parts(1_841_329, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into())) } /// Storage: Session CurrentIndex (r:1 w:0) /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Distance DistanceStatusExpireOn (r:1 w:1) - /// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured) - /// Storage: Distance IdentityDistanceStatus (r:0 w:1) - /// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured) - fn force_set_distance_status() -> Weight { + /// Storage: Distance ValidEvaluationExpireOn (r:1 w:1) + /// Proof Skipped: Distance ValidEvaluationExpireOn (max_values: None, max_size: None, mode: Measured) + /// Storage: Identity Identities (r:1 w:0) + /// Proof Skipped: Identity Identities (max_values: None, max_size: None, mode: Measured) + /// Storage: Membership Membership (r:1 w:1) + /// Proof Skipped: Membership Membership (max_values: None, max_size: None, mode: Measured) + /// Storage: Membership MembershipsExpireOn (r:2 w:2) + /// Proof Skipped: Membership MembershipsExpireOn (max_values: None, max_size: None, mode: Measured) + /// Storage: Parameters ParametersStorage (r:1 w:0) + /// Proof Skipped: Parameters ParametersStorage (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Distance ValidEvaluationResult (r:0 w:1) + /// Proof Skipped: Distance ValidEvaluationResult (max_values: None, max_size: None, mode: Measured) + fn force_valid_distance_status() -> Weight { // Proof Size summary in bytes: - // Measured: `585` - // Estimated: `4050` - // Minimum execution time: 12_886_000 picoseconds. - Weight::from_parts(13_465_000, 0) - .saturating_add(Weight::from_parts(0, 4050)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `1181` + // Estimated: `7121` + // Minimum execution time: 873_892_000 picoseconds. + Weight::from_parts(1_081_510_000, 0) + .saturating_add(Weight::from_parts(0, 7121)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: Distance DidUpdate (r:1 w:1) /// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured) @@ -136,8 +176,8 @@ impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `170` // Estimated: `1655` - // Minimum execution time: 3_830_000 picoseconds. - Weight::from_parts(4_065_000, 0) + // Minimum execution time: 93_595_000 picoseconds. + Weight::from_parts(109_467_000, 0) .saturating_add(Weight::from_parts(0, 1655)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/runtime/g1/src/lib.rs b/runtime/g1/src/lib.rs index 40a3025acac0603b7069edfef33d2c977b0497b5..8c9bca67c4336aaf69d36a807bf07a27cb6f6657 100644 --- a/runtime/g1/src/lib.rs +++ b/runtime/g1/src/lib.rs @@ -174,9 +174,6 @@ impl Contains<RuntimeCall> for BaseCallFilter { call, RuntimeCall::System( frame_system::Call::remark { .. } | frame_system::Call::remark_with_event { .. } - ) | RuntimeCall::Membership( - pallet_membership::Call::claim_membership { .. } - | pallet_membership::Call::revoke_membership { .. } ) | RuntimeCall::Session(_) ) } @@ -287,7 +284,7 @@ construct_runtime!( // Web Of Trust Wot: pallet_duniter_wot::{Pallet} = 40, Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41, - Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>} = 42, + Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>} = 42, Certification: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>} = 43, Distance: pallet_distance::{Pallet, Call, Storage, Inherent, Event<T>} = 44, diff --git a/runtime/g1/src/parameters.rs b/runtime/g1/src/parameters.rs index 22ef4efa43e1bf4424bbd86c5b6bf00daeb62586..adfc0183ad11ca025a18b29e335966fbdb9257ff 100644 --- a/runtime/g1/src/parameters.rs +++ b/runtime/g1/src/parameters.rs @@ -105,7 +105,7 @@ parameter_types! { // Membership parameter_types! { pub const MembershipPeriod: BlockNumber = YEARS; - pub const PendingMembershipPeriod: BlockNumber = 2 * MONTHS; + pub const MembershipRenewalPeriod: BlockNumber = 2 * MONTHS; } // Certification diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs index 04366d5dbe9b44698365b1531d33653819e914bd..d64153d645e659cc59d1a789cbfd253115618e62 100644 --- a/runtime/gdev/src/lib.rs +++ b/runtime/gdev/src/lib.rs @@ -177,10 +177,9 @@ impl Contains<RuntimeCall> for BaseCallFilter { fn contains(call: &RuntimeCall) -> bool { !matches!( call, - // in main web of trust, membership request and revoke are handeled through identity pallet - // the user can not call them directly - RuntimeCall::Membership(pallet_membership::Call::revoke_membership { .. }) - | RuntimeCall::Session(_) + // session calls can not be called directly + // it should be done through authority-members pallet + RuntimeCall::Session(_) ) } } @@ -257,6 +256,7 @@ common_runtime::pallets_config! { pub type ConfirmPeriod = pallet_duniter_test_parameters::IdtyConfirmPeriod<Runtime>; pub type IdtyCreationPeriod = pallet_duniter_test_parameters::IdtyCreationPeriod<Runtime>; pub type MembershipPeriod = pallet_duniter_test_parameters::MembershipPeriod<Runtime>; + pub type MembershipRenewalPeriod = pallet_duniter_test_parameters::MembershipRenewalPeriod<Runtime>; pub type UdCreationPeriod = pallet_duniter_test_parameters::UdCreationPeriod<Runtime>; pub type UdReevalPeriod = pallet_duniter_test_parameters::UdReevalPeriod<Runtime>; pub type WotFirstCertIssuableOn = pallet_duniter_test_parameters::WotFirstCertIssuableOn<Runtime>; @@ -329,7 +329,7 @@ construct_runtime!( // Web Of Trust Wot: pallet_duniter_wot::{Pallet} = 40, Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41, - Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>} = 42, + Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>} = 42, Certification: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>} = 43, Distance: pallet_distance::{Pallet, Call, Storage, Inherent, Event<T>} = 44, diff --git a/runtime/gdev/src/parameters.rs b/runtime/gdev/src/parameters.rs index 7dc4e8177245114e7df93fb3cd8a2d4e7e7e29c2..abd164fe652d74a58e2d90d8cfd61b3c224534ac 100644 --- a/runtime/gdev/src/parameters.rs +++ b/runtime/gdev/src/parameters.rs @@ -90,6 +90,11 @@ frame_support::parameter_types! { pub const ChangeOwnerKeyPeriod: BlockNumber = 7 * DAYS; } +// Membership +frame_support::parameter_types! { + pub const SmithMembershipRenewalPeriod: BlockNumber = MONTHS; +} + /*************/ /* UTILITIES */ /*************/ diff --git a/runtime/gdev/tests/common/mod.rs b/runtime/gdev/tests/common/mod.rs index 261755f759209283c2acbbfa4dd0de41c26f24c8..0ca8aa74cc02be2f068724ebafd8811ce6b58cb1 100644 --- a/runtime/gdev/tests/common/mod.rs +++ b/runtime/gdev/tests/common/mod.rs @@ -105,7 +105,7 @@ impl ExtBuilder { idty_confirm_period: 40, idty_creation_period: 50, membership_period: 100, - pending_membership_period: 500, + membership_renewal_period: 10, ud_creation_period: 60_000, ud_reeval_period: 60_000 * 20, smith_cert_max_by_issuer: 8, diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs index 49755e0c49b7b054aa61f21cceed8cd5ca94a649..6f1dbc89d94f221a40058afef6c28a7383bb8188 100644 --- a/runtime/gdev/tests/integration_tests.rs +++ b/runtime/gdev/tests/integration_tests.rs @@ -311,7 +311,7 @@ fn test_remove_identity() { }); } -/// test identity is validated when membership is claimed +/// test identity is "validated" (= membership is claimed) when distance is evaluated positively #[test] fn test_validate_identity_when_claim() { ExtBuilder::new(1, 3, 4) @@ -346,11 +346,14 @@ fn test_validate_identity_when_claim() { 5 )); + // eve request distance evaluation for herself assert_ok!(Distance::request_distance_evaluation( frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), )); - run_to_block(51); // Pass 2 sessions + // Pass 2 sessions + run_to_block(51); + // simulate an evaluation published by smith Alice assert_ok!(Distance::force_update_evaluation( frame_system::RawOrigin::Root.into(), AccountKeyring::Alice.to_account_id(), @@ -358,20 +361,142 @@ fn test_validate_identity_when_claim() { distances: vec![Perbill::one()], } )); - run_to_block(76); // Pass 1 session + run_to_block(75); // Pass 1 session + System::assert_has_event(RuntimeEvent::Distance( + pallet_distance::Event::EvaluatedValid { idty_index: 5 }, + )); + + // eve can not claim her membership manually because it is done automatically + // the following call does not exist anymore + // assert_noop!( + // Membership::claim_membership( + // frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + // ), + // pallet_membership::Error::<Runtime>::AlreadyMember + // ); + + // println!("{:?}", System::events()); + System::assert_has_event(RuntimeEvent::Membership( + pallet_membership::Event::MembershipAdded { + member: 5, + expire_on: 75 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(), + }, + )); + }); +} - // eve can claim her membership - assert_ok!(Membership::claim_membership( +/// test identity creation workflow +// with distance requested by last certifier +#[test] +fn test_identity_creation_workflow() { + ExtBuilder::new(1, 3, 4) + .with_initial_balances(vec![ + (AccountKeyring::Charlie.to_account_id(), 10_000), // necessary for evalation distance reserve + (AccountKeyring::Eve.to_account_id(), 2_000), + (AccountKeyring::Ferdie.to_account_id(), 1_000), + ]) + .build() + .execute_with(|| { + run_to_block(1); + // alice create identity for Eve + assert_ok!(Identity::create_identity( + frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), + AccountKeyring::Eve.to_account_id(), + )); + assert_eq!( + Identity::identity(5), + Some(pallet_identity::IdtyValue { + data: Default::default(), + next_creatable_identity_on: 0u32, + old_owner_key: None, + owner_key: AccountKeyring::Eve.to_account_id(), + next_scheduled: 1 + 40, + status: pallet_identity::IdtyStatus::Unconfirmed, + }) + ); + run_to_block(2); + // eve confirms her identity + assert_ok!(Identity::confirm_identity( frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + "Eeeeeveeeee".into(), + )); + assert_eq!( + Identity::identity(5), + Some(pallet_identity::IdtyValue { + data: Default::default(), + next_creatable_identity_on: 0u32, + old_owner_key: None, + owner_key: AccountKeyring::Eve.to_account_id(), + next_scheduled: 2 + 876600, + status: pallet_identity::IdtyStatus::Unvalidated, + }) + ); + run_to_block(3); + // eve gets certified by bob and charlie + assert_ok!(Certification::add_cert( + frame_system::RawOrigin::Signed(AccountKeyring::Bob.to_account_id()).into(), + 2, + 5 )); + assert_ok!(Certification::add_cert( + frame_system::RawOrigin::Signed(AccountKeyring::Charlie.to_account_id()).into(), + 3, + 5 + )); + // charlie also request distance evaluation for eve + // (could be done in batch) + assert_ok!(Distance::request_distance_evaluation_for( + frame_system::RawOrigin::Signed(AccountKeyring::Charlie.to_account_id()).into(), + 5 + )); + // then the evaluation is pending + assert_eq!( + Distance::pending_evaluation_request(5), + Some(AccountKeyring::Charlie.to_account_id(),) + ); + // Pass 2 sessions + run_to_block(51); + // simulate evaluation published by smith Alice + assert_ok!(Distance::force_update_evaluation( + frame_system::RawOrigin::Root.into(), + AccountKeyring::Alice.to_account_id(), + pallet_distance::ComputationResult { + distances: vec![Perbill::one()], + } + )); + // Pass 1 session + run_to_block(75); + + // eve should not even have to claim her membership System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipAdded { member: 5, - expire_on: 76 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(), + expire_on: 75 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(), }, )); - // not possible anymore to validate identity of someone else + + // test state coherence + assert_eq!( + Identity::identity(5), + Some(pallet_identity::IdtyValue { + data: IdtyData { + // ud creation period is 60_000 ms ~ 10 blocks + // block time is 6_000 ms + // first ud is at 24_000 ms ~ 4 blocks + // at block 75 this should be the UD number 7, so next is 8 not 9 + // FIXME wrong UD count + first_eligible_ud: pallet_universal_dividend::FirstEligibleUd(Some( + sp_std::num::NonZeroU16::new(9).unwrap() + )) + }, + next_creatable_identity_on: 0u32, + old_owner_key: None, + owner_key: AccountKeyring::Eve.to_account_id(), + next_scheduled: 0, + status: pallet_identity::IdtyStatus::Member, + }) + ); }); } @@ -424,6 +549,16 @@ fn test_membership_renewal() { .with_initial_balances(vec![(AccountKeyring::Alice.to_account_id(), 2000)]) .build() .execute_with(|| { + // can not renew membership immediately + assert_noop!( + Distance::request_distance_evaluation( + frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), + ), + pallet_duniter_wot::Error::<Runtime>::MembershipRenewalPeriodNotRespected, + ); + + // but ok after waiting 10 blocks delay + run_to_block(11); assert_ok!(Distance::request_distance_evaluation( frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), )); @@ -436,33 +571,28 @@ fn test_membership_renewal() { distances: vec![Perbill::one()], } )); - run_to_block(76); // Pass 1 session - - // renew at block 76 - assert_ok!(Membership::renew_membership( - frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), - )); + // Pass 1 session, membership is renewed automatically + run_to_block(75); System::assert_has_event(RuntimeEvent::Membership( - pallet_membership::Event::MembershipAdded { + pallet_membership::Event::MembershipRenewed { member: 1, - expire_on: 76 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(), + expire_on: 75 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(), }, )); - // renew at block 77 - run_to_block(77); - assert_ok!(Membership::renew_membership( - frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), - )); - System::assert_has_event(RuntimeEvent::Membership( - pallet_membership::Event::MembershipAdded { - member: 1, - expire_on: 77 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(), - }, - )); + run_to_block(76); + // not possible to renew manually + // can not ask renewal when period is not respected + // TODO check that it is possible again within the right delay + assert_noop!( + Distance::request_distance_evaluation( + frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), + ), + pallet_duniter_wot::Error::<Runtime>::MembershipRenewalPeriodNotRespected, + ); - // should expire at block 177 = 77+100 - run_to_block(177); + // should expire at block 175 = 75+100 + run_to_block(175); System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipRemoved { member: 1, @@ -583,18 +713,19 @@ fn test_ud_claimed_membership_on_and_off() { }, )); - // alice claims back her membership - assert_ok!(Distance::force_set_distance_status( + // alice claims back her membership through distance evaluation + assert_ok!(Distance::force_valid_distance_status( frame_system::RawOrigin::Root.into(), 1, - Some(( - AccountKeyring::Alice.to_account_id(), - pallet_distance::DistanceStatus::Valid - )) - )); - assert_ok!(Membership::claim_membership( - frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into() )); + // it can not be done manually + // because the call does not exist anymore + // assert_noop!( + // Membership::claim_membership( + // frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), + // ), + // pallet_membership::Error::<Runtime>::AlreadyMember + // ); System::assert_has_event(RuntimeEvent::Membership( pallet_membership::Event::MembershipAdded { member: 1, @@ -963,12 +1094,22 @@ fn test_create_new_idty_without_founds() { .build() .execute_with(|| { run_to_block(2); + assert_eq!( + Balances::free_balance(AccountKeyring::Eve.to_account_id()), + 0 + ); // Should be able to create an identity without founds assert_ok!(Identity::create_identity( frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), AccountKeyring::Eve.to_account_id(), )); + System::assert_has_event(RuntimeEvent::Identity( + pallet_identity::Event::IdtyCreated { + idty_index: 5, + owner_key: AccountKeyring::Eve.to_account_id(), + }, + )); // At next block, nothing should be preleved run_to_block(3); @@ -1029,24 +1170,26 @@ fn test_validate_new_idty_after_few_uds() { pallet_identity::IdtyName::from("Eve"), )); - // At next block, Bob should be able to certify and validate the new identity + // At next block, Bob should be able to certify the new identity run_to_block(23); assert_ok!(Certification::add_cert( frame_system::RawOrigin::Signed(AccountKeyring::Bob.to_account_id()).into(), 2, 5, )); - assert_ok!(Distance::force_set_distance_status( + // valid distance status should trigger identity validation + assert_ok!(Distance::force_valid_distance_status( frame_system::RawOrigin::Root.into(), 5, - Some(( - AccountKeyring::Bob.to_account_id(), - pallet_distance::DistanceStatus::Valid - )) - )); - assert_ok!(Membership::claim_membership( - frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), )); + // and it is not possible to call it manually + // because the call does not exist anymore + // assert_noop!( + // Membership::claim_membership( + // frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + // ), + // pallet_membership::Error::<Runtime>::AlreadyMember + // ); // The new member should have first_eligible_ud equal to three assert!(Identity::identity(5).is_some()); @@ -1095,18 +1238,19 @@ fn test_claim_memberhsip_after_few_uds() { 5, )); - // eve should be able to claim her membership - assert_ok!(Distance::force_set_distance_status( + // eve membership should be able to be claimed through distance evaluation + assert_ok!(Distance::force_valid_distance_status( frame_system::RawOrigin::Root.into(), 5, - Some(( - AccountKeyring::Eve.to_account_id(), - pallet_distance::DistanceStatus::Valid - )) - )); - assert_ok!(Membership::claim_membership( - frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), )); + // but not manually + // because the call does not exist + // assert_noop!( + // Membership::claim_membership( + // frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + // ), + // pallet_membership::Error::<Runtime>::AlreadyMember + // ); // The new member should have first_eligible_ud equal to three assert!(Identity::identity(5).is_some()); @@ -1451,16 +1595,6 @@ fn test_new_account_linked() { ); }) } -#[test] -#[ignore = "what was this test supposed to do?"] -fn smith_data_problem() { - ExtBuilder::new(1, 3, 4) - .change_parameters(|_parameters| {}) - .build() - .execute_with(|| { - run_to_block(4); - }); -} /// test killed account // The only way to kill an account is to kill the identity diff --git a/runtime/gdev/tests/xt_tests.rs b/runtime/gdev/tests/xt_tests.rs index 1663d6221475aaea797b388399d8f0d1364aeeca..5caf3a87e26d69a6514446862cf9c86a7a7b55d2 100644 --- a/runtime/gdev/tests/xt_tests.rs +++ b/runtime/gdev/tests/xt_tests.rs @@ -15,6 +15,7 @@ // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // these integration tests aim to test fees and extrinsic-related externalities +// they do not work with runtim-benchmark because it has a different fees model mod common; diff --git a/runtime/gtest/src/lib.rs b/runtime/gtest/src/lib.rs index a4b6e010a2133efd043cbb7a05ae5f6dd9fa3e13..7fe0cfeba60b0c77c6746d931b272e13e582f3d3 100644 --- a/runtime/gtest/src/lib.rs +++ b/runtime/gtest/src/lib.rs @@ -292,7 +292,7 @@ construct_runtime!( // Web Of Trust Wot: pallet_duniter_wot::{Pallet} = 40, Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41, - Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>} = 42, + Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>} = 42, Certification: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>} = 43, Distance: pallet_distance::{Pallet, Call, Storage, Inherent, Event<T>} = 44, diff --git a/runtime/gtest/src/parameters.rs b/runtime/gtest/src/parameters.rs index 252ae211a2571ead87fa67ab4d13f64b98ae8c88..c7731b0a9950fcee4ae933a7c43438949d980b29 100644 --- a/runtime/gtest/src/parameters.rs +++ b/runtime/gtest/src/parameters.rs @@ -107,7 +107,7 @@ parameter_types! { // Membership parameter_types! { pub const MembershipPeriod: BlockNumber = 73 * DAYS; - pub const PendingMembershipPeriod: BlockNumber = 12 * DAYS; + pub const MembershipRenewalPeriod: BlockNumber = 56 * DAYS; } // Certification