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 1934 additions and 713 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 account;
pub mod blockchain;
pub mod certification;
pub mod cesium;
pub mod collective; pub mod collective;
pub mod distance;
pub mod expire; pub mod expire;
pub mod identity; pub mod identity;
pub mod net_test; pub mod net_test;
pub mod oneshot; pub mod oneshot;
pub mod publish;
pub mod revocation; pub mod revocation;
pub mod runtime; pub mod runtime;
pub mod smith; pub mod smith;
pub mod sudo; pub mod sudo;
pub mod transfer; pub mod transfer;
pub mod ud;
pub mod vault;
use crate::*; use crate::*;
use anyhow::Result; /// 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?;
}
};
pub async fn get_balance(data: Data) -> Result<()> { Ok(())
}
/// get balance
pub async fn get_balance(data: Data) -> Result<(), subxt::Error> {
let account_id = data.address(); let account_id = data.address();
let account_info = get_account_info(data.client(), &account_id).await?; let account_info = get_account_info(data.client(), &account_id).await?;
if let Some(account_info) = account_info { if let Some(account_info) = account_info {
...@@ -16,12 +72,24 @@ pub async fn get_balance(data: Data) -> Result<()> { ...@@ -16,12 +72,24 @@ pub async fn get_balance(data: Data) -> Result<()> {
Ok(()) Ok(())
} }
/// get account info
pub async fn get_account_info( pub async fn get_account_info(
client: &Client, client: &Client,
account_id: &AccountId, account_id: &AccountId,
) -> Result<Option<AccountInfo>> { ) -> Result<Option<AccountInfo>, subxt::Error> {
Ok(client client
.storage() .storage()
.fetch(&runtime::storage().system().account(account_id), None) .at_latest()
.await?) .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, subxt::Error> {
Ok(data
.client()
.storage()
.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::indexer::*;
use crate::*; use crate::*;
use anyhow::Result; /// define technical committee subcommands
use sp_core::{sr25519::Pair, H256}; #[derive(Clone, Default, Debug, clap::Parser)]
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner}; 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(client: Client, args: &Args) -> Result<()> { /// handle technical committee commands
let parent_hash = client pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
.storage() let data = data.build_client().await?.build_indexer().await?;
.fetch(&runtime::storage().system().parent_hash(), None) match command {
.await? Subcommand::Members => technical_committee_members(&data).await?,
.unwrap(); 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 gql_client = reqwest::Client::builder() Ok(())
.user_agent("gcli/0.1.0") }
.build()?;
let indexer = if args.no_indexer { /// list technical committee members
None pub async fn technical_committee_members(data: &Data) -> Result<(), subxt::Error> {
} else { let client = data.client();
Some(Indexer { let indexer = &data.indexer;
gql_client,
gql_url: args.indexer.clone(),
})
};
for account_id in client for account_id in client
.storage() .storage()
.fetch( .at_latest()
&runtime::storage().technical_committee().members(), .await?
Some(parent_hash), .fetch(&runtime::storage().technical_committee().members())
)
.await? .await?
.unwrap_or_default() .unwrap_or_default()
{ {
println!( println!(
"{}", "{}",
if let Some(indexer) = &indexer { if let Some(indexer) = indexer {
indexer // indexer is set, we can get the name
.username_by_pubkey(&account_id.to_string()) let name = indexer.username_by_pubkey(&account_id.to_string()).await;
.await if name.is_some() {
.ok() name
.flatten() } else {
indexer
.wasname_by_pubkey(&account_id.to_string())
.await
.map(|name| format!("{name}\t(old account)"))
}
} else { } else {
// indexer is not set, we can get the idty index by accountid
client client
.storage() .storage()
.fetch( .at_latest()
&runtime::storage().identity().identity_index_of(&account_id), .await?
Some(parent_hash), .fetch(&runtime::storage().identity().identity_index_of(&account_id))
) .await?
.await
.ok()
.flatten()
.map(|identity_id| format!("{identity_id}")) .map(|identity_id| format!("{identity_id}"))
} }
// no idty found, display account_id
.unwrap_or_else(|| account_id.to_string(),) .unwrap_or_else(|| account_id.to_string(),)
); );
} }
Ok(()) Ok(())
} }
/// list technical committee proposals
// TODO: // TODO:
// * better formatting (format pubkeys to SS58 and add usernames) // * better formatting (format pubkeys to SS58 and add usernames)
// * display proposals indices // * 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 let parent_hash = client
.storage() .storage()
.fetch(&runtime::storage().system().parent_hash(), None) .at_latest()
.await?
.fetch(&runtime::storage().system().parent_hash())
.await? .await?
.unwrap(); .unwrap();
let mut proposals_iter = client let mut proposals_iter = client
.storage() .storage()
.iter( .at(parent_hash)
runtime::storage() .iter(runtime::storage().technical_committee().proposal_of_iter())
.technical_committee()
.proposal_of(H256::default()),
10,
Some(parent_hash),
)
.await?; .await?;
while let Some((proposal_hash, proposal)) = proposals_iter.next().await? { while let Some(Ok(item)) = proposals_iter.next().await {
println!("{}", hex::encode(&proposal_hash.0[32..64])); println!("{}", hex::encode(&item.key_bytes[32..64]));
println!("{proposal:#?}"); println!("{:#?}", item.value);
println!(); println!();
} }
Ok(()) Ok(())
} }
/// submit vote to technical committee
pub async fn technical_committee_vote( pub async fn technical_committee_vote(
pair: Pair, data: &Data,
client: Client, proposal_hash: Hash,
proposal_hash: H256,
proposal_index: u32, proposal_index: u32,
vote: bool, vote: bool,
) -> Result<()> { ) -> Result<(), subxt::Error> {
client submit_call_and_look_event::<
.tx() runtime::technical_committee::events::Voted,
.sign_and_submit_then_watch( StaticPayload<runtime::technical_committee::calls::types::Vote>,
&runtime::tx() >(
.technical_committee() data,
.vote(proposal_hash, proposal_index, vote), &runtime::tx()
&PairSigner::new(pair), .technical_committee()
BaseExtrinsicParamsBuilder::new(), .vote(proposal_hash, proposal_index, vote),
) )
.await?; .await
}
Ok(()) /// 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);
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::indexer::*; use crate::{indexer::*, *};
use crate::*;
use anyhow::Result;
use futures::join; use futures::join;
use std::collections::BTreeMap; use std::collections::BTreeMap;
pub async fn monitor_expirations( pub async fn monitor_expirations(
client: Client, data: &Data,
blocks: u32, blocks: u32,
sessions: u32, _sessions: u32,
args: &Args, ) -> Result<(), subxt::Error> {
) -> Result<()> { let client = data.client();
let gql_client = reqwest::Client::builder() let indexer = data.indexer.clone();
.user_agent("gcli/0.1.0")
.build()?;
let parent_hash = client let parent_hash = client
.storage() .storage()
.fetch(&runtime::storage().system().parent_hash(), None) .at_latest()
.await?
.fetch(&runtime::storage().system().parent_hash())
.await? .await?
.unwrap(); .unwrap();
let addr_current_block = runtime::storage().system().number(); let addr_current_block = runtime::storage().system().number();
let addr_current_session = runtime::storage().session().current_index(); let addr_current_session = runtime::storage().session().current_index();
let (current_block, current_session) = join!( let (current_block, _current_session) = join!(
client client.storage().at(parent_hash).fetch(&addr_current_block),
.storage()
.fetch(&addr_current_block, Some(parent_hash)),
client client
.storage() .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_block = current_block?.unwrap_or_default();
let current_session = current_session?.unwrap_or_default();
let end_block = current_block + blocks; let end_block = current_block + blocks;
let end_session = current_session + sessions;
let mut identity_cache = cache::IdentityCache::new( let mut identity_cache = IdentityCache::new(client.clone(), indexer);
client.clone(),
if args.no_indexer {
None
} else {
Some(Indexer {
gql_client,
gql_url: args.indexer.clone(),
})
},
);
// 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
);
}
}
// Certifications // Certifications
let mut basic_certs_iter = client let mut basic_certs_iter = client
.storage() .storage()
.iter( .at(parent_hash)
runtime::storage().cert().storage_certs_removable_on(0), .iter(runtime::storage().certification().certs_removable_on_iter())
10,
Some(parent_hash),
)
.await?; .await?;
let mut basic_certs = BTreeMap::new(); let mut basic_certs = BTreeMap::new();
while let Some((k, v)) = basic_certs_iter.next().await? { while let Some(Ok(item)) = basic_certs_iter.next().await {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); let block_number = BlockNumber::from_le_bytes(item.key_bytes[40..44].try_into().unwrap());
if block_number < end_block { if block_number < end_block {
basic_certs.insert(block_number - current_block, v); basic_certs.insert(block_number - current_block, item.value);
} }
} }
let mut smith_certs_iter = client for (title, certs) in [("Certifications", basic_certs)] {
.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());
if block_number < end_block {
smith_certs.insert(block_number - current_block, v);
}
}
for (title, certs) in [
("Certifications", basic_certs),
("Smith certifications", smith_certs),
] {
println!("\n{title}:"); println!("\n{title}:");
for (blocks_left, certs) in certs { for (blocks_left, certs) in certs {
println!("{blocks_left} blocks before expiration:"); println!("{blocks_left} blocks before expiration:");
...@@ -128,12 +54,12 @@ pub async fn monitor_expirations( ...@@ -128,12 +54,12 @@ pub async fn monitor_expirations(
println!( println!(
" {} ({}) -> {} ({})", " {} ({}) -> {} ({})",
identity_cache identity_cache
.fetch_identity(issuer_id, parent_hash) .fetch_identity(issuer_id,)
.await .await
.unwrap_or_else(|_| "?".into()), .unwrap_or_else(|_| "?".into()),
issuer_id, issuer_id,
identity_cache identity_cache
.fetch_identity(receiver_id, parent_hash) .fetch_identity(receiver_id,)
.await .await
.unwrap_or_else(|_| "?".into()), .unwrap_or_else(|_| "?".into()),
receiver_id, receiver_id,
...@@ -145,45 +71,22 @@ pub async fn monitor_expirations( ...@@ -145,45 +71,22 @@ pub async fn monitor_expirations(
// Memberships // Memberships
let mut basic_membership_iter = client let mut basic_membership_iter = client
.storage() .storage()
.iter( .at_latest()
runtime::storage().membership().memberships_expire_on(0), .await?
10, .iter(runtime::storage().membership().memberships_expire_on_iter())
Some(parent_hash),
)
.await?; .await?;
let mut basic_memberships = BTreeMap::new(); let mut basic_memberships = BTreeMap::new();
while let Some((k, v)) = basic_membership_iter.next().await? { while let Some(Ok(item)) = basic_membership_iter.next().await {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); let block_number = BlockNumber::from_le_bytes(item.key_bytes[40..44].try_into().unwrap());
if block_number < end_block { if block_number < end_block {
if block_number < current_block { if block_number < current_block {
dbg!((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 for (title, memberships) in [("Memberships", basic_memberships)] {
.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),
] {
println!("\n{title}:"); println!("\n{title}:");
for (blocks_left, membership) in memberships { for (blocks_left, membership) in memberships {
println!("{blocks_left} blocks before expiration:"); println!("{blocks_left} blocks before expiration:");
...@@ -191,7 +94,7 @@ pub async fn monitor_expirations( ...@@ -191,7 +94,7 @@ pub async fn monitor_expirations(
println!( println!(
" {} ({})", " {} ({})",
identity_cache identity_cache
.fetch_identity(identity_id, parent_hash) .fetch_identity(identity_id)
.await .await
.unwrap_or_else(|_| "?".into()), .unwrap_or_else(|_| "?".into()),
identity_id, identity_id,
...@@ -202,3 +105,53 @@ pub async fn monitor_expirations( ...@@ -202,3 +105,53 @@ pub async fn monitor_expirations(
Ok(()) 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(),
})
}
}
This diff is collapsed.
use crate::*; use crate::*;
use anyhow::{anyhow, Result}; use sp_core::DeriveJunction;
use sp_core::{crypto::AccountId32, sr25519::Pair, DeriveJunction, Pair as _}; use subxt::ext::sp_runtime::MultiAddress;
use subxt::{
ext::sp_runtime::MultiAddress,
tx::{BaseExtrinsicParamsBuilder, PairSigner},
};
pub async fn repart( pub async fn repart(data: &Data, target: u32, actual_repart: Option<u32>) -> anyhow::Result<()> {
pair: Pair, let KeyPair::Sr25519(keypair) = data.keypair().await else {
client: Client, panic!("Cesium keys not implemented there")
target: u32, };
actual_repart: Option<u32>,
) -> Result<()> {
let mut pairs = Vec::new(); let mut pairs = Vec::new();
for i in actual_repart.unwrap_or_default()..target { 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) .derive(std::iter::once(DeriveJunction::hard::<u32>(i)), None)
.map_err(|_| anyhow!("Fail to derive //{}", i))? .map_err(|_| anyhow!("Fail to derive //{}", i))?
.0; .0;
...@@ -26,19 +20,19 @@ pub async fn repart( ...@@ -26,19 +20,19 @@ pub async fn repart(
/*let _ = api /*let _ = api
.tx() .tx()
.balances() .balances()
.transfer(MultiAddress::Id(pair_i.public().into()), 501)? .transfer_allow_death(MultiAddress::Id(pair_i.public().into()), 501)?
.sign_and_submit_then_watch(&signer, BaseExtrinsicParamsBuilder::new()) .sign_and_submit_then_watch(&signer, DefaultExtrinsicParamsBuilder::new())
.await? .await?
.wait_for_in_block() .wait_for_in_block()
.await?; .await?;
signer.increment_nonce();*/ signer.increment_nonce();*/
if let Some(pair_i_account) = client if let Some(pair_i_account) = data
.client()
.storage() .storage()
.fetch( .at_latest()
&runtime::storage().system().account(&pair_i.public().into()), .await?
None, .fetch(&runtime::storage().system().account(&pair_i.public().into()))
)
.await? .await?
{ {
log::info!("account //{} balance: {}", i, pair_i_account.data.free); log::info!("account //{} balance: {}", i, pair_i_account.data.free);
...@@ -48,11 +42,16 @@ pub async fn repart( ...@@ -48,11 +42,16 @@ pub async fn repart(
Ok(()) 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 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 { 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) .derive(std::iter::once(DeriveJunction::hard::<u32>(i as u32)), None)
.map_err(|_| anyhow!("Fail to derive //{}", i))? .map_err(|_| anyhow!("Fail to derive //{}", i))?
.0; .0;
...@@ -63,14 +62,15 @@ pub async fn spam_roll(pair: Pair, client: Client, actual_repart: usize) -> Resu ...@@ -63,14 +62,15 @@ pub async fn spam_roll(pair: Pair, client: Client, actual_repart: usize) -> Resu
loop { loop {
let mut watchers = Vec::with_capacity(actual_repart); let mut watchers = Vec::with_capacity(actual_repart);
for i in 0..(actual_repart - 1) { 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 let watcher = client
.tx() .tx()
.create_signed_with_nonce( .create_signed_offline(
&runtime::tx().balances().transfer(MultiAddress::Id(dest), 1), &runtime::tx()
.balances()
.transfer_allow_death(MultiAddress::Id(dest).into(), 1),
&pairs[i].0, &pairs[i].0,
nonce, DefaultExtrinsicParamsBuilder::new().nonce(nonce).build(),
BaseExtrinsicParamsBuilder::new(),
)? )?
.submit_and_watch() .submit_and_watch()
.await?; .await?;
...@@ -78,13 +78,15 @@ pub async fn spam_roll(pair: Pair, client: Client, actual_repart: usize) -> Resu ...@@ -78,13 +78,15 @@ pub async fn spam_roll(pair: Pair, client: Client, actual_repart: usize) -> Resu
log::info!("send 1 cent from //{} to //{}", i, i + 1); log::info!("send 1 cent from //{} to //{}", i, i + 1);
watchers.push(watcher); watchers.push(watcher);
} }
let dest: AccountId32 = pairs[0].1.clone(); let dest: AccountId = pairs[0].1.clone();
let watcher = client let watcher = client
.tx() .tx()
.sign_and_submit_then_watch( .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, &pairs[actual_repart - 1].0,
BaseExtrinsicParamsBuilder::new(), DefaultExtrinsicParamsBuilder::new().build(),
) )
.await?; .await?;
nonce += 1; nonce += 1;
...@@ -92,8 +94,9 @@ pub async fn spam_roll(pair: Pair, client: Client, actual_repart: usize) -> Resu ...@@ -92,8 +94,9 @@ pub async fn spam_roll(pair: Pair, client: Client, actual_repart: usize) -> Resu
watchers.push(watcher); watchers.push(watcher);
// Wait all transactions // Wait all transactions
for watcher in watchers { // FIXME fix after subxt update
watcher.wait_for_in_block().await?; // for watcher in watchers {
} // watcher.wait_for_in_block().await?;
// }
} }
} }
use crate::*; use crate::*;
use anyhow::Result; /// define oneshot account subcommands
use sp_core::{crypto::AccountId32, sr25519::Pair}; #[derive(Clone, Default, Debug, clap::Parser)]
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner}; pub enum Subcommand {
/// get balance of oneshot account
#[default]
Balance,
/// create a oneshot account
Create { balance: u64, dest: AccountId },
/// consume a oneshot account
Consume {
dest: AccountId,
#[clap(long = "oneshot")]
dest_oneshot: bool,
},
/// consume a oneshot account whith remaining sent to an other account
ConsumeWithRemaining {
balance: u64,
dest: AccountId,
#[clap(long = "one")]
dest_oneshot: bool,
remaining_to: AccountId,
#[clap(long = "rem-one")]
remaining_to_oneshot: bool,
},
}
pub async fn create_oneshot_account( /// handle oneshot commands
pair: Pair, pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
client: Client, // build indexer because it is needed for all subcommands
balance: u64, let mut data = data.build_client().await?;
dest: AccountId32, // match subcommand
) -> Result<()> { match command {
client Subcommand::Balance => oneshot_account_balance(&data).await?,
.tx() Subcommand::Create { balance, dest } => {
.sign_and_submit_then_watch( data = data.build_client().await?;
&runtime::tx() create_oneshot_account(&data, balance, dest).await?;
.oneshot_account() }
.create_oneshot_account(dest.into(), balance), Subcommand::Consume { dest, dest_oneshot } => {
&PairSigner::new(pair), data = data.build_client().await?;
BaseExtrinsicParamsBuilder::new(), consume_oneshot_account(&data, dest, dest_oneshot).await?;
) }
.await?; Subcommand::ConsumeWithRemaining {
balance,
dest,
dest_oneshot,
remaining_to,
remaining_to_oneshot,
} => {
data = data.build_client().await?;
consume_oneshot_account_with_remaining(
&data,
balance,
dest,
dest_oneshot,
remaining_to,
remaining_to_oneshot,
)
.await?;
}
};
Ok(())
}
/// get balance of oneshot account
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()),
)
.await?
.unwrap_or(0)
);
Ok(()) Ok(())
} }
/// create oneshot account
pub async fn create_oneshot_account(
data: &Data,
balance: u64,
dest: AccountId,
) -> Result<(), subxt::Error> {
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( pub async fn consume_oneshot_account(
pair: Pair, data: &Data,
client: Client, dest: AccountId,
dest: AccountId32,
dest_oneshot: bool, dest_oneshot: bool,
) -> Result<()> { ) -> Result<(), subxt::Error> {
let client = data.client();
let number = client let number = client
.storage() .storage()
.fetch(&runtime::storage().system().number(), None) .at_latest()
.await?
.fetch(&runtime::storage().system().number())
.await? .await?
.unwrap(); .unwrap();
client let payload = &runtime::tx().oneshot_account().consume_oneshot_account(
.tx() number,
.sign_and_submit_then_watch( if dest_oneshot {
&runtime::tx().oneshot_account().consume_oneshot_account( runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(dest.into())
number, } else {
if dest_oneshot { runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(dest.into())
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot( },
dest.into(), );
) submit_call_and_look_event::<
} else { runtime::oneshot_account::events::OneshotAccountConsumed,
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal( StaticPayload<runtime::oneshot_account::calls::types::ConsumeOneshotAccount>,
dest.into(), >(data, payload)
) .await
},
),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
Ok(())
} }
/// consume oneshot account with remaining
pub async fn consume_oneshot_account_with_remaining( pub async fn consume_oneshot_account_with_remaining(
pair: Pair, data: &Data,
client: Client,
balance: u64, balance: u64,
dest: AccountId32, dest: AccountId,
dest_oneshot: bool, dest_oneshot: bool,
remaining_to: AccountId32, remaining_to: AccountId,
remaining_to_oneshot: bool, remaining_to_oneshot: bool,
) -> Result<()> { ) -> Result<(), subxt::Error> {
let client = data.client();
let number = client let number = client
.storage() .storage()
.fetch(&runtime::storage().system().number(), None) .at_latest()
.await?
.fetch(&runtime::storage().system().number())
.await? .await?
.unwrap(); .unwrap();
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(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
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,
);
pub async fn oneshot_account_balance(client: Client, account: AccountId32) -> Result<()> { submit_call_and_look_event::<
log::info!( runtime::oneshot_account::events::OneshotAccountConsumed,
"{}", StaticPayload<runtime::oneshot_account::calls::types::ConsumeOneshotAccountWithRemaining>,
client >(data, payload)
.storage() .await
.fetch(
&runtime::storage()
.oneshot_account()
.oneshot_accounts(&account),
None
)
.await?
.unwrap_or(0)
);
Ok(())
} }
// 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 crate::*;
use sp_core::sr25519::Signature;
// TODO include prefix in RevocationPayload and use below // TODO include prefix in RevocationPayload and use below
// use crate::runtime::runtime_types::pallet_identity::types::RevocationPayload; // use crate::runtime::runtime_types::pallet_identity::types::RevocationPayload;
type EncodedRevocationPayload = Vec<u8>; type EncodedRevocationPayload = Vec<u8>;
pub fn print_revoc_sig(data: &Data) -> () { pub async fn print_revoc_sig(data: &Data) {
let (_, signature) = generate_revoc_doc(data); let (_, signature) = generate_revoc_doc(data).await;
println!("revocation payload signature"); println!("revocation payload signature");
println!("0x{}", hex::encode(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 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) (payload, signature)
} }
use crate::*; use crate::*;
pub async fn runtime_info(data: Data) -> () { pub async fn runtime_info(data: Data) {
let api = data.client(); 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 // identity
let cert_period = api println!("--- identity ---");
.constants() println!(
.at(&runtime::constants().cert().cert_period()) "confirm period: {} blocks",
.unwrap(); getu32(consts.identity().confirm_period())
let max_by_issuer = api );
.constants() println!(
.at(&runtime::constants().cert().max_by_issuer()) "validation period: {} blocks",
.unwrap(); getu32(consts.identity().validation_period())
let validity_period = api );
.constants() println!(
.at(&runtime::constants().cert().validity_period()) "autorevocation period: {} blocks",
.unwrap(); getu32(consts.identity().autorevocation_period())
);
println!("certification period: {cert_period} blocks"); println!(
println!("max certs by issuer: {max_by_issuer}"); "deletion period: {} blocks",
println!("certification validity: {validity_period} blocks"); getu32(consts.identity().deletion_period())
);
// account println!(
let new_account_price = api "change owner key period: {} blocks",
.constants() getu32(consts.identity().change_owner_key_period())
.at(&runtime::constants().account().new_account_price()) );
.unwrap(); println!(
// balances "identity creation period: {} blocks",
let existential_deposit = api getu32(consts.identity().idty_creation_period())
.constants() );
.at(&runtime::constants().balances().existential_deposit()) // certification
.unwrap(); 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!( println!(
"new account price: {}", "min cert for create identity: {}",
data.format_balance(new_account_price) 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!( println!(
"existential deposit: {}", "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::indexer::Indexer;
use crate::*; use crate::*;
use anyhow::{anyhow, Result}; use commands::identity::try_get_idty_index_by_name;
use sp_core::{crypto::AccountId32, sr25519::Pair, Pair as _}; #[cfg(feature = "gdev")]
use runtime::runtime_types::gdev_runtime::opaque::SessionKeys as RuntimeSessionKeys;
use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner, TxStatus};
type SessionKeys = [u8; 128]; type SessionKeys = [u8; 128];
pub async fn rotate_keys(client: &Client) -> Result<SessionKeys> { /// decode byte array into runtime session keys
client // TODO find a way to avoid doing this manually by importing session keys trait implementation
.rpc() fn session_keys_decode(session_keys: SessionKeys) -> RuntimeSessionKeys {
.rotate_keys() RuntimeSessionKeys {
.await? grandpa: runtime::runtime_types::sp_consensus_grandpa::app::Public(
.deref() session_keys[0..32].try_into().unwrap(),
.try_into() ),
.map_err(|e| anyhow!("Session keys have wrong length: {:?}", e)) 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(),
),
}
} }
pub async fn set_session_keys(pair: Pair, client: Client, session_keys: SessionKeys) -> Result<()> { /// define smith subcommands
client #[derive(Clone, Default, Debug, clap::Parser)]
.tx() pub enum Subcommand {
.sign_and_submit_then_watch( /// go online
&runtime::tx() GoOnline,
.authority_members() /// go offline
.set_session_keys(session_keys), #[default]
&PairSigner::new(pair), GoOffline,
BaseExtrinsicParamsBuilder::new(), /// Rotate and set session keys
) UpdateKeys,
.await?; /// 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(()) Ok(())
} }
pub async fn update_session_keys(pair: Pair, client: Client) -> Result<()> { /// rotate session keys
let session_keys = rotate_keys(&client).await?; /// (needs to be connected to unsafe RPC)
set_session_keys(pair, client, session_keys).await 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| GcliError::Duniter(format!("Session keys have wrong length: {:?}", e)))
} }
pub async fn go_online(pair: Pair, client: Client) -> Result<()> { /// set session keys
if client pub async fn set_session_keys(
data: &Data,
session_keys: RuntimeSessionKeys,
) -> Result<TxProgress, subxt::Error> {
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(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(data: &Data) -> Result<(), GcliError> {
if data
.client()
.storage() .storage()
.fetch( .at_latest()
&runtime::storage() .await?
.session() .fetch(&runtime::storage().session().next_keys(data.address()))
.next_keys(AccountId32::from(pair.public())),
None,
)
.await? .await?
.is_none() .is_none()
{ {
return Err(anyhow!("This account has not set session keys!")); return Err(GcliError::Logic(
"This account has not set session keys!".to_string(),
));
} }
client submit_call_and_look_event::<
.tx() runtime::authority_members::events::MemberGoOnline,
.sign_and_submit_then_watch( StaticPayload<runtime::authority_members::calls::types::GoOnline>,
&runtime::tx().authority_members().go_online(), >(data, &runtime::tx().authority_members().go_online())
&PairSigner::new(pair), .await
BaseExtrinsicParamsBuilder::new(), .map_err(|e| e.into())
)
.await?;
Ok(())
} }
pub async fn go_offline(pair: Pair, client: Client) -> Result<()> { /// submit go_offline
client pub async fn go_offline(data: &Data) -> Result<(), subxt::Error> {
.tx() submit_call_and_look_event::<
.sign_and_submit_then_watch( runtime::authority_members::events::MemberGoOffline,
&runtime::tx().authority_members().go_offline(), StaticPayload<runtime::authority_members::calls::types::GoOffline>,
&PairSigner::new(pair), >(data, &runtime::tx().authority_members().go_offline())
BaseExtrinsicParamsBuilder::new(), .await
)
.await?;
Ok(())
} }
pub async fn online(client: Client, args: &Args) -> Result<()> { /// get online authorities
let parent_hash = client pub async fn online(data: &Data) -> Result<(), subxt::Error> {
.clone() let client = data.client();
.storage()
.fetch(&runtime::storage().system().parent_hash(), None)
.await?
.unwrap();
let gql_client = reqwest::Client::builder()
.user_agent("gcli/0.1.0")
.build()?;
let mut identity_cache = cache::IdentityCache::new(
client.clone(),
if args.no_indexer {
None
} else {
Some(Indexer {
gql_client,
gql_url: args.indexer.clone(),
})
},
);
let online_authorities = client let online_authorities = client
.storage() .storage()
.fetch( .at_latest()
&runtime::storage().authority_members().online_authorities(), .await?
Some(parent_hash), .fetch(&runtime::storage().authority_members().online_authorities())
)
.await? .await?
.unwrap_or_default(); .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 let incoming_authorities = client
.storage() .storage()
.at_latest()
.await?
.fetch( .fetch(
&runtime::storage() &runtime::storage()
.authority_members() .authority_members()
.incoming_authorities(), .incoming_authorities(),
Some(parent_hash),
) )
.await? .await?
.unwrap_or_default(); .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 let outgoing_authorities = client
.storage() .storage()
.at_latest()
.await?
.fetch( .fetch(
&runtime::storage() &runtime::storage()
.authority_members() .authority_members()
.outgoing_authorities(), .outgoing_authorities(),
Some(parent_hash),
) )
.await? .await?
.unwrap_or_default(); .unwrap_or_default();
println!("Outgoing:"); if let Some(indexer) = &data.indexer {
for identity_id in outgoing_authorities { 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!( println!(
" {}", "{}",
identity_cache online_authorities
.fetch_identity(identity_id, parent_hash) .iter()
.await .map(|i| names
.unwrap_or_else(|_| format!("{identity_id}")) .get(i)
.map(|n| n.to_string())
.unwrap_or(format!("{} <no name found>", i)))
.collect::<Vec<String>>()
.join(", ")
); );
}
Ok(()) 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(", ")
);
/// submit a certification and track progress println!("Outgoing:");
pub async fn cert(client: &Client, pair: Pair, issuer: u32, receiver: u32) -> Result<()> { println!(
let mut progress = client "{}",
.tx() outgoing_authorities
.sign_and_submit_then_watch( .iter()
&runtime::tx().smith_cert().add_cert(issuer, receiver), .map(|i| names
&PairSigner::new(pair), .get(i)
BaseExtrinsicParamsBuilder::new(), .map(|n| n.to_string())
) .unwrap_or(format!("{} <no name found>", i)))
.await?; .collect::<Vec<String>>()
.join(", ")
let in_block = loop { );
if let Some(status) = progress.next_item().await { } else {
match status? { println!("Online:");
TxStatus::Ready => { println!("{online_authorities:?}");
println!("transaction submitted to the network, waiting 6 seconds...");
}
TxStatus::InBlock(in_block) => break in_block,
TxStatus::Invalid => {
println!("Invalid");
}
_ => continue,
}
}
};
// get the block events and return if ExtrinsicFailed println!("Incoming:");
let events = in_block.wait_for_success().await?; println!("{incoming_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>()?;
if let Some(event) = new_cert_event { println!("Outgoing:");
println!("{event:?}"); println!("{outgoing_authorities:?}");
}
if let Some(event) = renew_cert_event {
println!("{event:?}");
} }
Ok(()) 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 crate::*;
use anyhow::Result; /// define sudo subcommands
use sp_core::{crypto::AccountId32, sr25519::Pair}; #[derive(Clone, Default, Debug, clap::Parser)]
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner}; 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<()> { /// handle smith commands
client pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
.tx() let data = data.build_client().await?;
.sign_and_submit_then_watch( match command {
&runtime::tx().sudo().set_key(new_key.into()), Subcommand::Nothing => todo!(),
&PairSigner::new(pair), Subcommand::SetKey { new_key } => {
BaseExtrinsicParamsBuilder::new(), set_key(&data, new_key).await?;
) }
.await?; Subcommand::SetDistanceOk { identity } => {
set_distance_ok(&data, identity).await?;
}
};
Ok(()) 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
}