diff --git a/crates/electrum/src/bdk_electrum_client.rs b/crates/electrum/src/bdk_electrum_client.rs index 4034a7fa69..1dfd4a268a 100644 --- a/crates/electrum/src/bdk_electrum_client.rs +++ b/crates/electrum/src/bdk_electrum_client.rs @@ -6,7 +6,9 @@ use bdk_core::{ }; use electrum_client::{ElectrumApi, Error, HeaderNotification}; use std::{ + borrow::Borrow, collections::HashSet, + marker::PhantomData, sync::{Arc, Mutex}, }; @@ -16,22 +18,36 @@ const CHAIN_SUFFIX_LENGTH: u32 = 8; /// Wrapper around an [`electrum_client::ElectrumApi`] which includes an internal in-memory /// transaction cache to avoid re-fetching already downloaded transactions. #[derive(Debug)] -pub struct BdkElectrumClient { +pub struct BdkElectrumClient { /// The internal [`electrum_client::ElectrumApi`] pub inner: E, /// The transaction cache tx_cache: Mutex>>, /// The header cache block_header_cache: Mutex>, + _marker: PhantomData, } -impl BdkElectrumClient { - /// Creates a new bdk client from a [`electrum_client::ElectrumApi`] +impl> BdkElectrumClient { + /// Creates a new bdk client from any type that can be borrowed as [`electrum_client::Client`]. pub fn new(client: E) -> Self { Self { inner: client, tx_cache: Default::default(), block_header_cache: Default::default(), + _marker: PhantomData, + } + } +} + +impl, C: ElectrumApi> BdkElectrumClient { + /// Creates a new bdk client from any type that implements [`electrum_client::ElectrumApi`]. + pub fn with_custom_client(client: E) -> Self { + Self { + inner: client, + tx_cache: Default::default(), + block_header_cache: Default::default(), + _marker: PhantomData, } } @@ -58,7 +74,7 @@ impl BdkElectrumClient { drop(tx_cache); - let tx = Arc::new(self.inner.transaction_get(&txid)?); + let tx = Arc::new(self.inner.borrow().transaction_get(&txid)?); self.tx_cache.lock().unwrap().insert(txid, Arc::clone(&tx)); @@ -82,7 +98,7 @@ impl BdkElectrumClient { /// Update a block header at given `height`. Returns the updated header. fn update_header(&self, height: u32) -> Result { - let header = self.inner.block_header(height as usize)?; + let header = self.inner.borrow().block_header(height as usize)?; self.block_header_cache .lock() @@ -96,7 +112,7 @@ impl BdkElectrumClient { /// /// This is a re-export of [`ElectrumApi::transaction_broadcast`]. pub fn transaction_broadcast(&self, tx: &Transaction) -> Result { - self.inner.transaction_broadcast(tx) + self.inner.borrow().transaction_broadcast(tx) } /// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and @@ -130,7 +146,7 @@ impl BdkElectrumClient { let mut request: FullScanRequest = request.into(); let tip_and_latest_blocks = match request.chain_tip() { - Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?), + Some(chain_tip) => Some(fetch_tip_and_latest_blocks(self.inner.borrow(), chain_tip)?), None => None, }; @@ -198,7 +214,7 @@ impl BdkElectrumClient { let mut request: SyncRequest = request.into(); let tip_and_latest_blocks = match request.chain_tip() { - Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?), + Some(chain_tip) => Some(fetch_tip_and_latest_blocks(self.inner.borrow(), chain_tip)?), None => None, }; @@ -260,6 +276,7 @@ impl BdkElectrumClient { let spk_histories = self .inner + .borrow() .batch_script_get_history(spks.iter().map(|(_, s)| s.as_script()))?; for ((spk_index, _spk), spk_history) in spks.into_iter().zip(spk_histories) { @@ -304,7 +321,11 @@ impl BdkElectrumClient { // add to our sparsechain `update`: let mut has_residing = false; // tx in which the outpoint resides let mut has_spending = false; // tx that spends the outpoint - for res in self.inner.script_get_history(&op_txout.script_pubkey)? { + for res in self + .inner + .borrow() + .script_get_history(&op_txout.script_pubkey)? + { if has_residing && has_spending { break; } @@ -356,6 +377,7 @@ impl BdkElectrumClient { // call to get confirmation status of our transaction if let Some(r) = self .inner + .borrow() .script_get_history(spk)? .into_iter() .find(|r| r.tx_hash == txid) @@ -378,6 +400,7 @@ impl BdkElectrumClient { ) -> Result<(), Error> { if let Ok(merkle_res) = self .inner + .borrow() .transaction_get_merkle(&txid, confirmation_height as usize) { let mut header = self.fetch_header(merkle_res.block_height as u32)?; diff --git a/crates/electrum/tests/test_electrum.rs b/crates/electrum/tests/test_electrum.rs index 7794589a6e..27b6d2eeaa 100644 --- a/crates/electrum/tests/test_electrum.rs +++ b/crates/electrum/tests/test_electrum.rs @@ -8,8 +8,11 @@ use bdk_chain::{ use bdk_electrum::BdkElectrumClient; use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv}; use core::time::Duration; -use std::collections::{BTreeSet, HashSet}; use std::str::FromStr; +use std::{ + collections::{BTreeSet, HashSet}, + sync::Arc, +}; // Batch size for `sync_with_electrum`. const BATCH_SIZE: usize = 5; @@ -676,3 +679,42 @@ fn test_check_fee_calculation() -> anyhow::Result<()> { } Ok(()) } + +#[test] +fn borrowed() -> anyhow::Result<()> { + let env = TestEnv::new()?; + + let electrum_client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + let _client = BdkElectrumClient::new(electrum_client); + drop(_client); + + let electrum_client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + let _client = BdkElectrumClient::new(&electrum_client); + drop(_client); + + let electrum_client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + let _client = BdkElectrumClient::new(Arc::new(electrum_client)); + drop(_client); + + let electrum_client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + let _client = BdkElectrumClient::new(Box::new(electrum_client)); + drop(_client); + + let electrum_client = + electrum_client::raw_client::RawClient::new(&env.electrsd.electrum_url, None)?; + let _client = + BdkElectrumClient::<_, electrum_client::raw_client::RawClient<_>>::with_custom_client( + electrum_client, + ); + drop(_client); + + let electrum_client = + electrum_client::raw_client::RawClient::new(&env.electrsd.electrum_url, None)?; + let _client = + BdkElectrumClient::<_, electrum_client::raw_client::RawClient<_>>::with_custom_client( + Box::new(electrum_client), + ); + drop(_client); + + Ok(()) +}