bitcoind_async_client/
error.rs1use 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
10const RPC_VERIFY_ERROR: i32 = -25;
13const RPC_VERIFY_REJECTED: i32 = -26;
16const RPC_VERIFY_ALREADY_IN_UTXO_SET: i32 = -27;
19
20#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub enum ClientError {
23 #[error("Missing username or password")]
25 MissingUserPassword,
26
27 #[error("RPC server returned error '{1}' (code {0})")]
36 Server(i32, String),
37
38 #[error("Error parsing rpc response: {0}")]
40 Parse(String),
41
42 #[error("Could not create RPC Param")]
44 Param(String),
45
46 #[error("{0}")]
48 Body(String),
49
50 #[error("Obtained failure status({0}): {1}")]
52 Status(u16, String),
53
54 #[error("Malformed Response: {0}")]
56 MalformedResponse(String),
57
58 #[error("Could not connect: {0}")]
60 Connection(String),
61
62 #[error("Timeout")]
64 Timeout,
65
66 #[error("HttpRedirect: {0}")]
68 HttpRedirect(String),
69
70 #[error("Could not build request: {0}")]
72 ReqBuilder(String),
73
74 #[error("Max retries {0} exceeded")]
76 MaxRetriesExceeded(u8),
77
78 #[error("Could not create request: {0}")]
80 Request(String),
81
82 #[error("Network address: {0}")]
84 WrongNetworkAddress(Network),
85
86 #[error(transparent)]
88 UnexpectedServerVersion(#[from] UnexpectedServerVersionError),
89
90 #[error(transparent)]
92 Sign(#[from] SignRawTransactionWithWalletError),
93
94 #[error("Could not get xpriv from wallet")]
96 Xpriv,
97
98 #[error("{0}")]
100 Other(String),
101}
102
103impl ClientError {
104 pub fn is_tx_not_found(&self) -> bool {
107 matches!(self, Self::Server(-5, _))
108 }
109
110 pub fn is_block_not_found(&self) -> bool {
113 matches!(self, Self::Server(-5, _))
114 }
115
116 pub fn is_rpc_verify_error(&self) -> bool {
119 matches!(self, Self::Server(RPC_VERIFY_ERROR, _))
120 }
121
122 pub fn is_rpc_verify_rejected(&self) -> bool {
125 matches!(self, Self::Server(RPC_VERIFY_REJECTED, _))
126 }
127
128 pub fn is_rpc_verify_already_in_utxo_set(&self) -> bool {
131 matches!(self, Self::Server(RPC_VERIFY_ALREADY_IN_UTXO_SET, _))
132 }
133
134 #[deprecated(
137 since = "0.10.4",
138 note = "use is_rpc_verify_error() to detect RPC_VERIFY_ERROR (-25)"
139 )]
140 pub fn is_missing_or_invalid_input(&self) -> bool {
141 self.is_rpc_verify_error()
142 }
143}
144
145impl From<BitreqError> for ClientError {
146 fn from(value: BitreqError) -> Self {
147 match value {
148 BitreqError::AddressNotFound
150 | BitreqError::IoError(_)
151 | BitreqError::RustlsCreateConnection(_) => ClientError::Connection(value.to_string()),
152
153 BitreqError::RedirectLocationMissing
155 | BitreqError::InfiniteRedirectionLoop
156 | BitreqError::TooManyRedirections => ClientError::HttpRedirect(value.to_string()),
157
158 BitreqError::HeadersOverflow
160 | BitreqError::StatusLineOverflow
161 | BitreqError::BodyOverflow
162 | BitreqError::MalformedChunkLength
163 | BitreqError::MalformedChunkEnd
164 | BitreqError::MalformedContentLength
165 | BitreqError::InvalidUtf8InResponse
166 | BitreqError::InvalidUtf8InBody(_) => {
167 ClientError::MalformedResponse(value.to_string())
168 }
169
170 _ => ClientError::Other(value.to_string()),
172 }
173 }
174}
175
176impl From<SerdeJsonError> for ClientError {
177 fn from(value: SerdeJsonError) -> Self {
178 Self::Parse(format!("Could not parse {value}"))
179 }
180}
181
182#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct BitcoinRpcError {
185 pub code: i32,
186 pub message: String,
187}
188
189impl fmt::Display for BitcoinRpcError {
190 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191 write!(f, "RPC error {}: {}", self.code, self.message)
192 }
193}
194
195impl From<BitcoinRpcError> for ClientError {
196 fn from(value: BitcoinRpcError) -> Self {
197 Self::Server(value.code, value.message)
198 }
199}
200
201#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
203pub struct SignRawTransactionWithWalletError {
204 txid: String,
206 vout: u32,
208 #[serde(rename = "scriptSig")]
210 script_sig: String,
211 sequence: u32,
213 error: String,
215}
216
217impl fmt::Display for SignRawTransactionWithWalletError {
218 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219 write!(
220 f,
221 "error signing raw transaction with wallet: {}",
222 self.error
223 )
224 }
225}
226
227#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct UnexpectedServerVersionError {
230 pub got: usize,
232 pub expected: Vec<usize>,
234}
235
236impl fmt::Display for UnexpectedServerVersionError {
237 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
238 let mut expected = String::new();
239 for version in &self.expected {
240 let v = format!(" {version} ");
241 expected.push_str(&v);
242 }
243 write!(
244 f,
245 "unexpected bitcoind version, got: {} expected one of: {}",
246 self.got, expected
247 )
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 #![allow(deprecated)]
254
255 use super::ClientError;
256
257 #[test]
258 fn classifies_rpc_verify_error() {
259 let error = ClientError::Server(-25, "Input not found or already spent".to_string());
260
261 assert!(error.is_rpc_verify_error());
262 assert!(error.is_missing_or_invalid_input());
263 assert!(!error.is_rpc_verify_rejected());
264 assert!(!error.is_rpc_verify_already_in_utxo_set());
265 }
266
267 #[test]
268 fn classifies_rpc_verify_rejected() {
269 let error = ClientError::Server(-26, "txn-already-in-mempool".to_string());
270
271 assert!(error.is_rpc_verify_rejected());
272 assert!(!error.is_missing_or_invalid_input());
273 assert!(!error.is_rpc_verify_error());
274 assert!(!error.is_rpc_verify_already_in_utxo_set());
275 }
276
277 #[test]
278 fn classifies_rpc_verify_already_in_utxo_set() {
279 let error = ClientError::Server(-27, "transaction already in block chain".to_string());
280
281 assert!(error.is_rpc_verify_already_in_utxo_set());
282 assert!(!error.is_rpc_verify_error());
283 assert!(!error.is_rpc_verify_rejected());
284 assert!(!error.is_missing_or_invalid_input());
285 }
286
287 #[test]
288 fn non_server_errors_do_not_match_rpc_code_helpers() {
289 let error = ClientError::Timeout;
290
291 assert!(!error.is_rpc_verify_error());
292 assert!(!error.is_rpc_verify_rejected());
293 assert!(!error.is_rpc_verify_already_in_utxo_set());
294 assert!(!error.is_missing_or_invalid_input());
295 }
296}