subxt/error/
mod.rs

1// Copyright 2019-2025 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! Types representing the errors that can be returned.
6
7mod dispatch_error;
8
9use subxt_core::error::{BlockError as CoreBlockError, Error as CoreError};
10
11crate::macros::cfg_unstable_light_client! {
12    pub use subxt_lightclient::LightClientError;
13}
14
15// Re-export dispatch error types:
16pub use dispatch_error::{
17    ArithmeticError, DispatchError, ModuleError, TokenError, TransactionalError,
18};
19
20// Re-expose the errors we use from other crates here:
21pub use crate::metadata::Metadata;
22pub use scale_decode::Error as DecodeError;
23pub use scale_encode::Error as EncodeError;
24pub use subxt_core::error::{ExtrinsicError, MetadataError, StorageAddressError};
25pub use subxt_metadata::TryFromError as MetadataTryFromError;
26
27/// The underlying error enum, generic over the type held by the `Runtime`
28/// variant. Prefer to use the [`Error<E>`] and [`Error`] aliases over
29/// using this type directly.
30#[derive(Debug, thiserror::Error)]
31#[non_exhaustive]
32pub enum Error {
33    /// Io error.
34    #[error("Io error: {0}")]
35    Io(#[from] std::io::Error),
36    /// Codec error.
37    #[error("Scale codec error: {0}")]
38    Codec(#[from] codec::Error),
39    /// Rpc error.
40    #[error(transparent)]
41    Rpc(#[from] RpcError),
42    /// Serde serialization error
43    #[error("Serde json error: {0}")]
44    Serialization(#[from] serde_json::error::Error),
45    /// Error working with metadata.
46    #[error("Metadata error: {0}")]
47    Metadata(#[from] MetadataError),
48    /// Error decoding metadata.
49    #[error("Metadata Decoding error: {0}")]
50    MetadataDecoding(#[from] MetadataTryFromError),
51    /// Runtime error.
52    #[error("Runtime error: {0}")]
53    Runtime(#[from] DispatchError),
54    /// Error decoding to a [`crate::dynamic::Value`].
55    #[error("Error decoding into dynamic value: {0}")]
56    Decode(#[from] DecodeError),
57    /// Error encoding from a [`crate::dynamic::Value`].
58    #[error("Error encoding from dynamic value: {0}")]
59    Encode(#[from] EncodeError),
60    /// Transaction progress error.
61    #[error("Transaction error: {0}")]
62    Transaction(#[from] TransactionError),
63    /// Error constructing the appropriate extrinsic params.
64    #[error("Extrinsic params error: {0}")]
65    Extrinsic(#[from] ExtrinsicError),
66    /// Block related error.
67    #[error("Block error: {0}")]
68    Block(#[from] BlockError),
69    /// An error encoding a storage address.
70    #[error("Error encoding storage address: {0}")]
71    StorageAddress(#[from] StorageAddressError),
72    /// The bytes representing an error that we were unable to decode.
73    #[error("An error occurred but it could not be decoded: {0:?}")]
74    Unknown(Vec<u8>),
75    /// Light client error.
76    #[cfg(feature = "unstable-light-client")]
77    #[cfg_attr(docsrs, doc(cfg(feature = "unstable-light-client")))]
78    #[error("An error occurred but it could not be decoded: {0}")]
79    LightClient(#[from] LightClientError),
80    /// Other error.
81    #[error("Other error: {0}")]
82    Other(String),
83}
84
85impl From<CoreError> for Error {
86    fn from(value: CoreError) -> Self {
87        match value {
88            CoreError::Codec(e) => Error::Codec(e),
89            CoreError::Metadata(e) => Error::Metadata(e),
90            CoreError::StorageAddress(e) => Error::StorageAddress(e),
91            CoreError::Decode(e) => Error::Decode(e),
92            CoreError::Encode(e) => Error::Encode(e),
93            CoreError::Extrinsic(e) => Error::Extrinsic(e),
94            CoreError::Block(e) => Error::Block(e.into()),
95        }
96    }
97}
98
99impl<'a> From<&'a str> for Error {
100    fn from(error: &'a str) -> Self {
101        Error::Other(error.into())
102    }
103}
104
105impl From<String> for Error {
106    fn from(error: String) -> Self {
107        Error::Other(error)
108    }
109}
110
111impl From<std::convert::Infallible> for Error {
112    fn from(value: std::convert::Infallible) -> Self {
113        match value {}
114    }
115}
116
117impl From<scale_decode::visitor::DecodeError> for Error {
118    fn from(value: scale_decode::visitor::DecodeError) -> Self {
119        Error::Decode(value.into())
120    }
121}
122
123impl From<subxt_rpcs::Error> for Error {
124    fn from(value: subxt_rpcs::Error) -> Self {
125        Error::Rpc(value.into())
126    }
127}
128
129impl Error {
130    /// Checks whether the error was caused by a RPC re-connection.
131    pub fn is_disconnected_will_reconnect(&self) -> bool {
132        matches!(
133            self,
134            Error::Rpc(RpcError::ClientError(
135                subxt_rpcs::Error::DisconnectedWillReconnect(_)
136            ))
137        )
138    }
139
140    /// Checks whether the error was caused by a RPC request being rejected.
141    pub fn is_rpc_limit_reached(&self) -> bool {
142        matches!(self, Error::Rpc(RpcError::LimitReached))
143    }
144}
145
146/// An RPC error. Since we are generic over the RPC client that is used,
147/// the error is boxed and could be casted.
148#[derive(Debug, thiserror::Error)]
149#[non_exhaustive]
150pub enum RpcError {
151    // Dev note: We need the error to be safely sent between threads
152    // for `subscribe_to_block_headers_filling_in_gaps` and friends.
153    /// Error related to the RPC client.
154    #[error("RPC error: {0}")]
155    ClientError(#[from] subxt_rpcs::Error),
156    /// This error signals that we got back a [`subxt_rpcs::methods::chain_head::MethodResponse::LimitReached`],
157    /// which is not technically an RPC error but is treated as an error in our own APIs.
158    #[error("RPC error: limit reached")]
159    LimitReached,
160    /// The RPC subscription dropped.
161    #[error("RPC error: subscription dropped.")]
162    SubscriptionDropped,
163}
164
165/// Block error
166#[derive(Clone, Debug, thiserror::Error)]
167#[non_exhaustive]
168pub enum BlockError {
169    /// An error containing the hash of the block that was not found.
170    #[error("Could not find a block with hash {0} (perhaps it was on a non-finalized fork?)")]
171    NotFound(String),
172    /// Leftover bytes found after decoding the extrinsic.
173    #[error(
174        "After decoding the exntrinsic at index {extrinsic_index}, {num_leftover_bytes} bytes were left, suggesting that decoding may have failed"
175    )]
176    LeftoverBytes {
177        /// Index of the extrinsic that failed to decode.
178        extrinsic_index: usize,
179        /// Number of bytes leftover after decoding the extrinsic.
180        num_leftover_bytes: usize,
181    },
182    /// Decoding error.
183    #[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
184    ExtrinsicDecodeError {
185        /// Index of the extrinsic that failed to decode.
186        extrinsic_index: usize,
187        /// The decode error.
188        error: subxt_core::error::ExtrinsicDecodeError,
189    },
190}
191
192impl From<CoreBlockError> for BlockError {
193    fn from(value: CoreBlockError) -> Self {
194        match value {
195            CoreBlockError::LeftoverBytes {
196                extrinsic_index,
197                num_leftover_bytes,
198            } => BlockError::LeftoverBytes {
199                extrinsic_index,
200                num_leftover_bytes,
201            },
202            CoreBlockError::ExtrinsicDecodeError {
203                extrinsic_index,
204                error,
205            } => BlockError::ExtrinsicDecodeError {
206                extrinsic_index,
207                error,
208            },
209        }
210    }
211}
212
213impl BlockError {
214    /// Produce an error that a block with the given hash cannot be found.
215    pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError {
216        let hash = format!("0x{}", hex::encode(hash));
217        BlockError::NotFound(hash)
218    }
219}
220
221/// Transaction error.
222#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
223#[non_exhaustive]
224pub enum TransactionError {
225    /// The block hash that the transaction was added to could not be found.
226    /// This is probably because the block was retracted before being finalized.
227    #[error(
228        "The block containing the transaction can no longer be found (perhaps it was on a non-finalized fork?)"
229    )]
230    BlockNotFound,
231    /// An error happened on the node that the transaction was submitted to.
232    #[error("Error handling transaction: {0}")]
233    Error(String),
234    /// The transaction was deemed invalid.
235    #[error("The transaction is not valid: {0}")]
236    Invalid(String),
237    /// The transaction was dropped.
238    #[error("The transaction was dropped: {0}")]
239    Dropped(String),
240}