Skip to content

Commit

Permalink
Unique Usernames in Identity Pallet (#2651)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
5 people authored Jan 10, 2024
1 parent a419532 commit d1f678c
Show file tree
Hide file tree
Showing 22 changed files with 2,653 additions and 328 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 29 additions & 3 deletions cumulus/parachains/runtimes/people/people-rococo/src/people.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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>;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
32 changes: 29 additions & 3 deletions cumulus/parachains/runtimes/people/people-westend/src/people.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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>;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
10 changes: 8 additions & 2 deletions polkadot/runtime/common/src/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 = ();
}

Expand Down
12 changes: 12 additions & 0 deletions polkadot/runtime/rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}

Expand Down Expand Up @@ -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, (), ()>,
Expand Down Expand Up @@ -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>,
Expand Down
Loading

0 comments on commit d1f678c

Please sign in to comment.