Skip to content
Snippets Groups Projects
Commit b145c4a8 authored by Hugo Trentesaux's avatar Hugo Trentesaux
Browse files

refac call submission and event watch

parent bea7e02e
No related branches found
No related tags found
No related merge requests found
......@@ -66,3 +66,11 @@ Secret and/or public keys can always be passed using `--secret` and `--address`.
Secret key format can be changed using `--secret-format` with the following values:
* `substrate`: a Substrate secret address (optionally followed by a derivation path), or BIP39 mnemonic
* `seed`: a 32-bytes seed in hexadecimal (Duniter v1 compatible)
## TODO
- [x] implement config formatter
- [x] add link/unlink account commands
- [ ] migrate all xt to submit_call_and_look_event
- [ ] add transfer with unit (ĞD, UD...)
- [ ]
\ No newline at end of file
# Ğcli config
Some Ğcli commands require to have an address configured (for example to get account balance), some require to have a secret configured (to sign extrinsics).
Ğcli allows to save what you want in a config file and to overwrite parts in command line arguments. Example:
```sh
# save Alice secret to config file
cargo run -- -S predefined -s Alice config save
# show config
cargo run -- config show
# [stdout]
# Ğcli config
# duniter endpoint ws://localhost:9944
# indexer endpoint http://localhost:8080/v1/graphql
# address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY (secret defined)
# use different address in command line
cargo run -- --address 5Fxune7f71ZbpP2FoY3mhYcmM596Erhv1gRue4nsPwkxMR4n config show
# [stdout]
# Ğcli config
# duniter endpoint ws://localhost:9944
# indexer endpoint http://localhost:8080/v1/graphql
# address 5Fxune7f71ZbpP2FoY3mhYcmM596Erhv1gRue4nsPwkxMR4n (no secret)
```
You can see that if a secret is defined, the associated address is used, but if an other address is given, the secret is silenced.
\ No newline at end of file
......@@ -20,7 +20,7 @@ gcli -S predefined -s Alice config save
gcli --network local -S predefined -s test1 config save
```
In the following, we assume this last command was run.
In the following, we assume this last command was run. More about the config in [config.md](./config.md).
## Commands
......
......@@ -27,6 +27,8 @@ pub enum Subcommand {
/// List of target addresses
dests: Vec<AccountId>,
},
/// Unlink the account from the linked identity
Unlink,
}
/// handle account commands
......@@ -48,6 +50,9 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
Subcommand::TransferMultiple { amount, dests } => {
commands::transfer::transfer_multiple(&data, amount, dests).await?;
}
Subcommand::Unlink => {
unlink_account(&data).await?;
}
};
Ok(())
......@@ -78,3 +83,12 @@ pub async fn get_account_info(
.fetch(&runtime::storage().system().account(account_id), None)
.await
}
/// unlink account
pub async fn unlink_account(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::account::events::AccountUnlinked,
StaticTxPayload<runtime::account::calls::UnlinkIdentity>,
>(data, &runtime::tx().account().unlink_identity())
.await
}
......@@ -2,31 +2,17 @@ use crate::*;
/// submit a certification and track progress
pub async fn certify(data: &Data, receiver: u32) -> Result<(), anyhow::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().cert().add_cert(data.idty_index(), receiver),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let progress = submit_call(
data,
&runtime::tx().cert().add_cert(data.idty_index(), receiver),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
// look for the expected event
let new_cert_event = events.find_first::<runtime::cert::events::NewCert>()?;
let renew_cert_event = events.find_first::<runtime::cert::events::RenewedCert>()?;
if let Some(event) = new_cert_event {
println!("{event:?}");
}
if let Some(event) = renew_cert_event {
println!("{event:?}");
}
look_event::<runtime::cert::events::NewCert>(&events)?;
look_event::<runtime::cert::events::RenewedCert>(&events)?;
Ok(())
}
......@@ -135,26 +135,14 @@ pub async fn technical_committee_vote(
proposal_index: u32,
vote: bool,
) -> Result<(), subxt::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.technical_committee()
.vote(proposal_hash, proposal_index, vote),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::technical_committee::events::Voted>()? {
println!("{e:?}");
}
Ok(())
submit_call_and_look_event::<
runtime::technical_committee::events::Voted,
StaticTxPayload<runtime::technical_committee::calls::Vote>,
>(
data,
&runtime::tx()
.technical_committee()
.vote(proposal_hash, proposal_index, vote),
)
.await
}
......@@ -3,7 +3,9 @@ use crate::*;
use crate::{
commands::revocation::generate_revoc_doc,
runtime::runtime_types::{
common_runtime::entities::IdtyData, pallet_identity::types::*, sp_core::sr25519::Signature,
common_runtime::entities::{IdtyData, NewOwnerKeySignature},
pallet_identity::types::*,
sp_core::sr25519::Signature,
sp_runtime::MultiSignature,
},
};
......@@ -32,6 +34,12 @@ pub enum Subcommand {
///
/// To be called by the certified not-yet-member account, to become member.
Confirm { name: String },
/// Validate an identity
/// Should be called when the distance has been evaluated positively
Validate { index: u32 },
/// Renew membership
/// When membership comes to and end, it should be renewed for the identity to stay member
RenewMembership,
/// Certify an identity
Certify { target: u32 },
/// Revoke an identity immediately
......@@ -40,6 +48,11 @@ pub enum Subcommand {
GenRevocDoc,
/// Display member count
MemberCount,
/// Link an account to the identity
LinkAccount {
/// address of the account that has to be linked
address: AccountId,
},
}
/// handle identity commands
......@@ -61,6 +74,12 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
Subcommand::Confirm { name } => {
confirm_identity(&data, name).await?;
}
Subcommand::Validate { index } => {
validate_identity(&data, index).await?;
}
Subcommand::RenewMembership => {
renew_membership(&data).await?;
}
Subcommand::Certify { target } => {
data = data.fetch_idty_index().await?;
// TODO fetch target username / key / index
......@@ -69,7 +88,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
}
Subcommand::Revoke => {
data = data.fetch_idty_index().await?;
revoke_identity(data).await?;
revoke_identity(&data).await?;
}
Subcommand::GenRevocDoc => {
data = data.fetch_idty_index().await?;
......@@ -88,6 +107,10 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
.unwrap()
)
}
Subcommand::LinkAccount { address } => {
data = data.fetch_idty_index().await?; // idty index required for payload
link_account(&data, address).await?;
}
};
Ok(())
......@@ -186,50 +209,42 @@ pub async fn get_identity_by_index(
/// created identity
pub async fn create_identity(data: &Data, target: AccountId) -> Result<(), subxt::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().identity().create_identity(target),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::identity::events::IdtyCreated>()? {
println!("{e:?}");
}
Ok(())
submit_call_and_look_event::<
runtime::identity::events::IdtyCreated,
StaticTxPayload<runtime::identity::calls::CreateIdentity>,
>(data, &runtime::tx().identity().create_identity(target))
.await
}
/// confirm identity
pub async fn confirm_identity(data: &Data, name: String) -> Result<(), subxt::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().identity().confirm_identity(name),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
submit_call_and_look_event::<
runtime::identity::events::IdtyConfirmed,
StaticTxPayload<runtime::identity::calls::ConfirmIdentity>,
>(data, &runtime::tx().identity().confirm_identity(name))
.await
}
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::identity::events::IdtyConfirmed>()? {
println!("{e:?}");
}
Ok(())
/// confirm identity
pub async fn validate_identity(data: &Data, index: u32) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::identity::events::IdtyValidated,
StaticTxPayload<runtime::identity::calls::ValidateIdentity>,
>(data, &runtime::tx().identity().validate_identity(index))
.await
}
/// renew membership
pub async fn renew_membership(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::membership::events::MembershipRenewed,
StaticTxPayload<runtime::membership::calls::RenewMembership>,
>(data, &runtime::tx().membership().renew_membership())
.await
}
/// generate revokation document and submit it immediately
pub async fn revoke_identity(data: Data) -> Result<(), subxt::Error> {
pub async fn revoke_identity(data: &Data) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_revoc_doc(&data);
// Transform signature to MultiSignature
......@@ -237,24 +252,45 @@ pub async fn revoke_identity(data: Data) -> Result<(), subxt::Error> {
let signature = Signature(signature.0);
let multisign = MultiSignature::Sr25519(signature);
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.identity()
.revoke_identity(data.idty_index(), data.address(), multisign),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
submit_call_and_look_event::<
runtime::identity::events::IdtyRemoved,
StaticTxPayload<runtime::identity::calls::RevokeIdentity>,
>(
data,
&runtime::tx()
.identity()
.revoke_identity(data.idty_index(), data.address(), multisign),
)
.await
}
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::identity::events::IdtyRemoved>()? {
println!("{e:?}");
}
Ok(())
type LinkAccountPayload = Vec<u8>;
/// generate link account document
pub fn generate_link_account(
data: &Data,
address: AccountId,
) -> (LinkAccountPayload, sp_core::sr25519::Signature) {
let payload = (b"link", data.genesis_hash, data.idty_index(), address).encode();
let signature = data.keypair().sign(&payload);
(payload, signature)
}
/// link an account to the identity
pub async fn link_account(data: &Data, address: AccountId) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_link_account(data, address.clone());
// this is a hack, see
// https://substrate.stackexchange.com/questions/10309/how-to-use-core-crypto-types-instead-of-runtime-types
let signature = Signature(signature.0);
submit_call_and_look_event::<
runtime::account::events::AccountLinked,
StaticTxPayload<runtime::identity::calls::LinkAccount>,
>(
data,
&runtime::tx()
.identity()
.link_account(address, NewOwnerKeySignature(signature)),
)
.await
}
......@@ -176,46 +176,31 @@ pub async fn consume_oneshot_account_with_remaining(
.fetch(&runtime::storage().system().number(), None)
.await?
.unwrap();
let progress = client
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.oneshot_account()
.consume_oneshot_account_with_remaining(
number,
if dest_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
dest.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
dest.into(),
)
},
if remaining_to_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
remaining_to.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
remaining_to.into(),
)
},
balance,
),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
if let Some(e) =
events.find_first::<runtime::oneshot_account::events::OneshotAccountConsumed>()?
{
println!("{e:?}");
}
Ok(())
let call = &runtime::tx()
.oneshot_account()
.consume_oneshot_account_with_remaining(
number,
if dest_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(dest.into())
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(dest.into())
},
if remaining_to_oneshot {
runtime::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
remaining_to.into(),
)
} else {
runtime::runtime_types::pallet_oneshot_account::types::Account::Normal(
remaining_to.into(),
)
},
balance,
);
submit_call_and_look_event::<
runtime::oneshot_account::events::OneshotAccountConsumed,
StaticTxPayload<runtime::oneshot_account::calls::ConsumeOneshotAccountWithRemaining>,
>(data, call)
.await
}
......@@ -310,35 +310,21 @@ pub async fn online(data: &Data) -> Result<(), anyhow::Error> {
Ok(())
}
/// submit a certification and track progress
/// submit a smith certification and track progress
pub async fn cert(data: &Data, receiver: u32) -> Result<(), anyhow::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx()
.smith_cert()
.add_cert(data.idty_index(), receiver),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let progress = submit_call(
data,
&runtime::tx()
.smith_cert()
.add_cert(data.idty_index(), receiver),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
// 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!("{event:?}");
}
if let Some(event) = renew_cert_event {
println!("{event:?}");
}
look_event::<runtime::smith_cert::events::NewCert>(&events)?;
look_event::<runtime::smith_cert::events::RenewedCert>(&events)?;
Ok(())
}
......@@ -2,19 +2,9 @@ use crate::*;
/// set sudo key
pub async fn set_key(data: &Data, new_key: AccountId) -> Result<(), subxt::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().sudo().set_key(new_key.into()),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let _ = track_progress(progress).await?; // TODO
Ok(())
submit_call_and_look_event::<
runtime::sudo::events::KeyChanged,
StaticTxPayload<runtime::sudo::calls::SetKey>,
>(data, &runtime::tx().sudo().set_key(new_key.into()))
.await
}
......@@ -78,25 +78,10 @@ pub async fn transfer_multiple(
})
})
.collect();
// wrap these calls in a batch call
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().utility().batch(transactions),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
// TODO all transfer
if let Some(e) = events.find_first::<runtime::balances::events::Transfer>()? {
println!("{e:?}");
}
Ok(())
submit_call_and_look_event::<
runtime::utility::events::BatchCompleted,
StaticTxPayload<runtime::utility::calls::Batch>,
>(data, &runtime::tx().utility().batch(transactions))
.await
}
......@@ -15,7 +15,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
// match subcommand
match command {
Subcommand::Claim => {
claim_ud(data).await?;
claim_ud(&data).await?;
}
};
......@@ -23,24 +23,10 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
}
/// claim universal dividend
pub async fn claim_ud(data: Data) -> Result<(), anyhow::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().universal_dividend().claim_uds(),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::universal_dividend::events::UdsClaimed>()? {
println!("{e:?}");
}
Ok(())
pub async fn claim_ud(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::universal_dividend::events::UdsClaimed,
StaticTxPayload<runtime::universal_dividend::calls::ClaimUds>,
>(data, &runtime::tx().universal_dividend().claim_uds())
.await
}
......@@ -27,6 +27,25 @@ impl std::default::Default for Config {
}
}
impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let secret = if self.secret.is_some() {
"(secret defined)"
} else {
"(no secret)"
};
let address = if let Some(address) = &self.address {
format!("{}", address)
} else {
"(no address)".to_string()
};
writeln!(f, "Ğcli config")?;
writeln!(f, "duniter endpoint {}", self.duniter_endpoint)?;
writeln!(f, "indexer endpoint {}", self.indexer_endpoint)?;
write!(f, "address {address} {secret}")
}
}
/// load config file and manage error if could not
pub fn load_conf() -> Config {
match confy::load(APP_NAME, None) {
......@@ -68,7 +87,7 @@ pub fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
);
}
Subcommand::Show => {
println!("{:?}", data.cfg);
println!("{}", data.cfg);
}
Subcommand::Save => {
confy::store(APP_NAME, None, &data.cfg).expect("unable to write config");
......
......@@ -92,7 +92,7 @@ pub enum Subcommand {
Config(conf::Subcommand),
}
/// maint function
/// main function
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), GcliError> {
// init logger
......
// #[allow(clippy::enum_variant_names)]
#[cfg(feature = "gdev")]
#[subxt::subxt(
runtime_metadata_path = "res/metadata.scale",
......@@ -5,8 +6,8 @@
)]
pub mod runtime {
// IF NEEDED
// #[subxt(substitute_type = "spcore::sr25519::Signature")]
// use crate::gdev::runtime_types::sp_core::sr25519::Signature;
// #[subxt(substitute_type = "sp_core::sr25519::Signature")]
// use crate::runtime::runtime_types::sp_core::sr25519::Signature;
}
// declare custom types
......
......@@ -28,24 +28,40 @@ pub async fn submit_call_and_look_event<E: std::fmt::Debug + StaticEvent, Call:
data: &Data,
call: &Call,
) -> Result<(), subxt::Error> {
let progress = data
.client()
// submit call
let progress = submit_call(data, call).await?;
// if no wait, return immediately
if data.args.no_wait {
return Ok(());
}
// collect events
let events = track_progress(progress).await?;
// print given event if there
look_event::<E>(&events)
}
/// submit call
pub async fn submit_call<Call: TxPayload>(
data: &Data,
call: &Call,
) -> Result<TxProgress, subxt::Error> {
data.client()
.tx()
.sign_and_submit_then_watch(
call,
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
.await
}
if data.args.no_wait {
return Ok(());
}
let events = track_progress(progress).await?;
/// look event
pub fn look_event<E: std::fmt::Debug + StaticEvent>(
events: &ExtrinsicEvents<Runtime>,
) -> Result<(), subxt::Error> {
if let Some(e) = events.find_first::<E>()? {
println!("{e:?}");
}
Ok(())
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment