diff --git a/CHANGELOG.md b/CHANGELOG.md index f1cf9fe3ce..7275a1e43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Fixed + +- Kusama People: clear requested judgements that do not have corresponding deposits reserved ([polkadot-fellows/runtimes#339](https://github.com/polkadot-fellows/runtimes/pull/339)) + ### Removed - Removed Identity-related code from Kusama Relay Chain ([polkadot-fellows/runtimes#315](https://github.com/polkadot-fellows/runtimes/pull/315)) diff --git a/system-parachains/people/people-kusama/src/identity_ops.rs b/system-parachains/people/people-kusama/src/identity_ops.rs new file mode 100644 index 0000000000..1c0a5a4352 --- /dev/null +++ b/system-parachains/people/people-kusama/src/identity_ops.rs @@ -0,0 +1,237 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +pub mod pallet_identity_ops { + use crate::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use pallet_identity::Judgement; + + /// Weight information for extrinsics in this pallet. + pub trait WeightInfo { + /// Weight for clearing judgement. + fn clear_judgement() -> Weight; + } + + type IdentityPallet = pallet_identity::Pallet; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::error] + pub enum Error { + /// No judgement to clear. + NotFound, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The invalid judgements have been cleared. + JudgementsCleared { target: AccountId }, + } + + pub(crate) type Identity = ( + pallet_identity::Registration< + ::Balance, + ::MaxRegistrars, + ::IdentityInformation, + >, + Option::MaxUsernameLength>>, + ); + + /// Alias for `IdentityOf` from `pallet_identity`. + #[frame_support::storage_alias(pallet_name)] + pub(crate) type IdentityOf = + StorageMap; + + #[pallet::call] + impl Pallet { + /// Clear requested judgements that do not have a corresponding deposit reserved. + /// + /// This is successful only if the `target` account has judgements to clear. The transaction + /// fee is refunded to the caller if successful. + #[pallet::call_index(0)] + #[pallet::weight(weights::pallet_identity_ops::WeightInfo::::clear_judgement())] + pub fn clear_judgement( + _origin: OriginFor, + target: AccountId, + ) -> DispatchResultWithPostInfo { + let identity = IdentityPallet::identity(&target).ok_or(Error::::NotFound)?; + let (removed, identity) = Self::do_clear_judgement(&target, identity); + ensure!(removed > 0, Error::::NotFound); + + IdentityOf::insert(&target, identity); + + Self::deposit_event(Event::JudgementsCleared { target }); + + Ok(Pays::No.into()) + } + } + + impl Pallet { + fn do_clear_judgement(account_id: &AccountId, mut identity: Identity) -> (u32, Identity) { + // `subs_of`'s query kind is value option, if non subs the deposit is zero. + let (subs_deposit, _) = IdentityPallet::subs_of(account_id); + // deposit without deposits for judgement request. + let identity_deposit = identity.0.deposit.saturating_add(subs_deposit); + // total reserved balance. + let reserved = Balances::reserved_balance(account_id); + // expected deposit with judgement deposits. + let mut expected_total_deposit = identity_deposit; + // count before cleaning up the judgements. + let judgements_count = identity.0.judgements.len(); + + identity.0.judgements.retain(|(_, judgement)| { + if let Judgement::FeePaid(deposit) = judgement { + expected_total_deposit = expected_total_deposit.saturating_add(*deposit); + reserved >= expected_total_deposit && *deposit > 0 + } else { + true + } + }); + + ((judgements_count - identity.0.judgements.len()) as u32, identity) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + use crate::Balance; + use sp_core::crypto::Ss58Codec; + + let mut total_invalid_identity_count = 0; + let mut total_invalid_judgement_count = 0; + let mut identity_count = 0; + IdentityOf::iter().for_each(|(account_id, registration)| { + let (identity, username) = registration; + + let (subs_deposit, _) = IdentityPallet::subs_of(&account_id); + let deposit_wo_judgement = identity.deposit.saturating_add(subs_deposit); + let reserved = Balances::reserved_balance(&account_id); + + let (invalid_judgement_count, expected_total_deposit) = + identity.judgements.iter().fold( + (0, deposit_wo_judgement), + |(count, total_deposit): (u32, Balance), (_, judgement)| { + if let Judgement::FeePaid(deposit) = judgement { + if total_deposit.saturating_add(*deposit) > reserved || + *deposit == 0 + { + return (count + 1, total_deposit.saturating_add(*deposit)) + } + } + (count, total_deposit) + }, + ); + + if expected_total_deposit >= reserved && invalid_judgement_count > 0 { + total_invalid_identity_count += 1; + total_invalid_judgement_count += invalid_judgement_count; + + log::info!( + "account with invalid state: {:?}, expected reserve at least: {:?}, actual: {:?}, invalid judgements: {:?}", + account_id.clone().to_ss58check_with_version(2u8.into()), + expected_total_deposit, + reserved, + invalid_judgement_count, + ); + + assert_eq!( + invalid_judgement_count, + Self::do_clear_judgement(&account_id, (identity, username)).0 + ); + + if deposit_wo_judgement != reserved { + log::warn!( + "unexpected state: {:?}, deposit w/o judgement: {:?}, not equal to the total reserved: {:?}", + account_id.clone().to_ss58check_with_version(2u8.into()), + deposit_wo_judgement, + reserved, + ); + } + } else { + assert_eq!(0, Self::do_clear_judgement(&account_id, (identity, username)).0); + } + if deposit_wo_judgement > reserved { + log::warn!( + "unexpected state: {:?}, deposit w/o judgement: {:?}, greater than the total reserved: {:?}", + account_id.clone().to_ss58check_with_version(2u8.into()), + deposit_wo_judgement, + reserved, + ); + } + identity_count += 1; + }); + + log::info!("total identities processed: {:?}", identity_count); + log::info!("invalid identities: {:?}", total_invalid_identity_count); + log::info!("invalid judgements: {:?}", total_invalid_judgement_count); + + Ok(()) + } + } +} + +#[cfg(feature = "runtime-benchmarks")] +#[frame_benchmarking::v2::benchmarks(where T: pallet_identity_ops::Config)] +mod benchmarks { + use crate::{people::IdentityInfo, *}; + use frame_benchmarking::BenchmarkError; + use frame_system::RawOrigin; + use pallet_identity::{IdentityInformationProvider, Judgement}; + use pallet_identity_ops::{Event, Identity, *}; + use parachains_common::{AccountId, Balance}; + use sp_core::Get; + use sp_runtime::traits::One; + + #[benchmark] + fn clear_judgement() -> Result<(), BenchmarkError> { + let max_registrars = + <::MaxRegistrars as Get>::get(); + let mut judgements = Vec::<(u32, Judgement)>::new(); + for i in 0..max_registrars { + judgements.push((i, Judgement::FeePaid(Balance::one()))); + } + let identity: Identity = ( + pallet_identity::Registration { + deposit: Balance::one(), + judgements: judgements.try_into().unwrap(), + info: IdentityInfo::create_identity_info(), + }, + None, + ); + + let target: AccountId = [1u8; 32].into(); + + IdentityOf::insert(&target, identity); + + #[extrinsic_call] + _(RawOrigin::None, target.clone()); + + crate::System::assert_last_event(Event::::JudgementsCleared { target }.into()); + + Ok(()) + } +} diff --git a/system-parachains/people/people-kusama/src/lib.rs b/system-parachains/people/people-kusama/src/lib.rs index 1e5b582e2f..d0e57af886 100644 --- a/system-parachains/people/people-kusama/src/lib.rs +++ b/system-parachains/people/people-kusama/src/lib.rs @@ -18,6 +18,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +mod identity_ops; pub mod people; mod weights; pub mod xcm_config; @@ -41,6 +42,7 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; +use identity_ops::pallet_identity_ops; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use parachains_common::{ message_queue::{NarrowOriginToSibling, ParaIdToSibling}, @@ -512,6 +514,10 @@ impl pallet_utility::Config for Runtime { type WeightInfo = weights::pallet_utility::WeightInfo; } +impl pallet_identity_ops::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -546,6 +552,9 @@ construct_runtime!( // The main stage. Identity: pallet_identity = 50, + + // Identity operations pallet. + IdentityOps: pallet_identity_ops = 247, } ); @@ -568,6 +577,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsiscsBenchmark::] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [pallet_identity_ops, IdentityOps] ); } diff --git a/system-parachains/people/people-kusama/src/weights/mod.rs b/system-parachains/people/people-kusama/src/weights/mod.rs index 34658af9ee..0eee5fa387 100644 --- a/system-parachains/people/people-kusama/src/weights/mod.rs +++ b/system-parachains/people/people-kusama/src/weights/mod.rs @@ -23,6 +23,7 @@ pub mod frame_system; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_identity; +pub mod pallet_identity_ops; pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_proxy; diff --git a/system-parachains/people/people-kusama/src/weights/pallet_identity_ops.rs b/system-parachains/people/people-kusama/src/weights/pallet_identity_ops.rs new file mode 100644 index 0000000000..f26f114e49 --- /dev/null +++ b/system-parachains/people/people-kusama/src/weights/pallet_identity_ops.rs @@ -0,0 +1,66 @@ +// Copyright (C) Parity Technologies and the various Polkadot contributors, see Contributions.md +// for a list of specific contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_identity_ops` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-06-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `ggwpez-ref-hw`, CPU: `AMD EPYC 7232P 8-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("./people-kusama-chain-spec.json")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot +// benchmark +// pallet +// --chain=./people-kusama-chain-spec.json +// --steps=50 +// --repeat=20 +// --pallet=pallet_identity_ops +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./people-kusama-weights/ +// --header=./file_header.txt + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_identity_ops`. +pub struct WeightInfo(PhantomData); +impl crate::pallet_identity_ops::WeightInfo for WeightInfo { + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(838), added: 3313, mode: `MaxEncodedLen`) + /// Storage: `Identity::SubsOf` (r:1 w:0) + /// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn clear_judgement() -> Weight { + // Proof Size summary in bytes: + // Measured: `828` + // Estimated: `6723` + // Minimum execution time: 22_170_000 picoseconds. + Weight::from_parts(22_770_000, 0) + .saturating_add(Weight::from_parts(0, 6723)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } +}