Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction state handling #770

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/async/examples/compose_extrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use substrate_api_client::{
GenericAdditionalParams, SignExtrinsic,
},
rpc::JsonrpseeClient,
Api, GetChainInfo, SubmitAndWatch, XtStatus,
Api, GetChainInfo, SubmitAndWatch, TransactionStatusDeterminant, XtStatus,
};

type AssetExtrinsicSigner = <AssetRuntimeConfig as Config>::ExtrinsicSigner;
Expand Down Expand Up @@ -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
Expand Down
93 changes: 65 additions & 28 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -119,36 +151,29 @@ pub enum TransactionStatus<Hash: Encode + Decode, BlockHash: Encode + Decode> {
impl<Hash: Encode + Decode, BlockHash: Encode + Decode> TransactionStatus<Hash, BlockHash> {
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)),
}
}

Expand All @@ -160,13 +185,25 @@ impl<Hash: Encode + Decode, BlockHash: Encode + Decode> TransactionStatus<Hash,

pub fn get_maybe_block_hash(&self) -> 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
Expand Down
69 changes: 66 additions & 3 deletions src/api/rpc_api/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -211,6 +212,27 @@ pub trait SubmitAndWatch {
encoded_extrinsic: &Bytes,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>;

/// 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<Address, Call, Signature, SignedExtra>(
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
expected_status: &[TransactionStatusDeterminant],
) -> Result<ExtrinsicReport<Self::Hash>>
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<Self::Hash>,
) -> Result<ExtrinsicReport<Self::Hash>>;
}

#[maybe_async::maybe_async(?Send)]
Expand Down Expand Up @@ -269,18 +291,25 @@ where
encoded_extrinsic: &Bytes,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>> {
let mut report = self
let report: ExtrinsicReport<<T as Config>::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<Self::Hash>,
) -> Result<ExtrinsicReport<Self::Hash>> {
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() {
Expand Down Expand Up @@ -355,4 +384,38 @@ where
}
Err(Error::NoStream)
}

async fn submit_and_watch_extrinsic_until_status<Address, Call, Signature, SignedExtra>(
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
expected_status: &[TransactionStatusDeterminant],
) -> Result<ExtrinsicReport<Self::Hash>>
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::Client, Self::Hash> =
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)
}
}