mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 21:11:07 +00:00
feat: refactor signing in order to more easily be able to dryrun (#547)
* feat: refactor signing in order to more easily be able to dryrun Co-authored-by: Sander Bosma <sanderbosma@gmail.com> Co-authored-by: Daniel Savu <savudani04@yahoo.ro> * chore: move dry_run to rpc file * fix: failing dryrun test * fix: run cargo fmt * chore(dryrun): Replace complex SubmittableExtrinsic type with bytes array * cargo fmt * feat: add dry_run method to signed submittable extrinsic * fmt Co-authored-by: Sander Bosma <sanderbosma@gmail.com>
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+79
-21
@@ -15,8 +15,11 @@
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::future;
|
||||
use sp_runtime::traits::Hash;
|
||||
pub use sp_runtime::traits::SignedExtension;
|
||||
use sp_runtime::{
|
||||
traits::Hash,
|
||||
ApplyExtrinsicResult,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{
|
||||
@@ -287,7 +290,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<T> + Send + Sync),
|
||||
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError>
|
||||
where
|
||||
@@ -302,22 +305,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<T> + Send + Sync),
|
||||
other_params: X::OtherParams,
|
||||
) -> Result<TransactionProgress<'client, T, E, Evs>, 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
|
||||
@@ -331,7 +326,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<T> + Send + Sync),
|
||||
) -> Result<T::Hash, BasicError>
|
||||
where
|
||||
@@ -349,12 +344,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<T> + Send + Sync),
|
||||
other_params: X::OtherParams,
|
||||
) -> Result<T::Hash, BasicError> {
|
||||
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.
|
||||
@@ -362,7 +359,7 @@ where
|
||||
&self,
|
||||
signer: &(dyn Signer<T> + Send + Sync),
|
||||
other_params: X::OtherParams,
|
||||
) -> Result<Encoded, BasicError> {
|
||||
) -> Result<SignedSubmittableExtrinsic<'client, T, X, E, Evs>, BasicError> {
|
||||
// 1. Get nonce
|
||||
let account_nonce = if let Some(nonce) = signer.nonce() {
|
||||
nonce
|
||||
@@ -449,6 +446,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<T>,
|
||||
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<T>,
|
||||
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<TransactionProgress<'client, T, E, Evs>, 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<T::Hash, BasicError> {
|
||||
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<T::Hash>,
|
||||
) -> Result<ApplyExtrinsicResult, BasicError> {
|
||||
self.client.rpc().dry_run(self.encoded(), at).await
|
||||
}
|
||||
|
||||
/// Returns the SCALE encoded extrinsic bytes.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.encoded.0
|
||||
}
|
||||
}
|
||||
|
||||
+21
-3
@@ -146,9 +146,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
|
||||
@@ -643,6 +646,21 @@ impl<T: Config> Rpc<T> {
|
||||
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<T::Hash>,
|
||||
) -> Result<ApplyExtrinsicResult, BasicError> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user