feat: add simple electrum client
This commit is contained in:
parent
c88b33473b
commit
8d30c86076
56
bdk-ffi/Cargo.lock
generated
56
bdk-ffi/Cargo.lock
generated
@ -161,6 +161,7 @@ version = "1.0.0-alpha.10"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"bdk",
|
"bdk",
|
||||||
|
"bdk_electrum",
|
||||||
"bdk_esplora",
|
"bdk_esplora",
|
||||||
"bdk_file_store",
|
"bdk_file_store",
|
||||||
"bitcoin-internals",
|
"bitcoin-internals",
|
||||||
@ -179,6 +180,16 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "bdk_esplora"
|
name = "bdk_esplora"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -286,6 +297,12 @@ version = "3.16.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -382,6 +399,23 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
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]]
|
[[package]]
|
||||||
name = "esplora-client"
|
name = "esplora-client"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -1129,6 +1163,28 @@ dependencies = [
|
|||||||
"nom",
|
"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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -20,6 +20,7 @@ default = ["uniffi/cli"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bdk = { version = "1.0.0-alpha.11", features = ["all-keys", "keys-bip39"] }
|
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_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" }
|
bdk_file_store = { version = "0.11.0" }
|
||||||
|
|
||||||
uniffi = { version = "=0.26.1" }
|
uniffi = { version = "=0.26.1" }
|
||||||
|
@ -101,6 +101,26 @@ interface DescriptorKeyError {
|
|||||||
Bip32(string error_message);
|
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]
|
[Error]
|
||||||
interface EsploraError {
|
interface EsploraError {
|
||||||
Minreq(string error_message);
|
Minreq(string error_message);
|
||||||
@ -467,6 +487,15 @@ interface EsploraClient {
|
|||||||
void broadcast([ByRef] Transaction transaction);
|
void broadcast([ByRef] Transaction transaction);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// bdk_electrum crate
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ElectrumClient {
|
||||||
|
[Throws=ElectrumClientError]
|
||||||
|
constructor(string url);
|
||||||
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk-ffi-defined types
|
// bdk-ffi-defined types
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
12
bdk-ffi/src/electrum.rs
Normal file
12
bdk-ffi/src/electrum.rs
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ use bdk::wallet::error::CreateTxError as BdkCreateTxError;
|
|||||||
use bdk::wallet::signer::SignerError as BdkSignerError;
|
use bdk::wallet::signer::SignerError as BdkSignerError;
|
||||||
use bdk::wallet::tx_builder::AddUtxoError;
|
use bdk::wallet::tx_builder::AddUtxoError;
|
||||||
use bdk::wallet::NewOrLoadError;
|
use bdk::wallet::NewOrLoadError;
|
||||||
|
use bdk_electrum::electrum_client::Error as BdkElectrumError;
|
||||||
use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error};
|
use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error};
|
||||||
use bdk_file_store::FileError as BdkFileError;
|
use bdk_file_store::FileError as BdkFileError;
|
||||||
use bitcoin_internals::hex::display::DisplayHex;
|
use bitcoin_internals::hex::display::DisplayHex;
|
||||||
@ -255,6 +256,57 @@ pub enum DescriptorKeyError {
|
|||||||
Bip32 { error_message: String },
|
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)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum EsploraError {
|
pub enum EsploraError {
|
||||||
#[error("minreq error: {error_message}")]
|
#[error("minreq error: {error_message}")]
|
||||||
@ -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 {
|
impl From<ParseError> for AddressError {
|
||||||
fn from(error: ParseError) -> Self {
|
fn from(error: ParseError) -> Self {
|
||||||
match error {
|
match error {
|
||||||
@ -977,11 +1074,7 @@ impl From<NewOrLoadError> for WalletCreationError {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::error::{
|
use crate::error::{AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError, DescriptorKeyError, ElectrumClientError, EsploraError, ExtractTxError, FeeRateError, ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError, WalletCreationError};
|
||||||
AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError,
|
|
||||||
DescriptorKeyError, EsploraError, ExtractTxError, FeeRateError, ParseAmountError,
|
|
||||||
PersistenceError, PsbtParseError, TransactionError, TxidParseError, WalletCreationError,
|
|
||||||
};
|
|
||||||
use crate::CalculateFeeError;
|
use crate::CalculateFeeError;
|
||||||
use crate::OutPoint;
|
use crate::OutPoint;
|
||||||
use crate::SignerError;
|
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]
|
#[test]
|
||||||
fn test_error_esplora() {
|
fn test_error_esplora() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mod bitcoin;
|
mod bitcoin;
|
||||||
mod descriptor;
|
mod descriptor;
|
||||||
|
mod electrum;
|
||||||
mod error;
|
mod error;
|
||||||
mod esplora;
|
mod esplora;
|
||||||
mod keys;
|
mod keys;
|
||||||
@ -15,6 +16,7 @@ use crate::bitcoin::Script;
|
|||||||
use crate::bitcoin::Transaction;
|
use crate::bitcoin::Transaction;
|
||||||
use crate::bitcoin::TxOut;
|
use crate::bitcoin::TxOut;
|
||||||
use crate::descriptor::Descriptor;
|
use crate::descriptor::Descriptor;
|
||||||
|
use crate::electrum::ElectrumClient;
|
||||||
use crate::error::AddressError;
|
use crate::error::AddressError;
|
||||||
use crate::error::Bip32Error;
|
use crate::error::Bip32Error;
|
||||||
use crate::error::Bip39Error;
|
use crate::error::Bip39Error;
|
||||||
@ -23,6 +25,7 @@ use crate::error::CannotConnectError;
|
|||||||
use crate::error::CreateTxError;
|
use crate::error::CreateTxError;
|
||||||
use crate::error::DescriptorError;
|
use crate::error::DescriptorError;
|
||||||
use crate::error::DescriptorKeyError;
|
use crate::error::DescriptorKeyError;
|
||||||
|
use crate::error::ElectrumClientError;
|
||||||
use crate::error::EsploraError;
|
use crate::error::EsploraError;
|
||||||
use crate::error::ExtractTxError;
|
use crate::error::ExtractTxError;
|
||||||
use crate::error::FeeRateError;
|
use crate::error::FeeRateError;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user