Skip to content
Snippets Groups Projects
Select Git revision
  • ac4ceede7b4a0effd7c45c1c0bb0af61b7d16d89
  • master default protected
  • upgradable-multisig
  • 308-add-a-runtime-api-to-simulate-max-net-tx-cost-for-the-end-user-fees-refund
  • 270-parametrage-de-la-gtest
  • network/gdev-800 protected
  • cgeek/issue-297-cpu
  • gdev-800-tests
  • update-docker-compose-rpc-squid-names
  • fix-252
  • 1000i100-test
  • hugo/tmp-0.9.1
  • network/gdev-803 protected
  • hugo/endpoint-gossip
  • network/gdev-802 protected
  • hugo/distance-precompute
  • network/gdev-900 protected
  • tuxmain/anonymous-tx
  • debug/podman
  • hugo/195-doc
  • hugo/195-graphql-schema
  • gdev-900-0.10.1 protected
  • gdev-900-0.10.0 protected
  • gdev-900-0.9.2 protected
  • gdev-800-0.8.0 protected
  • gdev-900-0.9.1 protected
  • gdev-900-0.9.0 protected
  • gdev-803 protected
  • gdev-802 protected
  • runtime-801 protected
  • gdev-800 protected
  • runtime-800-bis protected
  • runtime-800 protected
  • runtime-800-backup protected
  • runtime-701 protected
  • runtime-700 protected
  • runtime-600 protected
  • runtime-500 protected
  • v0.4.1 protected
  • runtime-401 protected
  • v0.4.0 protected
41 results

build-for-arm.md

