From b249dae875266a52afb711516ff4d0a0f9867083 Mon Sep 17 00:00:00 2001 From: Matthew <6657488+reez@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:09:46 -0600 Subject: [PATCH] feat: add esplora error --- bdk-ffi/src/bdk.udl | 17 ++++- bdk-ffi/src/error.rs | 138 +++++++++++++++++++++++++++++++++++++++++ bdk-ffi/src/esplora.rs | 8 +-- bdk-ffi/src/lib.rs | 1 + 4 files changed, 159 insertions(+), 5 deletions(-) diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 560f3a5..aa8a3ab 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -27,6 +27,21 @@ interface WalletCreationError { LoadedNetworkDoesNotMatch(Network expected, Network? got); }; +[Error] +interface EsploraError { + Ureq(string error_message); + UreqTransport(string error_message); + Http(u16 status_code); + Io(string error_message); + NoHeader(); + Parsing(string error_message); + BitcoinEncoding(string error_message); + Hex(string error_message); + TransactionNotFound(); + HeaderHeightNotFound(u32 height); + HeaderHashNotFound(); +}; + // ------------------------------------------------------------------------ // bdk crate - types module // ------------------------------------------------------------------------ @@ -274,7 +289,7 @@ interface Descriptor { interface EsploraClient { constructor(string url); - [Throws=Alpha3Error] + [Throws=EsploraError] Update full_scan(Wallet wallet, u64 stop_gap, u64 parallel_requests); [Throws=Alpha3Error] diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 68a13aa..756ed98 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -1,6 +1,7 @@ use crate::bitcoin::OutPoint; use bdk::chain::tx_graph::CalculateFeeError as BdkCalculateFeeError; +use bdk_esplora::esplora_client::Error as BdkEsploraError; use std::fmt; @@ -186,8 +187,88 @@ impl From for CalculateFeeError { impl std::error::Error for CalculateFeeError {} +#[derive(Debug)] +pub enum EsploraError { + Ureq { error_message: String }, + UreqTransport { error_message: String }, + Http { status_code: u16 }, + Io { error_message: String }, + NoHeader, + Parsing { error_message: String }, + BitcoinEncoding { error_message: String }, + Hex { error_message: String }, + TransactionNotFound, + HeaderHeightNotFound { height: u32 }, + HeaderHashNotFound, +} + +impl fmt::Display for EsploraError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + EsploraError::Ureq { error_message } => write!(f, "Ureq error: {}", error_message), + EsploraError::UreqTransport { error_message } => { + write!(f, "Ureq transport error: {}", error_message) + } + EsploraError::Http { status_code } => { + write!(f, "HTTP error with status code: {}", status_code) + } + EsploraError::Io { error_message } => write!(f, "IO error: {}", error_message), + EsploraError::NoHeader => write!(f, "No header found in the response"), + EsploraError::Parsing { error_message } => { + write!(f, "Parsing error: {}", error_message) + } + EsploraError::BitcoinEncoding { error_message } => { + write!(f, "Bitcoin encoding error: {}", error_message) + } + EsploraError::Hex { error_message } => { + write!(f, "Hex decoding error: {}", error_message) + } + EsploraError::TransactionNotFound => write!(f, "Transaction not found"), + EsploraError::HeaderHeightNotFound { height } => { + write!(f, "Header height {} not found", height) + } + EsploraError::HeaderHashNotFound => write!(f, "Header hash not found"), + } + } +} + +impl From for EsploraError { + fn from(error: BdkEsploraError) -> Self { + match error { + BdkEsploraError::Ureq(e) => EsploraError::Ureq { + error_message: e.to_string(), + }, + BdkEsploraError::UreqTransport(e) => EsploraError::UreqTransport { + error_message: e.to_string(), + }, + BdkEsploraError::HttpResponse(code) => EsploraError::Http { status_code: code }, + BdkEsploraError::Io(e) => EsploraError::Io { + error_message: e.to_string(), + }, + BdkEsploraError::NoHeader => EsploraError::NoHeader, + BdkEsploraError::Parsing(e) => EsploraError::Parsing { + error_message: e.to_string(), + }, + BdkEsploraError::BitcoinEncoding(e) => EsploraError::BitcoinEncoding { + error_message: e.to_string(), + }, + BdkEsploraError::Hex(e) => EsploraError::Hex { + error_message: e.to_string(), + }, + BdkEsploraError::TransactionNotFound(_) => EsploraError::TransactionNotFound, + BdkEsploraError::HeaderHeightNotFound(height) => { + EsploraError::HeaderHeightNotFound { height } + } + BdkEsploraError::HeaderHashNotFound(_) => EsploraError::HeaderHashNotFound, + } + } +} + +impl std::error::Error for EsploraError {} + #[cfg(test)] mod test { + use crate::error::EsploraError; use crate::CalculateFeeError; use crate::OutPoint; @@ -231,4 +312,61 @@ mod test { assert_eq!(error.to_string(), "Negative fee value: -100"); } + + #[test] + fn test_esplora_errors() { + let cases = vec![ + ( + EsploraError::Ureq { + error_message: "Network error".to_string(), + }, + "Ureq error: Network error", + ), + ( + EsploraError::UreqTransport { + error_message: "Timeout occurred".to_string(), + }, + "Ureq transport error: Timeout occurred", + ), + ( + EsploraError::Http { status_code: 404 }, + "HTTP error with status code: 404", + ), + ( + EsploraError::Io { + error_message: "File not found".to_string(), + }, + "IO error: File not found", + ), + (EsploraError::NoHeader, "No header found in the response"), + ( + EsploraError::Parsing { + error_message: "Invalid JSON".to_string(), + }, + "Parsing error: Invalid JSON", + ), + ( + EsploraError::BitcoinEncoding { + error_message: "Bad format".to_string(), + }, + "Bitcoin encoding error: Bad format", + ), + ( + EsploraError::Hex { + error_message: "Invalid hex".to_string(), + }, + "Hex decoding error: Invalid hex", + ), + (EsploraError::TransactionNotFound, "Transaction not found"), + ( + EsploraError::HeaderHeightNotFound { height: 123456 }, + "Header height 123456 not found", + ), + (EsploraError::HeaderHashNotFound, "Header hash not found"), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } } diff --git a/bdk-ffi/src/esplora.rs b/bdk-ffi/src/esplora.rs index fc9ff2f..6fcde42 100644 --- a/bdk-ffi/src/esplora.rs +++ b/bdk-ffi/src/esplora.rs @@ -1,4 +1,4 @@ -use crate::error::Alpha3Error; +use crate::error::{Alpha3Error, EsploraError}; use crate::wallet::{Update, Wallet}; use bdk::bitcoin::Transaction as BdkTransaction; @@ -24,7 +24,7 @@ impl EsploraClient { wallet: Arc, stop_gap: u64, parallel_requests: u64, - ) -> Result, Alpha3Error> { + ) -> Result, EsploraError> { let wallet = wallet.get_wallet(); let previous_tip = wallet.latest_checkpoint(); @@ -33,13 +33,13 @@ impl EsploraClient { let (update_graph, last_active_indices) = self .0 .full_scan(keychain_spks, stop_gap as usize, parallel_requests as usize) - .unwrap(); + .map_err(|e| EsploraError::from(*e))?; let missing_heights = update_graph.missing_heights(wallet.local_chain()); let chain_update = self .0 .update_local_chain(previous_tip, missing_heights) - .unwrap(); + .map_err(|e| EsploraError::from(*e))?; let update = BdkUpdate { last_active_indices, diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index ce1c458..31828e3 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -15,6 +15,7 @@ use crate::bitcoin::TxOut; use crate::descriptor::Descriptor; use crate::error::Alpha3Error; use crate::error::CalculateFeeError; +use crate::error::EsploraError; use crate::esplora::EsploraClient; use crate::keys::DerivationPath; use crate::keys::DescriptorPublicKey;