Repository: scs/substrate-api-client (language: rust)

Issue:
Return full extrinsic information, even when xt has failed
We currently throw away all xt information if it has failed: 

In function
https://github.com/scs/substrate-api-client/blob/47d1173c0299050555f2bbb6b7765837c049a031/src/api/rpc_api/author.rs#L309-L330
We only return the dispatch error if the xt has failed.  This only tells us why the extrinsic has failed, but not the xt_hash, nor the events for it. Even though it has been fetched previously.

So I'm proposing to return an Error that returns all the information: see https://github.com/scs/substrate-api-client/pull/616




Discussion / hints:
Currently blocked by #397, because to not make the Error generic over the hash or introduce a new Error type, we need to encode the returned ExtrinsicReport.

Fix patch:
diff --git a/src/api/error.rs b/src/api/error.rs
index 589374042..80944c764 100644
--- a/src/api/error.rs
+++ b/src/api/error.rs
@@ -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 alloc::{boxed::Box, vec::Vec};
+use codec::{Decode, Encode};
 
 pub type Result<T> = core::result::Result<T, Error>;
 
@@ -42,8 +43,8 @@ pub enum Error {
 	Codec(codec::Error),
 	/// Could not convert NumberOrHex with try_from.
 	TryFromIntError,
-	/// Node Api Dispatch Error.
-	Dispatch(DispatchError),
+	/// Extrinsic failed onchain. Contains the encoded report and the associated dispatch error.
+	FailedExtrinsic(FailedExtrinsicError),
 	/// Encountered unexpected tx status during watch process.
 	UnexpectedTxStatus(UnexpectedTxStatus),
 	/// Could not send update because the Stream has been closed unexpectedly.
@@ -57,3 +58,29 @@ pub enum Error {
 	/// Any custom Error.
 	Other(Box<dyn core::error::Error + Send + Sync + 'static>),
 }
+
+/// Encountered unexpected tx status during watch process or the extrinsic failed.
+#[derive(Debug)]
+pub struct FailedExtrinsicError {
+	dispatch_error: DispatchError,
+	encoded_report: Vec<u8>,
+}
+
+impl FailedExtrinsicError {
+	pub fn new(dispatch_error: DispatchError, encoded_report: Vec<u8>) -> Self {
+		Self { dispatch_error, encoded_report }
+	}
+
+	pub fn dispatch_error(&self) -> &DispatchError {
+		&self.dispatch_error
+	}
+
+	pub fn get_report<Hash: Encode + Decode>(&self) -> Result<ExtrinsicReport<Hash>> {
+		let report = Decode::decode(&mut self.encoded_report.as_slice())?;
+		Ok(report)
+	}
+
+	pub fn encoded_report(&self) -> &[u8] {
+		&self.encoded_report
+	}
+}
diff --git a/src/api/rpc_api/author.rs b/src/api/rpc_api/author.rs
index dac237f9a..a5fcdcf25 100644
--- a/src/api/rpc_api/author.rs
+++ b/src/api/rpc_api/author.rs
@@ -15,6 +15,7 @@
 
 use crate::{
 	api::{rpc_api::events::FetchEvents, Error, Result},
+	error::FailedExtrinsicError,
 	rpc::{HandleSubscription, Request, Subscribe},
 	Api, ExtrinsicReport, TransactionStatus, XtStatus,
 };
@@ -279,14 +280,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(Error::FailedExtrinsic(FailedExtrinsicError::new(
+				dispatch_error,
+				report.encode(),
+			)))
+		}
+
 		Ok(report)
 	}
 


Test patch:
diff --git a/testing/async/examples/dispatch_errors_tests.rs b/testing/async/examples/dispatch_errors_tests.rs
index 8761da431..0ebc516b7 100644
--- a/testing/async/examples/dispatch_errors_tests.rs
+++ b/testing/async/examples/dispatch_errors_tests.rs
@@ -15,11 +15,12 @@
 
 //! Tests for the dispatch error.
 
+use sp_core::H256;
 use sp_keyring::AccountKeyring;
 use sp_runtime::MultiAddress;
 use substrate_api_client::{
 	ac_primitives::AssetRuntimeConfig, extrinsic::BalancesExtrinsics, rpc::JsonrpseeClient, Api,
-	GetAccountInformation, SubmitAndWatch, XtStatus,
+	Error, GetAccountInformation, SubmitAndWatch, XtStatus,
 };
 
 #[tokio::main]
@@ -51,8 +52,16 @@ async fn main() {
 		.unwrap();
 
 	let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await;
-	assert!(result.is_err());
-	assert!(format!("{result:?}").contains("BadOrigin"));
+	match result {
+		Err(Error::FailedExtrinsic(extrinsic_error)) => {
+			let dispatch_error = extrinsic_error.dispatch_error();
+			let report = extrinsic_error.get_report::<H256>().unwrap();
+			assert!(report.block_hash.is_some());
+			assert!(report.events.is_some());
+			assert!(format!("{dispatch_error:?}").contains("BadOrigin"));
+		},
+		_ => panic!("Expected Failed Extrinisc Error"),
+	}
 	println!("[+] BadOrigin error: Bob can't force set balance");
 
 	//BelowMinimum
@@ -62,7 +71,14 @@ async fn main() {
 		.await
 		.unwrap();
 	let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await;
-	assert!(result.is_err());
-	assert!(format!("{result:?}").contains("(BelowMinimum"));
+	match result {
+		Err(Error::FailedExtrinsic(extrinsic_error)) => {
+			let dispatch_error = extrinsic_error.dispatch_error();
+			let report = extrinsic_error.get_report::<H256>().unwrap();
+			assert!(report.block_hash.is_some());
+			assert!(format!("{dispatch_error:?}").contains("BelowMinimum"));
+		},
+		_ => panic!("Expected Failed Extrinisc Error"),
+	}
 	println!("[+] BelowMinimum error: balance (999999) is below the existential deposit");
 }