mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-18 15:21:05 +00:00
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:
+5
-2
@@ -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`].
|
||||
|
||||
@@ -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)>;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1484
-1398
File diff suppressed because one or more lines are too long
@@ -154,9 +154,9 @@ async fn transfer_error() {
|
||||
let alice_addr = alice.account_id().clone().into();
|
||||
let hans = pair_signer(Pair::generate().0);
|
||||
let hans_address = hans.account_id().clone().into();
|
||||
let cxt = test_context().await;
|
||||
let ctx = test_context().await;
|
||||
|
||||
cxt.api
|
||||
ctx.api
|
||||
.tx()
|
||||
.balances()
|
||||
.transfer(hans_address, 100_000_000_000_000_000)
|
||||
@@ -167,7 +167,7 @@ async fn transfer_error() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let res = cxt
|
||||
let res = ctx
|
||||
.api
|
||||
.tx()
|
||||
.balances()
|
||||
@@ -178,10 +178,9 @@ async fn transfer_error() {
|
||||
.wait_for_finalized_success()
|
||||
.await;
|
||||
|
||||
if let Err(Error::Runtime(err)) = res {
|
||||
let details = err.inner().details().unwrap();
|
||||
assert_eq!(details.pallet, "Balances");
|
||||
assert_eq!(details.error, "InsufficientBalance");
|
||||
if let Err(Error::Module(err)) = res {
|
||||
assert_eq!(err.pallet, "Balances");
|
||||
assert_eq!(err.error, "InsufficientBalance");
|
||||
} else {
|
||||
panic!("expected a runtime module error");
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ fn default_validator_prefs() -> ValidatorPrefs {
|
||||
#[async_std::test]
|
||||
async fn validate_with_controller_account() {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let cxt = test_context().await;
|
||||
cxt.api
|
||||
let ctx = test_context().await;
|
||||
ctx.api
|
||||
.tx()
|
||||
.staking()
|
||||
.validate(default_validator_prefs())
|
||||
@@ -69,8 +69,8 @@ async fn validate_with_controller_account() {
|
||||
#[async_std::test]
|
||||
async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchError>> {
|
||||
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
|
||||
let cxt = test_context().await;
|
||||
let announce_validator = cxt
|
||||
let ctx = test_context().await;
|
||||
let announce_validator = ctx
|
||||
.api
|
||||
.tx()
|
||||
.staking()
|
||||
@@ -79,10 +79,9 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await;
|
||||
assert_matches!(announce_validator, Err(Error::Runtime(err)) => {
|
||||
let details = err.inner().details().unwrap();
|
||||
assert_eq!(details.pallet, "Staking");
|
||||
assert_eq!(details.error, "NotController");
|
||||
assert_matches!(announce_validator, Err(Error::Module(err)) => {
|
||||
assert_eq!(err.pallet, "Staking");
|
||||
assert_eq!(err.error, "NotController");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -91,9 +90,9 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
|
||||
async fn nominate_with_controller_account() {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let cxt = test_context().await;
|
||||
let ctx = test_context().await;
|
||||
|
||||
cxt.api
|
||||
ctx.api
|
||||
.tx()
|
||||
.staking()
|
||||
.nominate(vec![bob.account_id().clone().into()])
|
||||
@@ -109,9 +108,9 @@ async fn nominate_with_controller_account() {
|
||||
async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchError>> {
|
||||
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let cxt = test_context().await;
|
||||
let ctx = test_context().await;
|
||||
|
||||
let nomination = cxt
|
||||
let nomination = ctx
|
||||
.api
|
||||
.tx()
|
||||
.staking()
|
||||
@@ -121,10 +120,9 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
|
||||
.wait_for_finalized_success()
|
||||
.await;
|
||||
|
||||
assert_matches!(nomination, Err(Error::Runtime(err)) => {
|
||||
let details = err.inner().details().unwrap();
|
||||
assert_eq!(details.pallet, "Staking");
|
||||
assert_eq!(details.error, "NotController");
|
||||
assert_matches!(nomination, Err(Error::Module(err)) => {
|
||||
assert_eq!(err.pallet, "Staking");
|
||||
assert_eq!(err.error, "NotController");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -134,10 +132,10 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
|
||||
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
|
||||
let bob_stash = pair_signer(get_from_seed("Bob//stash"));
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let cxt = test_context().await;
|
||||
let ctx = test_context().await;
|
||||
|
||||
// this will fail the second time, which is why this is one test, not two
|
||||
cxt.api
|
||||
ctx.api
|
||||
.tx()
|
||||
.staking()
|
||||
.nominate(vec![bob_stash.account_id().clone().into()])
|
||||
@@ -146,7 +144,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
|
||||
let ledger = cxt
|
||||
let ledger = ctx
|
||||
.api
|
||||
.storage()
|
||||
.staking()
|
||||
@@ -155,7 +153,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
|
||||
.unwrap();
|
||||
assert_eq!(alice_stash.account_id(), &ledger.stash);
|
||||
|
||||
let chill = cxt
|
||||
let chill = ctx
|
||||
.api
|
||||
.tx()
|
||||
.staking()
|
||||
@@ -165,13 +163,12 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
|
||||
.wait_for_finalized_success()
|
||||
.await;
|
||||
|
||||
assert_matches!(chill, Err(Error::Runtime(err)) => {
|
||||
let details = err.inner().details().unwrap();
|
||||
assert_eq!(details.pallet, "Staking");
|
||||
assert_eq!(details.error, "NotController");
|
||||
assert_matches!(chill, Err(Error::Module(err)) => {
|
||||
assert_eq!(err.pallet, "Staking");
|
||||
assert_eq!(err.error, "NotController");
|
||||
});
|
||||
|
||||
let is_chilled = cxt
|
||||
let is_chilled = ctx
|
||||
.api
|
||||
.tx()
|
||||
.staking()
|
||||
@@ -189,9 +186,9 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
|
||||
#[async_std::test]
|
||||
async fn tx_bond() -> Result<(), Error<DispatchError>> {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let cxt = test_context().await;
|
||||
let ctx = test_context().await;
|
||||
|
||||
let bond = cxt
|
||||
let bond = ctx
|
||||
.api
|
||||
.tx()
|
||||
.staking()
|
||||
@@ -207,7 +204,7 @@ async fn tx_bond() -> Result<(), Error<DispatchError>> {
|
||||
|
||||
assert!(bond.is_ok());
|
||||
|
||||
let bond_again = cxt
|
||||
let bond_again = ctx
|
||||
.api
|
||||
.tx()
|
||||
.staking()
|
||||
@@ -221,26 +218,25 @@ async fn tx_bond() -> Result<(), Error<DispatchError>> {
|
||||
.wait_for_finalized_success()
|
||||
.await;
|
||||
|
||||
assert_matches!(bond_again, Err(Error::Runtime(err)) => {
|
||||
let details = err.inner().details().unwrap();
|
||||
assert_eq!(details.pallet, "Staking");
|
||||
assert_eq!(details.error, "AlreadyBonded");
|
||||
assert_matches!(bond_again, Err(Error::Module(err)) => {
|
||||
assert_eq!(err.pallet, "Staking");
|
||||
assert_eq!(err.error, "AlreadyBonded");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn storage_history_depth() -> Result<(), Error<DispatchError>> {
|
||||
let cxt = test_context().await;
|
||||
let history_depth = cxt.api.storage().staking().history_depth(None).await?;
|
||||
let ctx = test_context().await;
|
||||
let history_depth = ctx.api.storage().staking().history_depth(None).await?;
|
||||
assert_eq!(history_depth, 84);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn storage_current_era() -> Result<(), Error<DispatchError>> {
|
||||
let cxt = test_context().await;
|
||||
let _current_era = cxt
|
||||
let ctx = test_context().await;
|
||||
let _current_era = ctx
|
||||
.api
|
||||
.storage()
|
||||
.staking()
|
||||
@@ -252,8 +248,8 @@ async fn storage_current_era() -> Result<(), Error<DispatchError>> {
|
||||
|
||||
#[async_std::test]
|
||||
async fn storage_era_reward_points() -> Result<(), Error<DispatchError>> {
|
||||
let cxt = test_context().await;
|
||||
let current_era_result = cxt
|
||||
let ctx = test_context().await;
|
||||
let current_era_result = ctx
|
||||
.api
|
||||
.storage()
|
||||
.staking()
|
||||
|
||||
Reference in New Issue
Block a user