Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • clients/rust/gcli-v2s
  • d0p1/gcli-v2s
  • flebon/gcli-v2s
  • zicmama/gcli-v2s
  • Nicolas80/gcli-v2s
5 results
Show changes
Showing
with 1805 additions and 670 deletions
[toolchain]
channel = "stable"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>gcli</string>
<key>CFBundleExecutable</key>
<string>osascript</string>
<key>CFBundleIdentifier</key>
<string>com.axiomteam.gcli</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>gcli</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2.13</string>
<key>CFBundleVersion</key>
<string>20240522.234244</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSAppleScriptEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>gcli script</string>
<key>CFBundleURLSchemes</key>
<array>
<string>file</string>
</array>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>scpt</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ScriptEditorAppIcon.icns</string>
<key>CFBundleTypeName</key>
<string>AppleScript</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>osascript</string>
<key>CFBundleScriptPath</key>
<string>Contents/Resources/Scripts/install_gcli.scpt</string>
</dict>
</plist>
-- Ask user if they want to install gcli
set userResponse to display dialog "Do you want to install gcli to /usr/local/bin?" buttons {"No", "Yes"} default button "Yes"
if button returned of userResponse is "Yes" then
-- Run shell script to move gcli with administrator privileges
try
do shell script "mv gcli.app/Contents/MacOS/gcli /usr/local/bin" with administrator privileges
display dialog "gcli has been installed to /usr/local/bin and is now available in your PATH."
on error errMsg number errorNumber
if errorNumber is -128 then
display dialog "Installation cancelled."
else
display dialog "An error occurred: " & errMsg
end if
end try
else
display dialog "Installation cancelled."
end if
use crate::indexer::*;
use crate::*;
use anyhow::{anyhow, Result};
use std::collections::{hash_map, HashMap};
pub struct IdentityCache {
client: Client,
identities: HashMap<u32, String>,
indexer: Option<Indexer>,
}
impl IdentityCache {
pub fn new(client: Client, indexer: Option<Indexer>) -> Self {
Self {
client,
identities: HashMap::new(),
indexer,
}
}
pub async fn fetch_identity(
&mut self,
identity_id: u32,
parent_hash: sp_core::H256,
) -> Result<String> {
Ok(match self.identities.entry(identity_id) {
hash_map::Entry::Occupied(entry) => entry.get().clone(),
hash_map::Entry::Vacant(entry) => entry
.insert({
let pubkey = self
.client
.storage()
.fetch(
&runtime::storage().identity().identities(identity_id),
Some(parent_hash),
)
.await?
.ok_or_else(|| anyhow!("Identity {} not found", identity_id))?
.owner_key
.to_string();
format!(
"“ {} ”",
if let Some(indexer) = &self.indexer {
if let Ok(Some(username)) = indexer.username_by_pubkey(&pubkey).await {
username
} else {
pubkey
}
} else {
pubkey
}
)
})
.clone(),
})
}
}
pub mod account;
pub mod blockchain;
pub mod certification;
pub mod cesium;
pub mod collective;
pub mod distance;
pub mod expire;
pub mod identity;
pub mod net_test;
pub mod oneshot;
pub mod publish;
pub mod revocation;
pub mod runtime;
pub mod smith;
pub mod sudo;
pub mod transfer;
pub mod ud;
pub mod vault;
use crate::*;
/// define account subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
/// Fetch account balance
#[default]
Balance,
/// Transfer some currency to an account
Transfer {
/// Amount to transfer
amount: u64,
/// Destination address
#[clap(value_name = "ADDRESS")]
dest: AccountId,
/// Prevent from going below account existential deposit
#[clap(short = 'k', long = "keep-alive")]
keep_alive: bool,
/// Use universal dividends instead of units
#[clap(short = 'u', long = "ud")]
is_ud: bool,
},
/// Transfer the same amount for each space-separated address.
/// If an address appears multiple times, it will get multiple times the same amount
TransferMultiple {
/// Amount given to each destination address
amount: u64,
/// List of target addresses
#[clap(value_name = "ADDRESSES")]
dests: Vec<AccountId>,
},
/// Unlink the account from the linked identity
Unlink,
}
/// handle account commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
let data = data.build_client().await?.fetch_system_properties().await?;
match command {
Subcommand::Balance => get_balance(data).await?,
Subcommand::Transfer {
amount,
dest,
keep_alive,
is_ud,
} => {
commands::transfer::transfer(&data, amount, dest, keep_alive, is_ud).await?;
}
Subcommand::TransferMultiple { amount, dests } => {
commands::transfer::transfer_multiple(&data, amount, dests).await?;
}
Subcommand::Unlink => {
unlink_account(&data).await?;
}
};
Ok(())
}
/// get balance
pub async fn get_balance(data: Data) -> Result<(), anyhow::Error> {
pub async fn get_balance(data: Data) -> Result<(), subxt::Error> {
let account_id = data.address();
let account_info = get_account_info(data.client(), &account_id).await?;
if let Some(account_info) = account_info {
......@@ -22,6 +79,17 @@ pub async fn get_account_info(
) -> Result<Option<AccountInfo>, subxt::Error> {
client
.storage()
.fetch(&runtime::storage().system().account(account_id), None)
.at_latest()
.await?
.fetch(&runtime::storage().system().account(account_id))
.await
}
/// unlink account
pub async fn unlink_account(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::account::events::AccountUnlinked,
StaticPayload<runtime::account::calls::types::UnlinkIdentity>,
>(data, &runtime::tx().account().unlink_identity())
.await
}
use crate::*;
/// define blockchain subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
#[clap(hide = true)]
Repart {
// Number of transactions per block to target
target: u32,
#[clap(short = 'o', long = "old-repart")]
// Old/actual repartition
actual_repart: Option<u32>,
},
#[clap(hide = true)]
SpamRoll { actual_repart: usize },
/// Get information about runtime
RuntimeInfo,
/// Check current block
#[default]
CurrentBlock,
/// Create one block manually (manual sealing)
#[clap(hide = true)]
CreateBlock,
}
/// handle blockchain commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
let mut data = data.build_client().await?;
match command {
Subcommand::Repart {
target,
actual_repart,
} => commands::net_test::repart(&data, target, actual_repart).await?,
Subcommand::SpamRoll { actual_repart } => {
commands::net_test::spam_roll(&data, actual_repart).await?
}
Subcommand::RuntimeInfo => {
data = data.fetch_system_properties().await?;
commands::runtime::runtime_info(data).await;
}
Subcommand::CurrentBlock => {
let finalized_number = fetch_finalized_number(&data).await?;
let current_number = fetch_latest_number_and_hash(&data).await?.0;
println!("on {}", data.cfg.duniter_endpoint);
println!("finalized block\t{}", finalized_number);
println!("current block\t{}", current_number);
}
Subcommand::CreateBlock => {
todo!()
// data.client()
// .backend()
// .call("engine_createBlock", subxt::backend::rpc::rpc_params![true, true]) // create empty block and finalize
// .await?; // FIXME this gives a serialization error
}
}
Ok(())
}
/// get genesis hash
pub async fn fetch_genesis_hash(data: &Data) -> Result<Hash, anyhow::Error> {
pub async fn fetch_genesis_hash(data: &Data) -> Result<Hash, subxt::Error> {
Ok(data
.client()
.storage()
.fetch(&runtime::storage().system().block_hash(0), None)
.at_latest()
.await?
.fetch(&runtime::storage().system().block_hash(0))
.await?
.unwrap())
}
/// get finalized number
pub async fn fetch_finalized_number(data: &Data) -> Result<BlockNumber, subxt::Error> {
Ok(data
.client()
.storage()
.at_latest()
.await?
.fetch(&runtime::storage().system().number())
.await?
.unwrap())
}
/// get finalized hash and number (require legacy)
pub async fn fetch_finalized_number_and_hash(
data: &Data,
) -> Result<(BlockNumber, Hash), subxt::Error> {
let hash = data
.legacy_rpc_methods()
.await
.chain_get_finalized_head()
.await?;
let number = data
.legacy_rpc_methods()
.await
.chain_get_block(Some(hash))
.await?
.unwrap()
.block
.header
.number;
Ok((number, hash))
}
/// get latest hash and number
pub async fn fetch_latest_number_and_hash(
data: &Data,
) -> Result<(BlockNumber, Hash), subxt::Error> {
let number = data
.legacy_rpc_methods()
.await
.chain_get_header(None)
.await?
.unwrap()
.number;
let hash = data
.legacy_rpc_methods()
.await
.chain_get_block_hash(Some(number.into()))
.await?
.unwrap();
Ok((number, hash))
}
use crate::*;
/// submit a certification and track progress
pub async fn certify(data: &Data, target: IdtyId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::certification::events::CertAdded,
StaticPayload<runtime::certification::calls::types::AddCert>,
>(data, &runtime::tx().certification().add_cert(target))
.await
}
/// renew certification
pub async fn renew(data: &Data, target: IdtyId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::certification::events::CertRenewed,
StaticPayload<runtime::certification::calls::types::RenewCert>,
>(data, &runtime::tx().certification().renew_cert(target))
.await
}
use crate::data::Data;
use crate::keys;
use crate::keys::KeyPair;
use crate::runtime_config::AccountId;
use crate::utils::GcliError;
use bs58;
use sp_core::{ed25519, Pair};
use std::str::FromStr;
/// define cesium subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
// Nothing
#[default]
#[clap(hide = true)]
Nothing,
/// Analyse a base58 pubkey and gives it in all its form
Pubkey { pubkey: String },
/// Prompt for cesium input
Prompt,
}
/// handle blockchain commands
pub async fn handle_command(_data: Data, command: Subcommand) -> Result<(), GcliError> {
match command {
Subcommand::Nothing => {}
Subcommand::Pubkey { pubkey } => {
let raw_pubkey = bs58::decode(pubkey).into_vec().unwrap();
let raw_pubkey: [u8; 32] = if raw_pubkey.len() > 32 {
return Err(GcliError::Input("invalid pubkey size".to_string()));
} else {
[vec![0; 32 - raw_pubkey.len()], raw_pubkey]
.concat()
.try_into()
.unwrap()
};
println!("Pubkey (hex): 0x{}", hex::encode(raw_pubkey));
let address: AccountId = sp_core::ed25519::Public::from_raw(raw_pubkey).into();
println!("Address (SS58): {}", address);
}
Subcommand::Prompt => {
let pair = keys::prompt_secret_cesium();
println!(
"Pubkey: {}",
compute_g1v1_public_key_from_ed25519_pair(&pair)
);
let address: AccountId = pair.public().into();
println!("Address: {}", address);
}
}
Ok(())
}
/// Computes G1v1 public key from a KeyPair of type sp_core::ed25519::Pair - fails otherwise
pub fn compute_g1v1_public_key(key_pair: &KeyPair) -> Result<String, GcliError> {
match key_pair {
KeyPair::Sr25519(_) => Err(GcliError::Logic(
"key pair is not of type ed25519".to_string(),
)),
KeyPair::Ed25519(key_pair) => Ok(compute_g1v1_public_key_from_ed25519_pair(key_pair)),
}
}
/// Computes G1v1 public key from an ed25519 Pair
pub fn compute_g1v1_public_key_from_ed25519_pair(ed25519_key_pair: &ed25519::Pair) -> String {
compute_g1v1_public_key_from_ed25519_public(&ed25519_key_pair.public())
}
/// Computes G1v1 public key from an ed25519 Public
pub fn compute_g1v1_public_key_from_ed25519_public(ed25519_public: &ed25519::Public) -> String {
bs58::encode(ed25519_public).into_string()
}
/// Computes G1v1 public key from an ed25519 AccountId
pub fn compute_g1v1_public_key_from_ed25519_account_id(ed25519_account_id: &AccountId) -> String {
let ed25519_public: ed25519::Public = ed25519::Public::from(ed25519_account_id.0);
bs58::encode(ed25519_public).into_string()
}
/// Computes G1v1 public key from an ed25519 SS58 Address
#[allow(unused)]
pub fn compute_g1v1_public_key_from_ed25519_ss58_address(
ed25519_ss58_address: &str,
) -> Result<String, GcliError> {
Ok(compute_g1v1_public_key_from_ed25519_account_id(
&AccountId::from_str(ed25519_ss58_address).map_err(|e| GcliError::Input(e.to_string()))?,
))
}
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
/// Test data:
/// cesium_id = "test_cesium_id"
/// cesium_pwd = "test_cesium_pwd"
///
/// G1v1 base58 public key: 86pW1doyJPVH3jeDPZNQa1UZFBo5zcdvHERcaeE758W7
///
/// ```
/// subkey inspect --scheme ed25519
/// URI:
/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84` is account:
/// Network ID: substrate
/// Secret seed: 0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84
/// Public key (hex): 0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
/// Account ID: 0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
/// Public key (SS58): 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
/// SS58 Address: 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
/// ```
#[test]
fn test_compute_g1v1_public_key() {
let expected_base58_public_key = "86pW1doyJPVH3jeDPZNQa1UZFBo5zcdvHERcaeE758W7";
let ss58_address = "5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4";
assert_eq!(
expected_base58_public_key,
compute_g1v1_public_key_from_ed25519_ss58_address(ss58_address).unwrap()
);
let account_id = AccountId::from_str(ss58_address).unwrap();
assert_eq!(
expected_base58_public_key,
compute_g1v1_public_key_from_ed25519_account_id(&account_id)
);
let ed25519_public = ed25519::Public::from(account_id.0);
assert_eq!(
expected_base58_public_key,
compute_g1v1_public_key_from_ed25519_public(&ed25519_public)
);
let cesium_id = "test_cesium_id".to_string();
let cesium_pwd = "test_cesium_pwd".to_string();
let seed = keys::seed_from_cesium(&cesium_id, &cesium_pwd);
let ed25519_pair_from_seed = ed25519::Pair::from_seed(&seed);
assert_eq!(
expected_base58_public_key,
compute_g1v1_public_key_from_ed25519_pair(&ed25519_pair_from_seed)
);
}
}
use crate::*;
use anyhow::Result;
use sp_core::{sr25519::Pair, H256};
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
/// define technical committee subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
#[default]
/// List members of the technical committee
Members,
/// Propose a hex encoded call
Propose { hex: String },
/// List proposals to the technical committee
Proposals,
/// Vote a proposal to the technical committee
Vote {
/// Proposal hash
hash: Hash,
/// Proposal index
index: u32,
/// Vote (0=against, 1=for)
vote: u8,
},
}
pub async fn technical_committee_members(data: &Data) -> Result<()> {
let client = data.client();
let indexer = data.indexer.clone();
/// handle technical committee commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
let data = data.build_client().await?.build_indexer().await?;
match command {
Subcommand::Members => technical_committee_members(&data).await?,
Subcommand::Propose { hex } => technical_committee_propose(&data, &hex).await?,
Subcommand::Proposals => technical_committee_proposals(data.client()).await?,
Subcommand::Vote { hash, index, vote } => {
let vote = match vote {
0 => false,
1 => true,
_ => panic!("Vote must be written 0 if you disagree, or 1 if you agree."),
};
technical_committee_vote(
&data, hash, //Hash::from_str(&hash).expect("Invalid hash formatting"),
index, vote,
)
.await?;
}
};
let parent_hash = client
.storage()
.fetch(&runtime::storage().system().parent_hash(), None)
.await?
.unwrap();
Ok(())
}
/// list technical committee members
pub async fn technical_committee_members(data: &Data) -> Result<(), subxt::Error> {
let client = data.client();
let indexer = &data.indexer;
for account_id in client
.storage()
.fetch(
&runtime::storage().technical_committee().members(),
Some(parent_hash),
)
.at_latest()
.await?
.fetch(&runtime::storage().technical_committee().members())
.await?
.unwrap_or_default()
{
println!(
"{}",
if let Some(indexer) = &indexer {
indexer
.username_by_pubkey(&account_id.to_string())
.await
.ok()
.flatten()
if let Some(indexer) = indexer {
// indexer is set, we can get the name
let name = indexer.username_by_pubkey(&account_id.to_string()).await;
if name.is_some() {
name
} else {
indexer
.wasname_by_pubkey(&account_id.to_string())
.await
.map(|name| format!("{name}\t(old account)"))
}
} else {
// indexer is not set, we can get the idty index by accountid
client
.storage()
.fetch(
&runtime::storage().identity().identity_index_of(&account_id),
Some(parent_hash),
)
.await
.ok()
.flatten()
.at_latest()
.await?
.fetch(&runtime::storage().identity().identity_index_of(&account_id))
.await?
.map(|identity_id| format!("{identity_id}"))
}
// no idty found, display account_id
.unwrap_or_else(|| account_id.to_string(),)
);
}
Ok(())
}
/// list technical committee proposals
// TODO:
// * better formatting (format pubkeys to SS58 and add usernames)
// * display proposals indices
pub async fn technical_committee_proposals(client: &Client) -> Result<()> {
pub async fn technical_committee_proposals(client: &Client) -> Result<(), subxt::Error> {
let parent_hash = client
.storage()
.fetch(&runtime::storage().system().parent_hash(), None)
.at_latest()
.await?
.fetch(&runtime::storage().system().parent_hash())
.await?
.unwrap();
let mut proposals_iter = client
.storage()
.iter(
runtime::storage()
.technical_committee()
.proposal_of(H256::default()),
10,
Some(parent_hash),
)
.at(parent_hash)
.iter(runtime::storage().technical_committee().proposal_of_iter())
.await?;
while let Some((proposal_hash, proposal)) = proposals_iter.next().await? {
println!("{}", hex::encode(&proposal_hash.0[32..64]));
println!("{proposal:#?}");
while let Some(Ok(item)) = proposals_iter.next().await {
println!("{}", hex::encode(&item.key_bytes[32..64]));
println!("{:#?}", item.value);
println!();
}
Ok(())
}
/// submit vote to technical committee
pub async fn technical_committee_vote(
pair: Pair,
client: &Client,
proposal_hash: H256,
data: &Data,
proposal_hash: Hash,
proposal_index: u32,
vote: bool,
) -> Result<(), subxt::Error> {
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.technical_committee()
.vote(proposal_hash, proposal_index, vote),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
submit_call_and_look_event::<
runtime::technical_committee::events::Voted,
StaticPayload<runtime::technical_committee::calls::types::Vote>,
>(
data,
&runtime::tx()
.technical_committee()
.vote(proposal_hash, proposal_index, vote),
)
.await
}
if let Some(e) = events.find_first::<runtime::technical_committee::events::Voted>()? {
println!("{e:?}");
}
/// propose call given as hexadecimal
/// can be generated with `subxt explore` for example
pub async fn technical_committee_propose(data: &Data, proposal: &str) -> Result<(), subxt::Error> {
let raw_call = hex::decode(proposal).expect("invalid hex");
let call = codec::decode_from_bytes(raw_call.into()).expect("invalid call");
let payload = runtime::tx().technical_committee().propose(5, call, 100);
Ok(())
submit_call_and_look_event::<
runtime::technical_committee::events::Proposed,
StaticPayload<runtime::technical_committee::calls::types::Propose>,
>(data, &payload)
.await
}
use crate::*;
/// request distance evaluation
pub async fn request_distance_evaluation(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::distance::events::EvaluationRequested,
StaticPayload<runtime::distance::calls::types::RequestDistanceEvaluation>,
>(
data,
&runtime::tx().distance().request_distance_evaluation(),
)
.await
}
/// request distance evaluation for someone else (must be unvalidated)
pub async fn request_distance_evaluation_for(
data: &Data,
target: IdtyId,
) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::distance::events::EvaluationRequested,
StaticPayload<runtime::distance::calls::types::RequestDistanceEvaluationFor>,
>(
data,
&runtime::tx()
.distance()
.request_distance_evaluation_for(target),
)
.await
}
use crate::*;
use anyhow::Result;
use crate::{indexer::*, *};
use futures::join;
use std::collections::BTreeMap;
pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Result<()> {
pub async fn monitor_expirations(
data: &Data,
blocks: u32,
_sessions: u32,
) -> Result<(), subxt::Error> {
let client = data.client();
let indexer = data.indexer.clone();
let parent_hash = client
.storage()
.fetch(&runtime::storage().system().parent_hash(), None)
.at_latest()
.await?
.fetch(&runtime::storage().system().parent_hash())
.await?
.unwrap();
let addr_current_block = runtime::storage().system().number();
let addr_current_session = runtime::storage().session().current_index();
let (current_block, current_session) = join!(
let (current_block, _current_session) = join!(
client.storage().at(parent_hash).fetch(&addr_current_block),
client
.storage()
.fetch(&addr_current_block, Some(parent_hash)),
client
.storage()
.fetch(&addr_current_session, Some(parent_hash),)
.at(parent_hash)
.fetch(&addr_current_session)
);
let current_block = current_block?.unwrap_or_default();
let current_session = current_session?.unwrap_or_default();
let end_block = current_block + blocks;
let end_session = current_session + sessions;
let mut identity_cache = cache::IdentityCache::new(client.clone(), indexer);
// Rotate keys
let mut must_rotate_keys_before_iter = client
.storage()
.iter(
runtime::storage()
.authority_members()
.must_rotate_keys_before(0),
10,
Some(parent_hash),
)
.await?;
let mut must_rotate_keys_before = BTreeMap::new();
while let Some((k, v)) = must_rotate_keys_before_iter.next().await? {
let session_index = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if session_index < end_session {
must_rotate_keys_before.insert(session_index - current_session, v);
}
}
println!("\nAuthority members:");
for (sessions_left, identity_ids) in must_rotate_keys_before {
println!("Must rotate keys before {sessions_left} sessions:");
for identity_id in identity_ids {
println!(
" {} ({})",
identity_cache
.fetch_identity(identity_id, parent_hash)
.await
.unwrap_or_else(|_| "?".into()),
identity_id
);
}
}
let mut identity_cache = IdentityCache::new(client.clone(), indexer);
// Certifications
let mut basic_certs_iter = client
.storage()
.iter(
runtime::storage().cert().storage_certs_removable_on(0),
10,
Some(parent_hash),
)
.at(parent_hash)
.iter(runtime::storage().certification().certs_removable_on_iter())
.await?;
let mut basic_certs = BTreeMap::new();
while let Some((k, v)) = basic_certs_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if block_number < end_block {
basic_certs.insert(block_number - current_block, v);
}
}
let mut smith_certs_iter = client
.storage()
.iter(
runtime::storage()
.smith_cert()
.storage_certs_removable_on(0),
10,
Some(parent_hash),
)
.await?;
let mut smith_certs = BTreeMap::new();
while let Some((k, v)) = smith_certs_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
while let Some(Ok(item)) = basic_certs_iter.next().await {
let block_number = BlockNumber::from_le_bytes(item.key_bytes[40..44].try_into().unwrap());
if block_number < end_block {
smith_certs.insert(block_number - current_block, v);
basic_certs.insert(block_number - current_block, item.value);
}
}
for (title, certs) in [
("Certifications", basic_certs),
("Smith certifications", smith_certs),
] {
for (title, certs) in [("Certifications", basic_certs)] {
println!("\n{title}:");
for (blocks_left, certs) in certs {
println!("{blocks_left} blocks before expiration:");
......@@ -111,12 +54,12 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
println!(
" {} ({}) -> {} ({})",
identity_cache
.fetch_identity(issuer_id, parent_hash)
.fetch_identity(issuer_id,)
.await
.unwrap_or_else(|_| "?".into()),
issuer_id,
identity_cache
.fetch_identity(receiver_id, parent_hash)
.fetch_identity(receiver_id,)
.await
.unwrap_or_else(|_| "?".into()),
receiver_id,
......@@ -128,45 +71,22 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
// Memberships
let mut basic_membership_iter = client
.storage()
.iter(
runtime::storage().membership().memberships_expire_on(0),
10,
Some(parent_hash),
)
.at_latest()
.await?
.iter(runtime::storage().membership().memberships_expire_on_iter())
.await?;
let mut basic_memberships = BTreeMap::new();
while let Some((k, v)) = basic_membership_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
while let Some(Ok(item)) = basic_membership_iter.next().await {
let block_number = BlockNumber::from_le_bytes(item.key_bytes[40..44].try_into().unwrap());
if block_number < end_block {
if block_number < current_block {
dbg!((block_number, current_block));
}
basic_memberships.insert(block_number - current_block, v);
basic_memberships.insert(block_number - current_block, item.value);
}
}
let mut smith_membership_iter = client
.storage()
.iter(
runtime::storage()
.smith_membership()
.memberships_expire_on(0),
10,
Some(parent_hash),
)
.await?;
let mut smith_memberships = BTreeMap::new();
while let Some((k, v)) = smith_membership_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if block_number < end_block {
smith_memberships.insert(block_number - current_block, v);
}
}
for (title, memberships) in [
("Memberships", basic_memberships),
("Smith memberships", smith_memberships),
] {
for (title, memberships) in [("Memberships", basic_memberships)] {
println!("\n{title}:");
for (blocks_left, membership) in memberships {
println!("{blocks_left} blocks before expiration:");
......@@ -174,7 +94,7 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
println!(
" {} ({})",
identity_cache
.fetch_identity(identity_id, parent_hash)
.fetch_identity(identity_id)
.await
.unwrap_or_else(|_| "?".into()),
identity_id,
......@@ -185,3 +105,53 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
Ok(())
}
use std::collections::{hash_map, HashMap};
pub struct IdentityCache {
client: Client,
identities: HashMap<IdtyId, String>,
indexer: Option<Indexer>,
}
impl IdentityCache {
pub fn new(client: Client, indexer: Option<Indexer>) -> Self {
Self {
client,
identities: HashMap::new(),
indexer,
}
}
pub async fn fetch_identity(&mut self, identity_id: IdtyId) -> Result<String, GcliError> {
Ok(match self.identities.entry(identity_id) {
hash_map::Entry::Occupied(entry) => entry.get().clone(),
hash_map::Entry::Vacant(entry) => entry
.insert({
let pubkey = self
.client
.storage()
.at_latest()
.await?
.fetch(&runtime::storage().identity().identities(identity_id))
.await?
.ok_or_else(|| anyhow!("Identity {} not found", identity_id))?
.owner_key
.to_string();
format!(
"“ {} ”",
if let Some(indexer) = &self.indexer {
if let Some(username) = indexer.username_by_pubkey(&pubkey).await {
username
} else {
pubkey
}
} else {
pubkey
}
)
})
.clone(),
})
}
}
use crate::*;
use crate::commands::revocation::generate_revoc_doc;
use crate::runtime::runtime_types::common_runtime::entities::IdtyData;
use crate::runtime::runtime_types::pallet_identity::types::*;
use crate::runtime::runtime_types::sp_core::sr25519::Signature;
use crate::runtime::runtime_types::sp_runtime::MultiSignature;
use sp_core::{crypto::AccountId32, sr25519::Pair};
use std::str::FromStr;
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 identity
LinkAccount {
/// Secret key format (seed, substrate)
#[clap(short = 'S', long, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
/// Secret of account to link
/// most likely different from the one owning the identity
#[clap(short, long)]
secret: Option<String>,
},
/// Migrate identity to another account
/// Change Owner Key
ChangeOwnerKey {
/// Secret key format (seed, substrate)
#[clap(short = 'S', long, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
/// Secret of account to link
/// most likely different from the one owning the identity
#[clap(short, long)]
secret: Option<String>,
},
}
/// 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_format,
secret,
} => {
let keypair = get_keypair(secret_format, secret.as_deref())?;
let address = keypair.address();
data = data.fetch_idty_index().await?; // idty index required for payload
link_account(&data, address, keypair).await?;
}
Subcommand::ChangeOwnerKey {
secret_format,
secret,
} => {
let keypair = get_keypair(secret_format, secret.as_deref())?;
let address = keypair.address();
data = data.fetch_idty_index().await?; // idty index required for payload
change_owner_key(&data, address, 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,
mut account_id: Option<AccountId32>,
mut identity_id: Option<u32>,
mut username: Option<String>,
) -> Result<(), anyhow::Error> {
account_id: Option<AccountId>,
identity_id: Option<IdtyId>,
pseudo: Option<String>,
) -> Result<(), GcliError> {
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 = AccountId32::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"));
}
}
// 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(anyhow!(
"One and only one argument is needed to fetch the identity."
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()
});
// print result
println!(
"Account id: {}",
account_id
.as_ref()
.map_or(String::new(), AccountId32::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?;
// 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))?);
}
}
println!("Username: {}", username.unwrap_or_default());
Ok(())
}
/// get identity index by account id
pub async fn get_idty_index_by_account_id(
client: &Client,
account_id: &AccountId32,
) -> Result<Option<u32>, anyhow::Error> {
Ok(client
account_id: &AccountId,
) -> Result<Option<IdtyId>, subxt::Error> {
client
.storage()
.fetch(
&runtime::storage().identity().identity_index_of(account_id),
None,
)
.await?)
.at_latest()
.await?
.fetch(&runtime::storage().identity().identity_index_of(account_id))
.await
}
pub async fn get_identity_by_index(
/// get smith info by index
pub async fn get_smith(
client: &Client,
idty_index: u32,
) -> Result<Option<IdtyValue<u32, AccountId32, IdtyData>>, anyhow::Error> {
Ok(client
index: IdtyId,
) -> Result<Option<SmithMeta<IdtyId>>, subxt::Error> {
client
.storage()
.fetch(&runtime::storage().identity().identities(idty_index), None)
.await?)
.at_latest()
.await?
.fetch(&runtime::storage().smith_members().smiths(index))
.await
}
pub async fn create_identity(
pair: Pair,
/// get identity index by name
pub async fn get_idty_index_by_name(
client: &Client,
target: AccountId32,
) -> Result<(), subxt::Error> {
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().identity().create_identity(target),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
name: &str,
) -> Result<Option<IdtyId>, subxt::Error> {
client
.storage()
.at_latest()
.await?
.fetch(
&runtime::storage()
.identity()
.identities_names(IdtyName(name.into())),
)
.await?;
.await
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::identity::events::IdtyCreated>()? {
println!("{e:?}");
}
Ok(())
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}")))
}
pub async fn confirm_identity(
pair: Pair,
/// get identityt value by index
pub async fn get_identity_by_index(
client: &Client,
name: String,
) -> Result<(), subxt::Error> {
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().identity().confirm_identity(name),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
idty_index: IdtyId,
) -> Result<Option<IdtyValue<IdtyId, AccountId, IdtyData>>, subxt::Error> {
client
.storage()
.at_latest()
.await?
.fetch(&runtime::storage().identity().identities(idty_index))
.await
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::identity::events::IdtyConfirmed>()? {
println!("{e:?}");
}
Ok(())
/// 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);
pub async fn revoke_identity(data: &Data) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_revoc_doc(data).await;
// 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);
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.identity()
.revoke_identity(data.idty_index(), data.address(), multisign),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
// TODO: allow other signature formats
let multisign = MultiSignature::Sr25519(signature.into());
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::identity::events::IdtyRemoved>()? {
println!("{e:?}");
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))
}
}
Ok(())
}
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
}
use crate::*;
use anyhow::{anyhow, Result};
use sp_core::{crypto::AccountId32, sr25519::Pair, DeriveJunction, Pair as _};
use subxt::{
ext::sp_runtime::MultiAddress,
tx::{BaseExtrinsicParamsBuilder, PairSigner},
};
use sp_core::DeriveJunction;
use subxt::ext::sp_runtime::MultiAddress;
pub async fn repart(
pair: Pair,
client: &Client,
target: u32,
actual_repart: Option<u32>,
) -> Result<()> {
pub async fn repart(data: &Data, target: u32, actual_repart: Option<u32>) -> anyhow::Result<()> {
let KeyPair::Sr25519(keypair) = data.keypair().await else {
panic!("Cesium keys not implemented there")
};
let mut pairs = Vec::new();
for i in actual_repart.unwrap_or_default()..target {
let pair_i = pair
let pair_i = keypair
.derive(std::iter::once(DeriveJunction::hard::<u32>(i)), None)
.map_err(|_| anyhow!("Fail to derive //{}", i))?
.0;
......@@ -26,19 +20,19 @@ pub async fn repart(
/*let _ = api
.tx()
.balances()
.transfer(MultiAddress::Id(pair_i.public().into()), 501)?
.sign_and_submit_then_watch(&signer, BaseExtrinsicParamsBuilder::new())
.transfer_allow_death(MultiAddress::Id(pair_i.public().into()), 501)?
.sign_and_submit_then_watch(&signer, DefaultExtrinsicParamsBuilder::new())
.await?
.wait_for_in_block()
.await?;
signer.increment_nonce();*/
if let Some(pair_i_account) = client
if let Some(pair_i_account) = data
.client()
.storage()
.fetch(
&runtime::storage().system().account(&pair_i.public().into()),
None,
)
.at_latest()
.await?
.fetch(&runtime::storage().system().account(&pair_i.public().into()))
.await?
{
log::info!("account //{} balance: {}", i, pair_i_account.data.free);
......@@ -48,11 +42,16 @@ pub async fn repart(
Ok(())
}
pub async fn spam_roll(pair: Pair, client: &Client, actual_repart: usize) -> Result<()> {
pub async fn spam_roll(data: &Data, actual_repart: usize) -> anyhow::Result<()> {
let KeyPair::Sr25519(keypair) = data.keypair().await else {
panic!("Cesium keys not implemented there")
};
let client = data.client();
let mut nonce = 0;
let mut pairs = Vec::<(PairSigner<Runtime, Pair>, AccountId32)>::with_capacity(actual_repart);
let mut pairs =
Vec::<(PairSigner<Runtime, sr25519::Pair>, AccountId)>::with_capacity(actual_repart);
for i in 0..actual_repart {
let pair_i = pair
let pair_i = keypair
.derive(std::iter::once(DeriveJunction::hard::<u32>(i as u32)), None)
.map_err(|_| anyhow!("Fail to derive //{}", i))?
.0;
......@@ -63,14 +62,15 @@ pub async fn spam_roll(pair: Pair, client: &Client, actual_repart: usize) -> Res
loop {
let mut watchers = Vec::with_capacity(actual_repart);
for i in 0..(actual_repart - 1) {
let dest: AccountId32 = pairs[i + 1].1.clone();
let dest: AccountId = pairs[i + 1].1.clone();
let watcher = client
.tx()
.create_signed_with_nonce(
&runtime::tx().balances().transfer(MultiAddress::Id(dest), 1),
.create_signed_offline(
&runtime::tx()
.balances()
.transfer_allow_death(MultiAddress::Id(dest).into(), 1),
&pairs[i].0,
nonce,
BaseExtrinsicParamsBuilder::new(),
DefaultExtrinsicParamsBuilder::new().nonce(nonce).build(),
)?
.submit_and_watch()
.await?;
......@@ -78,13 +78,15 @@ pub async fn spam_roll(pair: Pair, client: &Client, actual_repart: usize) -> Res
log::info!("send 1 cent from //{} to //{}", i, i + 1);
watchers.push(watcher);
}
let dest: AccountId32 = pairs[0].1.clone();
let dest: AccountId = pairs[0].1.clone();
let watcher = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().balances().transfer(MultiAddress::Id(dest), 1),
&runtime::tx()
.balances()
.transfer_allow_death(MultiAddress::Id(dest).into(), 1),
&pairs[actual_repart - 1].0,
BaseExtrinsicParamsBuilder::new(),
DefaultExtrinsicParamsBuilder::new().build(),
)
.await?;
nonce += 1;
......@@ -92,8 +94,9 @@ pub async fn spam_roll(pair: Pair, client: &Client, actual_repart: usize) -> Res
watchers.push(watcher);
// Wait all transactions
for watcher in watchers {
watcher.wait_for_in_block().await?;
}
// FIXME fix after subxt update
// for watcher in watchers {
// watcher.wait_for_in_block().await?;
// }
}
}
......@@ -27,7 +27,7 @@ pub enum Subcommand {
}
/// handle oneshot commands
pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
// build indexer because it is needed for all subcommands
let mut data = data.build_client().await?;
// match subcommand
......@@ -65,17 +65,18 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
}
/// get balance of oneshot account
pub async fn oneshot_account_balance(data: &Data) -> Result<(), anyhow::Error> {
pub async fn oneshot_account_balance(data: &Data) -> Result<(), subxt::Error> {
println!(
"balance of oneshot account {} is: {}",
data.address(),
data.client()
.storage()
.at_latest()
.await?
.fetch(
&runtime::storage()
.oneshot_account()
.oneshot_accounts(data.address()),
None
)
.await?
.unwrap_or(0)
......@@ -90,74 +91,50 @@ pub async fn create_oneshot_account(
balance: u64,
dest: AccountId,
) -> Result<(), subxt::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.oneshot_account()
.create_oneshot_account(dest.into(), balance),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) =
events.find_first::<runtime::oneshot_account::events::OneshotAccountCreated>()?
{
println!("{e:?}");
}
Ok(())
submit_call_and_look_event::<
runtime::oneshot_account::events::OneshotAccountCreated,
StaticPayload<runtime::oneshot_account::calls::types::CreateOneshotAccount>,
>(
data,
&runtime::tx()
.oneshot_account()
.create_oneshot_account(dest.into(), balance),
)
.await
}
/// consume oneshot account
pub async fn consume_oneshot_account(
data: &Data,
dest: AccountId,
dest_oneshot: bool,
) -> Result<(), subxt::Error> {
let client = data.client();
let number = client
.storage()
.fetch(&runtime::storage().system().number(), None)
.at_latest()
.await?
.fetch(&runtime::storage().system().number())
.await?
.unwrap();
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().oneshot_account().consume_oneshot_account(
number,
if dest_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
dest.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
dest.into(),
)
},
),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) =
events.find_first::<runtime::oneshot_account::events::OneshotAccountConsumed>()?
{
println!("{e:?}");
}
Ok(())
let payload = &runtime::tx().oneshot_account().consume_oneshot_account(
number,
if dest_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(dest.into())
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(dest.into())
},
);
submit_call_and_look_event::<
runtime::oneshot_account::events::OneshotAccountConsumed,
StaticPayload<runtime::oneshot_account::calls::types::ConsumeOneshotAccount>,
>(data, payload)
.await
}
/// consume oneshot account with remaining
pub async fn consume_oneshot_account_with_remaining(
data: &Data,
balance: u64,
dest: AccountId,
dest_oneshot: bool,
......@@ -168,46 +145,36 @@ pub async fn consume_oneshot_account_with_remaining(
let number = client
.storage()
.fetch(&runtime::storage().system().number(), None)
.at_latest()
.await?
.fetch(&runtime::storage().system().number())
.await?
.unwrap();
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.oneshot_account()
.consume_oneshot_account_with_remaining(
number,
if dest_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
dest.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
dest.into(),
)
},
if remaining_to_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
remaining_to.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
remaining_to.into(),
)
},
balance,
),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) =
events.find_first::<runtime::oneshot_account::events::OneshotAccountConsumed>()?
{
println!("{e:?}");
}
Ok(())
let payload = &runtime::tx()
.oneshot_account()
.consume_oneshot_account_with_remaining(
number,
if dest_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(dest.into())
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(dest.into())
},
if remaining_to_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
remaining_to.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
remaining_to.into(),
)
},
balance,
);
submit_call_and_look_event::<
runtime::oneshot_account::events::OneshotAccountConsumed,
StaticPayload<runtime::oneshot_account::calls::types::ConsumeOneshotAccountWithRemaining>,
>(data, payload)
.await
}
// commands/publish.rs
// This module handles the 'publish' command of the CLI.
use crate::GcliError;
use anyhow::anyhow;
use inquire::Confirm;
use std::process::Command;
/// Executes the 'publish' operation.
pub async fn handle_command() -> Result<(), GcliError> {
// Step 1: Get actual version of gcli
const VERSION: &str = env!("CARGO_PKG_VERSION");
// Fetch the latest tags from the remote repository
Command::new("git")
.args(["fetch", "--tags"])
.status()
.map_err(|e| anyhow!(e))?;
// Step 2: Check if the git tag already exists
let tag_check_output = Command::new("git").args(["tag", "-l", VERSION]).output()?;
if !tag_check_output.stdout.is_empty() {
return Err(GcliError::Logic(format!("Tag {VERSION} already exists")));
}
// Display a confirmation prompt with the version number.
match Confirm::new(&format!(
"Are you sure you want to publish version {VERSION} ?"
))
.with_default(false)
.prompt()
{
Ok(true) => {
// User confirmed, proceed publishing
// Step 3: Create and push the git tag
Command::new("git")
.args(["tag", "-a", VERSION, "-m", &format!("Release v{VERSION}")])
.status()
.map_err(|e| anyhow!(e))?;
Command::new("git")
.args(["push", "origin", &format!("refs/tags/{VERSION}")])
.status()
.map_err(|e| anyhow!(e))?;
println!("Publication of version {VERSION} completed successfully.");
Ok(())
}
Ok(false) => {
// User did not confirm, cancel the operation
println!("Publication cancelled.");
Ok(())
}
Err(_) => {
// There was an error with the prompt, return an error
Err(GcliError::Input(
"Failed to display confirmation prompt".to_string(),
))
}
}
}
use crate::*;
use sp_core::sr25519::Signature;
// TODO include prefix in RevocationPayload and use below
// use crate::runtime::runtime_types::pallet_identity::types::RevocationPayload;
type EncodedRevocationPayload = Vec<u8>;
pub fn print_revoc_sig(data: &Data) {
let (_, signature) = generate_revoc_doc(data);
pub async fn print_revoc_sig(data: &Data) {
let (_, signature) = generate_revoc_doc(data).await;
println!("revocation payload signature");
println!("0x{}", hex::encode(signature));
}
pub fn generate_revoc_doc(data: &Data) -> (EncodedRevocationPayload, Signature) {
pub async fn generate_revoc_doc(data: &Data) -> (EncodedRevocationPayload, sr25519::Signature) {
let payload = (b"revo", data.genesis_hash, data.idty_index()).encode();
let signature = data.keypair().sign(&payload);
let KeyPair::Sr25519(keypair) = data.keypair().await else {
panic!("Cesium keys not implemented there")
};
let signature = keypair.sign(&payload);
(payload, signature)
}
......@@ -2,42 +2,147 @@ use crate::*;
pub async fn runtime_info(data: Data) {
let api = data.client();
let consts = runtime::constants();
// get constant u32 value
let getu32 = |c| api.constants().at(&c).unwrap();
// get constant u64 value
let getu64 = |c| api.constants().at(&c).unwrap();
// get constant perbill value
let getp = |c| api.constants().at(&c).unwrap();
// get formatted currency value
let getf = |c| data.format_balance(api.constants().at(&c).unwrap());
// certifications
let cert_period = api
.constants()
.at(&runtime::constants().cert().cert_period())
.unwrap();
let max_by_issuer = api
.constants()
.at(&runtime::constants().cert().max_by_issuer())
.unwrap();
let validity_period = api
.constants()
.at(&runtime::constants().cert().validity_period())
.unwrap();
println!("certification period: {cert_period} blocks");
println!("max certs by issuer: {max_by_issuer}");
println!("certification validity: {validity_period} blocks");
// account
let new_account_price = api
.constants()
.at(&runtime::constants().account().new_account_price())
.unwrap();
// balances
let existential_deposit = api
.constants()
.at(&runtime::constants().balances().existential_deposit())
.unwrap();
// identity
println!("--- identity ---");
println!(
"confirm period: {} blocks",
getu32(consts.identity().confirm_period())
);
println!(
"validation period: {} blocks",
getu32(consts.identity().validation_period())
);
println!(
"autorevocation period: {} blocks",
getu32(consts.identity().autorevocation_period())
);
println!(
"deletion period: {} blocks",
getu32(consts.identity().deletion_period())
);
println!(
"change owner key period: {} blocks",
getu32(consts.identity().change_owner_key_period())
);
println!(
"identity creation period: {} blocks",
getu32(consts.identity().idty_creation_period())
);
// certification
println!("--- certification ---");
println!(
"certification period: {} blocks",
getu32(consts.certification().cert_period())
);
println!(
"max certs by issuer: {}",
getu32(consts.certification().max_by_issuer())
);
println!(
"min received cert to issue cert: {}",
getu32(
consts
.certification()
.min_received_cert_to_be_able_to_issue_cert()
)
);
println!(
"certification validity: {} blocks",
getu32(consts.certification().validity_period())
);
// wot
println!("--- wot ---");
println!(
"first issuable on: {}",
getu32(consts.wot().first_issuable_on())
);
println!(
"min cert for membership: {}",
getu32(consts.wot().min_cert_for_membership())
);
println!(
"new account price: {}",
data.format_balance(new_account_price)
"min cert for create identity: {}",
getu32(consts.wot().min_cert_for_create_idty_right())
);
// membership
println!("--- membership ---");
println!(
"membership validity: {} blocks",
getu32(consts.membership().membership_period())
);
// smith members
println!("--- smith members ---");
println!(
"max certs by issuer: {}",
getu32(consts.smith_members().max_by_issuer())
);
println!(
"min cert for membership: {}",
getu32(consts.smith_members().min_cert_for_membership())
);
println!(
"smith inactivity max duration: {}",
getu32(consts.smith_members().smith_inactivity_max_duration())
);
// todo membership renewal period
// distance
println!("--- distance ---");
println!(
"max referee distance: {}",
getu32(consts.distance().max_referee_distance())
);
println!(
"min accessible referees: {:?}",
getp(consts.distance().min_accessible_referees())
);
println!(
"distance evaluation price: {}",
getf(consts.distance().evaluation_price())
);
// currency
println!("--- currency ---");
println!(
"existential deposit: {}",
data.format_balance(existential_deposit)
getf(consts.balances().existential_deposit())
);
// provide randomness
println!("--- provide randomness ---");
println!(
"max requests: {}",
getu32(consts.provide_randomness().max_requests())
);
println!(
"request price: {}",
getf(consts.provide_randomness().request_price())
);
// universal dividend
println!("--- universal dividend ---");
println!(
"max past reevals: {}",
getu32(consts.universal_dividend().max_past_reeval())
);
println!(
"square money growth rate: {:?}",
getp(consts.universal_dividend().square_money_growth_rate())
);
println!(
"UD creation period: {}",
getu64(consts.universal_dividend().ud_creation_period())
);
println!(
"UD reeval period: {}",
getu64(consts.universal_dividend().ud_reeval_period())
);
// todo treasury, technical committee, transaction payment, authority members
// consts.system().ss58_prefix()
}
use crate::*;
use sp_core::{crypto::AccountId32, sr25519::Pair, Pair as _};
use commands::identity::try_get_idty_index_by_name;
#[cfg(feature = "gdev")]
use runtime::runtime_types::gdev_runtime::opaque::SessionKeys as RuntimeSessionKeys;
use std::collections::HashMap;
use std::ops::Deref;
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
type SessionKeys = [u8; 128];
/// decode byte array into runtime session keys
// TODO find a way to avoid doing this manually by importing session keys trait implementation
fn session_keys_decode(session_keys: SessionKeys) -> RuntimeSessionKeys {
RuntimeSessionKeys {
grandpa: runtime::runtime_types::sp_consensus_grandpa::app::Public(
session_keys[0..32].try_into().unwrap(),
),
babe: runtime::runtime_types::sp_consensus_babe::app::Public(
session_keys[32..64].try_into().unwrap(),
),
im_online: runtime::runtime_types::pallet_im_online::sr25519::app_sr25519::Public(
session_keys[64..96].try_into().unwrap(),
),
authority_discovery: runtime::runtime_types::sp_authority_discovery::app::Public(
session_keys[96..128].try_into().unwrap(),
),
}
}
/// define smith subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
/// go online
GoOnline,
/// go offline
#[default]
GoOffline,
/// Rotate and set session keys
UpdateKeys,
/// Set session keys
SetSessionKeys { session_keys: String },
/// List upcoming expirations that require an action
ShowExpire {
/// Show certs that expire within less than this number of blocks
#[clap(short, long, default_value_t = 100800)]
blocks: u32,
/// Show authorities that should rotate keys within less than this number of sessions
#[clap(short, long, default_value_t = 100)]
sessions: u32,
},
/// List online authorities
ShowOnline,
/// Invite identity to become smith
Invite {
#[clap(value_name = "USERNAME")]
target: String,
},
/// Accept invitation
Accept,
/// Certify smith
#[clap(alias = "cert")]
Certify {
#[clap(value_name = "USERNAME")]
target: String,
},
}
/// handle smith commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
let mut data = data.build_client().await?.build_indexer().await?;
match command {
Subcommand::GoOnline => {
go_online(&data).await?;
}
Subcommand::GoOffline => {
go_offline(&data).await?;
}
Subcommand::UpdateKeys => {
update_session_keys(&data).await?;
}
Subcommand::SetSessionKeys { session_keys } => {
let session_keys = session_keys_decode(
hex::decode(session_keys)
.expect("wrong hexadecimal")
.try_into()
.expect("wrong format"),
); // decode session keys from hex string
set_session_keys(&data, session_keys).await?;
}
Subcommand::ShowExpire { blocks, sessions } => {
data = data.build_indexer().await?;
commands::expire::monitor_expirations(&data, blocks, sessions).await?
}
Subcommand::ShowOnline => online(&data).await?,
Subcommand::Invite { target } => {
let target = try_get_idty_index_by_name(&data, &target).await?;
invite_smith(&data, target).await?
}
Subcommand::Accept => accept_invitation(&data).await?,
Subcommand::Certify { target } => {
let target = try_get_idty_index_by_name(&data, &target).await?;
certify_smith(&data, target).await?
}
};
Ok(())
}
/// rotate session keys
pub async fn rotate_keys(client: &Client) -> Result<SessionKeys, anyhow::Error> {
client
.rpc()
.rotate_keys()
.await?
/// (needs to be connected to unsafe RPC)
pub async fn rotate_keys(data: &Data) -> Result<SessionKeys, GcliError> {
data.legacy_rpc_methods()
.await
.author_rotate_keys()
.await
.map_err(|e| {
GcliError::Duniter(format!(
"Please make sure you are connected to your validator node with the unsafe RPC \
API enabled {e}"
))
})?
.deref()
.try_into()
.map_err(|e| anyhow!("Session keys have wrong length: {:?}", e))
.map_err(|e| GcliError::Duniter(format!("Session keys have wrong length: {:?}", e)))
}
/// set session keys
pub async fn set_session_keys(
pair: Pair,
client: &Client,
session_keys: SessionKeys,
data: &Data,
session_keys: RuntimeSessionKeys,
) -> Result<TxProgress, subxt::Error> {
client
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.authority_members()
.set_session_keys(session_keys),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await
submit_call::<StaticPayload<runtime::authority_members::calls::types::SetSessionKeys>>(
data,
&runtime::tx()
.authority_members()
.set_session_keys(session_keys),
)
.await
}
// use runtime::runtime_types::sp_consensus_grandpa::app::Public
/// update session keys
pub async fn update_session_keys(pair: Pair, client: &Client) -> Result<(), GcliError> {
let session_keys = rotate_keys(client).await?;
let progress = set_session_keys(pair, client, session_keys).await?;
pub async fn update_session_keys(data: &Data) -> Result<(), GcliError> {
let session_keys = rotate_keys(data).await?;
// manual session key conversion
let session_keys = session_keys_decode(session_keys);
let progress = set_session_keys(data, session_keys).await?;
if data.args.no_wait {
return Ok(());
}
let _ = track_progress(progress).await?; // TODO
Ok(())
}
/// submit go_online
pub async fn go_online(pair: Pair, client: &Client) -> Result<(), GcliError> {
if client
pub async fn go_online(data: &Data) -> Result<(), GcliError> {
if data
.client()
.storage()
.fetch(
&runtime::storage()
.session()
.next_keys(AccountId32::from(pair.public())),
None,
)
.at_latest()
.await?
.fetch(&runtime::storage().session().next_keys(data.address()))
.await?
.is_none()
{
......@@ -62,149 +170,141 @@ pub async fn go_online(pair: Pair, client: &Client) -> Result<(), GcliError> {
));
}
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().authority_members().go_online(),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::authority_members::events::MemberGoOnline>()? {
println!("{e:?}");
}
Ok(())
submit_call_and_look_event::<
runtime::authority_members::events::MemberGoOnline,
StaticPayload<runtime::authority_members::calls::types::GoOnline>,
>(data, &runtime::tx().authority_members().go_online())
.await
.map_err(|e| e.into())
}
/// submit go_offline
pub async fn go_offline(pair: Pair, client: &Client) -> Result<(), subxt::Error> {
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().authority_members().go_offline(),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::authority_members::events::MemberGoOffline>()? {
println!("{e:?}");
}
Ok(())
pub async fn go_offline(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::authority_members::events::MemberGoOffline,
StaticPayload<runtime::authority_members::calls::types::GoOffline>,
>(data, &runtime::tx().authority_members().go_offline())
.await
}
/// get online authorities
pub async fn online(data: &Data) -> Result<(), anyhow::Error> {
pub async fn online(data: &Data) -> Result<(), subxt::Error> {
let client = data.client();
let indexer = data.indexer.clone();
let parent_hash = client
.clone()
.storage()
.fetch(&runtime::storage().system().parent_hash(), None)
.await?
.unwrap();
let mut identity_cache = cache::IdentityCache::new(client.clone(), indexer);
let online_authorities = client
.storage()
.fetch(
&runtime::storage().authority_members().online_authorities(),
Some(parent_hash),
)
.at_latest()
.await?
.fetch(&runtime::storage().authority_members().online_authorities())
.await?
.unwrap_or_default();
println!("Online:");
for identity_id in online_authorities {
println!(
" {}",
identity_cache
.fetch_identity(identity_id, parent_hash)
.await
.unwrap_or_else(|_| format!("{identity_id}"))
);
}
let incoming_authorities = client
.storage()
.at_latest()
.await?
.fetch(
&runtime::storage()
.authority_members()
.incoming_authorities(),
Some(parent_hash),
)
.await?
.unwrap_or_default();
println!("Incoming:");
for identity_id in incoming_authorities {
println!(
" {}",
identity_cache
.fetch_identity(identity_id, parent_hash)
.await
.unwrap_or_else(|_| format!("{identity_id}"))
);
}
let outgoing_authorities = client
.storage()
.at_latest()
.await?
.fetch(
&runtime::storage()
.authority_members()
.outgoing_authorities(),
Some(parent_hash),
)
.await?
.unwrap_or_default();
println!("Outgoing:");
for identity_id in outgoing_authorities {
if let Some(indexer) = &data.indexer {
let mut names = HashMap::<IdtyId, String>::new();
indexer
.names_by_indexes(&online_authorities)
.await
.into_iter()
.for_each(|e| {
names.insert(e.0, e.1);
});
println!("Online:");
println!(
" {}",
identity_cache
.fetch_identity(identity_id, parent_hash)
.await
.unwrap_or_else(|_| format!("{identity_id}"))
"{}",
online_authorities
.iter()
.map(|i| names
.get(i)
.map(|n| n.to_string())
.unwrap_or(format!("{} <no name found>", i)))
.collect::<Vec<String>>()
.join(", ")
);
}
Ok(())
}
/// submit a certification and track progress
pub async fn cert(
client: &Client,
pair: Pair,
issuer: u32,
receiver: u32,
) -> Result<(), anyhow::Error> {
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().smith_cert().add_cert(issuer, receiver),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
println!("Incoming:");
println!(
"{}",
incoming_authorities
.iter()
.map(|i| names
.get(i)
.map(|n| n.to_string())
.unwrap_or(format!("{} <no name found>", i)))
.collect::<Vec<String>>()
.join(", ")
);
let events = track_progress(progress).await?;
println!("Outgoing:");
println!(
"{}",
outgoing_authorities
.iter()
.map(|i| names
.get(i)
.map(|n| n.to_string())
.unwrap_or(format!("{} <no name found>", i)))
.collect::<Vec<String>>()
.join(", ")
);
} else {
println!("Online:");
println!("{online_authorities:?}");
// look for the expected event
let new_cert_event = events.find_first::<runtime::smith_cert::events::NewCert>()?;
let renew_cert_event = events.find_first::<runtime::smith_cert::events::RenewedCert>()?;
println!("Incoming:");
println!("{incoming_authorities:?}");
if let Some(event) = new_cert_event {
println!("{event:?}");
}
if let Some(event) = renew_cert_event {
println!("{event:?}");
println!("Outgoing:");
println!("{outgoing_authorities:?}");
}
Ok(())
}
/// invite identity to join smith
pub async fn invite_smith(data: &Data, target: IdtyId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::smith_members::events::InvitationSent,
StaticPayload<runtime::smith_members::calls::types::InviteSmith>,
>(data, &runtime::tx().smith_members().invite_smith(target))
.await
}
/// accept invitation
pub async fn accept_invitation(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::smith_members::events::InvitationAccepted,
StaticPayload<runtime::smith_members::calls::types::AcceptInvitation>,
>(data, &runtime::tx().smith_members().accept_invitation())
.await
}
/// invite identity to join smith
pub async fn certify_smith(data: &Data, target: IdtyId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::smith_members::events::SmithCertAdded,
StaticPayload<runtime::smith_members::calls::types::CertifySmith>,
>(data, &runtime::tx().smith_members().certify_smith(target))
.await
}
use crate::*;
use sp_core::{crypto::AccountId32, sr25519::Pair};
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
/// define sudo subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
/// Nothing
#[default]
#[clap(hide = true)]
Nothing,
/// set sudo keys
SetKey { new_key: AccountId },
/// force valid distance status
SetDistanceOk { identity: IdtyId },
}
pub async fn set_key(
pair: Pair,
client: &Client,
new_key: AccountId32,
) -> Result<(), subxt::Error> {
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx().sudo().set_key(new_key.into()),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
/// handle smith commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
let data = data.build_client().await?;
match command {
Subcommand::Nothing => todo!(),
Subcommand::SetKey { new_key } => {
set_key(&data, new_key).await?;
}
Subcommand::SetDistanceOk { identity } => {
set_distance_ok(&data, identity).await?;
}
};
let _ = track_progress(progress).await?; // TODO
Ok(())
}
/// set sudo key
pub async fn set_key(data: &Data, new_key: AccountId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::sudo::events::KeyChanged,
StaticPayload<runtime::sudo::calls::types::SetKey>,
>(data, &runtime::tx().sudo().set_key(new_key.into()))
.await
}
/// set distance ok
pub async fn set_distance_ok(data: &Data, identity: IdtyId) -> Result<(), subxt::Error> {
let inner = runtime::distance::Call::force_valid_distance_status { identity };
let inner = runtime::Call::Distance(inner);
submit_call_and_look_event::<
runtime::sudo::events::Sudid,
StaticPayload<runtime::sudo::calls::types::Sudo>,
>(data, &runtime::tx().sudo().sudo(inner))
.await
}