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 account;
pub mod blockchain; 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 ud;
pub mod vault;
use crate::*; 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 /// 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_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 {
...@@ -22,6 +79,17 @@ pub async fn get_account_info( ...@@ -22,6 +79,17 @@ pub async fn get_account_info(
) -> Result<Option<AccountInfo>, subxt::Error> { ) -> Result<Option<AccountInfo>, subxt::Error> {
client client
.storage() .storage()
.fetch(&runtime::storage().system().account(account_id), None) .at_latest()
.await?
.fetch(&runtime::storage().system().account(account_id))
.await .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::*; 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 /// 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 Ok(data
.client() .client()
.storage() .storage()
.fetch(&runtime::storage().system().block_hash(0), None) .at_latest()
.await?
.fetch(&runtime::storage().system().block_hash(0))
.await? .await?
.unwrap()) .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 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(data: &Data) -> Result<()> { /// handle technical committee commands
let client = data.client(); pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
let indexer = data.indexer.clone(); 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 Ok(())
.storage() }
.fetch(&runtime::storage().system().parent_hash(), None)
.await? /// list technical committee members
.unwrap(); pub async fn technical_committee_members(data: &Data) -> Result<(), subxt::Error> {
let client = data.client();
let indexer = &data.indexer;
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<(), subxt::Error> { ) -> Result<(), subxt::Error> {
let progress = 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
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::technical_committee::events::Voted>()? { /// propose call given as hexadecimal
println!("{e:?}"); /// 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 crate::{indexer::*, *};
use anyhow::Result;
use futures::join; use futures::join;
use std::collections::BTreeMap; 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 client = data.client();
let indexer = data.indexer.clone(); let indexer = data.indexer.clone();
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.storage().at(parent_hash).fetch(&addr_current_block),
client client
.storage() .storage()
.fetch(&addr_current_block, Some(parent_hash)), .at(parent_hash)
client .fetch(&addr_current_session)
.storage()
.fetch(&addr_current_session, Some(parent_hash),)
); );
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(client.clone(), indexer);
// Rotate keys let mut identity_cache = IdentityCache::new(client.clone(), indexer);
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 {
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());
if block_number < end_block { 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 [ for (title, certs) in [("Certifications", basic_certs)] {
("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:");
...@@ -111,12 +54,12 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res ...@@ -111,12 +54,12 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
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,
...@@ -128,45 +71,22 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res ...@@ -128,45 +71,22 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
// 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:");
...@@ -174,7 +94,7 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res ...@@ -174,7 +94,7 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
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,
...@@ -185,3 +105,53 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res ...@@ -185,3 +105,53 @@ pub async fn monitor_expirations(data: &Data, blocks: u32, sessions: u32) -> Res
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) -> Res ...@@ -63,14 +62,15 @@ pub async fn spam_roll(pair: Pair, client: &Client, actual_repart: usize) -> Res
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) -> Res ...@@ -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); 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) -> Res ...@@ -92,8 +94,9 @@ pub async fn spam_roll(pair: Pair, client: &Client, actual_repart: usize) -> Res
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?;
// }
} }
} }
...@@ -27,7 +27,7 @@ pub enum Subcommand { ...@@ -27,7 +27,7 @@ pub enum Subcommand {
} }
/// handle oneshot commands /// 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 // build indexer because it is needed for all subcommands
let mut data = data.build_client().await?; let mut data = data.build_client().await?;
// match subcommand // match subcommand
...@@ -65,17 +65,18 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<( ...@@ -65,17 +65,18 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
} }
/// get balance of oneshot account /// 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!( println!(
"balance of oneshot account {} is: {}", "balance of oneshot account {} is: {}",
data.address(), data.address(),
data.client() data.client()
.storage() .storage()
.at_latest()
.await?
.fetch( .fetch(
&runtime::storage() &runtime::storage()
.oneshot_account() .oneshot_account()
.oneshot_accounts(data.address()), .oneshot_accounts(data.address()),
None
) )
.await? .await?
.unwrap_or(0) .unwrap_or(0)
...@@ -90,74 +91,50 @@ pub async fn create_oneshot_account( ...@@ -90,74 +91,50 @@ pub async fn create_oneshot_account(
balance: u64, balance: u64,
dest: AccountId, dest: AccountId,
) -> Result<(), subxt::Error> { ) -> Result<(), subxt::Error> {
let progress = data submit_call_and_look_event::<
.client() runtime::oneshot_account::events::OneshotAccountCreated,
.tx() StaticPayload<runtime::oneshot_account::calls::types::CreateOneshotAccount>,
.sign_and_submit_then_watch( >(
&runtime::tx() data,
.oneshot_account() &runtime::tx()
.create_oneshot_account(dest.into(), balance), .oneshot_account()
&PairSigner::new(data.keypair()), .create_oneshot_account(dest.into(), balance),
BaseExtrinsicParamsBuilder::new(), )
) .await
.await?;
let events = track_progress(progress).await?;
if let Some(e) =
events.find_first::<runtime::oneshot_account::events::OneshotAccountCreated>()?
{
println!("{e:?}");
}
Ok(())
} }
/// consume oneshot account /// consume oneshot account
pub async fn consume_oneshot_account( pub async fn consume_oneshot_account(
data: &Data, data: &Data,
dest: AccountId, dest: AccountId,
dest_oneshot: bool, dest_oneshot: bool,
) -> Result<(), subxt::Error> { ) -> Result<(), subxt::Error> {
let client = data.client(); 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();
let progress = 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(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(())
} }
/// consume oneshot account with remaining /// consume oneshot account with remaining
pub async fn consume_oneshot_account_with_remaining( pub async fn consume_oneshot_account_with_remaining(
data: &Data, data: &Data,
balance: u64, balance: u64,
dest: AccountId, dest: AccountId,
dest_oneshot: bool, dest_oneshot: bool,
...@@ -168,46 +145,36 @@ pub async fn consume_oneshot_account_with_remaining( ...@@ -168,46 +145,36 @@ pub async fn consume_oneshot_account_with_remaining(
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();
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?; let payload = &runtime::tx()
if let Some(e) = .oneshot_account()
events.find_first::<runtime::oneshot_account::events::OneshotAccountConsumed>()? .consume_oneshot_account_with_remaining(
{ number,
println!("{e:?}"); if dest_oneshot {
} runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(dest.into())
Ok(()) } 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 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)
} }
...@@ -2,42 +2,147 @@ use crate::*; ...@@ -2,42 +2,147 @@ 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::*; 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 std::ops::Deref;
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
type SessionKeys = [u8; 128]; 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 /// rotate session keys
pub async fn rotate_keys(client: &Client) -> Result<SessionKeys, anyhow::Error> { /// (needs to be connected to unsafe RPC)
client pub async fn rotate_keys(data: &Data) -> Result<SessionKeys, GcliError> {
.rpc() data.legacy_rpc_methods()
.rotate_keys() .await
.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() .deref()
.try_into() .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 /// set session keys
pub async fn set_session_keys( pub async fn set_session_keys(
pair: Pair, data: &Data,
client: &Client, session_keys: RuntimeSessionKeys,
session_keys: SessionKeys,
) -> Result<TxProgress, subxt::Error> { ) -> Result<TxProgress, subxt::Error> {
client submit_call::<StaticPayload<runtime::authority_members::calls::types::SetSessionKeys>>(
.tx() data,
.sign_and_submit_then_watch( &runtime::tx()
&runtime::tx() .authority_members()
.authority_members() .set_session_keys(session_keys),
.set_session_keys(session_keys), )
&PairSigner::new(pair), .await
BaseExtrinsicParamsBuilder::new(),
)
.await
} }
// use runtime::runtime_types::sp_consensus_grandpa::app::Public
/// update session keys /// update session keys
pub async fn update_session_keys(pair: Pair, client: &Client) -> Result<(), GcliError> { pub async fn update_session_keys(data: &Data) -> Result<(), GcliError> {
let session_keys = rotate_keys(client).await?; let session_keys = rotate_keys(data).await?;
let progress = set_session_keys(pair, client, session_keys).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 let _ = track_progress(progress).await?; // TODO
Ok(()) Ok(())
} }
/// submit go_online /// submit go_online
pub async fn go_online(pair: Pair, client: &Client) -> Result<(), GcliError> { pub async fn go_online(data: &Data) -> Result<(), GcliError> {
if client 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()
{ {
...@@ -62,149 +170,141 @@ pub async fn go_online(pair: Pair, client: &Client) -> Result<(), GcliError> { ...@@ -62,149 +170,141 @@ pub async fn go_online(pair: Pair, client: &Client) -> Result<(), GcliError> {
)); ));
} }
let progress = 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?;
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::authority_members::events::MemberGoOnline>()? {
println!("{e:?}");
}
Ok(())
} }
/// submit go_offline /// submit go_offline
pub async fn go_offline(pair: Pair, client: &Client) -> Result<(), subxt::Error> { pub async fn go_offline(data: &Data) -> Result<(), subxt::Error> {
let progress = client submit_call_and_look_event::<
.tx() runtime::authority_members::events::MemberGoOffline,
.sign_and_submit_then_watch( StaticPayload<runtime::authority_members::calls::types::GoOffline>,
&runtime::tx().authority_members().go_offline(), >(data, &runtime::tx().authority_members().go_offline())
&PairSigner::new(pair), .await
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::authority_members::events::MemberGoOffline>()? {
println!("{e:?}");
}
Ok(())
} }
/// get online authorities /// 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 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 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(())
}
/// submit a certification and track progress println!("Incoming:");
pub async fn cert( println!(
client: &Client, "{}",
pair: Pair, incoming_authorities
issuer: u32, .iter()
receiver: u32, .map(|i| names
) -> Result<(), anyhow::Error> { .get(i)
let progress = client .map(|n| n.to_string())
.tx() .unwrap_or(format!("{} <no name found>", i)))
.sign_and_submit_then_watch( .collect::<Vec<String>>()
&runtime::tx().smith_cert().add_cert(issuer, receiver), .join(", ")
&PairSigner::new(pair), );
BaseExtrinsicParamsBuilder::new(),
)
.await?;
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 println!("Incoming:");
let new_cert_event = events.find_first::<runtime::smith_cert::events::NewCert>()?; println!("{incoming_authorities:?}");
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 sp_core::{crypto::AccountId32, sr25519::Pair}; /// define sudo subcommands
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner}; #[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( /// handle smith commands
pair: Pair, pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
client: &Client, let data = data.build_client().await?;
new_key: AccountId32, match command {
) -> Result<(), subxt::Error> { Subcommand::Nothing => todo!(),
let progress = client Subcommand::SetKey { new_key } => {
.tx() set_key(&data, new_key).await?;
.sign_and_submit_then_watch( }
&runtime::tx().sudo().set_key(new_key.into()), Subcommand::SetDistanceOk { identity } => {
&PairSigner::new(pair), set_distance_ok(&data, identity).await?;
BaseExtrinsicParamsBuilder::new(), }
) };
.await?;
let _ = track_progress(progress).await?; // TODO
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
}