Skip to content

Commit

Permalink
feat: add simple electrum client
Browse files Browse the repository at this point in the history
  • Loading branch information
thunderbiscuit committed May 14, 2024
1 parent 97d9bb6 commit 19016a8
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 5 deletions.
56 changes: 56 additions & 0 deletions bdk-ffi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
29 changes: 29 additions & 0 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -464,6 +484,15 @@ interface EsploraClient {
void broadcast([ByRef] Transaction transaction);
};

// ------------------------------------------------------------------------
// bdk_electrum crate
// ------------------------------------------------------------------------

interface ElectrumClient {
[Throws=ElectrumClientError]
constructor(string url);
};

// ------------------------------------------------------------------------
// bdk-ffi-defined types
// ------------------------------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions bdk-ffi/src/electrum.rs
Original file line number Diff line number Diff line change
@@ -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<Self, ElectrumClientError> {
let client = BdkBlockingClient::new(url.as_str())?;
Ok(Self(client))
}
}
189 changes: 184 additions & 5 deletions bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}")]
Expand Down Expand Up @@ -503,6 +555,51 @@ impl From<BdkAddressError> for AddressError {
}
}

impl From<BdkElectrumError> 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<ParseError> for AddressError {
fn from(error: ParseError) -> Self {
match error {
Expand Down Expand Up @@ -977,11 +1074,7 @@ impl From<NewOrLoadError> 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;
Expand Down Expand Up @@ -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![
Expand Down
Loading

0 comments on commit 19016a8

Please sign in to comment.