Files
pezkuwi-subxt/subxt/src/error/dispatch_error.rs
T
Tadeo Hepperle fc5a18aaa0 Use scale-typegen as a backend for the codegen (#1260)
* integrate scale-typegen, remove types mod

* reintroduce default substitutes and derives

* support runtime_types only again

* generating polkadot.rs ok

* update scale-typegen to discrete error types

* scale-typegen-api-changes

* add note about UncheckedExtrinsic in default substitutes

* add resursive attributes and derives

* adjust example where Clone bound recursive

* move scale-typegen dependency to workspace

* expose default typegen settings

* lightclient: Fix wasm socket closure called after being dropped (#1289)

* lightclient: Close wasm socket while dropping from connecting state

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Construct one time only closures

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Enable console logs for lightclient WASM testing

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Separate wakes and check connectivity on poll_read

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Close the socket depending on internal state

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "lightclient: Separate wakes and check connectivity on poll_read"

This reverts commit 866094001d4c0b119a80ed681a74b323f74eae1b.

* lightclient: Return pending if socket is opening from poll_read

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Close the socket on `poll_close`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Reset closures on Drop to avoid recursive invokation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Close the socket if not already closing

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* workflows: Install rustup component for building substrate (#1295)

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Command to fetch chainSpec and optimise its size (#1278)

* cli: Add chainSpec command

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli/chainSpec: Move to dedicated module

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Compute the state root hash

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Remove code substitutes

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Update polkadot.json

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Generate the chain spec

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Remove testing artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Fix clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Apply rustfmt

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Introduce feature flag for smoldot dependency

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Rename chain-spec to chain-spec-pruning

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Update chain-spec command

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* remove comments and unused args

* Update substrate- and signer-related dependencies (#1297)

* update crypto dependencies, adjust keypair

* add scale_info::TypeInfo derive in some places

* add multi signature derive

* fix lock file

* fix lock file again :|

* adjust to new interface in scale-typegen

* use released scale typegen

* reintroduce type aliases

* introduce type aliases again using scale-typegen

* cargo fmt and clippy

* reconcile changes with master branch

* update polkadot.rs

* bump scale-typgen to fix substitution

* implemented Alex suggestions, regenerated polkadot.rs (did not change)

* resolve conflicts in Cargo.lock

* make expect messages more clear

* correct typos

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
2024-01-11 16:42:51 +01:00

331 lines
13 KiB
Rust

// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! A representation of the dispatch error; an error returned when
//! something fails in trying to submit/execute a transaction.
use crate::metadata::{DecodeWithMetadata, Metadata};
use core::fmt::Debug;
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType};
use std::borrow::Cow;
use super::{Error, MetadataError};
/// An error dispatching a transaction.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum DispatchError {
/// Some error occurred.
#[error("Some unknown error occurred.")]
Other,
/// Failed to lookup some data.
#[error("Failed to lookup some data.")]
CannotLookup,
/// A bad origin.
#[error("Bad origin.")]
BadOrigin,
/// A custom error in a module.
#[error("Pallet error: {0}")]
Module(ModuleError),
/// At least one consumer is remaining so the account cannot be destroyed.
#[error("At least one consumer is remaining so the account cannot be destroyed.")]
ConsumerRemaining,
/// There are no providers so the account cannot be created.
#[error("There are no providers so the account cannot be created.")]
NoProviders,
/// There are too many consumers so the account cannot be created.
#[error("There are too many consumers so the account cannot be created.")]
TooManyConsumers,
/// An error to do with tokens.
#[error("Token error: {0}")]
Token(TokenError),
/// An arithmetic error.
#[error("Arithmetic error: {0}")]
Arithmetic(ArithmeticError),
/// The number of transactional layers has been reached, or we are not in a transactional layer.
#[error("Transactional error: {0}")]
Transactional(TransactionalError),
/// Resources exhausted, e.g. attempt to read/write data which is too large to manipulate.
#[error(
"Resources exhausted, e.g. attempt to read/write data which is too large to manipulate."
)]
Exhausted,
/// The state is corrupt; this is generally not going to fix itself.
#[error("The state is corrupt; this is generally not going to fix itself.")]
Corruption,
/// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later.
#[error(
"Some resource (e.g. a preimage) is unavailable right now. This might fix itself later."
)]
Unavailable,
}
/// An error relating to tokens when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TokenError {
/// Funds are unavailable.
#[error("Funds are unavailable.")]
FundsUnavailable,
/// Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved.
#[error("Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved.")]
OnlyProvider,
/// Account cannot exist with the funds that would be given.
#[error("Account cannot exist with the funds that would be given.")]
BelowMinimum,
/// Account cannot be created.
#[error("Account cannot be created.")]
CannotCreate,
/// The asset in question is unknown.
#[error("The asset in question is unknown.")]
UnknownAsset,
/// Funds exist but are frozen.
#[error("Funds exist but are frozen.")]
Frozen,
/// Operation is not supported by the asset.
#[error("Operation is not supported by the asset.")]
Unsupported,
/// Account cannot be created for a held balance.
#[error("Account cannot be created for a held balance.")]
CannotCreateHold,
/// Withdrawal would cause unwanted loss of account.
#[error("Withdrawal would cause unwanted loss of account.")]
NotExpendable,
}
/// An error relating to arithmetic when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum ArithmeticError {
/// Underflow.
#[error("Underflow.")]
Underflow,
/// Overflow.
#[error("Overflow.")]
Overflow,
/// Division by zero.
#[error("Division by zero.")]
DivisionByZero,
}
/// An error relating to thr transactional layers when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransactionalError {
/// Too many transactional layers have been spawned.
#[error("Too many transactional layers have been spawned.")]
LimitReached,
/// A transactional layer was expected, but does not exist.
#[error("A transactional layer was expected, but does not exist.")]
NoLayer,
}
/// Details about a module error that has occurred.
#[derive(Clone, thiserror::Error)]
#[non_exhaustive]
pub struct ModuleError {
metadata: Metadata,
/// Bytes representation:
/// - `bytes[0]`: pallet index
/// - `bytes[1]`: error index
/// - `bytes[2..]`: 3 bytes specific for the module error
bytes: [u8; 5],
}
impl PartialEq for ModuleError {
fn eq(&self, other: &Self) -> bool {
// A module error is the same if the raw underlying details are the same.
self.bytes == other.bytes
}
}
impl Eq for ModuleError {}
/// Custom `Debug` implementation, ignores the very large `metadata` field, using it instead (as
/// intended) to resolve the actual pallet and error names. This is much more useful for debugging.
impl Debug for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let details = self.details_string();
write!(f, "ModuleError(<{details}>)")
}
}
impl std::fmt::Display for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let details = self.details_string();
write!(f, "{details}")
}
}
impl ModuleError {
/// Return more details about this error.
pub fn details(&self) -> Result<ModuleErrorDetails, MetadataError> {
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
let variant = pallet
.error_variant_by_index(self.error_index())
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.error_index()))?;
Ok(ModuleErrorDetails { pallet, variant })
}
/// Return a formatted string of the resolved error details for debugging/display purposes.
pub fn details_string(&self) -> String {
match self.details() {
Ok(details) => format!(
"{pallet_name}::{variant_name}",
pallet_name = details.pallet.name(),
variant_name = details.variant.name,
),
Err(_) => format!(
"Unknown pallet error '{bytes:?}' (pallet and error details cannot be retrieved)",
bytes = self.bytes
),
}
}
/// Return the underlying module error data that was decoded.
pub fn bytes(&self) -> [u8; 5] {
self.bytes
}
/// Obtain the pallet index from the underlying byte data.
pub fn pallet_index(&self) -> u8 {
self.bytes[0]
}
/// Obtain the error index from the underlying byte data.
pub fn error_index(&self) -> u8 {
self.bytes[1]
}
/// Attempts to decode the ModuleError into the top outer Error enum.
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, Error> {
let decoded = E::decode_as_type(
&mut &self.bytes[..],
self.metadata.outer_enums().error_enum_ty(),
self.metadata.types(),
)?;
Ok(decoded)
}
}
/// Details about the module error.
pub struct ModuleErrorDetails<'a> {
/// The pallet that the error is in
pub pallet: crate::metadata::types::PalletMetadata<'a>,
/// The variant representing the error
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
impl DispatchError {
/// Attempt to decode a runtime [`DispatchError`].
#[doc(hidden)]
pub fn decode_from<'a>(
bytes: impl Into<Cow<'a, [u8]>>,
metadata: Metadata,
) -> Result<Self, super::Error> {
let bytes = bytes.into();
let dispatch_error_ty_id = metadata
.dispatch_error_ty()
.ok_or(MetadataError::DispatchErrorNotFound)?;
// The aim is to decode our bytes into roughly this shape. This is copied from
// `sp_runtime::DispatchError`; we need the variant names and any inner variant
// names/shapes to line up in order for decoding to be successful.
#[derive(scale_decode::DecodeAsType)]
enum DecodedDispatchError {
Other,
CannotLookup,
BadOrigin,
Module(DecodedModuleErrorBytes),
ConsumerRemaining,
NoProviders,
TooManyConsumers,
Token(TokenError),
Arithmetic(ArithmeticError),
Transactional(TransactionalError),
Exhausted,
Corruption,
Unavailable,
}
// ModuleError is a bit special; we want to support being decoded from either
// a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes
// out when decoding to manually work with them.
struct DecodedModuleErrorBytes(Vec<u8>);
struct DecodedModuleErrorBytesVisitor;
impl scale_decode::Visitor for DecodedModuleErrorBytesVisitor {
type Error = scale_decode::Error;
type Value<'scale, 'info> = DecodedModuleErrorBytes;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
_type_id: scale_decode::visitor::TypeId,
_types: &'info scale_info::PortableRegistry,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>>
{
DecodeAsTypeResult::Decoded(Ok(DecodedModuleErrorBytes(input.to_vec())))
}
}
impl scale_decode::IntoVisitor for DecodedModuleErrorBytes {
type Visitor = DecodedModuleErrorBytesVisitor;
fn into_visitor() -> Self::Visitor {
DecodedModuleErrorBytesVisitor
}
}
// Decode into our temporary error:
let decoded_dispatch_err = DecodedDispatchError::decode_with_metadata(
&mut &*bytes,
dispatch_error_ty_id,
&metadata,
)?;
// Convert into the outward-facing error, mainly by handling the Module variant.
let dispatch_error = match decoded_dispatch_err {
// Mostly we don't change anything from our decoded to our outward-facing error:
DecodedDispatchError::Other => DispatchError::Other,
DecodedDispatchError::CannotLookup => DispatchError::CannotLookup,
DecodedDispatchError::BadOrigin => DispatchError::BadOrigin,
DecodedDispatchError::ConsumerRemaining => DispatchError::ConsumerRemaining,
DecodedDispatchError::NoProviders => DispatchError::NoProviders,
DecodedDispatchError::TooManyConsumers => DispatchError::TooManyConsumers,
DecodedDispatchError::Token(val) => DispatchError::Token(val),
DecodedDispatchError::Arithmetic(val) => DispatchError::Arithmetic(val),
DecodedDispatchError::Transactional(val) => DispatchError::Transactional(val),
DecodedDispatchError::Exhausted => DispatchError::Exhausted,
DecodedDispatchError::Corruption => DispatchError::Corruption,
DecodedDispatchError::Unavailable => DispatchError::Unavailable,
// But we apply custom logic to transform the module error into the outward facing version:
DecodedDispatchError::Module(module_bytes) => {
let module_bytes = module_bytes.0;
// The old version is 2 bytes; a pallet and error index.
// The new version is 5 bytes; a pallet and error index and then 3 extra bytes.
let bytes = if module_bytes.len() == 2 {
[module_bytes[0], module_bytes[1], 0, 0, 0]
} else if module_bytes.len() == 5 {
[
module_bytes[0],
module_bytes[1],
module_bytes[2],
module_bytes[3],
module_bytes[4],
]
} else {
tracing::warn!("Can't decode error sp_runtime::DispatchError: bytes do not match known shapes");
// Return _all_ of the bytes; every "unknown" return should be consistent.
return Err(super::Error::Unknown(bytes.to_vec()));
};
// And return our outward-facing version:
DispatchError::Module(ModuleError { metadata, bytes })
}
};
Ok(dispatch_error)
}
}