diff --git a/bdk-ffi/Cargo.lock b/bdk-ffi/Cargo.lock index 9437eb2..1d552f7 100644 --- a/bdk-ffi/Cargo.lock +++ b/bdk-ffi/Cargo.lock @@ -161,6 +161,7 @@ version = "1.0.0-alpha.10" dependencies = [ "assert_matches", "bdk", + "bdk_electrum", "bdk_esplora", "bdk_file_store", "bitcoin-internals", @@ -179,6 +180,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bdk_electrum" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bbf3b0031651a37a48bdfab0c1d96a305b587f616593d34df9b1ff63efc4ff" +dependencies = [ + "bdk_chain", + "electrum-client", +] + [[package]] name = "bdk_esplora" version = "0.13.0" @@ -286,6 +297,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -382,6 +399,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "electrum-client" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89008f106be6f303695522f2f4c1f28b40c3e8367ed8b3bb227f1f882cb52cc2" +dependencies = [ + "bitcoin", + "byteorder", + "libc", + "log", + "rustls", + "serde", + "serde_json", + "webpki-roots", + "winapi", +] + [[package]] name = "esplora-client" version = "0.7.0" @@ -1129,6 +1163,28 @@ dependencies = [ "nom", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/bdk-ffi/Cargo.toml b/bdk-ffi/Cargo.toml index ba6bfbf..88c575c 100644 --- a/bdk-ffi/Cargo.toml +++ b/bdk-ffi/Cargo.toml @@ -20,6 +20,7 @@ default = ["uniffi/cli"] [dependencies] bdk = { version = "1.0.0-alpha.11", features = ["all-keys", "keys-bip39"] } bdk_esplora = { version = "0.13.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] } +bdk_electrum = { version = "0.13.0" } bdk_file_store = { version = "0.11.0" } uniffi = { version = "=0.26.1" } diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index e9fa9ec..7ad9f6e 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -101,6 +101,26 @@ interface DescriptorKeyError { Bip32(string error_message); }; +[Error] +interface ElectrumClientError { + IOError(string error_message); + Json(string error_message); + Hex(string error_message); + Protocol(string error_message); + Bitcoin(string error_message); + AlreadySubscribed(); + NotSubscribed(); + InvalidResponse(string error_message); + Message(string error_message); + InvalidDNSNameError(string domain); + MissingDomain(); + AllAttemptsErrored(); + SharedIOError(string error_message); + CouldntLockReader(); + Mpsc(); + CouldNotCreateConnection(string error_message); +}; + [Error] interface EsploraError { Minreq(string error_message); @@ -467,6 +487,15 @@ interface EsploraClient { void broadcast([ByRef] Transaction transaction); }; +// ------------------------------------------------------------------------ +// bdk_electrum crate +// ------------------------------------------------------------------------ + +interface ElectrumClient { + [Throws=ElectrumClientError] + constructor(string url); +}; + // ------------------------------------------------------------------------ // bdk-ffi-defined types // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/electrum.rs b/bdk-ffi/src/electrum.rs new file mode 100644 index 0000000..8f5ee2e --- /dev/null +++ b/bdk-ffi/src/electrum.rs @@ -0,0 +1,12 @@ +use crate::error::ElectrumClientError; + +use bdk_electrum::electrum_client::Client as BdkBlockingClient; + +pub struct ElectrumClient(BdkBlockingClient); + +impl ElectrumClient { + pub fn new(url: String) -> Result { + let client = BdkBlockingClient::new(url.as_str())?; + Ok(Self(client)) + } +} \ No newline at end of file diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index f950149..1f2e95f 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -13,6 +13,7 @@ use bdk::wallet::error::CreateTxError as BdkCreateTxError; use bdk::wallet::signer::SignerError as BdkSignerError; use bdk::wallet::tx_builder::AddUtxoError; use bdk::wallet::NewOrLoadError; +use bdk_electrum::electrum_client::Error as BdkElectrumError; use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error}; use bdk_file_store::FileError as BdkFileError; use bitcoin_internals::hex::display::DisplayHex; @@ -255,6 +256,57 @@ pub enum DescriptorKeyError { Bip32 { error_message: String }, } +#[derive(Debug, thiserror::Error)] +pub enum ElectrumClientError { + #[error("{error_message}")] + IOError { error_message: String }, + + #[error("{error_message}")] + Json { error_message: String }, + + #[error("{error_message}")] + Hex { error_message: String }, + + #[error("electrum server error: {error_message}")] + Protocol { error_message: String }, + + #[error("{error_message}")] + Bitcoin { error_message: String }, + + #[error("already subscribed to the notifications of an address")] + AlreadySubscribed, + + #[error("not subscribed to the notifications of an address")] + NotSubscribed, + + #[error("error during the deserialization of a response from the server: {error_message}")] + InvalidResponse { error_message: String }, + + #[error("{error_message}")] + Message { error_message: String }, + + #[error("invalid domain name {domain} not matching SSL certificate")] + InvalidDNSNameError { domain: String }, + + #[error("missing domain while it was explicitly asked to validate it")] + MissingDomain, + + #[error("made one or multiple attempts, all errored")] + AllAttemptsErrored, + + #[error("{error_message}")] + SharedIOError { error_message: String }, + + #[error("couldn't take a lock on the reader mutex. This means that there's already another reader thread is running")] + CouldntLockReader, + + #[error("broken IPC communication channel: the other thread probably has exited")] + Mpsc, + + #[error("{error_message}")] + CouldNotCreateConnection { error_message: String }, +} + #[derive(Debug, thiserror::Error)] pub enum EsploraError { #[error("minreq error: {error_message}")] @@ -503,6 +555,51 @@ impl From for AddressError { } } +impl From for ElectrumClientError { + fn from(error: BdkElectrumError) -> Self { + match error { + BdkElectrumError::IOError(e) => ElectrumClientError::IOError { + error_message: e.to_string(), + }, + BdkElectrumError::JSON(e) => ElectrumClientError::Json { + error_message: e.to_string(), + }, + BdkElectrumError::Hex(e) => ElectrumClientError::Hex { + error_message: e.to_string(), + }, + BdkElectrumError::Protocol(e) => ElectrumClientError::Protocol { + error_message: e.to_string(), + }, + BdkElectrumError::Bitcoin(e) => ElectrumClientError::Bitcoin { + error_message: e.to_string(), + }, + BdkElectrumError::AlreadySubscribed(_) => ElectrumClientError::AlreadySubscribed, + BdkElectrumError::NotSubscribed(_) => ElectrumClientError::NotSubscribed, + BdkElectrumError::InvalidResponse(e) => ElectrumClientError::InvalidResponse { + error_message: e.to_string(), + }, + BdkElectrumError::Message(e) => ElectrumClientError::Message { + error_message: e.to_string(), + }, + BdkElectrumError::InvalidDNSNameError(domain) => { + ElectrumClientError::InvalidDNSNameError { domain } + } + BdkElectrumError::MissingDomain => ElectrumClientError::MissingDomain, + BdkElectrumError::AllAttemptsErrored(_) => ElectrumClientError::AllAttemptsErrored, + BdkElectrumError::SharedIOError(e) => ElectrumClientError::SharedIOError { + error_message: e.to_string(), + }, + BdkElectrumError::CouldntLockReader => ElectrumClientError::CouldntLockReader, + BdkElectrumError::Mpsc => ElectrumClientError::Mpsc, + BdkElectrumError::CouldNotCreateConnection(error_message) => { + ElectrumClientError::CouldNotCreateConnection { + error_message: error_message.to_string(), + } + } + } + } +} + impl From for AddressError { fn from(error: ParseError) -> Self { match error { @@ -977,11 +1074,7 @@ impl From for WalletCreationError { #[cfg(test)] mod test { - use crate::error::{ - AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError, - DescriptorKeyError, EsploraError, ExtractTxError, FeeRateError, ParseAmountError, - PersistenceError, PsbtParseError, TransactionError, TxidParseError, WalletCreationError, - }; + use crate::error::{AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError, DescriptorKeyError, ElectrumClientError, EsploraError, ExtractTxError, FeeRateError, ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError, WalletCreationError}; use crate::CalculateFeeError; use crate::OutPoint; use crate::SignerError; @@ -1383,6 +1476,92 @@ mod test { } } + #[test] + fn test_error_electrum_client() { + let cases = vec![ + ( + ElectrumClientError::IOError { error_message: "message".to_string(), }, + "message", + ), + ( + ElectrumClientError::Json { error_message: "message".to_string(), }, + "message", + ), + ( + ElectrumClientError::Hex { error_message: "message".to_string(), }, + "message", + ), + ( + ElectrumClientError::Protocol { error_message: "message".to_string(), }, + "electrum server error: message", + ), + ( + ElectrumClientError::Bitcoin { + error_message: "message".to_string(), + }, + "message", + ), + ( + ElectrumClientError::AlreadySubscribed, + "already subscribed to the notifications of an address", + ), + ( + ElectrumClientError::NotSubscribed, + "not subscribed to the notifications of an address", + ), + ( + ElectrumClientError::InvalidResponse { + error_message: "message".to_string(), + }, + "error during the deserialization of a response from the server: message", + ), + ( + ElectrumClientError::Message { + error_message: "message".to_string(), + }, + "message", + ), + ( + ElectrumClientError::InvalidDNSNameError { + domain: "domain".to_string(), + }, + "invalid domain name domain not matching SSL certificate", + ), + ( + ElectrumClientError::MissingDomain, + "missing domain while it was explicitly asked to validate it", + ), + ( + ElectrumClientError::AllAttemptsErrored, + "made one or multiple attempts, all errored", + ), + ( + ElectrumClientError::SharedIOError { + error_message: "message".to_string(), + }, + "message", + ), + ( + ElectrumClientError::CouldntLockReader, + "couldn't take a lock on the reader mutex. This means that there's already another reader thread is running" + ), + ( + ElectrumClientError::Mpsc, + "broken IPC communication channel: the other thread probably has exited", + ), + ( + ElectrumClientError::CouldNotCreateConnection { + error_message: "message".to_string(), + }, + "message", + ) + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + #[test] fn test_error_esplora() { let cases = vec![ diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 878cae3..280d48a 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -1,5 +1,6 @@ mod bitcoin; mod descriptor; +mod electrum; mod error; mod esplora; mod keys; @@ -15,6 +16,7 @@ use crate::bitcoin::Script; use crate::bitcoin::Transaction; use crate::bitcoin::TxOut; use crate::descriptor::Descriptor; +use crate::electrum::ElectrumClient; use crate::error::AddressError; use crate::error::Bip32Error; use crate::error::Bip39Error; @@ -23,6 +25,7 @@ use crate::error::CannotConnectError; use crate::error::CreateTxError; use crate::error::DescriptorError; use crate::error::DescriptorKeyError; +use crate::error::ElectrumClientError; use crate::error::EsploraError; use crate::error::ExtractTxError; use crate::error::FeeRateError;