Blame
  • identity.rs 18.16 KiB
    use crate::*;
    
    use crate::commands::vault::retrieve_account_tree_node_for_name;
    use crate::{
    	commands::revocation::generate_revoc_doc,
    	runtime::runtime_types::{
    		common_runtime::entities::IdtyData, pallet_identity::types::*,
    		pallet_smith_members::types::SmithMeta, pallet_smith_members::SmithStatus,
    		sp_runtime::MultiSignature,
    	},
    };
    
    /// define identity subcommands
    #[derive(Clone, Default, Debug, clap::Parser)]
    pub enum Subcommand {
    	/// Show identity
    	/// (same as get but without arg)
    	#[default]
    	Show,
    	/// Fetch identity
    	Get {
    		#[clap(short = 'a', long = "address", value_name = "ADDRESS")]
    		account_id: Option<AccountId>,
    		#[clap(short = 'i', long = "identity")]
    		identity_id: Option<IdtyId>,
    		#[clap(short = 'u', long = "username")]
    		username: Option<String>,
    	},
    	/// Create and certify an identity
    	///
    	/// Caller must be member, and the target account must exist.
    	Create {
    		#[clap(value_name = "ADDRESS")]
    		target: AccountId,
    	},
    	/// Confirm an identity
    	///
    	/// To be called by the certified not-yet-member account, to become member.
    	Confirm { name: String },
    	/// Request distance evaluation
    	/// make sure that it's ok otherwise currency is slashed
    	RequestDistanceEvaluation,
    	/// Request distance evaluation for unvalidated identity
    	RequestDistanceEvaluationFor {
    		#[clap(value_name = "USERNAME")]
    		target: String,
    	},
    	/// Certify an identity
    	#[clap(alias = "cert")]
    	Certify {
    		#[clap(value_name = "USERNAME")]
    		target: String,
    	},
    	/// Renew a certification
    	#[clap(alias = "renew")]
    	RenewCert {
    		#[clap(value_name = "USERNAME")]
    		target: String,
    	},
    	/// Revoke an identity immediately
    	Revoke,
    	/// Generate a revocation document for the provided account
    	GenRevocDoc,
    	/// Display member count
    	MemberCount,
    	/// Link an account to the [target] identity
    	#[clap(long_about = "Link an account to the [target] identity.\n\
    		\n\
    		The target identity can be passed as argument using any of the suggested options.")]
    	LinkAccount(SecretProvider),
    	/// Migrate identity to another [target] account
    	#[clap(long_about = "Migrate identity to another [target] account.\n\
    		\n\
    		The target account can be passed as argument using any of the suggested options.")]
    	ChangeOwnerKey(SecretProvider),
    }
    
    #[derive(clap::Args, Clone, Debug)]
    pub struct SecretProvider {
    	/// SS58 Address of target vault account
    	#[clap(short, conflicts_with_all=["vault_name","secret_format", "secret", "crypto_scheme"])]
    	address: Option<AccountId>,
    	/// Name of target vault account
    	#[clap(short = 'v', conflicts_with_all=["secret_format", "secret", "crypto_scheme"])]
    	vault_name: Option<String>,
    	/// Secret key format of target account (seed, substrate)
    	#[clap(short = 'S', long)]
    	secret_format: Option<SecretFormat>,
    	/// Secret of target account
    	#[clap(short, long)]
    	secret: Option<String>,
    	/// Crypto scheme of target account (sr25519, ed25519)
    	#[clap(short = 'c', long, required = false, default_value = CryptoScheme::Ed25519)]
    	crypto_scheme: CryptoScheme,
    }
    
    impl SecretProvider {
    	/// Analyses the SecretProvider data and tries to retrieve a keypair.
    	///
    	/// Will potentially request a password to decrypt the secret in keystore if it was asked to use a vault account.
    	///
    	/// Or it could request the `secret` if using `secret-format` without also providing the `secret`.
    	///
    	/// Will return an error if no data was provided or if it encountered another issue in the process.
    	async fn get_keypair(&self, data: &Data) -> Result<KeyPair, GcliError> {
    		let key_pair = if let Some(address) = self.address.clone() {
    			commands::vault::fetch_vault_keypair_for_address(data, address).await?
    		} else if let Some(vault_name) = self.vault_name.clone() {
    			let account_tree_node =
    				retrieve_account_tree_node_for_name(data.connect_db(), &vault_name).await?;
    			let address = account_tree_node.borrow().account.address.0.clone();
    
    			commands::vault::fetch_vault_keypair_for_address(data, address).await?
    		} else if let Some(secret_format) = self.secret_format {
    			let keypair = get_keypair(secret_format, self.secret.as_deref(), self.crypto_scheme)?;
    			println!(
    				"target address:'{}' (using crypto-scheme:{})",
    				keypair.address(),
    				<&'static str>::from(self.crypto_scheme)
    			);
    			keypair
    		} else {
    			return Err(GcliError::Input(
    				"One of `address`/`vault_name`/`secret_format`(and optional `secret` & `crypto_scheme`) must be provided".to_string(),
    			));
    		};
    
    		Ok(key_pair)
    	}
    }
    
    /// handle identity commands
    pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
    	let mut data = data.build_client().await?;
    	match command {
    		Subcommand::Show => {
    			data = data.build_indexer().await?;
    			get_identity(&data, Some(data.address()), None, None).await?
    		}
    		Subcommand::Get {
    			ref account_id,
    			identity_id,
    			ref username,
    		} => {
    			data = data.build_indexer().await?;
    			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::RequestDistanceEvaluation => {
    			commands::distance::request_distance_evaluation(&data).await?;
    		}
    		Subcommand::RequestDistanceEvaluationFor { target } => {
    			let target = try_get_idty_index_by_name(&data, &target).await?;
    			commands::distance::request_distance_evaluation_for(&data, target).await?;
    		}
    		Subcommand::Certify { target } => {
    			let targetid = try_get_idty_index_by_name(&data, &target).await?;
    			// ask user to confirm certification
    			if let Ok(true) = inquire::Confirm::new(&format!(
    				"Are you sure you want to certify {target} ({targetid})?"
    			))
    			.with_default(false)
    			.prompt()
    			{
    				commands::certification::certify(&data, targetid).await?;
    			};
    		}
    		Subcommand::RenewCert { target } => {
    			let target = try_get_idty_index_by_name(&data, &target).await?;
    			commands::certification::renew(&data, target).await?;
    		}
    		Subcommand::Revoke => {
    			data = data.fetch_idty_index().await?;
    			revoke_identity(&data).await?;
    		}
    		Subcommand::GenRevocDoc => {
    			data = data.fetch_idty_index().await?;
    			commands::revocation::print_revoc_sig(&data).await
    		}
    		Subcommand::MemberCount => {
    			println!(
    				"member count: {}",
    				data.client()
    					.storage()
    					.at_latest()
    					.await?
    					.fetch(&runtime::storage().membership().counter_for_membership(),)
    					.await?
    					.unwrap()
    			)
    		}
    		Subcommand::LinkAccount(secret_provider) => {
    			let target_keypair = secret_provider.get_keypair(&data).await?;
    
    			println!("Trying to make the link");
    			let address = target_keypair.address();
    			let data = data.fetch_idty_index().await?; // idty index required for payload
    			link_account(&data, address, target_keypair).await?;
    		}
    		Subcommand::ChangeOwnerKey(secret_provider) => {
    			let target_keypair = secret_provider.get_keypair(&data).await?;
    
    			println!("Trying to change owner key");
    			let address = target_keypair.address();
    			let data = data.fetch_idty_index().await?; // idty index required for payload
    			change_owner_key(&data, address, target_keypair).await?;
    		}
    	};
    
    	Ok(())
    }
    
    // ======================
    
    // TODO derive this automatically
    impl Serialize for IdtyStatus {
    	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    	where
    		S: serde::Serializer,
    	{
    		serializer.serialize_str(&format!("{:?}", self))
    	}
    }
    impl Serialize for SmithStatus {
    	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    	where
    		S: serde::Serializer,
    	{
    		serializer.serialize_str(&format!("{:?}", self))
    	}
    }
    
    // for why Arc<[T]> instead of Vec<T> see
    // https://www.youtube.com/watch?v=A4cKi7PTJSs&pp=ygULVmVjPFN0cmluZz4%3D
    /// struct to represent details of identity request
    #[derive(Serialize)]
    struct IdtyView {
    	index: IdtyId,
    	status: IdtyStatus,
    	pseudo: String,
    	owner_key: AccountId,
    	old_owner_key: Vec<AccountId>,
    	expire_on: BlockNumber,
    	cert_issued: Vec<String>,
    	cert_issued_count: u32,
    	cert_received: Vec<String>,
    	cert_received_count: u32,
    	smith: Option<SmithView>,
    	linked_account: Vec<AccountId>,
    }
    impl std::fmt::Display for IdtyView {
    	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    		writeln!(f, "Identity index: {}", self.index)?;
    		writeln!(f, "Username:       {}", self.pseudo)?;
    		writeln!(
    			f,
    			"Address:        {}",
    			AccountId::to_string(&self.owner_key)
    		)?;
    		writeln!(f, "Status:         {:?}", self.status)?;
    		writeln!(
    			f,
    			"Certifications: received {}, issued {}",
    			self.cert_received_count, self.cert_issued_count
    		)?;
    		if let Some(smith) = &self.smith {
    			writeln!(f, "Smith status:   {:?}", smith.status)?;
    			writeln!(
    				f,
    				"Smith certs:    received {}, issued {}",
    				smith.cert_received_count, smith.cert_issued_count
    			)?;
    		}
    		let a = self.linked_account.len();
    		if a > 1 {
    			writeln!(f, "Linked accounts count: {a}")?;
    		}
    		Ok(())
    	}
    }
    
    #[derive(Serialize)]
    struct SmithView {
    	status: SmithStatus,
    	cert_issued: Vec<String>,
    	cert_issued_count: usize,
    	cert_received: Vec<String>,
    	cert_received_count: usize,
    }
    
    /// get identity
    pub async fn get_identity(
    	data: &Data,
    	account_id: Option<AccountId>,
    	identity_id: Option<IdtyId>,
    	pseudo: Option<String>,
    ) -> Result<(), GcliError> {
    	let client = data.client();
    	let indexer = data.indexer.clone();
    
    	// get idty_id
    	let index = match (identity_id, &account_id, &pseudo) {
    		// idty_id
    		(Some(index), None, None) => index,
    		// account_id → idty_id
    		(None, Some(account_id), None) => get_idty_index_by_account_id(client, account_id)
    			.await?
    			.ok_or_else(|| {
    			GcliError::Duniter(format!("no identity for account '{account_id}'"))
    		})?,
    		// pseudo → idty_id
    		(None, None, Some(pseudo)) => get_idty_index_by_name(client, pseudo)
    			.await?
    			.ok_or_else(|| GcliError::Indexer(format!("no identity for name '{pseudo}'")))?,
    		_ => {
    			return Err(GcliError::Logic(
    				"One and only one argument is needed to fetch the identity.".to_string(),
    			));
    		}
    	};
    	// idty_id → value
    	let value = get_identity_by_index(client, index)
    		.await?
    		.ok_or_else(|| GcliError::Duniter(format!("no identity value for index {index}")))?;
    
    	// pseudo
    	let pseudo = pseudo.unwrap_or(if let Some(indexer) = &indexer {
    		indexer.username_by_index(index).await.ok_or_else(|| {
    			GcliError::Indexer(format!(
    				"indexer does not have username for this index {index}"
    			))
    		})?
    	} else {
    		"<no indexer>".to_string()
    	});
    
    	// get cert meta
    	let cert_meta = client
    		.storage()
    		.at_latest()
    		.await?
    		.fetch(
    			&runtime::storage()
    				.certification()
    				.storage_idty_cert_meta(index),
    		)
    		.await?
    		.expect("expected cert meta");
    
    	// get certs if possible
    	let (cert_issued, cert_received, linked_account, smith_info) = if let Some(indexer) = &indexer {
    		let info = indexer
    			.identity_info(index)
    			.await
    			.ok_or_else(|| GcliError::Indexer(format!("no info for identity {index}")))?;
    		Ok::<
    			(
    				Vec<String>,
    				Vec<String>,
    				Vec<AccountId>,
    				Option<crate::indexer::queries::identity_info::IdentityInfoIdentitySmith>,
    			),
    			GcliError,
    		>((
    			info.cert_issued
    				.into_iter()
    				.map(|i| i.receiver.unwrap().name.to_string())
    				.collect(),
    			info.cert_received
    				.into_iter()
    				.map(|i| i.issuer.unwrap().name.to_string())
    				.collect(),
    			info.linked_account
    				.into_iter()
    				.map(|i| AccountId::from_str(&i.id).unwrap())
    				.collect(),
    			info.smith,
    		))
    	} else {
    		Ok((vec![], vec![], vec![], None))
    	}?;
    
    	// get smith info
    	let smith_meta = get_smith(client, index).await?;
    
    	let smith = match (smith_meta, smith_info) {
    		(None, None) => Ok(None),
    		(Some(s), Some(i)) => Ok(Some(SmithView {
    			status: s.status,
    			cert_issued_count: s.issued_certs.len(),
    			cert_issued: i
    				.smith_cert_issued
    				.into_iter()
    				.map(|i| i.receiver.unwrap().identity.unwrap().name.to_string())
    				.collect(),
    			cert_received_count: s.received_certs.len(),
    			cert_received: i
    				.smith_cert_received
    				.into_iter()
    				.map(|i| i.issuer.unwrap().identity.unwrap().name.to_string())
    				.collect(),
    		})),
    		(Some(s), None) => match indexer {
    			Some(_) => Err(GcliError::Indexer(format!(
    				"Duniter and Indexer do not agree if {index} is smith"
    			))),
    			None => Ok(Some(SmithView {
    				status: s.status,
    				cert_issued_count: s.issued_certs.len(),
    				cert_issued: s.issued_certs.into_iter().map(|e| e.to_string()).collect(),
    				cert_received_count: s.received_certs.len(),
    				cert_received: s
    					.received_certs
    					.into_iter()
    					.map(|e| e.to_string())
    					.collect(),
    			})),
    		},
    		(None, Some(_)) => Err(GcliError::Indexer(format!(
    			"Duniter and Indexer do not agree if {pseudo} is smith"
    		))),
    	}?;
    
    	// build view
    	let view = IdtyView {
    		index,
    		status: value.status,
    		pseudo,
    		owner_key: value.owner_key,
    		old_owner_key: vec![],           // TODO fetch history of owner key change
    		expire_on: value.next_scheduled, // TODO if zero use membership instead
    		cert_issued,
    		cert_issued_count: cert_meta.issued_count,
    		cert_received,
    		cert_received_count: cert_meta.received_count,
    		smith,
    		linked_account,
    	};
    
    	// TODO generic way to do this shared between function
    	match data.args.output_format {
    		OutputFormat::Human => {
    			println!("{view}");
    		}
    		OutputFormat::Json => {
    			println!("{}", serde_json::to_string(&view).map_err(|e| anyhow!(e))?);
    		}
    	}
    
    	Ok(())
    }
    
    /// get identity index by account id
    pub async fn get_idty_index_by_account_id(
    	client: &Client,
    	account_id: &AccountId,
    ) -> Result<Option<IdtyId>, subxt::Error> {
    	client
    		.storage()
    		.at_latest()
    		.await?
    		.fetch(&runtime::storage().identity().identity_index_of(account_id))
    		.await
    }
    
    /// get smith info by index
    pub async fn get_smith(
    	client: &Client,
    	index: IdtyId,
    ) -> Result<Option<SmithMeta<IdtyId>>, subxt::Error> {
    	client
    		.storage()
    		.at_latest()
    		.await?
    		.fetch(&runtime::storage().smith_members().smiths(index))
    		.await
    }
    
    /// get identity index by name
    pub async fn get_idty_index_by_name(
    	client: &Client,
    	name: &str,
    ) -> Result<Option<IdtyId>, subxt::Error> {
    	client
    		.storage()
    		.at_latest()
    		.await?
    		.fetch(
    			&runtime::storage()
    				.identity()
    				.identities_names(IdtyName(name.into())),
    		)
    		.await
    }
    
    pub async fn try_get_idty_index_by_name(data: &Data, name: &str) -> Result<IdtyId, GcliError> {
    	get_idty_index_by_name(data.client(), name)
    		.await?
    		.ok_or_else(|| GcliError::Input(format!("no identity with name {name}")))
    }
    
    /// get identityt value by index
    pub async fn get_identity_by_index(
    	client: &Client,
    	idty_index: IdtyId,
    ) -> Result<Option<IdtyValue<IdtyId, AccountId, IdtyData>>, subxt::Error> {
    	client
    		.storage()
    		.at_latest()
    		.await?
    		.fetch(&runtime::storage().identity().identities(idty_index))
    		.await
    }
    
    /// created identity
    pub async fn create_identity(data: &Data, target: AccountId) -> Result<(), subxt::Error> {
    	submit_call_and_look_event::<
    		runtime::identity::events::IdtyCreated,
    		StaticPayload<runtime::identity::calls::types::CreateIdentity>,
    	>(data, &runtime::tx().identity().create_identity(target))
    	.await
    }
    
    /// confirm identity
    pub async fn confirm_identity(data: &Data, name: String) -> Result<(), subxt::Error> {
    	submit_call_and_look_event::<
    		runtime::identity::events::IdtyConfirmed,
    		StaticPayload<runtime::identity::calls::types::ConfirmIdentity>,
    	>(
    		data,
    		&runtime::tx()
    			.identity()
    			.confirm_identity(IdtyName(name.into())),
    	)
    	.await
    }
    
    /// generate revokation document and submit it immediately
    pub async fn revoke_identity(data: &Data) -> Result<(), subxt::Error> {
    	let (_payload, signature) = generate_revoc_doc(data).await;
    
    	// Transform signature to MultiSignature
    	// TODO: allow other signature formats
    	let multisign = MultiSignature::Sr25519(signature.into());
    
    	submit_call_and_look_event::<
    		runtime::identity::events::IdtyRemoved,
    		StaticPayload<runtime::identity::calls::types::RevokeIdentity>,
    	>(
    		data,
    		&runtime::tx()
    			.identity()
    			.revoke_identity(data.idty_index(), data.address(), multisign),
    	)
    	.await
    }
    
    type LinkAccountPayload = Vec<u8>;
    /// generate link account document
    pub fn generate_link_account(
    	data: &Data,
    	address: AccountId,
    	keypair: KeyPair,
    ) -> (LinkAccountPayload, Signature) {
    	let payload = (b"link", data.genesis_hash, data.idty_index(), address).encode();
    	match keypair {
    		KeyPair::Sr25519(keypair) => {
    			let signature = keypair.sign(&payload);
    			(payload, Signature::Sr25519(signature))
    		}
    		KeyPair::Ed25519(keypair) => {
    			let signature = keypair.sign(&payload);
    			(payload, Signature::Ed25519(signature))
    		}
    	}
    }
    
    type ChOkPayload = Vec<u8>;
    /// generate link account document
    pub fn generate_chok_payload(
    	data: &Data,
    	_address: AccountId,
    	keypair: KeyPair,
    ) -> (ChOkPayload, Signature) {
    	let payload = (
    		b"icok",
    		data.genesis_hash,
    		data.idty_index(),
    		data.address(),
    	)
    		.encode();
    	match keypair {
    		KeyPair::Sr25519(keypair) => {
    			let signature = keypair.sign(&payload);
    			(payload, Signature::Sr25519(signature))
    		}
    		KeyPair::Ed25519(keypair) => {
    			let signature = keypair.sign(&payload);
    			(payload, Signature::Ed25519(signature))
    		}
    	}
    }
    
    /// link an account to the identity
    pub async fn link_account(
    	data: &Data,
    	address: AccountId,
    	keypair: KeyPair,
    ) -> Result<(), subxt::Error> {
    	let (_payload, signature) = generate_link_account(data, address.clone(), keypair);
    
    	// TODO cleaner way to manage signature
    	let signature = match signature {
    		Signature::Sr25519(signature) => MultiSignature::Sr25519(signature.into()),
    		Signature::Ed25519(signature) => MultiSignature::Ed25519(signature.into()),
    	};
    
    	submit_call_and_look_event::<
    		runtime::account::events::AccountLinked,
    		StaticPayload<runtime::identity::calls::types::LinkAccount>,
    	>(
    		data,
    		&runtime::tx().identity().link_account(address, signature),
    	)
    	.await
    }
    
    /// change owner key
    pub async fn change_owner_key(
    	data: &Data,
    	address: AccountId,
    	keypair: KeyPair,
    ) -> Result<(), subxt::Error> {
    	let (_payload, signature) = generate_chok_payload(data, address.clone(), keypair);
    
    	// TODO cleaner way to manage signature
    	let signature = match signature {
    		Signature::Sr25519(signature) => MultiSignature::Sr25519(signature.into()),
    		Signature::Ed25519(signature) => MultiSignature::Ed25519(signature.into()),
    	};
    
    	submit_call_and_look_event::<
    		runtime::identity::events::IdtyChangedOwnerKey,
    		StaticPayload<runtime::identity::calls::types::ChangeOwnerKey>,
    	>(
    		data,
    		&runtime::tx()
    			.identity()
    			.change_owner_key(address, signature),
    	)
    	.await
    }