bitcoind_async_client/
error.rs

1//! Error types for the RPC client.
2use std::fmt;
3
4use bitcoin::Network;
5use bitreq::Error as BitreqError;
6use serde::{Deserialize, Serialize};
7use serde_json::Error as SerdeJsonError;
8use thiserror::Error;
9
10/// The error type for errors produced in this library.
11#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum ClientError {
13    /// Missing username or password for the RPC server
14    #[error("Missing username or password")]
15    MissingUserPassword,
16
17    /// RPC server returned an error
18    ///
19    /// # Note
20    ///
21    /// These errors are ABSOLUTELY UNDOCUMENTED.
22    /// Check
23    /// <https://github.com/bitcoin/bitcoin/blob/96b0a8f858ab24f3672360b8c830553b963de726/src/rpc/protocol.h#L24>
24    /// and good luck!
25    #[error("RPC server returned error '{1}' (code {0})")]
26    Server(i32, String),
27
28    #[error("Error parsing rpc response: {0}")]
29    Parse(String),
30
31    /// Error creating the RPC request, retry might help
32    #[error("Could not create RPC Param")]
33    Param(String),
34
35    /// Body error, unlikely to be recoverable by retrying
36    #[error("{0}")]
37    Body(String),
38
39    /// HTTP status error, not retryable
40    #[error("Obtained failure status({0}): {1}")]
41    Status(u16, String),
42
43    /// Error decoding the response, retry might not help
44    #[error("Malformed Response: {0}")]
45    MalformedResponse(String),
46
47    /// Connection error, retry might help
48    #[error("Could not connect: {0}")]
49    Connection(String),
50
51    /// Timeout error, retry might help
52    #[error("Timeout")]
53    Timeout,
54
55    /// Redirect error, not retryable
56    #[error("HttpRedirect: {0}")]
57    HttpRedirect(String),
58
59    /// Error building the request, unlikely to be recoverable
60    #[error("Could not build request: {0}")]
61    ReqBuilder(String),
62
63    /// Maximum retries exceeded, not retryable
64    #[error("Max retries {0} exceeded")]
65    MaxRetriesExceeded(u8),
66
67    /// General request error, retry might help
68    #[error("Could not create request: {0}")]
69    Request(String),
70
71    /// Wrong network address
72    #[error("Network address: {0}")]
73    WrongNetworkAddress(Network),
74
75    /// Server version is unexpected or incompatible
76    #[error(transparent)]
77    UnexpectedServerVersion(#[from] UnexpectedServerVersionError),
78
79    /// Could not sign raw transaction
80    #[error(transparent)]
81    Sign(#[from] SignRawTransactionWithWalletError),
82
83    /// Could not get a [`Xpriv`](bitcoin::bip32::Xpriv) from the wallet
84    #[error("Could not get xpriv from wallet")]
85    Xpriv,
86
87    /// Unknown error, unlikely to be recoverable
88    #[error("{0}")]
89    Other(String),
90}
91
92impl ClientError {
93    pub fn is_tx_not_found(&self) -> bool {
94        matches!(self, Self::Server(-5, _))
95    }
96
97    pub fn is_block_not_found(&self) -> bool {
98        matches!(self, Self::Server(-5, _))
99    }
100
101    pub fn is_missing_or_invalid_input(&self) -> bool {
102        matches!(self, Self::Server(-26, _)) || matches!(self, Self::Server(-25, _))
103    }
104}
105
106impl From<BitreqError> for ClientError {
107    fn from(value: BitreqError) -> Self {
108        match value {
109            // Connection errors
110            BitreqError::AddressNotFound
111            | BitreqError::IoError(_)
112            | BitreqError::RustlsCreateConnection(_) => ClientError::Connection(value.to_string()),
113
114            // Redirect errors
115            BitreqError::RedirectLocationMissing
116            | BitreqError::InfiniteRedirectionLoop
117            | BitreqError::TooManyRedirections => ClientError::HttpRedirect(value.to_string()),
118
119            // Size/parsing errors
120            BitreqError::HeadersOverflow
121            | BitreqError::StatusLineOverflow
122            | BitreqError::BodyOverflow
123            | BitreqError::MalformedChunkLength
124            | BitreqError::MalformedChunkEnd
125            | BitreqError::MalformedContentLength
126            | BitreqError::InvalidUtf8InResponse
127            | BitreqError::InvalidUtf8InBody(_) => {
128                ClientError::MalformedResponse(value.to_string())
129            }
130
131            // Other errors
132            _ => ClientError::Other(value.to_string()),
133        }
134    }
135}
136
137impl From<SerdeJsonError> for ClientError {
138    fn from(value: SerdeJsonError) -> Self {
139        Self::Parse(format!("Could not parse {value}"))
140    }
141}
142
143/// `bitcoind` RPC server error.
144#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct BitcoinRpcError {
146    pub code: i32,
147    pub message: String,
148}
149
150impl fmt::Display for BitcoinRpcError {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        write!(f, "RPC error {}: {}", self.code, self.message)
153    }
154}
155
156impl From<BitcoinRpcError> for ClientError {
157    fn from(value: BitcoinRpcError) -> Self {
158        Self::Server(value.code, value.message)
159    }
160}
161
162/// Error returned when signing a raw transaction with a wallet fails.
163#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub struct SignRawTransactionWithWalletError {
165    /// The transaction ID.
166    txid: String,
167    /// The index of the input.
168    vout: u32,
169    /// The script signature.
170    #[serde(rename = "scriptSig")]
171    script_sig: String,
172    /// The sequence number.
173    sequence: u32,
174    /// The error message.
175    error: String,
176}
177
178impl fmt::Display for SignRawTransactionWithWalletError {
179    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180        write!(
181            f,
182            "error signing raw transaction with wallet: {}",
183            self.error
184        )
185    }
186}
187
188/// Error returned when RPC client expects a different version than bitcoind reports.
189#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub struct UnexpectedServerVersionError {
191    /// Version from server.
192    pub got: usize,
193    /// Expected server version.
194    pub expected: Vec<usize>,
195}
196
197impl fmt::Display for UnexpectedServerVersionError {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        let mut expected = String::new();
200        for version in &self.expected {
201            let v = format!(" {version} ");
202            expected.push_str(&v);
203        }
204        write!(
205            f,
206            "unexpected bitcoind version, got: {} expected one of: {}",
207            self.got, expected
208        )
209    }
210}