Unique Usernames in Identity Pallet (#2651)

This PR allows _username authorities_ to issue unique usernames that
correspond with an account. It also provides two-way lookup, that is
from `AccountId` to a single, "primary" `Username` (alongside
`Registration`) and multiple unique `Username`s to an `AccountId`.

Key features:

- Username Authorities added (and removed) via privileged origin.
- Authorities have a `suffix` and an `allocation`. They can grant up to
`allocation` usernames. Their `suffix` will be appended to the usernames
that they issue. A suffix may be up to 7 characters long.
- Users can ask an authority to grant them a username. This will take
the form `myusername.suffix`. The entire name (including suffix) must be
less than or equal to 32 alphanumeric characters.
- Users can approve a username for themselves in one of two ways (that
is, authorities cannot grant them arbitrarily):
- Pre-sign the entire username (including suffix) with a secret key that
corresponds to their `AccountId` (for keyed accounts, obviously); or
- Accept the username after it has been granted by an authority (it will
be queued until accepted) (for non-keyed accounts like pure proxies or
multisigs).
- The system does not require any funds or deposits. Users without an
identity will be given a default one (presumably all fields set to
`None`). If they update this info, they will need to place the normal
storage deposit.
- If a user does not have any username, their first one will be set as
`Primary`, and their `AccountId` will map to that one. If they get
subsequent usernames, they can choose which one to be their primary via
`set_primary_username`.
- There are some state cleanup functions to remove expired usernames
that have not been accepted and dangling usernames whose owners have
called `clear_identity`.

TODO:

- [x] Add migration to runtimes
- [x] Probably do off-chain migration into People Chain genesis
- [x] Address a few TODO questions in code (please review)

---------

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Dónal Murray <donal.murray@parity.io>
This commit is contained in:
joe petrowski
2024-01-10 11:30:00 +01:00
committed by GitHub
parent a4195326b9
commit d1f678c0ec
22 changed files with 2656 additions and 331 deletions
Generated
+2
View File
@@ -10047,11 +10047,13 @@ dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
"pallet-balances",
"parity-scale-codec",
"scale-info",
"sp-core",
"sp-io",
"sp-keystore",
"sp-runtime",
"sp-std 8.0.0",
]
@@ -22,9 +22,12 @@ use frame_support::{
RuntimeDebugNoBound,
};
use pallet_identity::{Data, IdentityInformationProvider};
use parachains_common::impls::ToParentTreasury;
use parachains_common::{impls::ToParentTreasury, DAYS};
use scale_info::TypeInfo;
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
use sp_runtime::{
traits::{AccountIdConversion, Verify},
RuntimeDebug,
};
use sp_std::prelude::*;
parameter_types! {
@@ -51,6 +54,12 @@ impl pallet_identity::Config for Runtime {
type Slashed = ToParentTreasury<RelayTreasuryAccount, LocationToAccountId, Runtime>;
type ForceOrigin = EnsureRoot<Self::AccountId>;
type RegistrarOrigin = EnsureRoot<Self::AccountId>;
type OffchainSignature = Signature;
type SigningPublicKey = <Signature as Verify>::Signer;
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
}
@@ -84,7 +93,6 @@ pub enum IdentityField {
TypeInfo,
)]
#[codec(mel_bound())]
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
pub struct IdentityInfo {
/// A reasonable display name for the controller of the account. This should be whatever the
/// account is typically known as and should not be confusable with other entities, given
@@ -202,3 +210,21 @@ impl IdentityInfo {
res
}
}
/// A `Default` identity. This is given to users who get a username but have not set an identity.
impl Default for IdentityInfo {
fn default() -> Self {
IdentityInfo {
display: Data::None,
legal: Data::None,
web: Data::None,
matrix: Data::None,
email: Data::None,
pgp_fingerprint: None,
image: Data::None,
twitter: Data::None,
github: Data::None,
discord: Data::None,
}
}
}
@@ -312,4 +312,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn add_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 13_873_000 picoseconds.
Weight::from_parts(13_873_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn remove_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 10_653_000 picoseconds.
Weight::from_parts(10_653_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_username_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `80`
// Estimated: `11037`
// Minimum execution time: 75_928_000 picoseconds.
Weight::from_parts(75_928_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
fn accept_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `11037`
// Minimum execution time: 38_157_000 picoseconds.
Weight::from_parts(38_157_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
fn remove_expired_approval() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `3542`
// Minimum execution time: 46_821_000 picoseconds.
Weight::from_parts(46_821_000, 0)
.saturating_add(Weight::from_parts(0, 3542))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_primary_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `247`
// Estimated: `11037`
// Minimum execution time: 22_515_000 picoseconds.
Weight::from_parts(22_515_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:0)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn remove_dangling_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `126`
// Estimated: `11037`
// Minimum execution time: 15_997_000 picoseconds.
Weight::from_parts(15_997_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
}
@@ -22,9 +22,12 @@ use frame_support::{
RuntimeDebugNoBound,
};
use pallet_identity::{Data, IdentityInformationProvider};
use parachains_common::impls::ToParentTreasury;
use parachains_common::{impls::ToParentTreasury, DAYS};
use scale_info::TypeInfo;
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
use sp_runtime::{
traits::{AccountIdConversion, Verify},
RuntimeDebug,
};
use sp_std::prelude::*;
parameter_types! {
@@ -51,6 +54,12 @@ impl pallet_identity::Config for Runtime {
type Slashed = ToParentTreasury<RelayTreasuryAccount, LocationToAccountId, Runtime>;
type ForceOrigin = EnsureRoot<Self::AccountId>;
type RegistrarOrigin = EnsureRoot<Self::AccountId>;
type OffchainSignature = Signature;
type SigningPublicKey = <Signature as Verify>::Signer;
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
}
@@ -84,7 +93,6 @@ pub enum IdentityField {
TypeInfo,
)]
#[codec(mel_bound())]
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
pub struct IdentityInfo {
/// A reasonable display name for the controller of the account. This should be whatever it is
/// that it is typically known as and should not be confusable with other entities, given
@@ -202,3 +210,21 @@ impl IdentityInfo {
res
}
}
/// A `Default` identity. This is given to users who get a username but have not set an identity.
impl Default for IdentityInfo {
fn default() -> Self {
IdentityInfo {
display: Data::None,
legal: Data::None,
web: Data::None,
matrix: Data::None,
email: Data::None,
pgp_fingerprint: None,
image: Data::None,
twitter: Data::None,
github: Data::None,
discord: Data::None,
}
}
}
@@ -312,4 +312,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn add_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 13_873_000 picoseconds.
Weight::from_parts(13_873_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn remove_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 10_653_000 picoseconds.
Weight::from_parts(10_653_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_username_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `80`
// Estimated: `11037`
// Minimum execution time: 75_928_000 picoseconds.
Weight::from_parts(75_928_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
fn accept_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `11037`
// Minimum execution time: 38_157_000 picoseconds.
Weight::from_parts(38_157_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
fn remove_expired_approval() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `3542`
// Minimum execution time: 46_821_000 picoseconds.
Weight::from_parts(46_821_000, 0)
.saturating_add(Weight::from_parts(0, 3542))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_primary_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `247`
// Estimated: `11037`
// Minimum execution time: 22_515_000 picoseconds.
Weight::from_parts(22_515_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:0)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn remove_dangling_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `126`
// Estimated: `11037`
// Minimum execution time: 15_997_000 picoseconds.
Weight::from_parts(15_997_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
}
@@ -45,9 +45,9 @@ use sp_io::TestExternalities;
use sp_keyring::Sr25519Keyring;
use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup, One},
traits::{BlakeTwo256, IdentityLookup, One, Verify},
transaction_validity::TransactionPriority,
AccountId32, BuildStorage,
AccountId32, BuildStorage, MultiSignature,
};
use sp_std::sync::Arc;
@@ -293,6 +293,12 @@ impl pallet_identity::Config for Test {
type MaxRegistrars = ConstU32<20>;
type RegistrarOrigin = EnsureRoot<AccountId>;
type ForceOrigin = EnsureRoot<AccountId>;
type OffchainSignature = MultiSignature;
type SigningPublicKey = <MultiSignature as Verify>::Signer;
type UsernameAuthorityOrigin = EnsureRoot<AccountId>;
type PendingUsernameExpiration = ConstU32<100>;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = ();
}
+12
View File
@@ -668,6 +668,12 @@ impl pallet_identity::Config for Runtime {
type Slashed = Treasury;
type ForceOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
type RegistrarOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
type OffchainSignature = Signature;
type SigningPublicKey = <Signature as Verify>::Signer;
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
}
@@ -1620,6 +1626,9 @@ pub mod migrations {
}
}
// We don't have a limit in the Relay Chain.
const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX;
/// Unreleased migrations. Add new ones here:
pub type Unreleased = (
pallet_society::migrations::MigrateToV2<Runtime, (), ()>,
@@ -1655,6 +1664,9 @@ pub mod migrations {
// Remove `im-online` pallet on-chain storage
frame_support::migrations::RemovePallet<ImOnlinePalletName, <Runtime as frame_system::Config>::DbWeight>,
// Migrate Identity pallet for Usernames
pallet_identity::migration::versioned::V0ToV1<Runtime, IDENTITY_MIGRATION_KEY_LIMIT>,
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
// This needs to come after the `parachains_configuration` above as we are reading the configuration.
coretime::migration::MigrateToCoretime<Runtime, crate::xcm_config::XcmRouter, GetLegacyLeaseImpl>,
@@ -334,4 +334,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn add_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 13_873_000 picoseconds.
Weight::from_parts(13_873_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn remove_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 10_653_000 picoseconds.
Weight::from_parts(10_653_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_username_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `80`
// Estimated: `11037`
// Minimum execution time: 75_928_000 picoseconds.
Weight::from_parts(75_928_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
fn accept_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `11037`
// Minimum execution time: 38_157_000 picoseconds.
Weight::from_parts(38_157_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
fn remove_expired_approval() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `3542`
// Minimum execution time: 46_821_000 picoseconds.
Weight::from_parts(46_821_000, 0)
.saturating_add(Weight::from_parts(0, 3542))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_primary_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `247`
// Estimated: `11037`
// Minimum execution time: 22_515_000 picoseconds.
Weight::from_parts(22_515_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:0)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn remove_dangling_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `126`
// Estimated: `11037`
// Minimum execution time: 15_997_000 picoseconds.
Weight::from_parts(15_997_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
}
+11
View File
@@ -878,6 +878,12 @@ impl pallet_identity::Config for Runtime {
type MaxRegistrars = MaxRegistrars;
type ForceOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
type RegistrarOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
type OffchainSignature = Signature;
type SigningPublicKey = <Signature as Verify>::Signer;
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
}
@@ -1626,6 +1632,9 @@ pub mod migrations {
}
}
// We don't have a limit in the Relay Chain.
const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX;
/// Unreleased migrations. Add new ones here:
pub type Unreleased = (
parachains_configuration::migration::v7::MigrateToV7<Runtime>,
@@ -1644,6 +1653,8 @@ pub mod migrations {
ImOnlinePalletName,
<Runtime as frame_system::Config>::DbWeight,
>,
// Migrate Identity pallet for Usernames
pallet_identity::migration::versioned::V0ToV1<Runtime, IDENTITY_MIGRATION_KEY_LIMIT>,
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
);
}
@@ -338,4 +338,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn add_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 13_873_000 picoseconds.
Weight::from_parts(13_873_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn remove_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 10_653_000 picoseconds.
Weight::from_parts(10_653_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_username_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `80`
// Estimated: `11037`
// Minimum execution time: 75_928_000 picoseconds.
Weight::from_parts(75_928_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
fn accept_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `11037`
// Minimum execution time: 38_157_000 picoseconds.
Weight::from_parts(38_157_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
fn remove_expired_approval() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `3542`
// Minimum execution time: 46_821_000 picoseconds.
Weight::from_parts(46_821_000, 0)
.saturating_add(Weight::from_parts(0, 3542))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_primary_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `247`
// Estimated: `11037`
// Minimum execution time: 22_515_000 picoseconds.
Weight::from_parts(22_515_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:0)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn remove_dangling_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `126`
// Estimated: `11037`
// Minimum execution time: 15_997_000 picoseconds.
Weight::from_parts(15_997_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
}
+12
View File
@@ -0,0 +1,12 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
title: Unique Usernames for Identity
doc:
- audience: Runtime User
description: |
Adds the ability to add unique usernames for an account with reverse lookup (as in `AccountId`
to `Username` and `Username` to `AccountId`).
crates: [ ]
+1 -1
View File
@@ -64,7 +64,7 @@ impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
fn has_good_judgement(who: &AccountId) -> bool {
use pallet_identity::Judgement;
crate::Identity::identity(who)
.map(|registration| registration.judgements)
.map(|(registration, _)| registration.judgements)
.map_or(false, |judgements| {
judgements
.iter()
+10
View File
@@ -1500,6 +1500,12 @@ impl pallet_identity::Config for Runtime {
type Slashed = Treasury;
type ForceOrigin = EnsureRootOrHalfCouncil;
type RegistrarOrigin = EnsureRootOrHalfCouncil;
type OffchainSignature = Signature;
type SigningPublicKey = <Signature as traits::Verify>::Signer;
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = pallet_identity::weights::SubstrateWeight<Runtime>;
}
@@ -2208,6 +2214,9 @@ pub type Executive = frame_executive::Executive<
Migrations,
>;
// We don't have a limit in the Relay Chain.
const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX;
// All migrations executed on runtime upgrade as a nested tuple of types implementing
// `OnRuntimeUpgrade`. Note: These are examples and do not need to be run directly
// after the genesis block.
@@ -2215,6 +2224,7 @@ type Migrations = (
pallet_nomination_pools::migration::versioned::V6ToV7<Runtime>,
pallet_alliance::migration::Migration<Runtime>,
pallet_contracts::Migration<Runtime>,
pallet_identity::migration::versioned::V0ToV1<Runtime, IDENTITY_MIGRATION_KEY_LIMIT>,
);
type EventRecord = frame_system::EventRecord<
+31 -2
View File
@@ -19,7 +19,10 @@
pub use sp_core::H256;
use sp_runtime::traits::Hash;
pub use sp_runtime::{traits::BlakeTwo256, BuildStorage};
pub use sp_runtime::{
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Lazy, Verify},
BuildStorage, MultiSignature,
};
use sp_std::convert::{TryFrom, TryInto};
pub use frame_support::{
@@ -101,6 +104,7 @@ parameter_types! {
pub const MaxSubAccounts: u32 = 2;
pub const MaxAdditionalFields: u32 = 2;
pub const MaxRegistrars: u32 = 20;
pub const PendingUsernameExpiration: u64 = 100;
}
ord_parameter_types! {
pub const One: u64 = 1;
@@ -124,9 +128,34 @@ impl pallet_identity::Config for Test {
type Slashed = ();
type RegistrarOrigin = EnsureOneOrRoot;
type ForceOrigin = EnsureTwoOrRoot;
type OffchainSignature = AccountU64;
type SigningPublicKey = AccountU64;
type UsernameAuthorityOrigin = EnsureOneOrRoot;
type PendingUsernameExpiration = PendingUsernameExpiration;
type MaxSuffixLength = ConstU32<7>;
type MaxUsernameLength = ConstU32<32>;
type WeightInfo = ();
}
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)]
pub struct AccountU64(u64);
impl IdentifyAccount for AccountU64 {
type AccountId = u64;
fn into_account(self) -> u64 {
0u64
}
}
impl Verify for AccountU64 {
type Signer = AccountU64;
fn verify<L: Lazy<[u8]>>(
&self,
_msg: L,
_signer: &<Self::Signer as IdentifyAccount>::AccountId,
) -> bool {
false
}
}
pub struct AllianceIdentityVerifier;
impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
fn has_required_identities(who: &AccountId) -> bool {
@@ -135,7 +164,7 @@ impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
fn has_good_judgement(who: &AccountId) -> bool {
if let Some(judgements) =
Identity::identity(who).map(|registration| registration.judgements)
Identity::identity(who).map(|(registration, _)| registration.judgements)
{
judgements
.iter()
+4
View File
@@ -18,6 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] }
enumflags2 = { version = "0.7.7" }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true }
frame-support = { path = "../support", default-features = false }
@@ -29,6 +30,7 @@ sp-std = { path = "../../primitives/std", default-features = false }
[dev-dependencies]
pallet-balances = { path = "../balances" }
sp-core = { path = "../../primitives/core" }
sp-keystore = { path = "../../primitives/keystore" }
[features]
default = ["std"]
@@ -39,10 +41,12 @@ std = [
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-balances/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-keystore/std",
"sp-runtime/std",
"sp-std/std",
]
+207 -4
View File
@@ -22,22 +22,43 @@
use super::*;
use crate::Pallet as Identity;
use codec::Encode;
use frame_benchmarking::{
account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError,
};
use frame_support::{
ensure,
traits::{EnsureOrigin, Get},
assert_ok, ensure,
traits::{EnsureOrigin, Get, OnFinalize, OnInitialize},
};
use frame_system::RawOrigin;
use sp_runtime::traits::Bounded;
use sp_io::crypto::{sr25519_generate, sr25519_sign};
use sp_runtime::{
traits::{Bounded, IdentifyAccount, One},
MultiSignature, MultiSigner,
};
const SEED: u32 = 0;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
}
fn run_to_block<T: Config>(n: frame_system::pallet_prelude::BlockNumberFor<T>) {
while frame_system::Pallet::<T>::block_number() < n {
crate::Pallet::<T>::on_finalize(frame_system::Pallet::<T>::block_number());
frame_system::Pallet::<T>::on_finalize(frame_system::Pallet::<T>::block_number());
frame_system::Pallet::<T>::set_block_number(
frame_system::Pallet::<T>::block_number() + One::one(),
);
frame_system::Pallet::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
crate::Pallet::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
}
}
// Adds `r` registrars to the Identity Pallet. These registrars will have set fees and fields.
fn add_registrars<T: Config>(r: u32) -> Result<(), &'static str> {
for i in 0..r {
@@ -95,7 +116,28 @@ fn add_sub_accounts<T: Config>(
Ok(subs)
}
#[benchmarks]
fn bench_suffix() -> Vec<u8> {
b"bench".to_vec()
}
fn bench_username() -> Vec<u8> {
// len = 24
b"veryfastbenchmarkmachine".to_vec()
}
fn bounded_username<T: Config>(username: Vec<u8>, suffix: Vec<u8>) -> Username<T> {
let mut full_username = Vec::with_capacity(username.len() + suffix.len() + 1);
full_username.extend(username);
full_username.extend(b".");
full_username.extend(suffix);
Username::<T>::try_from(full_username).expect("test usernames should fit within bounds")
}
#[benchmarks(
where
<T as frame_system::Config>::AccountId: From<sp_runtime::AccountId32>,
T::OffchainSignature: From<MultiSignature>,
)]
mod benchmarks {
use super::*;
@@ -523,5 +565,166 @@ mod benchmarks {
Ok(())
}
#[benchmark]
fn add_username_authority() -> Result<(), BenchmarkError> {
let origin =
T::UsernameAuthorityOrigin::try_successful_origin().expect("can generate origin");
let authority: T::AccountId = account("authority", 0, SEED);
let authority_lookup = T::Lookup::unlookup(authority.clone());
let suffix = bench_suffix();
let allocation = 10;
#[extrinsic_call]
_(origin as T::RuntimeOrigin, authority_lookup, suffix, allocation);
assert_last_event::<T>(Event::<T>::AuthorityAdded { authority }.into());
Ok(())
}
#[benchmark]
fn remove_username_authority() -> Result<(), BenchmarkError> {
let origin =
T::UsernameAuthorityOrigin::try_successful_origin().expect("can generate origin");
let authority: T::AccountId = account("authority", 0, SEED);
let authority_lookup = T::Lookup::unlookup(authority.clone());
let suffix = bench_suffix();
let allocation = 10;
assert_ok!(Identity::<T>::add_username_authority(
origin.clone(),
authority_lookup.clone(),
suffix,
allocation
));
#[extrinsic_call]
_(origin as T::RuntimeOrigin, authority_lookup);
assert_last_event::<T>(Event::<T>::AuthorityRemoved { authority }.into());
Ok(())
}
#[benchmark]
fn set_username_for() -> Result<(), BenchmarkError> {
// Set up a username authority.
let auth_origin =
T::UsernameAuthorityOrigin::try_successful_origin().expect("can generate origin");
let authority: T::AccountId = account("authority", 0, SEED);
let authority_lookup = T::Lookup::unlookup(authority.clone());
let suffix = bench_suffix();
let allocation = 10;
Identity::<T>::add_username_authority(
auth_origin,
authority_lookup,
suffix.clone(),
allocation,
)?;
let username = bench_username();
let bounded_username = bounded_username::<T>(username.clone(), suffix.clone());
let encoded_username = Encode::encode(&bounded_username.to_vec());
let public = sr25519_generate(0.into(), None);
let who_account: T::AccountId = MultiSigner::Sr25519(public).into_account().into();
let who_lookup = T::Lookup::unlookup(who_account.clone());
let signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
// Verify signature here to avoid surprise errors at runtime
assert!(signature.verify(&encoded_username[..], &public.into()));
#[extrinsic_call]
_(RawOrigin::Signed(authority.clone()), who_lookup, username, Some(signature.into()));
assert_has_event::<T>(
Event::<T>::UsernameSet {
who: who_account.clone(),
username: bounded_username.clone(),
}
.into(),
);
assert_has_event::<T>(
Event::<T>::PrimaryUsernameSet { who: who_account, username: bounded_username }.into(),
);
Ok(())
}
#[benchmark]
fn accept_username() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let username = bounded_username::<T>(bench_username(), bench_suffix());
Identity::<T>::queue_acceptance(&caller, username.clone());
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), username.clone());
assert_last_event::<T>(Event::<T>::UsernameSet { who: caller, username }.into());
Ok(())
}
#[benchmark]
fn remove_expired_approval() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let username = bounded_username::<T>(bench_username(), bench_suffix());
Identity::<T>::queue_acceptance(&caller, username.clone());
let expected_exiration =
frame_system::Pallet::<T>::block_number() + T::PendingUsernameExpiration::get();
run_to_block::<T>(expected_exiration + One::one());
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), username);
assert_last_event::<T>(Event::<T>::PreapprovalExpired { whose: caller }.into());
Ok(())
}
#[benchmark]
fn set_primary_username() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let first_username = bounded_username::<T>(bench_username(), bench_suffix());
let second_username = bounded_username::<T>(b"slowbenchmark".to_vec(), bench_suffix());
// First one will be set as primary. Second will not be.
Identity::<T>::insert_username(&caller, first_username);
Identity::<T>::insert_username(&caller, second_username.clone());
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), second_username.clone());
assert_last_event::<T>(
Event::<T>::PrimaryUsernameSet { who: caller, username: second_username }.into(),
);
Ok(())
}
#[benchmark]
fn remove_dangling_username() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let first_username = bounded_username::<T>(bench_username(), bench_suffix());
let second_username = bounded_username::<T>(b"slowbenchmark".to_vec(), bench_suffix());
// First one will be set as primary. Second will not be.
Identity::<T>::insert_username(&caller, first_username);
Identity::<T>::insert_username(&caller, second_username.clone());
// User calls `clear_identity`, leaving their second username as "dangling"
Identity::<T>::clear_identity(RawOrigin::Signed(caller.clone()).into())?;
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), second_username.clone());
assert_last_event::<T>(
Event::<T>::DanglingUsernameRemoved { who: caller, username: second_username }.into(),
);
Ok(())
}
impl_benchmark_test_suite!(Identity, crate::tests::new_test_ext(), crate::tests::Test);
}
+16 -1
View File
@@ -75,7 +75,6 @@ impl TypeInfo for IdentityField {
TypeInfo,
)]
#[codec(mel_bound())]
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
#[scale_info(skip_type_params(FieldLimit))]
pub struct IdentityInfo<FieldLimit: Get<u32>> {
/// Additional fields of the identity that are not catered for with the struct's explicit
@@ -155,6 +154,22 @@ impl<FieldLimit: Get<u32> + 'static> IdentityInformationProvider for IdentityInf
}
}
impl<FieldLimit: Get<u32>> Default for IdentityInfo<FieldLimit> {
fn default() -> Self {
IdentityInfo {
additional: BoundedVec::default(),
display: Data::None,
legal: Data::None,
web: Data::None,
riot: Data::None,
email: Data::None,
pgp_fingerprint: None,
image: Data::None,
twitter: Data::None,
}
}
}
impl<FieldLimit: Get<u32>> IdentityInfo<FieldLimit> {
pub(crate) fn fields(&self) -> BitFlags<IdentityField> {
let mut res = <BitFlags<IdentityField>>::empty();
+459 -54
View File
@@ -40,32 +40,53 @@
//! The number of registrars should be limited, and the deposit made sufficiently large, to ensure
//! no state-bloat attack is viable.
//!
//! ### Usernames
//!
//! The pallet provides functionality for username authorities to issue usernames. When an account
//! receives a username, they get a default instance of `IdentityInfo`. Usernames also serve as a
//! reverse lookup from username to account.
//!
//! Username authorities are given an allocation by governance to prevent state bloat. Usernames
//! impose no cost or deposit on the user.
//!
//! Users can have multiple usernames that map to the same `AccountId`, however one `AccountId` can
//! only map to a single username, known as the _primary_.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! #### For general users
//! #### For General Users
//! * `set_identity` - Set the associated identity of an account; a small deposit is reserved if not
//! already taken.
//! * `clear_identity` - Remove an account's associated identity; the deposit is returned.
//! * `request_judgement` - Request a judgement from a registrar, paying a fee.
//! * `cancel_request` - Cancel the previous request for a judgement.
//! * `accept_username` - Accept a username issued by a username authority.
//! * `remove_expired_approval` - Remove a username that was issued but never accepted.
//! * `set_primary_username` - Set a given username as an account's primary.
//! * `remove_dangling_username` - Remove a username that maps to an account without an identity.
//!
//! #### For general users with sub-identities
//! #### For General Users with Sub-Identities
//! * `set_subs` - Set the sub-accounts of an identity.
//! * `add_sub` - Add a sub-identity to an identity.
//! * `remove_sub` - Remove a sub-identity of an identity.
//! * `rename_sub` - Rename a sub-identity of an identity.
//! * `quit_sub` - Remove a sub-identity of an identity (called by the sub-identity).
//!
//! #### For registrars
//! #### For Registrars
//! * `set_fee` - Set the fee required to be paid for a judgement to be given by the registrar.
//! * `set_fields` - Set the fields that a registrar cares about in their judgements.
//! * `provide_judgement` - Provide a judgement to an identity.
//!
//! #### For super-users
//! #### For Username Authorities
//! * `set_username_for` - Set a username for a given account. The account must approve it.
//!
//! #### For Superusers
//! * `add_registrar` - Add a new registrar to the system.
//! * `kill_identity` - Forcibly remove the associated identity; the deposit is lost.
//! * `add_username_authority` - Add an account with the ability to issue usernames.
//! * `remove_username_authority` - Remove an account with the ability to issue usernames.
//!
//! [`Call`]: ./enum.Call.html
//! [`Config`]: ./trait.Config.html
@@ -74,25 +95,29 @@
mod benchmarking;
pub mod legacy;
pub mod migration;
#[cfg(test)]
mod tests;
mod types;
pub mod weights;
use crate::types::{AuthorityPropertiesOf, Suffix, Username};
use codec::Encode;
use frame_support::{
ensure,
pallet_prelude::{DispatchError, DispatchResult},
traits::{BalanceStatus, Currency, Get, OnUnbalanced, ReservableCurrency},
traits::{BalanceStatus, Currency, Get, OnUnbalanced, ReservableCurrency, StorageVersion},
BoundedVec,
};
use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero};
use sp_std::prelude::*;
pub use weights::WeightInfo;
pub use pallet::*;
use sp_runtime::traits::{
AppendZerosInput, Hash, IdentifyAccount, Saturating, StaticLookup, Verify, Zero,
};
use sp_std::prelude::*;
pub use types::{
Data, IdentityInformationProvider, Judgement, RegistrarIndex, RegistrarInfo, Registration,
};
pub use weights::WeightInfo;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
@@ -115,7 +140,7 @@ pub mod pallet {
/// The currency trait.
type Currency: ReservableCurrency<Self::AccountId>;
/// The amount held on deposit for a registered identity
/// The amount held on deposit for a registered identity.
#[pallet::constant]
type BasicDeposit: Get<BalanceOf<Self>>;
@@ -150,14 +175,41 @@ pub mod pallet {
/// The origin which may add or remove registrars. Root can always do this.
type RegistrarOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Signature type for pre-authorizing usernames off-chain.
///
/// Can verify whether an `Self::SigningPublicKey` created a signature.
type OffchainSignature: Verify<Signer = Self::SigningPublicKey> + Parameter;
/// Public key that corresponds to an on-chain `Self::AccountId`.
type SigningPublicKey: IdentifyAccount<AccountId = Self::AccountId>;
/// The origin which may add or remove username authorities. Root can always do this.
type UsernameAuthorityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The number of blocks within which a username grant must be accepted.
#[pallet::constant]
type PendingUsernameExpiration: Get<BlockNumberFor<Self>>;
/// The maximum length of a suffix.
#[pallet::constant]
type MaxSuffixLength: Get<u32>;
/// The maximum length of a username, including its suffix and any system-added delimiters.
#[pallet::constant]
type MaxUsernameLength: Get<u32>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
/// Information that is pertinent to identify the entity behind an account.
/// Information that is pertinent to identify the entity behind an account. First item is the
/// registration, second is the account's primary username.
///
/// TWOX-NOTE: OK ― `AccountId` is a secure hash.
#[pallet::storage]
@@ -166,7 +218,7 @@ pub mod pallet {
_,
Twox64Concat,
T::AccountId,
Registration<BalanceOf<T>, T::MaxRegistrars, T::IdentityInformation>,
(Registration<BalanceOf<T>, T::MaxRegistrars, T::IdentityInformation>, Option<Username<T>>),
OptionQuery,
>;
@@ -213,6 +265,38 @@ pub mod pallet {
ValueQuery,
>;
/// A map of the accounts who are authorized to grant usernames.
#[pallet::storage]
#[pallet::getter(fn authority)]
pub(super) type UsernameAuthorities<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, AuthorityPropertiesOf<T>, OptionQuery>;
/// Reverse lookup from `username` to the `AccountId` that has registered it. The value should
/// be a key in the `IdentityOf` map, but it may not if the user has cleared their identity.
///
/// Multiple usernames may map to the same `AccountId`, but `IdentityOf` will only map to one
/// primary username.
#[pallet::storage]
#[pallet::getter(fn username)]
pub(super) type AccountOfUsername<T: Config> =
StorageMap<_, Blake2_128Concat, Username<T>, T::AccountId, OptionQuery>;
/// Usernames that an authority has granted, but that the account controller has not confirmed
/// that they want it. Used primarily in cases where the `AccountId` cannot provide a signature
/// because they are a pure proxy, multisig, etc. In order to confirm it, they should call
/// [`Call::accept_username`].
///
/// First tuple item is the account and second is the acceptance deadline.
#[pallet::storage]
#[pallet::getter(fn preapproved_usernames)]
pub type PendingUsernames<T: Config> = StorageMap<
_,
Blake2_128Concat,
Username<T>,
(T::AccountId, BlockNumberFor<T>),
OptionQuery,
>;
#[pallet::error]
pub enum Error<T> {
/// Too many subs-accounts.
@@ -249,6 +333,24 @@ pub mod pallet {
JudgementForDifferentIdentity,
/// Error that occurs when there is an issue paying for judgement.
JudgementPaymentFailed,
/// The provided suffix is too long.
InvalidSuffix,
/// The sender does not have permission to issue a username.
NotUsernameAuthority,
/// The authority cannot allocate any more usernames.
NoAllocation,
/// The signature on a username was not valid.
InvalidSignature,
/// Setting this username requires a signature, but none was provided.
RequiresSignature,
/// The username does not meet the requirements.
InvalidUsername,
/// The username is already taken.
UsernameTaken,
/// The requested username does not exist.
NoUsername,
/// The username cannot be forcefully removed because it can still be accepted.
NotExpired,
}
#[pallet::event]
@@ -275,6 +377,21 @@ pub mod pallet {
/// A sub-identity was cleared, and the given deposit repatriated from the
/// main identity account to the sub-identity account.
SubIdentityRevoked { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf<T> },
/// A username authority was added.
AuthorityAdded { authority: T::AccountId },
/// A username authority was removed.
AuthorityRemoved { authority: T::AccountId },
/// A username was set for `who`.
UsernameSet { who: T::AccountId, username: Username<T> },
/// A username was queued, but `who` must accept it prior to `expiration`.
UsernameQueued { who: T::AccountId, username: Username<T>, expiration: BlockNumberFor<T> },
/// A queued username passed its expiration without being claimed and was removed.
PreapprovalExpired { whose: T::AccountId },
/// A username was set as a primary and can be looked up from `who`.
PrimaryUsernameSet { who: T::AccountId, username: Username<T> },
/// A dangling username (as in, a username corresponding to an account that has removed its
/// identity) has been removed.
DanglingUsernameRemoved { who: T::AccountId, username: Username<T> },
}
#[pallet::call]
@@ -331,36 +448,34 @@ pub mod pallet {
info: Box<T::IdentityInformation>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let encoded_byte_size = info.encoded_size() as u32;
let byte_deposit =
T::ByteDeposit::get().saturating_mul(<BalanceOf<T>>::from(encoded_byte_size));
let mut id = match <IdentityOf<T>>::get(&sender) {
Some(mut id) => {
// Only keep non-positive judgements.
id.judgements.retain(|j| j.1.is_sticky());
id.info = *info;
id
},
None => Registration {
info: *info,
judgements: BoundedVec::default(),
deposit: Zero::zero(),
},
let (mut id, username) = match <IdentityOf<T>>::get(&sender) {
Some((mut id, maybe_username)) => (
{
// Only keep non-positive judgements.
id.judgements.retain(|j| j.1.is_sticky());
id.info = *info;
id
},
maybe_username,
),
None => (
Registration {
info: *info,
judgements: BoundedVec::default(),
deposit: Zero::zero(),
},
None,
),
};
let new_deposit = Self::calculate_identity_deposit(&id.info);
let old_deposit = id.deposit;
id.deposit = T::BasicDeposit::get().saturating_add(byte_deposit);
if id.deposit > old_deposit {
T::Currency::reserve(&sender, id.deposit - old_deposit)?;
}
if old_deposit > id.deposit {
let err_amount = T::Currency::unreserve(&sender, old_deposit - id.deposit);
debug_assert!(err_amount.is_zero());
}
Self::rejig_deposit(&sender, old_deposit, new_deposit)?;
id.deposit = new_deposit;
let judgements = id.judgements.len();
<IdentityOf<T>>::insert(&sender, id);
<IdentityOf<T>>::insert(&sender, (id, username));
Self::deposit_event(Event::IdentitySet { who: sender });
Ok(Some(T::WeightInfo::set_identity(judgements as u32)).into())
@@ -452,11 +567,15 @@ pub mod pallet {
let sender = ensure_signed(origin)?;
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&sender);
let id = <IdentityOf<T>>::take(&sender).ok_or(Error::<T>::NotNamed)?;
let (id, maybe_username) =
<IdentityOf<T>>::take(&sender).ok_or(Error::<T>::NoIdentity)?;
let deposit = id.total_deposit().saturating_add(subs_deposit);
for sub in sub_ids.iter() {
<SuperOf<T>>::remove(sub);
}
if let Some(username) = maybe_username {
AccountOfUsername::<T>::remove(username);
}
let err_amount = T::Currency::unreserve(&sender, deposit);
debug_assert!(err_amount.is_zero());
@@ -501,7 +620,7 @@ pub mod pallet {
.and_then(Option::as_ref)
.ok_or(Error::<T>::EmptyIndex)?;
ensure!(max_fee >= registrar.fee, Error::<T>::FeeChanged);
let mut id = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
let (mut id, username) = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
let item = (reg_index, Judgement::FeePaid(registrar.fee));
match id.judgements.binary_search_by_key(&reg_index, |x| x.0) {
@@ -518,7 +637,7 @@ pub mod pallet {
T::Currency::reserve(&sender, registrar.fee)?;
let judgements = id.judgements.len();
<IdentityOf<T>>::insert(&sender, id);
<IdentityOf<T>>::insert(&sender, (id, username));
Self::deposit_event(Event::JudgementRequested {
who: sender,
@@ -545,7 +664,7 @@ pub mod pallet {
reg_index: RegistrarIndex,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let mut id = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
let (mut id, username) = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
let pos = id
.judgements
@@ -560,7 +679,7 @@ pub mod pallet {
let err_amount = T::Currency::unreserve(&sender, fee);
debug_assert!(err_amount.is_zero());
let judgements = id.judgements.len();
<IdentityOf<T>>::insert(&sender, id);
<IdentityOf<T>>::insert(&sender, (id, username));
Self::deposit_event(Event::JudgementUnrequested {
who: sender,
@@ -679,6 +798,8 @@ pub mod pallet {
/// - `identity`: The hash of the [`IdentityInformationProvider`] for that the judgement is
/// provided.
///
/// Note: Judgements do not apply to a username.
///
/// Emits `JudgementGiven` if successful.
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::provide_judgement(T::MaxRegistrars::get()))]
@@ -697,7 +818,8 @@ pub mod pallet {
.and_then(Option::as_ref)
.filter(|r| r.account == sender)
.ok_or(Error::<T>::InvalidIndex)?;
let mut id = <IdentityOf<T>>::get(&target).ok_or(Error::<T>::InvalidTarget)?;
let (mut id, username) =
<IdentityOf<T>>::get(&target).ok_or(Error::<T>::InvalidTarget)?;
if T::Hashing::hash_of(&id.info) != identity {
return Err(Error::<T>::JudgementForDifferentIdentity.into())
@@ -724,7 +846,7 @@ pub mod pallet {
}
let judgements = id.judgements.len();
<IdentityOf<T>>::insert(&target, id);
<IdentityOf<T>>::insert(&target, (id, username));
Self::deposit_event(Event::JudgementGiven { target, registrar_index: reg_index });
Ok(Some(T::WeightInfo::provide_judgement(judgements as u32)).into())
@@ -757,11 +879,15 @@ pub mod pallet {
let target = T::Lookup::lookup(target)?;
// Grab their deposit (and check that they have one).
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&target);
let id = <IdentityOf<T>>::take(&target).ok_or(Error::<T>::NotNamed)?;
let (id, maybe_username) =
<IdentityOf<T>>::take(&target).ok_or(Error::<T>::NoIdentity)?;
let deposit = id.total_deposit().saturating_add(subs_deposit);
for sub in sub_ids.iter() {
<SuperOf<T>>::remove(sub);
}
if let Some(username) = maybe_username {
AccountOfUsername::<T>::remove(username);
}
// Slash their deposit from them.
T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0);
@@ -886,6 +1012,186 @@ pub mod pallet {
});
Ok(())
}
/// Add an `AccountId` with permission to grant usernames with a given `suffix` appended.
///
/// The authority can grant up to `allocation` usernames. To top up their allocation, they
/// should just issue (or request via governance) a new `add_username_authority` call.
#[pallet::call_index(15)]
#[pallet::weight(T::WeightInfo::add_username_authority())]
pub fn add_username_authority(
origin: OriginFor<T>,
authority: AccountIdLookupOf<T>,
suffix: Vec<u8>,
allocation: u32,
) -> DispatchResult {
T::UsernameAuthorityOrigin::ensure_origin(origin)?;
let authority = T::Lookup::lookup(authority)?;
// We don't need to check the length because it gets checked when casting into a
// `BoundedVec`.
Self::validate_username(&suffix, None).map_err(|_| Error::<T>::InvalidSuffix)?;
let suffix = Suffix::<T>::try_from(suffix).map_err(|_| Error::<T>::InvalidSuffix)?;
// The authority may already exist, but we don't need to check. They might be changing
// their suffix or adding allocation, so we just want to overwrite whatever was there.
UsernameAuthorities::<T>::insert(
&authority,
AuthorityPropertiesOf::<T> { suffix, allocation },
);
Self::deposit_event(Event::AuthorityAdded { authority });
Ok(())
}
/// Remove `authority` from the username authorities.
#[pallet::call_index(16)]
#[pallet::weight(T::WeightInfo::remove_username_authority())]
pub fn remove_username_authority(
origin: OriginFor<T>,
authority: AccountIdLookupOf<T>,
) -> DispatchResult {
T::UsernameAuthorityOrigin::ensure_origin(origin)?;
let authority = T::Lookup::lookup(authority)?;
UsernameAuthorities::<T>::take(&authority).ok_or(Error::<T>::NotUsernameAuthority)?;
Self::deposit_event(Event::AuthorityRemoved { authority });
Ok(())
}
/// Set the username for `who`. Must be called by a username authority.
///
/// The authority must have an `allocation`. Users can either pre-sign their usernames or
/// accept them later.
///
/// Usernames must:
/// - Only contain lowercase ASCII characters or digits.
/// - When combined with the suffix of the issuing authority be _less than_ the
/// `MaxUsernameLength`.
#[pallet::call_index(17)]
#[pallet::weight(T::WeightInfo::set_username_for())]
pub fn set_username_for(
origin: OriginFor<T>,
who: AccountIdLookupOf<T>,
username: Vec<u8>,
signature: Option<T::OffchainSignature>,
) -> DispatchResult {
// Ensure origin is a Username Authority and has an allocation. Decrement their
// allocation by one.
let sender = ensure_signed(origin)?;
let suffix = UsernameAuthorities::<T>::try_mutate(
&sender,
|maybe_authority| -> Result<Suffix<T>, DispatchError> {
let properties =
maybe_authority.as_mut().ok_or(Error::<T>::NotUsernameAuthority)?;
ensure!(properties.allocation > 0, Error::<T>::NoAllocation);
properties.allocation.saturating_dec();
Ok(properties.suffix.clone())
},
)?;
// Ensure that the username only contains allowed characters. We already know the suffix
// does.
let username_length = username.len().saturating_add(suffix.len()) as u32;
Self::validate_username(&username, Some(username_length))?;
// Concatenate the username with suffix and cast into a BoundedVec. Should be infallible
// since we already ensured it is below the max length.
let mut full_username =
Vec::with_capacity(username.len().saturating_add(suffix.len()).saturating_add(1));
full_username.extend(username);
full_username.extend(b".");
full_username.extend(suffix);
let bounded_username =
Username::<T>::try_from(full_username).map_err(|_| Error::<T>::InvalidUsername)?;
// Usernames must be unique. Ensure it's not taken.
ensure!(
!AccountOfUsername::<T>::contains_key(&bounded_username),
Error::<T>::UsernameTaken
);
ensure!(
!PendingUsernames::<T>::contains_key(&bounded_username),
Error::<T>::UsernameTaken
);
// Insert or queue.
let who = T::Lookup::lookup(who)?;
if let Some(s) = signature {
// Account has pre-signed an authorization. Verify the signature provided and grant
// the username directly.
let encoded = Encode::encode(&bounded_username.to_vec());
Self::validate_signature(&encoded, &s, &who)?;
Self::insert_username(&who, bounded_username);
} else {
// The user must accept the username, therefore, queue it.
Self::queue_acceptance(&who, bounded_username);
}
Ok(())
}
/// Accept a given username that an `authority` granted. The call must include the full
/// username, as in `username.suffix`.
#[pallet::call_index(18)]
#[pallet::weight(T::WeightInfo::accept_username())]
pub fn accept_username(
origin: OriginFor<T>,
username: Username<T>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let (approved_for, _) =
PendingUsernames::<T>::take(&username).ok_or(Error::<T>::NoUsername)?;
ensure!(approved_for == who.clone(), Error::<T>::InvalidUsername);
Self::insert_username(&who, username.clone());
Self::deposit_event(Event::UsernameSet { who: who.clone(), username });
Ok(Pays::No.into())
}
/// Remove an expired username approval. The username was approved by an authority but never
/// accepted by the user and must now be beyond its expiration. The call must include the
/// full username, as in `username.suffix`.
#[pallet::call_index(19)]
#[pallet::weight(T::WeightInfo::remove_expired_approval())]
pub fn remove_expired_approval(
origin: OriginFor<T>,
username: Username<T>,
) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;
if let Some((who, expiration)) = PendingUsernames::<T>::take(&username) {
let now = frame_system::Pallet::<T>::block_number();
ensure!(now > expiration, Error::<T>::NotExpired);
Self::deposit_event(Event::PreapprovalExpired { whose: who.clone() });
Ok(Pays::No.into())
} else {
Err(Error::<T>::NoUsername.into())
}
}
/// Set a given username as the primary. The username should include the suffix.
#[pallet::call_index(20)]
#[pallet::weight(T::WeightInfo::set_primary_username())]
pub fn set_primary_username(origin: OriginFor<T>, username: Username<T>) -> DispatchResult {
// ensure `username` maps to `origin` (i.e. has already been set by an authority).
let who = ensure_signed(origin)?;
ensure!(AccountOfUsername::<T>::contains_key(&username), Error::<T>::NoUsername);
let (registration, _maybe_username) =
IdentityOf::<T>::get(&who).ok_or(Error::<T>::NoIdentity)?;
IdentityOf::<T>::insert(&who, (registration, Some(username.clone())));
Self::deposit_event(Event::PrimaryUsernameSet { who: who.clone(), username });
Ok(())
}
/// Remove a username that corresponds to an account with no identity. Exists when a user
/// gets a username but then calls `clear_identity`.
#[pallet::call_index(21)]
#[pallet::weight(T::WeightInfo::remove_dangling_username())]
pub fn remove_dangling_username(
origin: OriginFor<T>,
username: Username<T>,
) -> DispatchResultWithPostInfo {
// ensure `username` maps to `origin` (i.e. has already been set by an authority).
let _ = ensure_signed(origin)?;
let who = AccountOfUsername::<T>::take(&username).ok_or(Error::<T>::NoUsername)?;
ensure!(!IdentityOf::<T>::contains_key(&who), Error::<T>::InvalidUsername);
Self::deposit_event(Event::DanglingUsernameRemoved { who: who.clone(), username });
Ok(Pays::No.into())
}
}
}
@@ -925,7 +1231,104 @@ impl<T: Config> Pallet<T> {
fields: <T::IdentityInformation as IdentityInformationProvider>::FieldsIdentifier,
) -> bool {
IdentityOf::<T>::get(who)
.map_or(false, |registration| (registration.info.has_identity(fields)))
.map_or(false, |(registration, _username)| (registration.info.has_identity(fields)))
}
/// Calculate the deposit required for an identity.
fn calculate_identity_deposit(info: &T::IdentityInformation) -> BalanceOf<T> {
let bytes = info.encoded_size() as u32;
let byte_deposit = T::ByteDeposit::get().saturating_mul(<BalanceOf<T>>::from(bytes));
T::BasicDeposit::get().saturating_add(byte_deposit)
}
/// Validate that a username conforms to allowed characters/format.
///
/// The function will validate the characters in `username` and that `length` (if `Some`)
/// conforms to the limit. It is not expected to pass a fully formatted username here (i.e. one
/// with any protocol-added characters included, such as a `.`). The suffix is also separately
/// validated by this function to ensure the full username conforms.
fn validate_username(username: &Vec<u8>, length: Option<u32>) -> DispatchResult {
// Verify input length before allocating a Vec with the user's input. `<` instead of `<=`
// because it needs one element for the point (`username` + `.` + `suffix`).
if let Some(l) = length {
ensure!(l < T::MaxUsernameLength::get(), Error::<T>::InvalidUsername);
}
// Usernames cannot be empty.
ensure!(!username.is_empty(), Error::<T>::InvalidUsername);
// Username must be lowercase and alphanumeric.
ensure!(
username.iter().all(|byte| byte.is_ascii_digit() || byte.is_ascii_lowercase()),
Error::<T>::InvalidUsername
);
Ok(())
}
/// Validate a signature. Supports signatures on raw `data` or `data` wrapped in HTML `<Bytes>`.
pub fn validate_signature(
data: &Vec<u8>,
signature: &T::OffchainSignature,
signer: &T::AccountId,
) -> DispatchResult {
// Happy path, user has signed the raw data.
if signature.verify(&data[..], &signer) {
return Ok(())
}
// NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into
// `<Bytes> + data + </Bytes>`, so why we support both wrapped and raw versions.
let prefix = b"<Bytes>";
let suffix = b"</Bytes>";
let mut wrapped: Vec<u8> = Vec::with_capacity(data.len() + prefix.len() + suffix.len());
wrapped.extend(prefix);
wrapped.extend(data);
wrapped.extend(suffix);
ensure!(signature.verify(&wrapped[..], &signer), Error::<T>::InvalidSignature);
Ok(())
}
/// A username has met all conditions. Insert the relevant storage items.
pub fn insert_username(who: &T::AccountId, username: Username<T>) {
// Check if they already have a primary. If so, leave it. If not, set it.
// Likewise, check if they have an identity. If not, give them a minimal one.
let (reg, primary_username, new_is_primary) = match <IdentityOf<T>>::get(&who) {
// User has an existing Identity and a primary username. Leave it.
Some((reg, Some(primary))) => (reg, primary, false),
// User has an Identity but no primary. Set the new one as primary.
Some((reg, None)) => (reg, username.clone(), true),
// User does not have an existing Identity. Give them a fresh default one and set
// their username as primary.
None => (
Registration {
info: Default::default(),
judgements: Default::default(),
deposit: Zero::zero(),
},
username.clone(),
true,
),
};
// Enter in identity map. Note: In the case that the user did not have a pre-existing
// Identity, we have given them the storage item for free. If they ever call
// `set_identity` with identity info, then they will need to place the normal identity
// deposit.
IdentityOf::<T>::insert(&who, (reg, Some(primary_username)));
// Enter in username map.
AccountOfUsername::<T>::insert(username.clone(), &who);
Self::deposit_event(Event::UsernameSet { who: who.clone(), username: username.clone() });
if new_is_primary {
Self::deposit_event(Event::PrimaryUsernameSet { who: who.clone(), username });
}
}
/// A username was granted by an authority, but must be accepted by `who`. Put the username
/// into a queue for acceptance.
pub fn queue_acceptance(who: &T::AccountId, username: Username<T>) {
let now = frame_system::Pallet::<T>::block_number();
let expiration = now.saturating_add(T::PendingUsernameExpiration::get());
PendingUsernames::<T>::insert(&username, (who.clone(), expiration));
Self::deposit_event(Event::UsernameQueued { who: who.clone(), username, expiration });
}
/// Reap an identity, clearing associated storage items and refunding any deposits. This
@@ -943,7 +1346,7 @@ impl<T: Config> Pallet<T> {
pub fn reap_identity(who: &T::AccountId) -> Result<(u32, u32, u32), DispatchError> {
// `take` any storage items keyed by `target`
// identity
let id = <IdentityOf<T>>::take(&who).ok_or(Error::<T>::NotNamed)?;
let (id, _maybe_username) = <IdentityOf<T>>::take(&who).ok_or(Error::<T>::NoIdentity)?;
let registrars = id.judgements.len() as u32;
let encoded_byte_size = id.info.encoded_size() as u32;
@@ -976,8 +1379,8 @@ impl<T: Config> Pallet<T> {
// Identity Deposit
let new_id_deposit = IdentityOf::<T>::try_mutate(
&target,
|registration| -> Result<BalanceOf<T>, DispatchError> {
let reg = registration.as_mut().ok_or(Error::<T>::NoIdentity)?;
|identity_of| -> Result<BalanceOf<T>, DispatchError> {
let (reg, _) = identity_of.as_mut().ok_or(Error::<T>::NoIdentity)?;
// Calculate what deposit should be
let encoded_byte_size = reg.info.encoded_size() as u32;
let byte_deposit =
@@ -1014,11 +1417,14 @@ impl<T: Config> Pallet<T> {
) -> DispatchResult {
IdentityOf::<T>::insert(
&who,
Registration {
judgements: Default::default(),
deposit: Zero::zero(),
info: info.clone(),
},
(
Registration {
judgements: Default::default(),
deposit: Zero::zero(),
info: info.clone(),
},
None::<Username<T>>,
),
);
Ok(())
}
@@ -1030,7 +1436,6 @@ impl<T: Config> Pallet<T> {
who: &T::AccountId,
subs: Vec<(T::AccountId, Data)>,
) -> DispatchResult {
use frame_support::BoundedVec;
let mut sub_accounts = BoundedVec::<T::AccountId, T::MaxSubAccounts>::default();
for (sub, name) in subs {
<SuperOf<T>>::insert(&sub, (who.clone(), name));
+124
View File
@@ -0,0 +1,124 @@
// 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.
//! Storage migrations for the Identity pallet.
use super::*;
use frame_support::{migrations::VersionedMigration, pallet_prelude::*, traits::OnRuntimeUpgrade};
#[cfg(feature = "try-runtime")]
use codec::{Decode, Encode};
#[cfg(feature = "try-runtime")]
use sp_runtime::TryRuntimeError;
pub mod versioned {
use super::*;
pub type V0ToV1<T, const KL: u64> = VersionedMigration<
0,
1,
v1::VersionUncheckedMigrateV0ToV1<T, KL>,
crate::pallet::Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
}
pub mod v1 {
use super::*;
/// The log target.
const TARGET: &'static str = "runtime::identity::migration::v1";
/// The old identity type, useful in pre-upgrade.
mod v0 {
use super::*;
use frame_support::storage_alias;
#[storage_alias]
pub type IdentityOf<T: Config> = StorageMap<
Pallet<T>,
Twox64Concat,
<T as frame_system::Config>::AccountId,
Registration<
BalanceOf<T>,
<T as pallet::Config>::MaxRegistrars,
<T as pallet::Config>::IdentityInformation,
>,
OptionQuery,
>;
}
/// Migration to add usernames to Identity info.
///
/// `T` is the runtime and `KL` is the key limit to migrate. This is just a safety guard to
/// prevent stalling a parachain by accumulating too much weight in the migration. To have an
/// unlimited migration (e.g. in a chain without PoV limits), set this to `u64::MAX`.
pub struct VersionUncheckedMigrateV0ToV1<T, const KL: u64>(PhantomData<T>);
impl<T: Config, const KL: u64> OnRuntimeUpgrade for VersionUncheckedMigrateV0ToV1<T, KL> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
let identities = v0::IdentityOf::<T>::iter().count();
log::info!(
target: TARGET,
"pre-upgrade state contains '{}' identities.",
identities
);
ensure!((identities as u64) < KL, "too many identities to migrate");
Ok((identities as u64).encode())
}
fn on_runtime_upgrade() -> Weight {
log::info!(
target: TARGET,
"running storage migration from version 0 to version 1."
);
let mut weight = T::DbWeight::get().reads(1);
let mut translated: u64 = 0;
let mut interrupted = false;
for (account, registration) in v0::IdentityOf::<T>::iter() {
IdentityOf::<T>::insert(account, (registration, None::<Username<T>>));
translated.saturating_inc();
if translated >= KL {
log::warn!(
"Incomplete! Migration limit reached. Only {} identities migrated.",
translated
);
interrupted = true;
break
}
}
if !interrupted {
log::info!("all {} identities migrated", translated);
}
weight.saturating_accrue(T::DbWeight::get().reads_writes(translated, translated));
weight.saturating_accrue(T::DbWeight::get().writes(1));
weight
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let identities_to_migrate: u64 = Decode::decode(&mut &state[..])
.expect("failed to decode the state from pre-upgrade.");
let identities = IdentityOf::<T>::iter().count() as u64;
log::info!("post-upgrade expects '{}' identities to have been migrated.", identities);
ensure!(identities_to_migrate == identities, "must migrate all identities.");
log::info!(target: TARGET, "migrated all identities.");
Ok(())
}
}
}
File diff suppressed because it is too large Load Diff
+23 -1
View File
@@ -232,7 +232,7 @@ impl<Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + Part
/// Information concerning the identity of the controller of an account.
pub trait IdentityInformationProvider:
Encode + Decode + MaxEncodedLen + Clone + Debug + Eq + PartialEq + TypeInfo
Encode + Decode + MaxEncodedLen + Clone + Debug + Eq + PartialEq + TypeInfo + Default
{
/// Type capable of holding information on which identity fields are set.
type FieldsIdentifier: Member + Encode + Decode + MaxEncodedLen + TypeInfo + Default;
@@ -319,6 +319,28 @@ pub struct RegistrarInfo<
pub fields: IdField,
}
/// Authority properties for a given pallet configuration.
pub type AuthorityPropertiesOf<T> = AuthorityProperties<Suffix<T>>;
/// The number of usernames that an authority may allocate.
type Allocation = u32;
/// A byte vec used to represent a username.
pub(crate) type Suffix<T> = BoundedVec<u8, <T as Config>::MaxSuffixLength>;
/// Properties of a username authority.
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Debug)]
pub struct AuthorityProperties<Suffix> {
/// The suffix added to usernames granted by this authority. Will be appended to usernames; for
/// example, a suffix of `wallet` will result in `.wallet` being appended to a user's selected
/// name.
pub suffix: Suffix,
/// The number of usernames remaining that this authority can grant.
pub allocation: Allocation,
}
/// A byte vec used to represent a username.
pub(crate) type Username<T> = BoundedVec<u8, <T as Config>::MaxUsernameLength>;
#[cfg(test)]
mod tests {
use super::*;
+195
View File
@@ -68,6 +68,13 @@ pub trait WeightInfo {
fn rename_sub(s: u32, ) -> Weight;
fn remove_sub(s: u32, ) -> Weight;
fn quit_sub(s: u32, ) -> Weight;
fn add_username_authority() -> Weight;
fn remove_username_authority() -> Weight;
fn set_username_for() -> Weight;
fn accept_username() -> Weight;
fn remove_expired_approval() -> Weight;
fn set_primary_username() -> Weight;
fn remove_dangling_username() -> Weight;
}
/// Weights for pallet_identity using the Substrate node and recommended hardware.
@@ -345,6 +352,100 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn add_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 13_873_000 picoseconds.
Weight::from_parts(13_873_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn remove_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 10_653_000 picoseconds.
Weight::from_parts(10_653_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_username_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `80`
// Estimated: `11037`
// Minimum execution time: 75_928_000 picoseconds.
Weight::from_parts(75_928_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
fn accept_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `11037`
// Minimum execution time: 38_157_000 picoseconds.
Weight::from_parts(38_157_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
fn remove_expired_approval() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `3542`
// Minimum execution time: 46_821_000 picoseconds.
Weight::from_parts(46_821_000, 0)
.saturating_add(Weight::from_parts(0, 3542))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_primary_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `247`
// Estimated: `11037`
// Minimum execution time: 22_515_000 picoseconds.
Weight::from_parts(22_515_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:0)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn remove_dangling_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `126`
// Estimated: `11037`
// Minimum execution time: 15_997_000 picoseconds.
Weight::from_parts(15_997_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
}
// For backwards compatibility and tests
@@ -621,4 +722,98 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn add_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 13_873_000 picoseconds.
Weight::from_parts(13_873_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn remove_username_authority() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 10_653_000 picoseconds.
Weight::from_parts(10_653_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_username_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `80`
// Estimated: `11037`
// Minimum execution time: 75_928_000 picoseconds.
Weight::from_parts(75_928_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
fn accept_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `11037`
// Minimum execution time: 38_157_000 picoseconds.
Weight::from_parts(38_157_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
fn remove_expired_approval() -> Weight {
// Proof Size summary in bytes:
// Measured: `106`
// Estimated: `3542`
// Minimum execution time: 46_821_000 picoseconds.
Weight::from_parts(46_821_000, 0)
.saturating_add(Weight::from_parts(0, 3542))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn set_primary_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `247`
// Estimated: `11037`
// Minimum execution time: 22_515_000 picoseconds.
Weight::from_parts(22_515_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
/// Storage: `Identity::IdentityOf` (r:1 w:0)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
fn remove_dangling_username() -> Weight {
// Proof Size summary in bytes:
// Measured: `126`
// Estimated: `11037`
// Minimum execution time: 15_997_000 picoseconds.
Weight::from_parts(15_997_000, 0)
.saturating_add(Weight::from_parts(0, 11037))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
}