Skip to main content

urma_runtime/
error.rs

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    /// Local history is insufficient to check contextual header difficulty.
16    /// This does not establish that the peer supplied an invalid header.
17    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    /// Includes contextual wrappers used when reading a persistent header cache.
161    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}