From 303eaa957843cd4e351a26f2cccc7779a87d1558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bigna=20H=C3=A4rdi?= Date: Thu, 22 Feb 2024 14:02:59 +0100 Subject: [PATCH] Add failed extrinsic error (#725) * add failed extrinsic error * fix no_std build * ensure events are in the error report --- src/api/error.rs | 35 ++++++++++++++++--- src/api/rpc_api/author.rs | 15 ++++++-- .../async/examples/dispatch_errors_tests.rs | 26 +++++++++++--- 3 files changed, 65 insertions(+), 11 deletions(-) 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 = core::result::Result; @@ -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), } + +/// Encountered unexpected tx status during watch process or the extrinsic failed. +#[derive(Debug)] +pub struct FailedExtrinsicError { + dispatch_error: DispatchError, + encoded_report: Vec, +} + +impl FailedExtrinsicError { + pub fn new(dispatch_error: DispatchError, encoded_report: Vec) -> Self { + Self { dispatch_error, encoded_report } + } + + pub fn dispatch_error(&self) -> &DispatchError { + &self.dispatch_error + } + + pub fn get_report(&self) -> Result> { + 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) } 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::().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::().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"); }