Newer
Older
use crate::{
commands::revocation::generate_revoc_doc,
runtime::runtime_types::{
common_runtime::entities::{IdtyData, NewOwnerKeySignature},
pallet_identity::types::*,
sp_core::sr25519::Signature,
sp_runtime::MultiSignature,
},
};
/// define identity subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
/// show identity
#[default]
Show,
/// Fetch identity
Get {
#[clap(short = 'a', long = "address")]
account_id: Option<AccountId>,
#[clap(short = 'i', long = "identity")]
identity_id: Option<u32>,
#[clap(short = 'u', long = "username")]
username: Option<String>,
},
/// Create and certify an identity
///
/// Caller must be member, and the target account must exist.
Create { target: AccountId },
/// Confirm an identity
///
/// To be called by the certified not-yet-member account, to become member.
Confirm { name: String },
/// Validate an identity
/// Should be called when the distance has been evaluated positively
Validate { index: u32 },
/// Renew membership
/// When membership comes to and end, it should be renewed for the identity to stay member
RenewMembership,
/// Certify an identity
Certify { target: u32 },
/// Revoke an identity immediately
Revoke,
/// Generate a revocation document for the provided account
GenRevocDoc,
/// Display member count
MemberCount,
/// Link an account to the identity
LinkAccount {
/// address of the account that has to be linked
address: AccountId,
},
}
/// handle identity commands
pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
match command {
Subcommand::Show => {}
Subcommand::Get {
ref account_id,
identity_id,
ref username,
} => {
get_identity(&data, account_id.clone(), identity_id, username.clone()).await?
}
Subcommand::Create { target } => {
create_identity(&data, target).await?;
}
Subcommand::Confirm { name } => {
confirm_identity(&data, name).await?;
}
Subcommand::Validate { index } => {
validate_identity(&data, index).await?;
}
Subcommand::RenewMembership => {
renew_membership(&data).await?;
}
Subcommand::Certify { target } => {
data = data.fetch_idty_index().await?;
// TODO fetch target username / key / index
// and ask user to confirm certification
commands::certification::certify(&data, target).await?;
data = data.fetch_idty_index().await?;
data = data.fetch_idty_index().await?;
commands::revocation::print_revoc_sig(&data)
}
Subcommand::MemberCount => {
println!(
"member count: {}",
data.client()
.storage()
.fetch(
&runtime::storage().membership().counter_for_membership(),
None,
)
.await?
.unwrap()
)
}
Subcommand::LinkAccount { address } => {
data = data.fetch_idty_index().await?; // idty index required for payload
link_account(&data, address).await?;
}
};
Ok(())
}
// ======================
/// get identity
mut account_id: Option<AccountId>,
mut identity_id: Option<u32>,
mut username: Option<String>,
let client = data.client();
let indexer = data.indexer.clone();
// fetch missing information
match (&account_id, identity_id, &username) {
(None, Some(identity_id), None) => {
account_id = get_identity_by_index(client, identity_id)
.await?
.map(|idty| idty.owner_key);
(Some(account_id), None, None) => {
identity_id = get_idty_index_by_account_id(client, account_id).await?;
}
(None, None, Some(username)) => {
let indexer = indexer.as_ref().ok_or(anyhow!(
"Cannot fetch identity from username without indexer."
))?;
if let Some(pubkey) = indexer.pubkey_by_username(username).await? {
// convert string to accountid
let fetched_account_id = AccountId::from_str(&pubkey).map_err(|e| anyhow!(e))?;
// in the future, also ask indexer the identity index
identity_id = get_idty_index_by_account_id(client, &fetched_account_id).await?;
account_id = Some(fetched_account_id);
} else {
return Err(anyhow!("no identity found for this username"));
}
}
_ => {
return Err(anyhow!(
"One and only one argument is needed to fetch the identity."
));
}
};
println!(
"Account id: {}",
account_id
.as_ref()
.map_or(String::new(), AccountId::to_string)
);
println!(
"Identity id: {}",
identity_id.map_or(String::new(), |identity_id| format!("{identity_id}"))
);
if let (Some(indexer), Some(account_id), None) = (&indexer, &account_id, &username) {
username = indexer.username_by_pubkey(&account_id.to_string()).await?;
}
println!(
"Username: {}",
username.unwrap_or("<no indexer>".to_string())
);
/// get identity index by account id
) -> Result<Option<u32>, anyhow::Error> {
) -> Result<Option<IdtyValue<u32, AccountId, IdtyData>>, anyhow::Error> {
.fetch(&runtime::storage().identity().identities(idty_index), None)
pub async fn create_identity(data: &Data, target: AccountId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::identity::events::IdtyCreated,
StaticTxPayload<runtime::identity::calls::CreateIdentity>,
>(data, &runtime::tx().identity().create_identity(target))
.await
pub async fn confirm_identity(data: &Data, name: String) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::identity::events::IdtyConfirmed,
StaticTxPayload<runtime::identity::calls::ConfirmIdentity>,
>(data, &runtime::tx().identity().confirm_identity(name))
.await
}
/// confirm identity
pub async fn validate_identity(data: &Data, index: u32) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::identity::events::IdtyValidated,
StaticTxPayload<runtime::identity::calls::ValidateIdentity>,
>(data, &runtime::tx().identity().validate_identity(index))
.await
}
/// renew membership
pub async fn renew_membership(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::membership::events::MembershipRenewed,
StaticTxPayload<runtime::membership::calls::RenewMembership>,
>(data, &runtime::tx().membership().renew_membership())
.await
pub async fn revoke_identity(data: &Data) -> Result<(), subxt::Error> {
// Transform signature to MultiSignature
// TODO: this is a hack, we should be able to use the signature directly
let signature = Signature(signature.0);
let multisign = MultiSignature::Sr25519(signature);
submit_call_and_look_event::<
runtime::identity::events::IdtyRemoved,
StaticTxPayload<runtime::identity::calls::RevokeIdentity>,
>(
data,
&runtime::tx()
.identity()
.revoke_identity(data.idty_index(), data.address(), multisign),
)
.await
}
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
type LinkAccountPayload = Vec<u8>;
/// generate link account document
pub fn generate_link_account(
data: &Data,
address: AccountId,
) -> (LinkAccountPayload, sp_core::sr25519::Signature) {
let payload = (b"link", data.genesis_hash, data.idty_index(), address).encode();
let signature = data.keypair().sign(&payload);
(payload, signature)
}
/// link an account to the identity
pub async fn link_account(data: &Data, address: AccountId) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_link_account(data, address.clone());
// this is a hack, see
// https://substrate.stackexchange.com/questions/10309/how-to-use-core-crypto-types-instead-of-runtime-types
let signature = Signature(signature.0);
submit_call_and_look_event::<
runtime::account::events::AccountLinked,
StaticTxPayload<runtime::identity::calls::LinkAccount>,
>(
data,
&runtime::tx()
.identity()
.link_account(address, NewOwnerKeySignature(signature)),
)
.await