From f562a95bbaca1902fba2731428079e8783256bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Moreau?= <cem.moreau@gmail.com> Date: Fri, 2 Feb 2024 22:09:24 +0100 Subject: [PATCH] Resolve #152 "remove random_id mechanism which is heavy and that we do not use" (nodes/rust/duniter-v2s!235) * remove unnecessary currency associated type * WIP: rebase * WIP: update docs * WIP: clippy * WIP: test pass * WIP: // TODO: decrement consumers? --- Cargo.lock | 1 - docs/api/runtime-events.md | 19 +---- live-tests/tests/sanity_gdev.rs | 6 +- node/src/chain_spec/gen_genesis_data.rs | 7 +- pallets/README.md | 2 +- pallets/duniter-account/Cargo.toml | 4 - pallets/duniter-account/README.md | 8 +- pallets/duniter-account/src/benchmarking.rs | 20 ----- pallets/duniter-account/src/lib.rs | 73 ++---------------- pallets/duniter-account/src/types.rs | 6 -- pallets/duniter-account/src/weights.rs | 29 ------- resources/metadata.scale | Bin 112080 -> 111870 bytes runtime/common/src/pallets_config.rs | 3 +- .../src/weights/pallet_duniter_account.rs | 47 ----------- runtime/gdev/tests/common/mod.rs | 2 - runtime/gdev/tests/integration_tests.rs | 59 ++++---------- 16 files changed, 32 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 026013d64..45eec7591 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6603,7 +6603,6 @@ dependencies = [ "maplit", "pallet-balances", "pallet-identity", - "pallet-provide-randomness", "pallet-quota", "pallet-transaction-payment", "pallet-treasury", diff --git a/docs/api/runtime-events.md b/docs/api/runtime-events.md index 71abd3323..0787b7f5f 100644 --- a/docs/api/runtime-events.md +++ b/docs/api/runtime-events.md @@ -1,6 +1,6 @@ # Runtime events -There are **129** events from **35** pallets. +There are **128** events from **35** pallets. <ul> <li>System - 0 @@ -112,20 +112,7 @@ balance: T::Balance <li> <details> <summary> -<code>RandomIdAssigned(who, random_id)</code> - 1</summary> -A random ID has been assigned to the account. - -```rust -who: T::AccountId -random_id: H256 -``` - -</details> -</li> -<li> -<details> -<summary> -<code>AccountLinked(who, identity)</code> - 2</summary> +<code>AccountLinked(who, identity)</code> - 1</summary> account linked to identity ```rust @@ -138,7 +125,7 @@ identity: IdtyIdOf<T> <li> <details> <summary> -<code>AccountUnlinked()</code> - 3</summary> +<code>AccountUnlinked()</code> - 2</summary> The account was unlinked from its identity. ```rust diff --git a/live-tests/tests/sanity_gdev.rs b/live-tests/tests/sanity_gdev.rs index 814af0786..86cec2524 100644 --- a/live-tests/tests/sanity_gdev.rs +++ b/live-tests/tests/sanity_gdev.rs @@ -235,10 +235,10 @@ mod verifier { if account_id.as_slice() != TREASURY_ACCOUNT_ID { // Rule 4: If the account is not a "special account", - // it should have a random id or a consumer + // it should have a consumer self.assert( - account_info.data.random_id.is_some() || account_info.consumers > 0, - format!("Account {} has no random_id nor consumer.", account_id), + account_info.consumers > 0, + format!("Account {} has no consumer.", account_id), ); } } diff --git a/node/src/chain_spec/gen_genesis_data.rs b/node/src/chain_spec/gen_genesis_data.rs index ba2609e03..b6ce08e5f 100644 --- a/node/src/chain_spec/gen_genesis_data.rs +++ b/node/src/chain_spec/gen_genesis_data.rs @@ -25,7 +25,7 @@ use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::crypto::AccountId32; -use sp_core::{blake2_256, ed25519, sr25519, Decode, Encode, H256}; +use sp_core::{ed25519, sr25519, Decode, Encode}; use sp_runtime::traits::{IdentifyAccount, Verify}; use sp_runtime::{MultiSignature, Perbill}; use std::collections::{BTreeMap, HashMap}; @@ -1162,7 +1162,6 @@ fn v1_wallets_to_v2_accounts( accounts.insert( owner_key.clone(), GenesisAccountData { - random_id: H256(blake2_256(&(balance, &owner_key).encode())), balance, idty_id: None, }, @@ -1425,7 +1424,6 @@ fn feed_identities( accounts.insert( identity.owner_key.clone(), GenesisAccountData { - random_id: H256(blake2_256(&(identity.index, &identity.owner_key).encode())), balance: identity.balance, idty_id: Some(identity.index), }, @@ -1747,9 +1745,6 @@ where ( owner_key.clone(), GenesisAccountData { - random_id: H256(blake2_256( - &(i as u32 + idty_index_start, owner_key).encode(), - )), // Should be sufficient for the overhead benchmark balance: initial_idty_balance, idty_id: Some(i as u32 + idty_index_start), diff --git a/pallets/README.md b/pallets/README.md index 4e229119e..e25c7907f 100644 --- a/pallets/README.md +++ b/pallets/README.md @@ -8,7 +8,7 @@ These pallets are at the core of Duniter/Ğ1 currency - **`authority-members`** Duniter authorities are not selected with staking but through a smith web of trust. - **`certification`** Certifications are the "edges" of Duniter's dynamic directed graph. They mean the acceptation of a Licence. -- **`duniter-account`** Duniter customized the `AccountData` defined in the `Balances` pallet to introduce a `RandomId`. +- **`duniter-account`** Duniter customized the `AccountData` defined in the `Balances` pallet to introduce a `linked_idty`. - **`duniter-wot`** Merges identities, membership, certifications and distance pallets to implement Duniter Web of Trust. - **`distance`** Publishes median of distance computation results provided by inherents coming from `distance-oracle` workers. - **`identity`** Identities are the "nodes" of Duniter's dynamic directed graph. They are one-to-one mapping to human being. diff --git a/pallets/duniter-account/Cargo.toml b/pallets/duniter-account/Cargo.toml index 8e1dca7dc..304f5f331 100644 --- a/pallets/duniter-account/Cargo.toml +++ b/pallets/duniter-account/Cargo.toml @@ -17,7 +17,6 @@ runtime-benchmarks = [ 'frame-system/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', 'pallet-identity/runtime-benchmarks', - 'pallet-provide-randomness/runtime-benchmarks', 'pallet-treasury/runtime-benchmarks', 'pallet-quota/runtime-benchmarks', 'pallet-balances/runtime-benchmarks', @@ -30,7 +29,6 @@ std = [ 'pallet-balances/std', 'pallet-transaction-payment/std', 'pallet-identity/std', - 'pallet-provide-randomness/std', 'pallet-treasury/std', 'pallet-quota/std', 'serde/std', @@ -46,7 +44,6 @@ try-runtime = [ 'frame-system/try-runtime', 'sp-runtime/try-runtime', 'pallet-identity/try-runtime', - 'pallet-provide-randomness/try-runtime', 'pallet-treasury/try-runtime', 'pallet-quota/try-runtime', 'pallet-balances/try-runtime', @@ -57,7 +54,6 @@ try-runtime = [ # local pallet-quota = { path = "../quota", default-features = false } pallet-identity = { path = "../identity", default-features = false } -pallet-provide-randomness = { path = "../provide-randomness", default-features = false } # crates.io codec = { package = 'parity-scale-codec', version = "3.6.9", default-features = false, features = ["derive"] } diff --git a/pallets/duniter-account/README.md b/pallets/duniter-account/README.md index 5d9e73b14..185e37ce5 100644 --- a/pallets/duniter-account/README.md +++ b/pallets/duniter-account/README.md @@ -1,12 +1,6 @@ # Duniter account pallet -Duniter customizes the `AccountData` of the `Balances` Substrate pallet. In particular, it adds the fields `random_id` and `linked_idty`. - -## RandomID - -The RandomId field was added with the idea to provide a unique id that can not be controlled by user to serve as a basis for robust identification. The discussion is available on the forum. - -https://forum.duniter.org/t/la-solution-pour-des-identicones-securisees-le-random-id/9126 +Duniter customizes the `AccountData` of the `Balances` Substrate pallet. In particular, it adds the field `linked_idty`. ## Account creation fee diff --git a/pallets/duniter-account/src/benchmarking.rs b/pallets/duniter-account/src/benchmarking.rs index 27a839e1a..b5594e7d2 100644 --- a/pallets/duniter-account/src/benchmarking.rs +++ b/pallets/duniter-account/src/benchmarking.rs @@ -21,14 +21,9 @@ use super::*; use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_support::sp_runtime::{traits::One, Saturating}; use frame_support::traits::{Currency, Get}; -use pallet_provide_randomness::OnFilledRandomness; 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 create_pending_accounts<T: Config>( i: u32, is_balance: bool, @@ -72,19 +67,4 @@ benchmarks! { on_initialize_no_balance { let i in 0 .. T::MaxNewAccountsPerBlock::get() => create_pending_accounts::<T>(i, false, false)?; }: { Pallet::<T>::on_initialize(BlockNumberFor::<T>::one()); } - on_filled_randomness_pending { - let caller: T::AccountId = whitelisted_caller(); - let randomness = H256(T::AccountIdToSalt::convert(caller.clone())); - let request_id = pallet_provide_randomness::Pallet::<T>::force_request(pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo, randomness); - PendingRandomIdAssignments::<T>::insert(request_id, caller.clone()); - }: { Pallet::<T>::on_filled_randomness(request_id, randomness); } - verify { - assert_has_event::<T>(Event::<T>::RandomIdAssigned { who: caller, random_id: randomness }.into()); - } - on_filled_randomness_no_pending { - let caller: T::AccountId = whitelisted_caller(); - let randomness = H256(T::AccountIdToSalt::convert(caller.clone())); - let request_id = pallet_provide_randomness::Pallet::<T>::force_request(pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo, randomness); - assert!(!PendingRandomIdAssignments::<T>::contains_key(request_id)); - }: { Pallet::<T>::on_filled_randomness(request_id, randomness); } } diff --git a/pallets/duniter-account/src/lib.rs b/pallets/duniter-account/src/lib.rs index 05f0c69e3..596554ec7 100644 --- a/pallets/duniter-account/src/lib.rs +++ b/pallets/duniter-account/src/lib.rs @@ -32,10 +32,8 @@ use frame_support::pallet_prelude::*; use frame_support::traits::{Currency, ExistenceRequirement, StorageVersion}; use frame_support::traits::{OnUnbalanced, StoredMap}; use frame_system::pallet_prelude::*; -use pallet_provide_randomness::RequestId; use pallet_quota::traits::RefundFee; use pallet_transaction_payment::OnChargeTransaction; -use sp_core::H256; use sp_runtime::traits::{Convert, DispatchInfoOf, PostDispatchInfoOf, Saturating}; use sp_std::fmt::Debug; @@ -61,7 +59,6 @@ pub mod pallet { pub trait Config: frame_system::Config<AccountData = AccountData<Self::Balance, IdtyIdOf<Self>>> + pallet_balances::Config - + pallet_provide_randomness::Config<Currency = pallet_balances::Pallet<Self>> + pallet_transaction_payment::Config + pallet_treasury::Config<Currency = pallet_balances::Pallet<Self>> + pallet_quota::Config @@ -75,6 +72,8 @@ pub mod pallet { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// Type representing the weight of this pallet type WeightInfo: WeightInfo; + /// Handler for the unbalanced reduction when the requestor pays fees. + type OnUnbalanced: OnUnbalanced<pallet_balances::NegativeImbalance<Self>>; /// wrapped type type InnerOnChargeTransaction: OnChargeTransaction<Self>; /// type implementing refund behavior @@ -83,11 +82,6 @@ pub mod pallet { // STORAGE // - #[pallet::storage] - #[pallet::getter(fn pending_random_id_assignments)] - pub type PendingRandomIdAssignments<T: Config> = - StorageMap<_, Twox64Concat, RequestId, T::AccountId, OptionQuery>; - #[pallet::storage] #[pallet::getter(fn pending_new_accounts)] pub type PendingNewAccounts<T: Config> = @@ -120,7 +114,6 @@ pub mod pallet { frame_system::Account::<T>::mutate( pallet_treasury::Pallet::<T>::account_id(), |account| { - account.data.random_id = None; account.data.free = self.treasury_balance; account.providers = 1; }, @@ -139,20 +132,11 @@ pub mod pallet { ); // Classic accounts - for ( - account_id, - GenesisAccountData { - random_id, - balance, - idty_id, - }, - ) in &self.accounts - { + for (account_id, GenesisAccountData { balance, idty_id }) in &self.accounts { // if the balance is below existential deposit, the account must be an identity assert!(balance >= &T::ExistentialDeposit::get() || idty_id.is_some()); // mutate account frame_system::Account::<T>::mutate(account_id, |account| { - account.data.random_id = Some(*random_id); account.data.free = *balance; if idty_id.is_some() { account.data.linked_idty = *idty_id; @@ -179,8 +163,6 @@ pub mod pallet { who: T::AccountId, balance: T::Balance, }, - /// A random ID has been assigned to the account. - RandomIdAssigned { who: T::AccountId, random_id: H256 }, /// account linked to identity AccountLinked { who: T::AccountId, @@ -243,11 +225,6 @@ pub mod pallet { { if frame_system::Pallet::<T>::sufficients(&account_id) > 0 { // If the account is self-sufficient, it is exempt from account creation fees - let request_id = pallet_provide_randomness::Pallet::<T>::force_request( - pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo, - H256(T::AccountIdToSalt::convert(account_id.clone())), - ); - PendingRandomIdAssignments::<T>::insert(request_id, account_id); total_weight += <T as pallet::Config>::WeightInfo::on_initialize_sufficient( T::MaxNewAccountsPerBlock::get(), ); @@ -258,11 +235,7 @@ pub mod pallet { if account_data.free >= T::ExistentialDeposit::get() + price { // The account can pay the new account price, we should: // 1. Withdraw the "new account price" amount - // 2. Increment consumers to prevent the destruction of the account before - // the random id is assigned - // 3. Manage the funds collected - // 4. Submit random id generation request - // 5. Save the id of the random generation request. + // 2. Manage the funds collected let res = <pallet_balances::Pallet<T> as Currency<T::AccountId>>::withdraw( &account_id, price, @@ -274,18 +247,7 @@ pub mod pallet { "Cannot fail because we checked that the free balance was sufficient" ); if let Ok(imbalance) = res { - let res = - frame_system::Pallet::<T>::inc_consumers_without_limit(&account_id); - debug_assert!( - res.is_ok(), - "Cannot fail because any account with funds should have providers" - ); T::OnUnbalanced::on_unbalanced(imbalance); - let request_id = pallet_provide_randomness::Pallet::<T>::force_request( - pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo, - H256(T::AccountIdToSalt::convert(account_id.clone())), - ); - PendingRandomIdAssignments::<T>::insert(request_id, account_id); total_weight += <T as pallet::Config>::WeightInfo::on_initialize_with_balance( T::MaxNewAccountsPerBlock::get(), @@ -332,28 +294,6 @@ where } } -// implement on filled randomness -impl<T> pallet_provide_randomness::OnFilledRandomness for Pallet<T> -where - T: Config, -{ - fn on_filled_randomness(request_id: RequestId, randomness: H256) -> Weight { - if let Some(account_id) = PendingRandomIdAssignments::<T>::take(request_id) { - frame_system::Account::<T>::mutate(&account_id, |account| { - account.consumers = account.consumers.saturating_sub(1); - account.data.random_id = Some(randomness); - }); - Self::deposit_event(Event::RandomIdAssigned { - who: account_id, - random_id: randomness, - }); - <T as pallet::Config>::WeightInfo::on_filled_randomness_pending() - } else { - <T as pallet::Config>::WeightInfo::on_filled_randomness_no_pending() - } - } -} - // implement accountdata storedmap impl<T, AccountId, Balance> frame_support::traits::StoredMap<AccountId, pallet_balances::AccountData<Balance>> for Pallet<T> @@ -378,8 +318,7 @@ where + scale_info::TypeInfo, T: Config + frame_system::Config<AccountId = AccountId, AccountData = AccountData<Balance, IdtyIdOf<T>>> - + pallet_balances::Config<Balance = Balance> - + pallet_provide_randomness::Config, + + pallet_balances::Config<Balance = Balance>, { fn get(k: &AccountId) -> pallet_balances::AccountData<Balance> { frame_system::Account::<T>::get(k).data.into() @@ -399,7 +338,7 @@ where let result = f(&mut some_data)?; let is_providing = some_data.is_some(); match (was_providing, is_providing) { - // the account has just been created, increment its provider and request a random_id + // the account has just been created, increment its provider (false, true) => { frame_system::Pallet::<T>::inc_providers(account_id); PendingNewAccounts::<T>::insert(account_id, ()); diff --git a/pallets/duniter-account/src/types.rs b/pallets/duniter-account/src/types.rs index 77553c4db..c44cb0873 100644 --- a/pallets/duniter-account/src/types.rs +++ b/pallets/duniter-account/src/types.rs @@ -17,15 +17,11 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::pallet_prelude::*; use scale_info::TypeInfo; -use sp_core::H256; use sp_runtime::traits::Zero; // see `struct AccountData` for details in substrate code #[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] // Default, pub struct AccountData<Balance, IdtyId> { - /// A random identifier that can not be chosen by the user - // this intends to be used as a robust identification system - pub(super) random_id: Option<H256>, // see Substrate AccountData pub(super) free: Balance, // see Substrate AccountData @@ -43,7 +39,6 @@ impl<Balance: Zero, IdtyId> Default for AccountData<Balance, IdtyId> { fn default() -> Self { Self { linked_idty: None, - random_id: None, free: Balance::zero(), reserved: Balance::zero(), fee_frozen: Balance::zero(), @@ -89,7 +84,6 @@ impl<Balance: Zero, IdtyId> From<AccountData<Balance, IdtyId>> )] #[serde(deny_unknown_fields)] pub struct GenesisAccountData<Balance, IdtyId> { - pub random_id: H256, pub balance: Balance, pub idty_id: Option<IdtyId>, } diff --git a/pallets/duniter-account/src/weights.rs b/pallets/duniter-account/src/weights.rs index d8ccf6bb5..f4bbe1588 100644 --- a/pallets/duniter-account/src/weights.rs +++ b/pallets/duniter-account/src/weights.rs @@ -23,8 +23,6 @@ pub trait WeightInfo { fn on_initialize_sufficient(i: u32) -> Weight; fn on_initialize_with_balance(i: u32) -> Weight; fn on_initialize_no_balance(i: u32) -> Weight; - fn on_filled_randomness_pending() -> Weight; - fn on_filled_randomness_no_pending() -> Weight; fn unlink_identity() -> Weight; } @@ -43,13 +41,7 @@ impl WeightInfo for () { } // Storage: Account PendingNewAccounts (r:1 w:0) - // Storage: ProvideRandomness RequestIdProvider (r:1 w:1) - // Storage: ProvideRandomness RequestsIds (r:1 w:1) - // Storage: ProvideRandomness CounterForRequestsIds (r:1 w:1) // Storage: Babe EpochIndex (r:1 w:0) - // Storage: ProvideRandomness NexEpochHookIn (r:1 w:0) - // Storage: ProvideRandomness RequestsReadyAtEpoch (r:1 w:1) - // Storage: Account PendingRandomIdAssignments (r:0 w:1) /// The range of component `i` is `[0, 1]`. fn on_initialize_sufficient(i: u32) -> Weight { // Minimum execution time: 12_958 nanoseconds. @@ -62,13 +54,7 @@ impl WeightInfo for () { } // Storage: Account PendingNewAccounts (r:1 w:0) - // Storage: ProvideRandomness RequestIdProvider (r:1 w:1) - // Storage: ProvideRandomness RequestsIds (r:1 w:1) - // Storage: ProvideRandomness CounterForRequestsIds (r:1 w:1) // Storage: Babe EpochIndex (r:1 w:0) - // Storage: ProvideRandomness NexEpochHookIn (r:1 w:0) - // Storage: ProvideRandomness RequestsReadyAtEpoch (r:1 w:1) - // Storage: Account PendingRandomIdAssignments (r:0 w:1) /// The range of component `i` is `[0, 1]`. fn on_initialize_with_balance(i: u32) -> Weight { // Minimum execution time: 12_965 nanoseconds. @@ -90,19 +76,4 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } - - // Storage: Account PendingRandomIdAssignments (r:1 w:1) - fn on_filled_randomness_pending() -> Weight { - // Minimum execution time: 66_963 nanoseconds. - Weight::from_parts(69_757_000 as u64, 0) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - - // Storage: Account PendingRandomIdAssignments (r:1 w:0) - fn on_filled_randomness_no_pending() -> Weight { - // Minimum execution time: 16_088 nanoseconds. - Weight::from_parts(27_963_000 as u64, 0) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - } } diff --git a/resources/metadata.scale b/resources/metadata.scale index f963c977883e0d240b34d87f1b77b461ffcdee71..26b30e4664d0ced1c6e5a8bb586ccfd1ace71403 100644 GIT binary patch delta 11514 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 delta 11613 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 diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index d1893985c..a2262503e 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -108,6 +108,7 @@ type RuntimeTask = (); // does currency adapter in any case, but adds "refund with quota" feature type InnerOnChargeTransaction = CurrencyAdapter<Balances, HandleFees>; type Refund = Quota; + type OnUnbalanced = Treasury; } // QUOTA // @@ -347,7 +348,7 @@ type RuntimeFreezeReason = (); type GetCurrentEpochIndex = GetCurrentEpochIndex<Self>; type MaxRequests = frame_support::traits::ConstU32<100>; type RequestPrice = frame_support::traits::ConstU64<2_000>; - type OnFilledRandomness = Account; + type OnFilledRandomness = (); type OnUnbalanced = Treasury; type ParentBlockRandomness = pallet_babe::ParentBlockRandomness<Self>; type RandomnessFromOneEpochAgo = pallet_babe::RandomnessFromOneEpochAgo<Self>; diff --git a/runtime/common/src/weights/pallet_duniter_account.rs b/runtime/common/src/weights/pallet_duniter_account.rs index 797603e66..3dcee20a0 100644 --- a/runtime/common/src/weights/pallet_duniter_account.rs +++ b/runtime/common/src/weights/pallet_duniter_account.rs @@ -61,20 +61,8 @@ impl<T: frame_system::Config> pallet_duniter_account::WeightInfo for WeightInfo< } /// Storage: `Account::PendingNewAccounts` (r:1 w:1) /// Proof: `Account::PendingNewAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::RequestIdProvider` (r:1 w:1) - /// Proof: `ProvideRandomness::RequestIdProvider` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::RequestsIds` (r:1 w:1) - /// Proof: `ProvideRandomness::RequestsIds` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::CounterForRequestsIds` (r:1 w:1) - /// Proof: `ProvideRandomness::CounterForRequestsIds` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Babe::EpochIndex` (r:1 w:0) /// Proof: `Babe::EpochIndex` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `ProvideRandomness::NexEpochHookIn` (r:1 w:0) - /// Proof: `ProvideRandomness::NexEpochHookIn` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::RequestsReadyAtEpoch` (r:1 w:1) - /// Proof: `ProvideRandomness::RequestsReadyAtEpoch` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Account::PendingRandomIdAssignments` (r:0 w:1) - /// Proof: `Account::PendingRandomIdAssignments` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[0, 1]`. fn on_initialize_sufficient(i: u32, ) -> Weight { // Proof Size summary in bytes: @@ -94,20 +82,8 @@ impl<T: frame_system::Config> pallet_duniter_account::WeightInfo for WeightInfo< /// Proof: `Account::PendingNewAccounts` (`max_values`: None, `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: `ProvideRandomness::RequestIdProvider` (r:1 w:1) - /// Proof: `ProvideRandomness::RequestIdProvider` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::RequestsIds` (r:1 w:1) - /// Proof: `ProvideRandomness::RequestsIds` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::CounterForRequestsIds` (r:1 w:1) - /// Proof: `ProvideRandomness::CounterForRequestsIds` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Babe::EpochIndex` (r:1 w:0) /// Proof: `Babe::EpochIndex` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `ProvideRandomness::NexEpochHookIn` (r:1 w:0) - /// Proof: `ProvideRandomness::NexEpochHookIn` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ProvideRandomness::RequestsReadyAtEpoch` (r:1 w:1) - /// Proof: `ProvideRandomness::RequestsReadyAtEpoch` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Account::PendingRandomIdAssignments` (r:0 w:1) - /// Proof: `Account::PendingRandomIdAssignments` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[0, 1]`. fn on_initialize_with_balance(i: u32, ) -> Weight { // Proof Size summary in bytes: @@ -139,27 +115,4 @@ impl<T: frame_system::Config> pallet_duniter_account::WeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 74).saturating_mul(i.into())) } - /// Storage: `Account::PendingRandomIdAssignments` (r:1 w:1) - /// Proof: `Account::PendingRandomIdAssignments` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn on_filled_randomness_pending() -> Weight { - // Proof Size summary in bytes: - // Measured: `116` - // Estimated: `3581` - // Minimum execution time: 20_089_000 picoseconds. - Weight::from_parts(21_261_000, 0) - .saturating_add(Weight::from_parts(0, 3581)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Account::PendingRandomIdAssignments` (r:1 w:0) - /// Proof: `Account::PendingRandomIdAssignments` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn on_filled_randomness_no_pending() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3507` - // Minimum execution time: 4_511_000 picoseconds. - Weight::from_parts(4_829_000, 0) - .saturating_add(Weight::from_parts(0, 3507)) - .saturating_add(T::DbWeight::get().reads(1)) - } } diff --git a/runtime/gdev/tests/common/mod.rs b/runtime/gdev/tests/common/mod.rs index 249653ec2..95933e525 100644 --- a/runtime/gdev/tests/common/mod.rs +++ b/runtime/gdev/tests/common/mod.rs @@ -73,7 +73,6 @@ impl ExtBuilder { get_account_id_from_seed::<sr25519::Public>(NAMES[i]), GenesisAccountData { balance: 0, - random_id: H256([i as u8; 32]), idty_id: Some(i as u32 + 1), }, ) @@ -123,7 +122,6 @@ impl ExtBuilder { self.initial_accounts .entry(account_id.clone()) .or_insert(GenesisAccountData { - random_id: H256(account_id.into()), ..Default::default() }) .balance = balance; diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs index 4624094d4..bd6332369 100644 --- a/runtime/gdev/tests/integration_tests.rs +++ b/runtime/gdev/tests/integration_tests.rs @@ -1056,42 +1056,25 @@ fn test_create_new_account() { // 100 initial + 300 deposit assert_eq!(Balances::free_balance(Treasury::account_id()), 400); - // A random id request should be registered - assert_eq!( - Account::pending_random_id_assignments(0), - Some(AccountKeyring::Eve.to_account_id()) - ); - - // We can't remove the account until the random id is assigned run_to_block(4); - // can not remove using transfer - assert_noop!( - Balances::transfer_allow_death( - frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), - MultiAddress::Id(AccountKeyring::Alice.to_account_id()), - 200 - ), - sp_runtime::DispatchError::ConsumerRemaining, - ); + // can remove an account using transfer + assert_ok!(Balances::transfer_allow_death( + frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + MultiAddress::Id(AccountKeyring::Alice.to_account_id()), + 200 + )); + // Account reaped assert_eq!( Balances::free_balance(AccountKeyring::Eve.to_account_id()), - 200 - ); - // can not remove using transfer_all either - assert_noop!( - Balances::transfer_all( - frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), - MultiAddress::Id(AccountKeyring::Alice.to_account_id()), - false - ), - sp_runtime::DispatchError::ConsumerRemaining, + 0 ); - // Transfer failed, so free_balance remains the same assert_eq!( - Balances::free_balance(AccountKeyring::Eve.to_account_id()), - 200 + frame_system::Pallet::<Runtime>::get(&AccountKeyring::Eve.to_account_id()), + pallet_duniter_account::AccountData::default() ); - // TODO detail account removal + System::assert_has_event(RuntimeEvent::System(frame_system::Event::KilledAccount { + account: AccountKeyring::Eve.to_account_id(), + })); }); } @@ -1118,12 +1101,6 @@ fn test_create_new_idty() { run_to_block(3); let events = System::events(); assert_eq!(events.len(), 0); - - // A random id request should be registered - assert_eq!( - Account::pending_random_id_assignments(0), - Some(AccountKeyring::Eve.to_account_id()) - ); }); } @@ -1156,25 +1133,19 @@ fn test_create_new_idty_without_founds() { let events = System::events(); assert_eq!(events.len(), 0); - // Deposit some founds on the identity account, - // this should trigger the random id assignemt + // Deposit some founds on the identity account assert_ok!(Balances::transfer_allow_death( frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), MultiAddress::Id(AccountKeyring::Eve.to_account_id()), 200 )); - // At next block, nothing should be preleved, - // and a random id request should be registered + // At next block, nothing should be preleved run_to_block(4); assert_eq!( Balances::free_balance(AccountKeyring::Eve.to_account_id()), 200 ); - assert_eq!( - Account::pending_random_id_assignments(0), - Some(AccountKeyring::Eve.to_account_id()) - ); }); } -- GitLab