diff --git a/integration-tests/src/client/mod.rs b/integration-tests/src/client/mod.rs
index 050ef2e32b..f06708f05a 100644
--- a/integration-tests/src/client/mod.rs
+++ b/integration-tests/src/client/mod.rs
@@ -17,14 +17,24 @@
use crate::{
test_node_process,
test_node_process_with,
- utils::node_runtime::system,
+ utils::{
+ node_runtime::system,
+ pair_signer,
+ test_context,
+ },
};
-use sp_core::storage::{
- well_known_keys,
- StorageKey,
+use sp_core::{
+ sr25519::Pair,
+ storage::{
+ well_known_keys,
+ StorageKey,
+ },
+ Pair as _,
};
use sp_keyring::AccountKeyring;
+use sp_runtime::DispatchOutcome;
+use subxt::Error;
#[tokio::test]
async fn insert_key() {
@@ -131,3 +141,86 @@ async fn fetch_system_info() {
assert_eq!(client.rpc().system_name().await.unwrap(), "Substrate Node");
assert!(!client.rpc().system_version().await.unwrap().is_empty());
}
+
+#[tokio::test]
+async fn dry_run_passes() {
+ let node_process = test_node_process().await;
+ let client = node_process.client();
+
+ let alice = pair_signer(AccountKeyring::Alice.pair());
+ let bob = pair_signer(AccountKeyring::Bob.pair());
+ let bob_address = bob.account_id().clone().into();
+ let cxt = test_context().await;
+ let api = &cxt.api;
+ let signed_extrinsic = api
+ .tx()
+ .balances()
+ .transfer(bob_address, 10_000)
+ .unwrap()
+ .create_signed(&alice, Default::default())
+ .await
+ .unwrap();
+
+ client
+ .rpc()
+ .dry_run(signed_extrinsic.encoded(), None)
+ .await
+ .expect("dryrunning failed")
+ .expect("expected dryrunning to be successful")
+ .unwrap();
+ signed_extrinsic
+ .submit_and_watch()
+ .await
+ .unwrap()
+ .wait_for_finalized_success()
+ .await
+ .unwrap();
+}
+
+#[tokio::test]
+async fn dry_run_fails() {
+ let node_process = test_node_process().await;
+ let client = node_process.client();
+
+ let alice = pair_signer(AccountKeyring::Alice.pair());
+ let hans = pair_signer(Pair::generate().0);
+ let hans_address = hans.account_id().clone().into();
+ let cxt = test_context().await;
+ let api = &cxt.api;
+ let signed_extrinsic = api
+ .tx()
+ .balances()
+ .transfer(
+ hans_address,
+ 100_000_000_000_000_000_000_000_000_000_000_000,
+ )
+ .unwrap()
+ .create_signed(&alice, Default::default())
+ .await
+ .unwrap();
+
+ let dry_run_res: DispatchOutcome = client
+ .rpc()
+ .dry_run(signed_extrinsic.encoded(), None)
+ .await
+ .expect("dryrunning failed")
+ .expect("expected dryrun transaction to be valid");
+ if let Err(sp_runtime::DispatchError::Module(module_error)) = dry_run_res {
+ assert_eq!(module_error.index, 6);
+ assert_eq!(module_error.error, 2);
+ } else {
+ panic!("expected a module error when dryrunning");
+ }
+ let res = signed_extrinsic
+ .submit_and_watch()
+ .await
+ .unwrap()
+ .wait_for_finalized_success()
+ .await;
+ if let Err(Error::Module(err)) = res {
+ assert_eq!(err.pallet, "Balances");
+ assert_eq!(err.error, "InsufficientBalance");
+ } else {
+ panic!("expected a runtime module error");
+ }
+}
diff --git a/subxt/src/client.rs b/subxt/src/client.rs
index 89d6cd2315..eaf3854ab8 100644
--- a/subxt/src/client.rs
+++ b/subxt/src/client.rs
@@ -15,8 +15,11 @@
// along with subxt. If not, see .
use futures::future;
-use sp_runtime::traits::Hash;
pub use sp_runtime::traits::SignedExtension;
+use sp_runtime::{
+ traits::Hash,
+ ApplyExtrinsicResult,
+};
use crate::{
error::{
@@ -242,7 +245,7 @@ where
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch_default(
- self,
+ &self,
signer: &(dyn Signer + Send + Sync),
) -> Result, BasicError>
where
@@ -257,22 +260,14 @@ where
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch(
- self,
+ &self,
signer: &(dyn Signer + Send + Sync),
other_params: X::OtherParams,
) -> Result, BasicError> {
- // Sign the call data to create our extrinsic.
- let extrinsic = self.create_signed(signer, other_params).await?;
-
- // Get a hash of the extrinsic (we'll need this later).
- let ext_hash = T::Hashing::hash_of(&extrinsic);
-
- tracing::info!("xt hash: {}", hex::encode(ext_hash.encode()));
-
- // Submit and watch for transaction progress.
- let sub = self.client.rpc().watch_extrinsic(extrinsic).await?;
-
- Ok(TransactionProgress::new(sub, self.client, ext_hash))
+ self.create_signed(signer, other_params)
+ .await?
+ .submit_and_watch()
+ .await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
@@ -286,7 +281,7 @@ where
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default(
- self,
+ &self,
signer: &(dyn Signer + Send + Sync),
) -> Result
where
@@ -304,12 +299,14 @@ where
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit(
- self,
+ &self,
signer: &(dyn Signer + Send + Sync),
other_params: X::OtherParams,
) -> Result {
- let extrinsic = self.create_signed(signer, other_params).await?;
- self.client.rpc().submit_extrinsic(extrinsic).await
+ self.create_signed(signer, other_params)
+ .await?
+ .submit()
+ .await
}
/// Creates a returns a raw signed extrinsic, without submitting it.
@@ -317,7 +314,7 @@ where
&self,
signer: &(dyn Signer + Send + Sync),
other_params: X::OtherParams,
- ) -> Result {
+ ) -> Result, BasicError> {
// 1. Get nonce
let account_nonce = if let Some(nonce) = signer.nonce() {
nonce
@@ -404,6 +401,67 @@ where
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
// maybe we can just return the raw bytes..
- Ok(Encoded(extrinsic))
+ Ok(SignedSubmittableExtrinsic {
+ client: self.client,
+ encoded: Encoded(extrinsic),
+ marker: self.marker,
+ })
+ }
+}
+
+pub struct SignedSubmittableExtrinsic<'client, T: Config, X, E: Decode, Evs: Decode> {
+ client: &'client Client,
+ encoded: Encoded,
+ marker: std::marker::PhantomData<(X, E, Evs)>,
+}
+
+impl<'client, T, X, E, Evs> SignedSubmittableExtrinsic<'client, T, X, E, Evs>
+where
+ T: Config,
+ X: ExtrinsicParams,
+ E: Decode + HasModuleError,
+ Evs: Decode,
+{
+ /// Submits the extrinsic to the chain.
+ ///
+ /// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
+ /// and obtain details about it, once it has made it into a block.
+ pub async fn submit_and_watch(
+ &self,
+ ) -> Result, BasicError> {
+ // Get a hash of the extrinsic (we'll need this later).
+ let ext_hash = T::Hashing::hash_of(&self.encoded);
+
+ // Submit and watch for transaction progress.
+ let sub = self.client.rpc().watch_extrinsic(&self.encoded).await?;
+
+ Ok(TransactionProgress::new(sub, self.client, ext_hash))
+ }
+
+ /// Submits the extrinsic to the chain for block inclusion.
+ ///
+ /// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
+ ///
+ /// # Note
+ ///
+ /// Success does not mean the extrinsic has been included in the block, just that it is valid
+ /// and has been included in the transaction pool.
+ pub async fn submit(&self) -> Result {
+ self.client.rpc().submit_extrinsic(&self.encoded).await
+ }
+
+ /// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
+ ///
+ /// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
+ pub async fn dry_run(
+ &self,
+ at: Option,
+ ) -> Result {
+ self.client.rpc().dry_run(self.encoded(), at).await
+ }
+
+ /// Returns the SCALE encoded extrinsic bytes.
+ pub fn encoded(&self) -> &[u8] {
+ &self.encoded.0
}
}
diff --git a/subxt/src/rpc.rs b/subxt/src/rpc.rs
index 1aff88e8a9..73d99fe158 100644
--- a/subxt/src/rpc.rs
+++ b/subxt/src/rpc.rs
@@ -73,9 +73,12 @@ use sp_core::{
Bytes,
U256,
};
-use sp_runtime::generic::{
- Block,
- SignedBlock,
+use sp_runtime::{
+ generic::{
+ Block,
+ SignedBlock,
+ },
+ ApplyExtrinsicResult,
};
/// A number type that can be serialized both as a number or a string that encodes a number in a
@@ -570,6 +573,21 @@ impl Rpc {
let params = rpc_params![public_key, key_type];
Ok(self.client.request("author_hasKey", params).await?)
}
+
+ /// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
+ ///
+ /// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
+ pub async fn dry_run(
+ &self,
+ encoded_signed: &[u8],
+ at: Option,
+ ) -> Result {
+ let params = rpc_params![format!("0x{}", hex::encode(encoded_signed)), at];
+ let result_bytes: Bytes = self.client.request("system_dryRun", params).await?;
+ let data: ApplyExtrinsicResult =
+ codec::Decode::decode(&mut result_bytes.0.as_slice())?;
+ Ok(data)
+ }
}
/// Build WS RPC client from URL