diff --git a/cli/polka-storage/storagext-cli/src/cmd/faucet.rs b/cli/polka-storage/storagext-cli/src/cmd/faucet.rs new file mode 100644 index 000000000..4f63dffe1 --- /dev/null +++ b/cli/polka-storage/storagext-cli/src/cmd/faucet.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use clap::Subcommand; +use storagext::{FaucetClientExt, PolkaStorageConfig}; +use url::Url; + +use crate::OutputFormat; + +#[derive(Debug, Subcommand)] +#[command(name = "faucet", about = "CLI Client to the Faucet Pallet", version)] +pub(crate) enum FaucetCommand { + /// Drip funds into target account. + Drip { + /// Drip target's account ID. + account_id: ::AccountId, + }, +} + +impl FaucetCommand { + /// Run a `faucet` command. + /// + /// Requires the target RPC address. + #[tracing::instrument(level = "info", skip(self, node_rpc), fields(node_rpc = node_rpc.as_str()))] + pub async fn run( + self, + node_rpc: Url, + n_retries: u32, + retry_interval: Duration, + output_format: OutputFormat, + wait_for_finalization: bool, + ) -> Result<(), anyhow::Error> { + let client = storagext::Client::new(node_rpc, n_retries, retry_interval).await?; + + match self { + FaucetCommand::Drip { account_id } => { + let submission_result = client.drip(account_id, wait_for_finalization).await?; + + let Some(submission_result) = submission_result else { + // Didn't wait for finalization + return Ok(()); + }; + + // This monstrosity first converts incoming events into a "generic" (subxt generated) event, + // and then we extract only the Market events. We could probably extract this into a proper + // iterator but the effort to improvement ratio seems low (for 2 pallets at least). + let submission_results = submission_result + .events + .iter() + .flat_map(|event| { + event.map(|details| details.as_root_event::()) + }) + .filter_map(|event| match event { + Ok(storagext::runtime::Event::Market(e)) => Some(Ok(e)), + Err(err) => Some(Err(err)), + _ => None, + }); + for event in submission_results { + let event = event?; + let output = output_format.format(&event)?; + match output_format { + OutputFormat::Plain => println!("[{}] {}", submission_result.hash, output), + OutputFormat::Json => println!("{}", output), + } + } + Ok(()) + } + } + } +} diff --git a/cli/polka-storage/storagext-cli/src/cmd/mod.rs b/cli/polka-storage/storagext-cli/src/cmd/mod.rs index 763220f51..459deb285 100644 --- a/cli/polka-storage/storagext-cli/src/cmd/mod.rs +++ b/cli/polka-storage/storagext-cli/src/cmd/mod.rs @@ -1,3 +1,4 @@ +pub mod faucet; pub mod market; pub mod proofs; pub mod randomness; diff --git a/cli/polka-storage/storagext-cli/src/main.rs b/cli/polka-storage/storagext-cli/src/main.rs index e9d11f8ef..b42037f52 100644 --- a/cli/polka-storage/storagext-cli/src/main.rs +++ b/cli/polka-storage/storagext-cli/src/main.rs @@ -6,8 +6,8 @@ use std::{fmt::Debug, time::Duration}; use clap::{ArgGroup, Parser, Subcommand}; use cmd::{ - market::MarketCommand, proofs::ProofsCommand, randomness::RandomnessCommand, - storage_provider::StorageProviderCommand, system::SystemCommand, + faucet::FaucetCommand, market::MarketCommand, proofs::ProofsCommand, + randomness::RandomnessCommand, storage_provider::StorageProviderCommand, system::SystemCommand, }; use storagext::multipair::{DebugPair, MultiPairSigner}; use subxt::ext::sp_core::{ @@ -89,6 +89,8 @@ struct Cli { #[derive(Debug, Subcommand)] enum SubCommand { + #[command(subcommand)] + Faucet(FaucetCommand), // Perform market operations. #[command(subcommand)] Market(MarketCommand), @@ -114,6 +116,16 @@ impl SubCommand { wait_for_finalization: bool, ) -> Result<(), anyhow::Error> { match self { + SubCommand::Faucet(cmd) => { + cmd.run( + node_rpc, + n_retries, + retry_interval, + output_format, + wait_for_finalization, + ) + .await?; + } SubCommand::Market(cmd) => { cmd.run( node_rpc, diff --git a/cli/polka-storage/storagext/src/clients/faucet.rs b/cli/polka-storage/storagext/src/clients/faucet.rs new file mode 100644 index 000000000..e6f3325eb --- /dev/null +++ b/cli/polka-storage/storagext/src/clients/faucet.rs @@ -0,0 +1,30 @@ +use std::future::Future; + +use crate::{ + runtime::{self, SubmissionResult}, + PolkaStorageConfig, +}; + +/// Client to interact with the faucet pallet. +pub trait FaucetClientExt { + /// Drip funds into the provided account. + fn drip( + &self, + account_id: ::AccountId, + wait_for_finalization: bool, + ) -> impl Future>, subxt::Error>>; +} + +impl FaucetClientExt for crate::runtime::client::Client { + async fn drip( + &self, + account_id: ::AccountId, + wait_for_finalization: bool, + ) -> Result>, subxt::Error> { + let payload = runtime::tx() + .faucet() + .drip(subxt::utils::AccountId32::from(account_id)); + + self.unsigned(&payload, wait_for_finalization).await + } +} diff --git a/cli/polka-storage/storagext/src/clients/mod.rs b/cli/polka-storage/storagext/src/clients/mod.rs index ee81627f5..268ca2551 100644 --- a/cli/polka-storage/storagext/src/clients/mod.rs +++ b/cli/polka-storage/storagext/src/clients/mod.rs @@ -1,9 +1,11 @@ +mod faucet; mod market; mod proofs; mod randomness; mod storage_provider; mod system; +pub use faucet::FaucetClientExt; pub use market::MarketClientExt; pub use proofs::ProofsClientExt; pub use randomness::RandomnessClientExt; diff --git a/cli/polka-storage/storagext/src/lib.rs b/cli/polka-storage/storagext/src/lib.rs index 54186b246..662ea065d 100644 --- a/cli/polka-storage/storagext/src/lib.rs +++ b/cli/polka-storage/storagext/src/lib.rs @@ -8,7 +8,10 @@ pub mod types; pub mod deser; pub use crate::{ - clients::{MarketClientExt, RandomnessClientExt, StorageProviderClientExt, SystemClientExt}, + clients::{ + FaucetClientExt, MarketClientExt, RandomnessClientExt, StorageProviderClientExt, + SystemClientExt, + }, runtime::{bounded_vec::IntoBoundedByteVec, client::Client}, }; diff --git a/cli/polka-storage/storagext/src/runtime/client.rs b/cli/polka-storage/storagext/src/runtime/client.rs index 302215210..3964f4bcd 100644 --- a/cli/polka-storage/storagext/src/runtime/client.rs +++ b/cli/polka-storage/storagext/src/runtime/client.rs @@ -2,7 +2,7 @@ use std::time::Duration; use codec::Encode; use hex::ToHex; -use subxt::{blocks::Block, events::Events, OnlineClient}; +use subxt::{blocks::Block, events::Events, utils::H256, OnlineClient}; use crate::PolkaStorageConfig; @@ -67,6 +67,32 @@ impl Client { } } + pub(crate) async fn unsigned( + &self, + call: &Call, + wait_for_finalization: bool, + ) -> Result>, subxt::Error> + where + Call: subxt::tx::Payload, + { + if wait_for_finalization { + let submitted_extrinsic_hash = self.client.tx().create_unsigned(call)?.submit().await?; + self.traced_submission_with_finalization(submitted_extrinsic_hash) + .await + .map(Option::Some) + } else { + tracing::trace!("submitting unsigned extrinsic"); + let extrinsic_hash = self.client.tx().create_unsigned(call)?.submit().await?; + + tracing::trace!( + extrinsic_hash = extrinsic_hash.encode_hex::(), + "waiting for finalization" + ); + + Ok(None) + } + } + /// Submit an extrinsic and wait for finalization, returning the block hash it was included in. /// /// Equivalent to performing [`OnlineClient::sign_and_submit_then_watch_default`], @@ -82,7 +108,12 @@ impl Client { Keypair: subxt::tx::Signer, { if wait_for_finalization { - self.traced_submission_with_finalization(call, account_keypair) + let submitted_extrinsic_hash = self + .client + .tx() + .sign_and_submit_default(call, account_keypair) + .await?; + self.traced_submission_with_finalization(submitted_extrinsic_hash) .await .map(Option::Some) } else { @@ -101,25 +132,14 @@ impl Client { } } - pub(crate) async fn traced_submission_with_finalization( + pub(crate) async fn traced_submission_with_finalization( &self, - call: &Call, - account_keypair: &Keypair, - ) -> Result, subxt::Error> - where - Call: subxt::tx::Payload, - Keypair: subxt::tx::Signer, - { + submitted_extrinsic_hash: H256, + ) -> Result, subxt::Error> { tracing::trace!("submitting extrinsic"); let mut finalized_block_stream = self.client.blocks().subscribe_finalized().await?; - let submitted_extrinsic_hash = self - .client - .tx() - .sign_and_submit_default(call, account_keypair) - .await?; - tracing::debug!( extrinsic_hash = submitted_extrinsic_hash.encode_hex::(), "waiting for finalization"