1use bitcoincore_rpc::{Error as RpcClientError, jsonrpc::Error as JsonRpcError};
2use std::fmt::{Display, Formatter};
3use urma_chain::validation::BlockValidationError;
4
5#[derive(Debug)]
6pub enum Error {
7 Identity(urma_identity::error::IdentityError),
8 Wallet(urma_wallet::wallet::WalletError),
9 Protocol(urma_core::error::Error),
10 Amount(bitcoin::amount::ParseAmountError),
11 Script(bitcoin::script::Error),
12 Invalid(String),
13 Unsupported(String),
14 Capacity(String),
15 HeaderContext(urma_chain::pow::PowError),
18 Missing(String),
19 RateLimited(std::time::Duration),
20 Context {
21 message: String,
22 cause: Box<Error>,
23 },
24 Io(std::io::Error),
25 Json(serde_json::Error),
26 Hex(hex::FromHexError),
27 Url(url::ParseError),
28 Integer(std::num::TryFromIntError),
29 IntegerText(std::num::ParseIntError),
30 Slice(std::array::TryFromSliceError),
31 Utf8(std::str::Utf8Error),
32 Text(std::string::FromUtf8Error),
33 Allocation(std::collections::TryReserveError),
34 Random(rand::Error),
35 Secp256k1(bitcoin::secp256k1::Error),
36 Transaction(bitcoin::consensus::encode::Error),
37 ChainDecode(urma_chain::decode::DecodeError),
38 Push(bitcoin::script::PushBytesError),
39 TaprootBuilder(bitcoin::taproot::TaprootBuilderError),
40 Taproot(bitcoin::taproot::TaprootError),
41 TaprootSighash(bitcoin::sighash::TaprootError),
42 SegwitSighash(bitcoin::sighash::P2wpkhError),
43 AddressScript(bitcoin::address::FromScriptError),
44 AddressParse(bitcoin::address::ParseError),
45 AddressNetwork(bitcoin::address::NetworkValidationError),
46 PublicKey(bitcoin::key::FromSliceError),
47 UncompressedKey(bitcoin::key::UncompressedPublicKeyError),
48 Ecdsa(bitcoin::ecdsa::Error),
49 Hash(bitcoin::hex::HexToArrayError),
50 ProofOfWork(bitcoin::block::ValidationError),
51 Rpc(bitcoincore_rpc::Error),
52 Http(minreq::Error),
53 Tls(rustls::Error),
54 Persist(tempfile::PersistError),
55 Ip(std::net::AddrParseError),
56}
57impl Display for Error {
58 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::Identity(cause) => Display::fmt(cause, formatter),
61 Self::Wallet(cause) => Display::fmt(cause, formatter),
62 Self::Protocol(cause) => Display::fmt(cause, formatter),
63 Self::Amount(cause) => Display::fmt(cause, formatter),
64 Self::Script(cause) => Display::fmt(cause, formatter),
65 Self::Invalid(message)
66 | Self::Missing(message)
67 | Self::Unsupported(message)
68 | Self::Capacity(message) => formatter.write_str(message),
69 Self::RateLimited(wait) => write!(
70 formatter,
71 "public provider rate limited; retry after {} s",
72 wait.as_secs()
73 ),
74 Self::Context { message, cause } => write!(formatter, "{message}: {cause}"),
75 Self::HeaderContext(cause) => {
76 write!(formatter, "local header context is insufficient: {cause}")
77 }
78 Self::Io(cause) => Display::fmt(cause, formatter),
79 Self::Json(cause) => Display::fmt(cause, formatter),
80 Self::Hex(cause) => Display::fmt(cause, formatter),
81 Self::Url(cause) => Display::fmt(cause, formatter),
82 Self::Integer(cause) => Display::fmt(cause, formatter),
83 Self::IntegerText(cause) => Display::fmt(cause, formatter),
84 Self::Slice(cause) => Display::fmt(cause, formatter),
85 Self::Utf8(cause) => Display::fmt(cause, formatter),
86 Self::Text(cause) => Display::fmt(cause, formatter),
87 Self::Allocation(cause) => Display::fmt(cause, formatter),
88 Self::Random(cause) => Display::fmt(cause, formatter),
89 Self::Secp256k1(cause) => Display::fmt(cause, formatter),
90 Self::Transaction(cause) => Display::fmt(cause, formatter),
91 Self::ChainDecode(cause) => Display::fmt(cause, formatter),
92 Self::Push(cause) => Display::fmt(cause, formatter),
93 Self::TaprootBuilder(cause) => Display::fmt(cause, formatter),
94 Self::Taproot(cause) => Display::fmt(cause, formatter),
95 Self::TaprootSighash(cause) => Display::fmt(cause, formatter),
96 Self::SegwitSighash(cause) => Display::fmt(cause, formatter),
97 Self::AddressScript(cause) => Display::fmt(cause, formatter),
98 Self::AddressParse(cause) => Display::fmt(cause, formatter),
99 Self::AddressNetwork(cause) => Display::fmt(cause, formatter),
100 Self::PublicKey(cause) => Display::fmt(cause, formatter),
101 Self::UncompressedKey(cause) => Display::fmt(cause, formatter),
102 Self::Ecdsa(cause) => Display::fmt(cause, formatter),
103 Self::Hash(cause) => Display::fmt(cause, formatter),
104 Self::ProofOfWork(cause) => Display::fmt(cause, formatter),
105 Self::Rpc(cause) => Display::fmt(cause, formatter),
106 Self::Http(cause) => Display::fmt(cause, formatter),
107 Self::Tls(cause) => Display::fmt(cause, formatter),
108 Self::Persist(cause) => Display::fmt(cause, formatter),
109 Self::Ip(cause) => Display::fmt(cause, formatter),
110 }
111 }
112}
113impl std::error::Error for Error {
114 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
115 match self {
116 Self::Identity(cause) => Some(cause),
117 Self::Wallet(cause) => Some(cause),
118 Self::Protocol(cause) => Some(cause),
119 Self::Amount(cause) => Some(cause),
120 Self::Script(cause) => Some(cause),
121 Self::Io(cause) => Some(cause),
122 Self::Json(cause) => Some(cause),
123 Self::Hex(cause) => Some(cause),
124 Self::Url(cause) => Some(cause),
125 Self::Integer(cause) => Some(cause),
126 Self::IntegerText(cause) => Some(cause),
127 Self::Slice(cause) => Some(cause),
128 Self::Utf8(cause) => Some(cause),
129 Self::Text(cause) => Some(cause),
130 Self::Allocation(cause) => Some(cause),
131 Self::Random(cause) => Some(cause),
132 Self::Secp256k1(cause) => Some(cause),
133 Self::Transaction(cause) => Some(cause),
134 Self::ChainDecode(cause) => Some(cause),
135 Self::Push(cause) => Some(cause),
136 Self::TaprootBuilder(cause) => Some(cause),
137 Self::Taproot(cause) => Some(cause),
138 Self::TaprootSighash(cause) => Some(cause),
139 Self::SegwitSighash(cause) => Some(cause),
140 Self::AddressScript(cause) => Some(cause),
141 Self::AddressParse(cause) => Some(cause),
142 Self::AddressNetwork(cause) => Some(cause),
143 Self::PublicKey(cause) => Some(cause),
144 Self::UncompressedKey(cause) => Some(cause),
145 Self::Ecdsa(cause) => Some(cause),
146 Self::Hash(cause) => Some(cause),
147 Self::ProofOfWork(cause) => Some(cause),
148 Self::Rpc(cause) => Some(cause),
149 Self::Http(cause) => Some(cause),
150 Self::Tls(cause) => Some(cause),
151 Self::Persist(cause) => Some(cause),
152 Self::Ip(cause) => Some(cause),
153 Self::Context { cause, .. } => Some(cause.as_ref()),
154 Self::HeaderContext(cause) => Some(cause),
155 _ => None,
156 }
157 }
158}
159impl Error {
160 pub fn is_header_context(&self) -> bool {
162 match self {
163 Self::HeaderContext(_) => true,
164 Self::Context { cause, .. } => cause.is_header_context(),
165 _ => false,
166 }
167 }
168
169 const MISSING_TRANSACTION_RPC_CODE: i32 = -5;
170
171 pub fn is_missing_transaction(&self) -> bool {
172 match self {
173 Self::Missing(_) => true,
174 Self::Rpc(RpcClientError::JsonRpc(JsonRpcError::Rpc(rejection))) => {
175 rejection.code == Self::MISSING_TRANSACTION_RPC_CODE
176 }
177 _ => false,
178 }
179 }
180}
181impl From<urma_chain::decode::DecodeError> for Error {
182 fn from(cause: urma_chain::decode::DecodeError) -> Self {
183 Self::ChainDecode(cause)
184 }
185}
186
187impl From<std::io::Error> for Error {
188 fn from(error: std::io::Error) -> Self {
189 Self::Io(error)
190 }
191}
192impl From<serde_json::Error> for Error {
193 fn from(error: serde_json::Error) -> Self {
194 Self::Json(error)
195 }
196}
197impl From<hex::FromHexError> for Error {
198 fn from(error: hex::FromHexError) -> Self {
199 Self::Hex(error)
200 }
201}
202impl From<url::ParseError> for Error {
203 fn from(error: url::ParseError) -> Self {
204 Self::Url(error)
205 }
206}
207impl From<std::num::TryFromIntError> for Error {
208 fn from(error: std::num::TryFromIntError) -> Self {
209 Self::Integer(error)
210 }
211}
212impl From<std::num::ParseIntError> for Error {
213 fn from(error: std::num::ParseIntError) -> Self {
214 Self::IntegerText(error)
215 }
216}
217impl From<std::array::TryFromSliceError> for Error {
218 fn from(error: std::array::TryFromSliceError) -> Self {
219 Self::Slice(error)
220 }
221}
222impl From<std::str::Utf8Error> for Error {
223 fn from(error: std::str::Utf8Error) -> Self {
224 Self::Utf8(error)
225 }
226}
227impl From<std::string::FromUtf8Error> for Error {
228 fn from(error: std::string::FromUtf8Error) -> Self {
229 Self::Text(error)
230 }
231}
232impl From<std::collections::TryReserveError> for Error {
233 fn from(error: std::collections::TryReserveError) -> Self {
234 Self::Allocation(error)
235 }
236}
237impl From<rand::Error> for Error {
238 fn from(error: rand::Error) -> Self {
239 Self::Random(error)
240 }
241}
242impl From<bitcoin::secp256k1::Error> for Error {
243 fn from(error: bitcoin::secp256k1::Error) -> Self {
244 Self::Secp256k1(error)
245 }
246}
247impl From<bitcoin::consensus::encode::Error> for Error {
248 fn from(error: bitcoin::consensus::encode::Error) -> Self {
249 Self::Transaction(error)
250 }
251}
252impl From<bitcoin::script::PushBytesError> for Error {
253 fn from(error: bitcoin::script::PushBytesError) -> Self {
254 Self::Push(error)
255 }
256}
257impl From<bitcoin::taproot::TaprootBuilderError> for Error {
258 fn from(error: bitcoin::taproot::TaprootBuilderError) -> Self {
259 Self::TaprootBuilder(error)
260 }
261}
262impl From<bitcoin::taproot::TaprootError> for Error {
263 fn from(error: bitcoin::taproot::TaprootError) -> Self {
264 Self::Taproot(error)
265 }
266}
267impl From<bitcoin::sighash::TaprootError> for Error {
268 fn from(error: bitcoin::sighash::TaprootError) -> Self {
269 Self::TaprootSighash(error)
270 }
271}
272impl From<bitcoin::sighash::P2wpkhError> for Error {
273 fn from(error: bitcoin::sighash::P2wpkhError) -> Self {
274 Self::SegwitSighash(error)
275 }
276}
277impl From<bitcoin::address::FromScriptError> for Error {
278 fn from(error: bitcoin::address::FromScriptError) -> Self {
279 Self::AddressScript(error)
280 }
281}
282impl From<bitcoin::address::ParseError> for Error {
283 fn from(error: bitcoin::address::ParseError) -> Self {
284 Self::AddressParse(error)
285 }
286}
287impl From<bitcoin::address::NetworkValidationError> for Error {
288 fn from(error: bitcoin::address::NetworkValidationError) -> Self {
289 Self::AddressNetwork(error)
290 }
291}
292impl From<bitcoin::key::FromSliceError> for Error {
293 fn from(error: bitcoin::key::FromSliceError) -> Self {
294 Self::PublicKey(error)
295 }
296}
297impl From<bitcoin::key::UncompressedPublicKeyError> for Error {
298 fn from(error: bitcoin::key::UncompressedPublicKeyError) -> Self {
299 Self::UncompressedKey(error)
300 }
301}
302impl From<bitcoin::ecdsa::Error> for Error {
303 fn from(error: bitcoin::ecdsa::Error) -> Self {
304 Self::Ecdsa(error)
305 }
306}
307impl From<bitcoin::hex::HexToArrayError> for Error {
308 fn from(error: bitcoin::hex::HexToArrayError) -> Self {
309 Self::Hash(error)
310 }
311}
312impl From<bitcoin::block::ValidationError> for Error {
313 fn from(error: bitcoin::block::ValidationError) -> Self {
314 Self::ProofOfWork(error)
315 }
316}
317impl From<bitcoincore_rpc::Error> for Error {
318 fn from(error: bitcoincore_rpc::Error) -> Self {
319 Self::Rpc(error)
320 }
321}
322impl From<minreq::Error> for Error {
323 fn from(error: minreq::Error) -> Self {
324 Self::Http(error)
325 }
326}
327impl From<rustls::Error> for Error {
328 fn from(error: rustls::Error) -> Self {
329 Self::Tls(error)
330 }
331}
332impl From<tempfile::PersistError> for Error {
333 fn from(error: tempfile::PersistError) -> Self {
334 Self::Persist(error)
335 }
336}
337impl From<std::net::AddrParseError> for Error {
338 fn from(error: std::net::AddrParseError) -> Self {
339 Self::Ip(error)
340 }
341}
342
343pub trait Context<T> {
344 fn context(self, message: impl Into<String>) -> Result<T, Error>;
345 fn with_context(self, message: impl FnOnce() -> String) -> Result<T, Error>;
346}
347impl<T> Context<T> for Option<T> {
348 fn context(self, message: impl Into<String>) -> Result<T, Error> {
349 match self {
350 Some(value) => Ok(value),
351 None => Err(Error::Missing(message.into())),
352 }
353 }
354 fn with_context(self, message: impl FnOnce() -> String) -> Result<T, Error> {
355 match self {
356 Some(value) => Ok(value),
357 None => Err(Error::Missing(message())),
358 }
359 }
360}
361impl<T, E: Into<Error>> Context<T> for Result<T, E> {
362 fn context(self, message: impl Into<String>) -> Result<T, Error> {
363 self.map_err(|cause| Error::Context {
364 message: message.into(),
365 cause: Box::new(cause.into()),
366 })
367 }
368 fn with_context(self, message: impl FnOnce() -> String) -> Result<T, Error> {
369 self.map_err(|cause| Error::Context {
370 message: message(),
371 cause: Box::new(cause.into()),
372 })
373 }
374}
375#[macro_export]
376macro_rules! ensure {
377 ($condition:expr, $($message:tt)*) => {
378 if !$condition { return Err($crate::error::Error::Invalid(format!($($message)*))); }
379 };
380}
381#[macro_export]
382macro_rules! bail {
383 ($($message:tt)*) => { return Err($crate::error::Error::Invalid(format!($($message)*))) };
384}
385pub use {bail, ensure};
386impl From<bitcoin::script::Error> for Error {
387 fn from(error: bitcoin::script::Error) -> Self {
388 Self::Script(error)
389 }
390}
391impl From<bitcoin::amount::ParseAmountError> for Error {
392 fn from(error: bitcoin::amount::ParseAmountError) -> Self {
393 Self::Amount(error)
394 }
395}
396
397impl From<urma_core::error::Error> for Error {
398 fn from(cause: urma_core::error::Error) -> Self {
399 Self::Protocol(cause)
400 }
401}
402
403impl From<urma_wallet::wallet::WalletError> for Error {
404 fn from(cause: urma_wallet::wallet::WalletError) -> Self {
405 Self::Wallet(cause)
406 }
407}
408
409impl From<urma_identity::error::IdentityError> for Error {
410 fn from(cause: urma_identity::error::IdentityError) -> Self {
411 Self::Identity(cause)
412 }
413}
414
415impl From<urma_io::Error> for Error {
416 fn from(cause: urma_io::Error) -> Self {
417 match cause {
418 urma_io::Error::Io(cause) => Self::Io(cause),
419 urma_io::Error::PrivatePermissions => {
420 Self::Invalid("file must have private permissions".into())
421 }
422 urma_io::Error::Integer(cause) => Self::Integer(cause),
423 urma_io::Error::LimitOverflow => Self::Missing("file read limit overflow".into()),
424 urma_io::Error::TooLarge { limit } => {
425 Self::Capacity(format!("file exceeds {limit} byte client capacity"))
426 }
427 urma_io::Error::Persist(cause) => Self::Persist(cause),
428 urma_io::Error::Context { message, cause } => Self::Context {
429 message,
430 cause: Box::new((*cause).into()),
431 },
432 }
433 }
434}
435
436impl From<BlockValidationError> for Error {
437 fn from(error: BlockValidationError) -> Self {
438 match error {
439 BlockValidationError::ProofOfWork(cause) => Self::Context {
440 message: "block proof of work".into(),
441 cause: Box::new(Self::ProofOfWork(cause)),
442 },
443 cause => Self::Invalid(cause.to_string()),
444 }
445 }
446}
447
448impl From<urma_chain::transaction::TransactionDecodeError> for Error {
449 fn from(cause: urma_chain::transaction::TransactionDecodeError) -> Self {
450 use urma_chain::transaction::TransactionDecodeError;
451 match cause {
452 TransactionDecodeError::LimitExceeded => Self::Invalid(cause.to_string()),
453 TransactionDecodeError::Hex(cause) => Self::Hex(cause),
454 TransactionDecodeError::Consensus(cause) => Self::Context {
455 message: "decode transaction".into(),
456 cause: Box::new(Self::Transaction(cause)),
457 },
458 }
459 }
460}