diff --git a/examples/async/examples/compose_extrinsic.rs b/examples/async/examples/compose_extrinsic.rs index 5b6b814ea..5d9cd43a2 100644 --- a/examples/async/examples/compose_extrinsic.rs +++ b/examples/async/examples/compose_extrinsic.rs @@ -28,7 +28,7 @@ use substrate_api_client::{ GenericAdditionalParams, SignExtrinsic, }, rpc::JsonrpseeClient, - Api, GetChainInfo, SubmitAndWatch, XtStatus, + Api, GetChainInfo, SubmitAndWatch, TransactionStatusDeterminant, XtStatus, }; type AssetExtrinsicSigner = ::ExtrinsicSigner; @@ -139,7 +139,7 @@ async fn main() { println!("[+] Composed Extrinsic:\n {:?}", xt); let hash = api - .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .submit_and_watch_extrinsic_until_status(xt, &[TransactionStatusDeterminant::InBlock]) .await .unwrap() .block_hash diff --git a/src/api/mod.rs b/src/api/mod.rs index 42ba95870..6a630fe15 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -85,6 +85,38 @@ pub enum UnexpectedTxStatus { Invalid, } +/// See `TransactionStatus` for a description of the different enum values +pub enum TransactionStatusDeterminant { + Future, + Ready, + Broadcast, + InBlock, + Retracted, + FinalityTimeout, + Finalized, + Usurped, + Dropped, + Invalid, +} + +impl TransactionStatusDeterminant { + pub fn as_u8(&self) -> u8 { + // The values must match those from TransactionStatus::as_u8()! + match self { + Self::Future => 0, + Self::Ready => 1, + Self::Broadcast => 2, + Self::InBlock => 3, + Self::Retracted => 4, + Self::FinalityTimeout => 5, + Self::Finalized => 6, + Self::Usurped => 7, + Self::Dropped => 8, + Self::Invalid => 9, + } + } +} + /// Possible transaction status events. // Copied from `sc-transaction-pool` // (https://github.com/paritytech/substrate/blob/dddfed3d9260cf03244f15ba3db4edf9af7467e9/client/transaction-pool/api/src/lib.rs) @@ -119,36 +151,29 @@ pub enum TransactionStatus { impl TransactionStatus { pub fn as_u8(&self) -> u8 { match self { - TransactionStatus::Future => 0, - TransactionStatus::Ready => 1, - TransactionStatus::Broadcast(_) => 2, - TransactionStatus::InBlock(_) => 3, - TransactionStatus::Retracted(_) => 4, - TransactionStatus::FinalityTimeout(_) => 5, - TransactionStatus::Finalized(_) => 6, - TransactionStatus::Usurped(_) => 7, - TransactionStatus::Dropped => 8, - TransactionStatus::Invalid => 9, + Self::Future => 0, + Self::Ready => 1, + Self::Broadcast(_) => 2, + Self::InBlock(_) => 3, + Self::Retracted(_) => 4, + Self::FinalityTimeout(_) => 5, + Self::Finalized(_) => 6, + Self::Usurped(_) => 7, + Self::Dropped => 8, + Self::Invalid => 9, } } pub fn is_expected(&self) -> Result<()> { match self { - TransactionStatus::Ready - | TransactionStatus::Broadcast(_) - | TransactionStatus::InBlock(_) - | TransactionStatus::Finalized(_) => Ok(()), - TransactionStatus::Future => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Future)), - TransactionStatus::Retracted(_) => - Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Retracted)), - TransactionStatus::FinalityTimeout(_) => + Self::Ready | Self::Broadcast(_) | Self::InBlock(_) | Self::Finalized(_) => Ok(()), + Self::Future => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Future)), + Self::Retracted(_) => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Retracted)), + Self::FinalityTimeout(_) => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::FinalityTimeout)), - TransactionStatus::Usurped(_) => - Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Usurped)), - TransactionStatus::Dropped => - Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Dropped)), - TransactionStatus::Invalid => - Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Invalid)), + Self::Usurped(_) => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Usurped)), + Self::Dropped => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Dropped)), + Self::Invalid => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Invalid)), } } @@ -160,13 +185,25 @@ impl TransactionStatus Option<&BlockHash> { match self { - TransactionStatus::InBlock(block_hash) => Some(block_hash), - TransactionStatus::Retracted(block_hash) => Some(block_hash), - TransactionStatus::FinalityTimeout(block_hash) => Some(block_hash), - TransactionStatus::Finalized(block_hash) => Some(block_hash), + Self::InBlock(block_hash) => Some(block_hash), + Self::Retracted(block_hash) => Some(block_hash), + Self::FinalityTimeout(block_hash) => Some(block_hash), + Self::Finalized(block_hash) => Some(block_hash), _ => None, } } + + /// Returns true if the Transaction reached its final Status + // See https://github.com/paritytech/polkadot-sdk/blob/289f5bbf7a45dc0380904a435464b15ec711ed03/substrate/client/transaction-pool/api/src/lib.rs#L161 + pub fn is_final(&self) -> bool { + matches!( + self, + Self::Usurped(_) + | Self::Finalized(_) + | Self::FinalityTimeout(_) + | Self::Invalid | Self::Dropped + ) + } } // Exact structure from diff --git a/src/api/rpc_api/author.rs b/src/api/rpc_api/author.rs index a5fcdcf25..461c4ad7a 100644 --- a/src/api/rpc_api/author.rs +++ b/src/api/rpc_api/author.rs @@ -17,12 +17,13 @@ use crate::{ api::{rpc_api::events::FetchEvents, Error, Result}, error::FailedExtrinsicError, rpc::{HandleSubscription, Request, Subscribe}, - Api, ExtrinsicReport, TransactionStatus, XtStatus, + Api, ExtrinsicReport, TransactionStatus, TransactionStatusDeterminant, XtStatus, }; use ac_compose_macros::rpc_params; use ac_primitives::{config::Config, UncheckedExtrinsicV4}; #[cfg(not(feature = "sync-api"))] use alloc::boxed::Box; +use alloc::vec::Vec; use codec::{Decode, Encode}; use log::*; use serde::de::DeserializeOwned; @@ -211,6 +212,27 @@ pub trait SubmitAndWatch { encoded_extrinsic: &Bytes, watch_until: XtStatus, ) -> Result>; + + /// Submit the extrinsic and watch it until it reaches one of the `expected_status`. + /// The method also returns if the transaction reaches a final state (regardless of contents of `expected_status`) + /// + /// This method is blocking if the sync-api feature is activated + async fn submit_and_watch_extrinsic_until_status( + &self, + extrinsic: UncheckedExtrinsicV4, + expected_status: &[TransactionStatusDeterminant], + ) -> Result> + where + Address: Encode, + Call: Encode, + Signature: Encode, + SignedExtra: Encode; + + /// Query the events for the specified `report` and attach them. + async fn populate_events( + &self, + report: ExtrinsicReport, + ) -> Result>; } #[maybe_async::maybe_async(?Send)] @@ -269,18 +291,25 @@ where encoded_extrinsic: &Bytes, watch_until: XtStatus, ) -> Result> { - let mut report = self + let report: ExtrinsicReport<::Hash> = self .submit_and_watch_opaque_extrinsic_until_without_events(encoded_extrinsic, watch_until) .await?; if watch_until < XtStatus::InBlock { return Ok(report) } + self.populate_events(report).await + } + + async fn populate_events( + &self, + mut report: ExtrinsicReport, + ) -> Result> { let block_hash = report.block_hash.ok_or(Error::BlockHashNotFound)?; let extrinsic_events = self.fetch_events_for_extrinsic(block_hash, report.extrinsic_hash).await?; - // Check if the extrinsic was succesfull or not. + // Check if the extrinsic was successful or not. let mut maybe_dispatch_error = None; for event in &extrinsic_events { if let Some(dispatch_error) = event.get_associated_dispatch_error() { @@ -355,4 +384,38 @@ where } Err(Error::NoStream) } + + async fn submit_and_watch_extrinsic_until_status( + &self, + extrinsic: UncheckedExtrinsicV4, + expected_status: &[TransactionStatusDeterminant], + ) -> Result> + where + Address: Encode, + Call: Encode, + Signature: Encode, + SignedExtra: Encode, + { + let encoded_extrinsic: Bytes = extrinsic.encode().into(); + let tx_hash = T::Hasher::hash(&encoded_extrinsic); + let mut subscription: TransactionSubscriptionFor = + self.submit_and_watch_opaque_extrinsic(&encoded_extrinsic).await?; + + let determinants: Vec<_> = expected_status.iter().map(|d| d.as_u8()).collect(); + + while let Some(transaction_status) = subscription.next().await { + let transaction_status = transaction_status?; + if transaction_status.is_final() || determinants.contains(&transaction_status.as_u8()) { + subscription.unsubscribe().await?; + let block_hash = transaction_status.get_maybe_block_hash(); + return Ok(ExtrinsicReport::new( + tx_hash, + block_hash.copied(), + transaction_status, + None, + )); + } + } + Err(Error::NoStream) + } }