Obtain DispatchError::Module info dynamically (#453)

* Add error information back into metadata to roll back removal in #394

* Go back to obtaining runtime error info

* re-do codegen too to check that it's all gravy

* Convert DispatchError module errors into a module variant to make them easier to work with

* Fix broken doc link
This commit is contained in:
James Wilson
2022-02-17 13:45:57 +00:00
committed by GitHub
parent eeb8b4b741
commit e866d744de
10 changed files with 1670 additions and 1610 deletions
+5 -2
View File
@@ -19,7 +19,10 @@ use sp_runtime::traits::Hash;
pub use sp_runtime::traits::SignedExtension;
use crate::{
error::BasicError,
error::{
BasicError,
HasModuleError,
},
extrinsic::{
self,
SignedExtra,
@@ -206,7 +209,7 @@ where
X: SignedExtra<T>,
A: AccountData,
C: Call + Send + Sync,
E: Decode,
E: Decode + HasModuleError,
Evs: Decode,
{
/// Create a new [`SubmittableExtrinsic`].
+25
View File
@@ -70,6 +70,9 @@ pub enum GenericError<E> {
/// Transaction progress error.
#[error("Transaction error: {0}")]
Transaction(#[from] TransactionError),
#[error("Module error: {0}")]
/// An error from the `Module` variant of the generated `DispatchError`.
Module(ModuleError),
/// Other error.
#[error("Other error: {0}")]
Other(String),
@@ -94,6 +97,7 @@ impl<E> GenericError<E> {
GenericError::Metadata(e) => GenericError::Metadata(e),
GenericError::EventsDecoding(e) => GenericError::EventsDecoding(e),
GenericError::Transaction(e) => GenericError::Transaction(e),
GenericError::Module(e) => GenericError::Module(e),
GenericError::Other(e) => GenericError::Other(e),
// This is the only branch we really care about:
GenericError::Runtime(e) => GenericError::Runtime(f(e)),
@@ -167,3 +171,24 @@ pub enum TransactionError {
#[error("The block containing the transaction can no longer be found (perhaps it was on a non-finalized fork?)")]
BlockHashNotFound,
}
/// Details about a module error that has occurred.
#[derive(Clone, Debug, thiserror::Error)]
#[error("{pallet}: {error}\n\n{}", .description.join("\n"))]
pub struct ModuleError {
/// The name of the pallet that the error came from.
pub pallet: String,
/// The name of the error.
pub error: String,
/// A description of the error.
pub description: Vec<String>,
}
/// This trait is automatically implemented for the generated `DispatchError`,
/// so that we can pluck out information about the `Module` error variant, if`
/// it exists.
pub trait HasModuleError {
/// If the error has a `Module` variant, return a tuple of the
/// pallet index and error index. Else, return `None`.
fn module_error_indices(&self) -> Option<(u8, u8)>;
}
+2
View File
@@ -81,6 +81,7 @@ pub use crate::{
BasicError,
Error,
GenericError,
HasModuleError,
RuntimeError,
TransactionError,
},
@@ -98,6 +99,7 @@ pub use crate::{
UncheckedExtrinsic,
},
metadata::{
ErrorMetadata,
Metadata,
MetadataError,
PalletMetadata,
+66
View File
@@ -84,6 +84,7 @@ pub struct Metadata {
metadata: RuntimeMetadataLastVersion,
pallets: HashMap<String, PalletMetadata>,
events: HashMap<(u8, u8), EventMetadata>,
errors: HashMap<(u8, u8), ErrorMetadata>,
}
impl Metadata {
@@ -107,6 +108,19 @@ impl Metadata {
Ok(event)
}
/// Returns the metadata for the error at the given pallet and error indices.
pub fn error(
&self,
pallet_index: u8,
error_index: u8,
) -> Result<&ErrorMetadata, MetadataError> {
let error = self
.errors
.get(&(pallet_index, error_index))
.ok_or(MetadataError::ErrorNotFound(pallet_index, error_index))?;
Ok(error)
}
/// Resolve a type definition.
pub fn resolve_type(&self, id: u32) -> Option<&Type<PortableForm>> {
self.metadata.types.resolve(id)
@@ -169,6 +183,7 @@ impl PalletMetadata {
}
}
/// Metadata for specific events.
#[derive(Clone, Debug)]
pub struct EventMetadata {
pallet: String,
@@ -193,6 +208,31 @@ impl EventMetadata {
}
}
/// Metadata for specific errors.
#[derive(Clone, Debug)]
pub struct ErrorMetadata {
pallet: String,
error: String,
variant: Variant<PortableForm>,
}
impl ErrorMetadata {
/// Get the name of the pallet from which the error originates.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// Get the name of the specific pallet error.
pub fn error(&self) -> &str {
&self.error
}
/// Get the description of the specific pallet error.
pub fn description(&self) -> &[String] {
self.variant.docs()
}
}
#[derive(Debug, thiserror::Error)]
pub enum InvalidMetadataError {
#[error("Invalid prefix")]
@@ -293,10 +333,36 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
})
.collect();
let pallet_errors = metadata
.pallets
.iter()
.filter_map(|pallet| {
pallet.error.as_ref().map(|error| {
let type_def_variant = get_type_def_variant(error.ty.id())?;
Ok((pallet, type_def_variant))
})
})
.collect::<Result<Vec<_>, _>>()?;
let errors = pallet_errors
.iter()
.flat_map(|(pallet, type_def_variant)| {
type_def_variant.variants().iter().map(move |var| {
let key = (pallet.index, var.index());
let value = ErrorMetadata {
pallet: pallet.name.clone(),
error: var.name().clone(),
variant: var.clone(),
};
(key, value)
})
})
.collect();
Ok(Self {
metadata,
pallets,
events,
errors,
})
}
}
+23 -9
View File
@@ -27,6 +27,8 @@ use crate::{
error::{
BasicError,
Error,
HasModuleError,
ModuleError,
RuntimeError,
TransactionError,
},
@@ -54,7 +56,7 @@ use jsonrpsee::core::{
/// returned from [`crate::SubmittableExtrinsic::sign_and_submit_then_watch()`].
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionProgress<'client, T: Config, E: Decode, Evs: Decode> {
pub struct TransactionProgress<'client, T: Config, E, Evs> {
sub: Option<RpcSubscription<SubstrateTransactionStatus<T::Hash, T::Hash>>>,
ext_hash: T::Hash,
client: &'client Client<T>,
@@ -64,12 +66,11 @@ pub struct TransactionProgress<'client, T: Config, E: Decode, Evs: Decode> {
// The above type is not `Unpin` by default unless the generic param `T` is,
// so we manually make it clear that Unpin is actually fine regardless of `T`
// (we don't care if this moves around in memory while it's "pinned").
impl<'client, T: Config, E: Decode, Evs: Decode> Unpin
for TransactionProgress<'client, T, E, Evs>
{
}
impl<'client, T: Config, E, Evs> Unpin for TransactionProgress<'client, T, E, Evs> {}
impl<'client, T: Config, E: Decode, Evs: Decode> TransactionProgress<'client, T, E, Evs> {
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
TransactionProgress<'client, T, E, Evs>
{
/// Instantiate a new [`TransactionProgress`] from a custom subscription.
pub fn new(
sub: RpcSubscription<SubstrateTransactionStatus<T::Hash, T::Hash>>,
@@ -171,7 +172,7 @@ impl<'client, T: Config, E: Decode, Evs: Decode> TransactionProgress<'client, T,
}
}
impl<'client, T: Config, E: Decode, Evs: Decode> Stream
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
for TransactionProgress<'client, T, E, Evs>
{
type Item = Result<TransactionStatus<'client, T, E, Evs>, BasicError>;
@@ -340,7 +341,9 @@ pub struct TransactionInBlock<'client, T: Config, E: Decode, Evs: Decode> {
_error: PhantomDataSendSync<(E, Evs)>,
}
impl<'client, T: Config, E: Decode, Evs: Decode> TransactionInBlock<'client, T, E, Evs> {
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
TransactionInBlock<'client, T, E, Evs>
{
pub(crate) fn new(
block_hash: T::Hash,
ext_hash: T::Hash,
@@ -387,7 +390,18 @@ impl<'client, T: Config, E: Decode, Evs: Decode> TransactionInBlock<'client, T,
let ev = ev?;
if &ev.pallet == "System" && &ev.variant == "ExtrinsicFailed" {
let dispatch_error = E::decode(&mut &*ev.data)?;
return Err(Error::Runtime(RuntimeError(dispatch_error)))
if let Some((pallet_idx, error_idx)) =
dispatch_error.module_error_indices()
{
let details = self.client.metadata().error(pallet_idx, error_idx)?;
return Err(Error::Module(ModuleError {
pallet: details.pallet().to_string(),
error: details.error().to_string(),
description: details.description().to_vec(),
}))
} else {
return Err(Error::Runtime(RuntimeError(dispatch_error)))
}
}
}