Skip to content

Commit

Permalink
Allow PartialExtrinsic to be held across await points (#1658)
Browse files Browse the repository at this point in the history
* Allow PartialTransaction to be held across await points, and example to prove it

* Add comment to tx_parital example

* Fix book link
  • Loading branch information
jsdw authored Jun 26, 2024
1 parent 4fcabe2 commit 75bb9b8
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 4 deletions.
2 changes: 1 addition & 1 deletion core/src/config/extrinsic_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use alloc::vec::Vec;
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are a part of the transaction payload
/// or the signer payload respectively.
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + 'static {
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + Send + 'static {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
Expand Down
2 changes: 1 addition & 1 deletion core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub trait Config: Sized + Send + Sync + 'static {
type ExtrinsicParams: ExtrinsicParams<Self>;

/// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension.
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType;
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType + Send;
}

/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
Expand Down
4 changes: 2 additions & 2 deletions core/src/config/signed_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
/// ones are actually required for the chain in the correct order, ignoring the rest. This
/// is a sensible default, and allows for a single configuration to work across multiple chains.
pub struct AnyOf<T, Params> {
params: Vec<Box<dyn ExtrinsicParamsEncoder>>,
params: Vec<Box<dyn ExtrinsicParamsEncoder + Send + 'static>>,
_marker: core::marker::PhantomData<(T, Params)>,
}

Expand Down Expand Up @@ -470,7 +470,7 @@ macro_rules! impl_tuples {
// Break and record as soon as we find a match:
if $ident::matches(e.identifier(), e.extra_ty(), types) {
let ext = $ident::new(client, params.$index)?;
let boxed_ext: Box<dyn ExtrinsicParamsEncoder> = Box::new(ext);
let boxed_ext: Box<dyn ExtrinsicParamsEncoder + Send + 'static> = Box::new(ext);
exts_by_index.insert(idx, boxed_ext);
break
}
Expand Down
53 changes: 53 additions & 0 deletions subxt/examples/tx_partial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), BoxedError> {
// Spawned tasks require things held across await points to impl Send,
// so we use one to demonstrate that this is possible with `PartialExtrinsic`
tokio::spawn(signing_example()).await??;
Ok(())
}

async fn signing_example() -> Result<(), BoxedError> {
let api = OnlineClient::<PolkadotConfig>::new().await?;

// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

let alice = dev::alice();

// Create partial tx, ready to be signed.
let partial_tx = api
.tx()
.create_partial_signed(
&balance_transfer_tx,
&alice.public_key().to_account_id(),
Default::default(),
)
.await?;

// Simulate taking some time to get a signature back, in part to
// show that the `PartialExtrinsic` can be held across await points.
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let signature = alice.sign(&partial_tx.signer_payload());

// Sign the transaction.
let tx = partial_tx
.sign_with_address_and_signature(&alice.public_key().to_address(), &signature.into());

// Submit it.
tx.submit_and_watch()
.await?
.wait_for_finalized_success()
.await?;

Ok(())
}
9 changes: 9 additions & 0 deletions subxt/src/book/usage/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@
#![doc = include_str!("../../../examples/tx_status_stream.rs")]
//! ```
//!
//! ### Signing transactions externally
//!
//! Subxt also allows you to get hold of the signer payload and hand that off to something else to be
//! signed. The signature can then be provided back to Subxt to build the final transaction to submit:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/tx_partial.rs")]
//! ```
//!
//! Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and
//! [`crate::tx::TxInBlock`] for more options.
//!

0 comments on commit 75bb9b8

Please sign in to comment.