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

Return ExtrinsicReport also for failed extrinsic #722

Closed
wants to merge 2 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
13 changes: 10 additions & 3 deletions examples/async/examples/check_extrinsic_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use sp_keyring::AccountKeyring;
use substrate_api_client::{
ac_node_api::RawEventDetails,
ac_primitives::{AssetRuntimeConfig, Config},
error::ExtrinsicError,
extrinsic::BalancesExtrinsics,
rpc::JsonrpseeClient,
Api, GetAccountInformation, SubmitAndWatch, TransactionStatus, XtStatus,
Expand Down Expand Up @@ -66,12 +67,18 @@ async fn main() {
// Check if the transfer really has failed:
match result {
Ok(_report) => {
panic!("Exptected the call to fail.");
panic!("Expected the call to fail.");
},
Err(e) => {
println!("[+] Couldn't execute the extrinsic due to {e:?}\n");
Err(ExtrinsicError::FailedExtrinsic(e)) => {
let dispatch_error = e.dispatch_error;
println!("[+] Couldn't execute the extrinsic due to {dispatch_error:?}\n");
let string_error = format!("{e:?}");
assert!(string_error.contains("FundsUnavailable"));
// The extrinsic with the associated events indicating it has failed should have been included in a block.
assert!(e.report.block_hash.is_some())
},
_ => {
panic!("Expected the call to fail with an extrinsic error.");
},
};

Expand Down
29 changes: 24 additions & 5 deletions src/api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@

*/

use crate::{api::UnexpectedTxStatus, rpc::Error as RpcClientError};
use crate::{api::UnexpectedTxStatus, rpc::Error as RpcClientError, ExtrinsicReport};
use ac_node_api::{
error::DispatchError,
metadata::{MetadataConversionError, MetadataError},
};
use alloc::boxed::Box;
use codec::{Decode, Encode};

pub type Result<T> = core::result::Result<T, Error>;

Expand All @@ -42,12 +43,8 @@ pub enum Error {
Codec(codec::Error),
/// Could not convert NumberOrHex with try_from.
TryFromIntError,
/// Node Api Dispatch Error.
Dispatch(DispatchError),
/// Encountered unexpected tx status during watch process.
UnexpectedTxStatus(UnexpectedTxStatus),
/// Could not send update because the Stream has been closed unexpectedly.
NoStream,
/// Could not find the expected extrinsic.
ExtrinsicNotFound,
/// Could not find the expected block hash.
Expand All @@ -57,3 +54,25 @@ pub enum Error {
/// Any custom Error.
Other(Box<dyn core::error::Error + Send + Sync + 'static>),
}

pub type ExtrinsicResult<T, Hash> = core::result::Result<T, ExtrinsicError<Hash>>;

/// Error Type returned upon submission or watch error.
#[derive(Debug, derive_more::From)]
pub enum ExtrinsicError<Hash: Encode + Decode> {
/// Extrinsic was not successfully executed onchain.
FailedExtrinsic(FailedExtrinsicError<Hash>),
/// Api Error.
ApiError(Error),
/// Rpc Client Error.
RpcClient(RpcClientError),
/// Could not send update because the Stream has been closed unexpectedly.
NoStream,
}

/// Encountered unexpected tx status during watch process or the extrinsic failed.
#[derive(Debug)]
pub struct FailedExtrinsicError<Hash: Encode + Decode> {
pub dispatch_error: DispatchError,
pub report: ExtrinsicReport<Hash>,
}
4 changes: 2 additions & 2 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
use sp_core::Bytes;

pub use api_client::Api;
pub use error::{Error, Result};
pub use error::{Error, ExtrinsicError, ExtrinsicResult, Result};
pub use rpc_api::{
FetchEvents, GetAccountInformation, GetBalance, GetChainInfo, GetStorage,
GetTransactionPayment, SubmitAndWatch, SubmitExtrinsic, SubscribeChain, SubscribeEvents,
Expand Down Expand Up @@ -307,7 +307,7 @@ mod tests {
fn encode_decode_extrinsic_report() {
let hash = H256::random();
let block_hash = H256::random();
let status = TransactionStatus::InBlock(block_hash.clone());
let status = TransactionStatus::InBlock(block_hash);
// RawEventDetails Encoding / Decoding is already tested separately, so we don't need to retest here.
let report = ExtrinsicReport::new(hash, Some(block_hash), status, None);

Expand Down
61 changes: 39 additions & 22 deletions src/api/rpc_api/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
//! Interface to common author rpc functions and helpers thereof.

use crate::{
api::{rpc_api::events::FetchEvents, Error, Result},
api::{rpc_api::events::FetchEvents, Error, ExtrinsicError, ExtrinsicResult},
error::FailedExtrinsicError,
rpc::{HandleSubscription, Request, Subscribe},
Api, ExtrinsicReport, TransactionStatus, XtStatus,
};
Expand All @@ -34,14 +35,14 @@ pub type TransactionSubscriptionFor<Client, Hash> =
/// Simple extrinsic submission without any subscription.
#[maybe_async::maybe_async(?Send)]
pub trait SubmitExtrinsic {
type Hash;
type Hash: Encode + Decode;

/// Submit an encodable extrinsic to the substrate node.
/// Returns the extrinsic hash.
async fn submit_extrinsic<Address, Call, Signature, SignedExtra>(
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
) -> Result<Self::Hash>
) -> ExtrinsicResult<Self::Hash, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -50,7 +51,10 @@ pub trait SubmitExtrinsic {

/// Submit an encoded, opaque extrinsic to the substrate node.
/// Returns the extrinsic hash.
async fn submit_opaque_extrinsic(&self, encoded_extrinsic: &Bytes) -> Result<Self::Hash>;
async fn submit_opaque_extrinsic(
&self,
encoded_extrinsic: &Bytes,
) -> ExtrinsicResult<Self::Hash, Self::Hash>;
}

#[maybe_async::maybe_async(?Send)]
Expand All @@ -64,7 +68,7 @@ where
async fn submit_extrinsic<Address, Call, Signature, SignedExtra>(
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
) -> Result<Self::Hash>
) -> ExtrinsicResult<Self::Hash, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -74,7 +78,10 @@ where
self.submit_opaque_extrinsic(&extrinsic.encode().into()).await
}

async fn submit_opaque_extrinsic(&self, encoded_extrinsic: &Bytes) -> Result<Self::Hash> {
async fn submit_opaque_extrinsic(
&self,
encoded_extrinsic: &Bytes,
) -> ExtrinsicResult<Self::Hash, Self::Hash> {
let hex_encoded_xt = rpc_params![encoded_extrinsic];
debug!("sending extrinsic: {:?}", hex_encoded_xt);
let xt_hash = self.client().request("author_submitExtrinsic", hex_encoded_xt).await?;
Expand All @@ -94,7 +101,7 @@ pub trait SubmitAndWatch {
async fn submit_and_watch_extrinsic<Address, Call, Signature, SignedExtra>(
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>>
) -> ExtrinsicResult<TransactionSubscriptionFor<Self::Client, Self::Hash>, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -108,7 +115,7 @@ pub trait SubmitAndWatch {
async fn submit_and_watch_opaque_extrinsic(
&self,
encoded_extrinsic: &Bytes,
) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>>;
) -> ExtrinsicResult<TransactionSubscriptionFor<Self::Client, Self::Hash>, Self::Hash>;

/// Submit an extrinsic and watch it until the desired status
/// is reached, if no error is encountered previously.
Expand All @@ -134,7 +141,7 @@ pub trait SubmitAndWatch {
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand Down Expand Up @@ -165,7 +172,7 @@ pub trait SubmitAndWatch {
&self,
encoded_extrinsic: &Bytes,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>;
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash>;

/// Submit an extrinsic and watch it until the desired status
/// is reached, if no error is encountered previously.
Expand All @@ -187,7 +194,7 @@ pub trait SubmitAndWatch {
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -209,7 +216,7 @@ pub trait SubmitAndWatch {
&self,
encoded_extrinsic: &Bytes,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>;
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash>;
}

#[maybe_async::maybe_async(?Send)]
Expand All @@ -224,7 +231,7 @@ where
async fn submit_and_watch_extrinsic<Address, Call, Signature, SignedExtra>(
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>>
) -> ExtrinsicResult<TransactionSubscriptionFor<Self::Client, Self::Hash>, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -237,7 +244,7 @@ where
async fn submit_and_watch_opaque_extrinsic(
&self,
encoded_extrinsic: &Bytes,
) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>> {
) -> ExtrinsicResult<TransactionSubscriptionFor<Self::Client, Self::Hash>, Self::Hash> {
self.client()
.subscribe(
"author_submitAndWatchExtrinsic",
Expand All @@ -252,7 +259,7 @@ where
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -267,7 +274,7 @@ where
&self,
encoded_extrinsic: &Bytes,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>> {
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash> {
let mut report = self
.submit_and_watch_opaque_extrinsic_until_without_events(encoded_extrinsic, watch_until)
.await?;
Expand All @@ -279,14 +286,24 @@ where
let extrinsic_events =
self.fetch_events_for_extrinsic(block_hash, report.extrinsic_hash).await?;

// Ensure the extrinsic was successful. If not, return an error.
// Check if the extrinsic was succesfull or not.
let mut maybe_dispatch_error = None;
for event in &extrinsic_events {
if let Some(dispatch_error) = event.get_associated_dispatch_error() {
return Err(Error::Dispatch(dispatch_error))
maybe_dispatch_error = Some(dispatch_error);
break;
}
}

report.events = Some(extrinsic_events.into_iter().map(|event| event.to_raw()).collect());

if let Some(dispatch_error) = maybe_dispatch_error {
return Err(ExtrinsicError::FailedExtrinsic(FailedExtrinsicError {
dispatch_error,
report,
}))
}

Ok(report)
}

Expand All @@ -299,7 +316,7 @@ where
&self,
extrinsic: UncheckedExtrinsicV4<Address, Call, Signature, SignedExtra>,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>>
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash>
where
Address: Encode,
Call: Encode,
Expand All @@ -317,7 +334,7 @@ where
&self,
encoded_extrinsic: &Bytes,
watch_until: XtStatus,
) -> Result<ExtrinsicReport<Self::Hash>> {
) -> ExtrinsicResult<ExtrinsicReport<Self::Hash>, Self::Hash> {
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?;
Expand All @@ -338,10 +355,10 @@ where
},
Err(e) => {
subscription.unsubscribe().await?;
return Err(e)
return Err(e.into())
},
}
}
Err(Error::NoStream)
Err(ExtrinsicError::NoStream)
}
}
Loading