Skip to main content

esplora_client/
lib.rs

1//! An extensible blocking/async Esplora client
2//!
3//! This library provides an extensible blocking and
4//! async Esplora client to query Esplora's backend.
5//!
6//! The library provides the possibility to build a blocking
7//! client using [`minreq`] and an async client using [`reqwest`].
8//! The library supports communicating to Esplora via a proxy
9//! and also using TLS (SSL) for secure communication.
10//!
11//!
12//! ## Usage
13//!
14//! You can create a blocking client as follows:
15//!
16//! ```no_run
17//! # #[cfg(feature = "blocking")]
18//! # {
19//! use esplora_client::Builder;
20//! let builder = Builder::new("https://blockstream.info/testnet/api");
21//! let blocking_client = builder.build_blocking();
22//! # Ok::<(), esplora_client::Error>(());
23//! # }
24//! ```
25//!
26//! Here is an example of how to create an asynchronous client.
27//!
28//! ```no_run
29//! # #[cfg(all(feature = "async", feature = "tokio"))]
30//! # {
31//! use esplora_client::Builder;
32//! let builder = Builder::new("https://blockstream.info/testnet/api");
33//! let async_client = builder.build_async();
34//! # Ok::<(), esplora_client::Error>(());
35//! # }
36//! ```
37//!
38//! ## Features
39//!
40//! By default the library enables all features. To specify
41//! specific features, set `default-features` to `false` in your `Cargo.toml`
42//! and specify the features you want. This will look like this:
43//!
44//! `esplora-client = { version = "*", default-features = false, features =
45//! ["blocking"] }`
46//!
47//! * `blocking` enables [`minreq`], the blocking client with proxy.
48//! * `blocking-https` enables [`minreq`], the blocking client with proxy and TLS (SSL) capabilities
49//!   using the default [`minreq`] backend.
50//! * `blocking-https-rustls` enables [`minreq`], the blocking client with proxy and TLS (SSL)
51//!   capabilities using the `rustls` backend.
52//! * `blocking-https-native` enables [`minreq`], the blocking client with proxy and TLS (SSL)
53//!   capabilities using the platform's native TLS backend (likely OpenSSL).
54//! * `blocking-https-bundled` enables [`minreq`], the blocking client with proxy and TLS (SSL)
55//!   capabilities using a bundled OpenSSL library backend.
56//! * `async` enables [`reqwest`], the async client with proxy capabilities.
57//! * `async-https` enables [`reqwest`], the async client with support for proxying and TLS (SSL)
58//!   using the default [`reqwest`] TLS backend.
59//! * `async-https-native` enables [`reqwest`], the async client with support for proxying and TLS
60//!   (SSL) using the platform's native TLS backend (likely OpenSSL).
61//! * `async-https-rustls` enables [`reqwest`], the async client with support for proxying and TLS
62//!   (SSL) using the `rustls` TLS backend.
63//! * `async-https-rustls-manual-roots` enables [`reqwest`], the async client with support for
64//!   proxying and TLS (SSL) using the `rustls` TLS backend without using the default root
65//!   certificates.
66//!
67//! [`dont remove this line or cargo doc will break`]: https://example.com
68#![cfg_attr(not(feature = "minreq"), doc = "[`minreq`]: https://docs.rs/minreq")]
69#![cfg_attr(not(feature = "reqwest"), doc = "[`reqwest`]: https://docs.rs/reqwest")]
70#![allow(clippy::result_large_err)]
71#![warn(missing_docs)]
72
73use std::collections::HashMap;
74use std::fmt;
75use std::num::TryFromIntError;
76use std::time::Duration;
77
78#[cfg(feature = "async")]
79pub use r#async::Sleeper;
80
81pub mod api;
82#[cfg(feature = "async")]
83pub mod r#async;
84#[cfg(feature = "blocking")]
85pub mod blocking;
86
87pub use api::*;
88#[cfg(feature = "blocking")]
89pub use blocking::BlockingClient;
90#[cfg(feature = "async")]
91pub use r#async::AsyncClient;
92
93/// Response status codes for which the request may be retried.
94pub const RETRYABLE_ERROR_CODES: [u16; 3] = [
95    429, // TOO_MANY_REQUESTS
96    500, // INTERNAL_SERVER_ERROR
97    503, // SERVICE_UNAVAILABLE
98];
99
100/// Base backoff in milliseconds.
101const BASE_BACKOFF_MILLIS: Duration = Duration::from_millis(256);
102
103/// Default max retries.
104const DEFAULT_MAX_RETRIES: usize = 6;
105
106/// Get a fee value in sats/vbytes from the estimates
107/// that matches the confirmation target set as parameter.
108///
109/// Returns `None` if no feerate estimate is found at or below `target`
110/// confirmations.
111pub fn convert_fee_rate(target: usize, estimates: HashMap<u16, f64>) -> Option<f32> {
112    estimates
113        .into_iter()
114        .filter(|(k, _)| *k as usize <= target)
115        .max_by_key(|(k, _)| *k)
116        .map(|(_, v)| v as f32)
117}
118
119/// A builder for an [`AsyncClient`] or [`BlockingClient`].
120#[derive(Debug, Clone)]
121pub struct Builder {
122    /// The URL of the Esplora server.
123    pub base_url: String,
124    /// Optional URL of the proxy to use to make requests to the Esplora server
125    ///
126    /// The string should be formatted as:
127    /// `<protocol>://<user>:<password>@host:<port>`.
128    ///
129    /// Note that the format of this value and the supported protocols change
130    /// slightly between the blocking version of the client (using `minreq`)
131    /// and the async version (using `reqwest`). For more details check with
132    /// the documentation of the two crates. Both of them are compiled with
133    /// the `socks` feature enabled.
134    ///
135    /// The proxy is ignored when targeting `wasm32`.
136    pub proxy: Option<String>,
137    /// Socket timeout.
138    pub timeout: Option<u64>,
139    /// HTTP headers to set on every request made to Esplora server.
140    pub headers: HashMap<String, String>,
141    /// Max retries
142    pub max_retries: usize,
143}
144
145impl Builder {
146    /// Instantiate a new builder
147    pub fn new(base_url: &str) -> Self {
148        Builder {
149            base_url: base_url.to_string(),
150            proxy: None,
151            timeout: None,
152            headers: HashMap::new(),
153            max_retries: DEFAULT_MAX_RETRIES,
154        }
155    }
156
157    /// Set the proxy of the builder
158    pub fn proxy(mut self, proxy: &str) -> Self {
159        self.proxy = Some(proxy.to_string());
160        self
161    }
162
163    /// Set the timeout of the builder
164    pub fn timeout(mut self, timeout: u64) -> Self {
165        self.timeout = Some(timeout);
166        self
167    }
168
169    /// Add a header to set on each request
170    pub fn header(mut self, key: &str, value: &str) -> Self {
171        self.headers.insert(key.to_string(), value.to_string());
172        self
173    }
174
175    /// Set the maximum number of times to retry a request if the response status
176    /// is one of [`RETRYABLE_ERROR_CODES`].
177    pub fn max_retries(mut self, count: usize) -> Self {
178        self.max_retries = count;
179        self
180    }
181
182    /// Build a blocking client from builder
183    #[cfg(feature = "blocking")]
184    pub fn build_blocking(self) -> BlockingClient {
185        BlockingClient::from_builder(self)
186    }
187
188    /// Build an asynchronous client from builder
189    #[cfg(all(feature = "async", feature = "tokio"))]
190    pub fn build_async(self) -> Result<AsyncClient, Error> {
191        AsyncClient::from_builder(self)
192    }
193
194    /// Build an asynchronous client from builder where the returned client uses a
195    /// user-defined [`Sleeper`].
196    #[cfg(feature = "async")]
197    pub fn build_async_with_sleeper<S: Sleeper>(self) -> Result<AsyncClient<S>, Error> {
198        AsyncClient::from_builder(self)
199    }
200}
201
202/// Errors that can happen during a request to `Esplora` servers.
203#[derive(Debug)]
204pub enum Error {
205    /// Error during `minreq` HTTP request
206    #[cfg(feature = "blocking")]
207    Minreq(::minreq::Error),
208    /// Error during reqwest HTTP request
209    #[cfg(feature = "async")]
210    Reqwest(::reqwest::Error),
211    /// HTTP response error
212    HttpResponse {
213        /// The HTTP status code returned by the server.
214        status: u16,
215        /// The error message content.
216        message: String,
217    },
218    /// Invalid number returned
219    Parsing(std::num::ParseIntError),
220    /// Invalid status code, unable to convert to `u16`
221    StatusCode(TryFromIntError),
222    /// Invalid Bitcoin data returned
223    BitcoinEncoding(bitcoin::consensus::encode::Error),
224    /// Invalid hex data returned (attempting to create an array)
225    HexToArray(bitcoin::hex::HexToArrayError),
226    /// Invalid hex data returned (attempting to create a vector)
227    HexToBytes(bitcoin::hex::HexToBytesError),
228    /// Transaction not found
229    TransactionNotFound(Txid),
230    /// Block Header height not found
231    HeaderHeightNotFound(u32),
232    /// Block Header hash not found
233    HeaderHashNotFound(BlockHash),
234    /// Invalid HTTP Header name specified
235    InvalidHttpHeaderName(String),
236    /// Invalid HTTP Header value specified
237    InvalidHttpHeaderValue(String),
238    /// The server sent an invalid response
239    InvalidResponse,
240}
241
242impl fmt::Display for Error {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        write!(f, "{self:?}")
245    }
246}
247
248macro_rules! impl_error {
249    ( $from:ty, $to:ident ) => {
250        impl_error!($from, $to, Error);
251    };
252    ( $from:ty, $to:ident, $impl_for:ty ) => {
253        impl std::convert::From<$from> for $impl_for {
254            fn from(err: $from) -> Self {
255                <$impl_for>::$to(err)
256            }
257        }
258    };
259}
260
261impl std::error::Error for Error {}
262#[cfg(feature = "blocking")]
263impl_error!(::minreq::Error, Minreq, Error);
264#[cfg(feature = "async")]
265impl_error!(::reqwest::Error, Reqwest, Error);
266impl_error!(std::num::ParseIntError, Parsing, Error);
267impl_error!(bitcoin::consensus::encode::Error, BitcoinEncoding, Error);
268impl_error!(bitcoin::hex::HexToArrayError, HexToArray, Error);
269impl_error!(bitcoin::hex::HexToBytesError, HexToBytes, Error);
270
271#[cfg(test)]
272mod test {
273    use super::*;
274    use electrsd::{corepc_node, ElectrsD};
275    use lazy_static::lazy_static;
276    use std::env;
277    use std::str::FromStr;
278    use tokio::sync::Mutex;
279    #[cfg(all(feature = "blocking", feature = "async"))]
280    use {
281        bitcoin::{hashes::Hash, Amount},
282        corepc_node::AddressType,
283        electrsd::electrum_client::ElectrumApi,
284        std::time::Duration,
285        tokio::sync::OnceCell,
286    };
287
288    lazy_static! {
289        static ref BITCOIND: corepc_node::Node = {
290            let bitcoind_exe = env::var("BITCOIND_EXE")
291                .ok()
292                .or_else(|| corepc_node::downloaded_exe_path().ok())
293                .expect(
294                    "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
295                );
296            let conf = corepc_node::Conf::default();
297            corepc_node::Node::with_conf(bitcoind_exe, &conf).unwrap()
298        };
299        static ref ELECTRSD: ElectrsD = {
300            let electrs_exe = env::var("ELECTRS_EXE")
301                .ok()
302                .or_else(electrsd::downloaded_exe_path)
303                .expect(
304                    "you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
305                );
306            let mut conf = electrsd::Conf::default();
307            conf.http_enabled = true;
308            ElectrsD::with_conf(electrs_exe, &BITCOIND, &conf).unwrap()
309        };
310        static ref MINER: Mutex<()> = Mutex::new(());
311    }
312
313    #[cfg(all(feature = "blocking", feature = "async"))]
314    static PREMINE: OnceCell<()> = OnceCell::const_new();
315
316    #[cfg(all(feature = "blocking", feature = "async"))]
317    async fn setup_clients() -> (BlockingClient, AsyncClient) {
318        setup_clients_with_headers(HashMap::new()).await
319    }
320
321    #[cfg(all(feature = "blocking", feature = "async"))]
322    async fn setup_clients_with_headers(
323        headers: HashMap<String, String>,
324    ) -> (BlockingClient, AsyncClient) {
325        PREMINE
326            .get_or_init(|| async {
327                let _miner = MINER.lock().await;
328                generate_blocks_and_wait(101);
329            })
330            .await;
331
332        let esplora_url = ELECTRSD.esplora_url.as_ref().unwrap();
333
334        let mut builder = Builder::new(&format!("http://{esplora_url}"));
335        if !headers.is_empty() {
336            builder.headers = headers;
337        }
338
339        let blocking_client = builder.build_blocking();
340
341        let builder_async = Builder::new(&format!("http://{esplora_url}"));
342
343        #[cfg(feature = "tokio")]
344        let async_client = builder_async.build_async().unwrap();
345
346        #[cfg(not(feature = "tokio"))]
347        let async_client = builder_async
348            .build_async_with_sleeper::<r#async::DefaultSleeper>()
349            .unwrap();
350
351        (blocking_client, async_client)
352    }
353
354    #[cfg(all(feature = "blocking", feature = "async"))]
355    fn generate_blocks_and_wait(num: usize) {
356        let cur_height = BITCOIND.client.get_block_count().unwrap().0;
357        generate_blocks(num);
358        wait_for_block(cur_height as usize + num);
359    }
360
361    #[cfg(all(feature = "blocking", feature = "async"))]
362    fn generate_blocks(num: usize) {
363        let address = BITCOIND
364            .client
365            .new_address_with_type(AddressType::Legacy)
366            .unwrap();
367        let _block_hashes = BITCOIND.client.generate_to_address(num, &address).unwrap();
368    }
369
370    #[cfg(all(feature = "blocking", feature = "async"))]
371    fn wait_for_block(min_height: usize) {
372        let mut header = ELECTRSD.client.block_headers_subscribe().unwrap();
373        loop {
374            if header.height >= min_height {
375                break;
376            }
377            header = exponential_backoff_poll(|| {
378                ELECTRSD.trigger().unwrap();
379                ELECTRSD.client.ping().unwrap();
380                ELECTRSD.client.block_headers_pop().unwrap()
381            });
382        }
383    }
384
385    #[cfg(all(feature = "blocking", feature = "async"))]
386    fn exponential_backoff_poll<T, F>(mut poll: F) -> T
387    where
388        F: FnMut() -> Option<T>,
389    {
390        let mut delay = Duration::from_millis(64);
391        loop {
392            match poll() {
393                Some(data) => break data,
394                None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
395                None => {}
396            }
397
398            std::thread::sleep(delay);
399        }
400    }
401
402    #[test]
403    fn feerate_parsing() {
404        let esplora_fees = serde_json::from_str::<HashMap<u16, f64>>(
405            r#"{
406  "25": 1.015,
407  "5": 2.3280000000000003,
408  "12": 2.0109999999999997,
409  "15": 1.018,
410  "17": 1.018,
411  "11": 2.0109999999999997,
412  "3": 3.01,
413  "2": 4.9830000000000005,
414  "6": 2.2359999999999998,
415  "21": 1.018,
416  "13": 1.081,
417  "7": 2.2359999999999998,
418  "8": 2.2359999999999998,
419  "16": 1.018,
420  "20": 1.018,
421  "22": 1.017,
422  "23": 1.017,
423  "504": 1,
424  "9": 2.2359999999999998,
425  "14": 1.018,
426  "10": 2.0109999999999997,
427  "24": 1.017,
428  "1008": 1,
429  "1": 4.9830000000000005,
430  "4": 2.3280000000000003,
431  "19": 1.018,
432  "144": 1,
433  "18": 1.018
434}
435"#,
436        )
437        .unwrap();
438        assert!(convert_fee_rate(1, HashMap::new()).is_none());
439        assert_eq!(convert_fee_rate(6, esplora_fees.clone()).unwrap(), 2.236);
440        assert_eq!(
441            convert_fee_rate(26, esplora_fees.clone()).unwrap(),
442            1.015,
443            "should inherit from value for 25"
444        );
445        assert!(
446            convert_fee_rate(0, esplora_fees).is_none(),
447            "should not return feerate for 0 target"
448        );
449    }
450
451    #[cfg(all(feature = "blocking", feature = "async"))]
452    #[tokio::test]
453    async fn test_get_tx() {
454        let (blocking_client, async_client) = setup_clients().await;
455
456        let address = BITCOIND
457            .client
458            .new_address_with_type(AddressType::Legacy)
459            .unwrap();
460        let txid = BITCOIND
461            .client
462            .send_to_address(&address, Amount::from_sat(1000))
463            .unwrap()
464            .txid()
465            .unwrap();
466        let _miner = MINER.lock().await;
467        generate_blocks_and_wait(1);
468
469        let tx = blocking_client.get_tx(&txid).unwrap();
470        let tx_async = async_client.get_tx(&txid).await.unwrap();
471        assert_eq!(tx, tx_async);
472    }
473
474    #[cfg(all(feature = "blocking", feature = "async"))]
475    #[tokio::test]
476    async fn test_get_tx_no_opt() {
477        let (blocking_client, async_client) = setup_clients().await;
478
479        let address = BITCOIND
480            .client
481            .new_address_with_type(AddressType::Legacy)
482            .unwrap();
483        let txid = BITCOIND
484            .client
485            .send_to_address(&address, Amount::from_sat(1000))
486            .unwrap()
487            .txid()
488            .unwrap();
489        let _miner = MINER.lock().await;
490        generate_blocks_and_wait(1);
491
492        let tx_no_opt = blocking_client.get_tx_no_opt(&txid).unwrap();
493        let tx_no_opt_async = async_client.get_tx_no_opt(&txid).await.unwrap();
494        assert_eq!(tx_no_opt, tx_no_opt_async);
495    }
496
497    #[cfg(all(feature = "blocking", feature = "async"))]
498    #[tokio::test]
499    async fn test_get_tx_status() {
500        let (blocking_client, async_client) = setup_clients().await;
501
502        let address = BITCOIND
503            .client
504            .new_address_with_type(AddressType::Legacy)
505            .unwrap();
506        let txid = BITCOIND
507            .client
508            .send_to_address(&address, Amount::from_sat(1000))
509            .unwrap()
510            .txid()
511            .unwrap();
512        let _miner = MINER.lock().await;
513        generate_blocks_and_wait(1);
514
515        let tx_status = blocking_client.get_tx_status(&txid).unwrap();
516        let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
517        assert_eq!(tx_status, tx_status_async);
518        assert!(tx_status.confirmed);
519
520        // Bogus txid returns a TxStatus with false, None, None, None
521        let txid = Txid::hash(b"ayyyy lmao");
522        let tx_status = blocking_client.get_tx_status(&txid).unwrap();
523        let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
524        assert_eq!(tx_status, tx_status_async);
525        assert!(!tx_status.confirmed);
526        assert!(tx_status.block_height.is_none());
527        assert!(tx_status.block_hash.is_none());
528        assert!(tx_status.block_time.is_none());
529    }
530
531    #[cfg(all(feature = "blocking", feature = "async"))]
532    #[tokio::test]
533    async fn test_get_tx_info() {
534        let (blocking_client, async_client) = setup_clients().await;
535
536        let address = BITCOIND
537            .client
538            .new_address_with_type(AddressType::Legacy)
539            .unwrap();
540        let txid = BITCOIND
541            .client
542            .send_to_address(&address, Amount::from_sat(1000))
543            .unwrap()
544            .txid()
545            .unwrap();
546        let _miner = MINER.lock().await;
547        generate_blocks_and_wait(1);
548
549        let tx_res = BITCOIND
550            .client
551            .get_transaction(txid)
552            .unwrap()
553            .into_model()
554            .unwrap();
555        let tx_exp: Transaction = tx_res.tx;
556        let tx_block_height = BITCOIND
557            .client
558            .get_block_header_verbose(&tx_res.block_hash.unwrap())
559            .unwrap()
560            .into_model()
561            .unwrap()
562            .height;
563
564        let tx_info = blocking_client
565            .get_tx_info(&txid)
566            .unwrap()
567            .expect("must get tx");
568        let tx_info_async = async_client
569            .get_tx_info(&txid)
570            .await
571            .unwrap()
572            .expect("must get tx");
573        assert_eq!(tx_info, tx_info_async);
574        assert_eq!(tx_info.txid, txid);
575        assert_eq!(tx_info.to_tx(), tx_exp);
576        assert_eq!(tx_info.size, tx_exp.total_size());
577        assert_eq!(tx_info.weight(), tx_exp.weight());
578        assert_eq!(tx_info.fee(), tx_res.fee.unwrap().unsigned_abs());
579        assert!(tx_info.status.confirmed);
580        assert_eq!(tx_info.status.block_height, Some(tx_block_height));
581        assert_eq!(tx_info.status.block_hash, tx_res.block_hash);
582        assert_eq!(
583            tx_info.status.block_time,
584            tx_res.block_time.map(|bt| bt as u64)
585        );
586
587        let txid = Txid::hash(b"not exist");
588        assert_eq!(blocking_client.get_tx_info(&txid).unwrap(), None);
589        assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None);
590    }
591
592    #[cfg(all(feature = "blocking", feature = "async"))]
593    #[tokio::test]
594    async fn test_get_header_by_hash() {
595        let (blocking_client, async_client) = setup_clients().await;
596
597        let block_hash = BITCOIND
598            .client
599            .get_block_hash(23)
600            .unwrap()
601            .block_hash()
602            .unwrap();
603
604        let block_header = blocking_client.get_header_by_hash(&block_hash).unwrap();
605        let block_header_async = async_client.get_header_by_hash(&block_hash).await.unwrap();
606        assert_eq!(block_header, block_header_async);
607    }
608
609    #[cfg(all(feature = "blocking", feature = "async"))]
610    #[tokio::test]
611    async fn test_get_block_status() {
612        let (blocking_client, async_client) = setup_clients().await;
613
614        let block_hash = BITCOIND
615            .client
616            .get_block_hash(21)
617            .unwrap()
618            .block_hash()
619            .unwrap();
620        let next_block_hash = BITCOIND
621            .client
622            .get_block_hash(22)
623            .unwrap()
624            .block_hash()
625            .unwrap();
626
627        let expected = BlockStatus {
628            in_best_chain: true,
629            height: Some(21),
630            next_best: Some(next_block_hash),
631        };
632
633        let block_status = blocking_client.get_block_status(&block_hash).unwrap();
634        let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
635        assert_eq!(expected, block_status);
636        assert_eq!(expected, block_status_async);
637    }
638
639    #[cfg(all(feature = "blocking", feature = "async"))]
640    #[tokio::test]
641    async fn test_get_non_existing_block_status() {
642        // Esplora returns the same status for orphaned blocks as for non-existing
643        // blocks: non-existing: https://blockstream.info/api/block/0000000000000000000000000000000000000000000000000000000000000000/status
644        // orphaned: https://blockstream.info/api/block/000000000000000000181b1a2354620f66868a723c0c4d5b24e4be8bdfc35a7f/status
645        // (Here the block is cited as orphaned: https://bitcoinchain.com/block_explorer/block/000000000000000000181b1a2354620f66868a723c0c4d5b24e4be8bdfc35a7f/ )
646        // For this reason, we only test for the non-existing case here.
647
648        let (blocking_client, async_client) = setup_clients().await;
649
650        let block_hash = BlockHash::all_zeros();
651
652        let expected = BlockStatus {
653            in_best_chain: false,
654            height: None,
655            next_best: None,
656        };
657
658        let block_status = blocking_client.get_block_status(&block_hash).unwrap();
659        let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
660        assert_eq!(expected, block_status);
661        assert_eq!(expected, block_status_async);
662    }
663
664    #[cfg(all(feature = "blocking", feature = "async"))]
665    #[tokio::test]
666    async fn test_get_block_by_hash() {
667        let (blocking_client, async_client) = setup_clients().await;
668
669        let block_hash = BITCOIND
670            .client
671            .get_block_hash(21)
672            .unwrap()
673            .block_hash()
674            .unwrap();
675
676        let expected = Some(BITCOIND.client.get_block(block_hash).unwrap());
677
678        let block = blocking_client.get_block_by_hash(&block_hash).unwrap();
679        let block_async = async_client.get_block_by_hash(&block_hash).await.unwrap();
680        assert_eq!(expected, block);
681        assert_eq!(expected, block_async);
682    }
683
684    #[cfg(all(feature = "blocking", feature = "async"))]
685    #[tokio::test]
686    async fn test_that_errors_are_propagated() {
687        let (blocking_client, async_client) = setup_clients().await;
688
689        let address = BITCOIND
690            .client
691            .new_address_with_type(AddressType::Legacy)
692            .unwrap();
693        let txid = BITCOIND
694            .client
695            .send_to_address(&address, Amount::from_sat(1000))
696            .unwrap()
697            .txid()
698            .unwrap();
699        let _miner = MINER.lock().await;
700        generate_blocks_and_wait(1);
701
702        let tx = blocking_client.get_tx(&txid).unwrap();
703        let async_res = async_client.broadcast(tx.as_ref().unwrap()).await;
704        let blocking_res = blocking_client.broadcast(tx.as_ref().unwrap());
705        assert!(async_res.is_err());
706        assert!(matches!(
707            async_res.unwrap_err(),
708            Error::HttpResponse { status: 400, message } if message.contains("-27")
709        ));
710        assert!(blocking_res.is_err());
711        assert!(matches!(
712            blocking_res.unwrap_err(),
713            Error::HttpResponse { status: 400, message } if message.contains("-27")
714        ));
715    }
716
717    #[cfg(all(feature = "blocking", feature = "async"))]
718    #[tokio::test]
719    async fn test_get_block_by_hash_not_existing() {
720        let (blocking_client, async_client) = setup_clients().await;
721
722        let block = blocking_client
723            .get_block_by_hash(&BlockHash::all_zeros())
724            .unwrap();
725        let block_async = async_client
726            .get_block_by_hash(&BlockHash::all_zeros())
727            .await
728            .unwrap();
729        assert!(block.is_none());
730        assert!(block_async.is_none());
731    }
732
733    #[cfg(all(feature = "blocking", feature = "async"))]
734    #[tokio::test]
735    async fn test_get_merkle_proof() {
736        let (blocking_client, async_client) = setup_clients().await;
737
738        let address = BITCOIND
739            .client
740            .new_address_with_type(AddressType::Legacy)
741            .unwrap();
742        let txid = BITCOIND
743            .client
744            .send_to_address(&address, Amount::from_sat(1000))
745            .unwrap()
746            .txid()
747            .unwrap();
748        let _miner = MINER.lock().await;
749        generate_blocks_and_wait(1);
750
751        let merkle_proof = blocking_client.get_merkle_proof(&txid).unwrap().unwrap();
752        let merkle_proof_async = async_client.get_merkle_proof(&txid).await.unwrap().unwrap();
753        assert_eq!(merkle_proof, merkle_proof_async);
754        assert!(merkle_proof.pos > 0);
755    }
756
757    #[cfg(all(feature = "blocking", feature = "async"))]
758    #[tokio::test]
759    async fn test_get_merkle_block() {
760        let (blocking_client, async_client) = setup_clients().await;
761
762        let address = BITCOIND
763            .client
764            .new_address_with_type(AddressType::Legacy)
765            .unwrap();
766        let txid = BITCOIND
767            .client
768            .send_to_address(&address, Amount::from_sat(1000))
769            .unwrap()
770            .txid()
771            .unwrap();
772        let _miner = MINER.lock().await;
773        generate_blocks_and_wait(1);
774
775        let merkle_block = blocking_client.get_merkle_block(&txid).unwrap().unwrap();
776        let merkle_block_async = async_client.get_merkle_block(&txid).await.unwrap().unwrap();
777        assert_eq!(merkle_block, merkle_block_async);
778
779        let mut matches = vec![txid];
780        let mut indexes = vec![];
781        let root = merkle_block
782            .txn
783            .extract_matches(&mut matches, &mut indexes)
784            .unwrap();
785        assert_eq!(root, merkle_block.header.merkle_root);
786        assert_eq!(indexes.len(), 1);
787        assert!(indexes[0] > 0);
788    }
789
790    #[cfg(all(feature = "blocking", feature = "async"))]
791    #[tokio::test]
792    async fn test_get_output_status() {
793        let (blocking_client, async_client) = setup_clients().await;
794
795        let address = BITCOIND
796            .client
797            .new_address_with_type(AddressType::Legacy)
798            .unwrap();
799        let txid = BITCOIND
800            .client
801            .send_to_address(&address, Amount::from_sat(1000))
802            .unwrap()
803            .txid()
804            .unwrap();
805        let _miner = MINER.lock().await;
806        generate_blocks_and_wait(1);
807
808        let output_status = blocking_client
809            .get_output_status(&txid, 1)
810            .unwrap()
811            .unwrap();
812        let output_status_async = async_client
813            .get_output_status(&txid, 1)
814            .await
815            .unwrap()
816            .unwrap();
817
818        assert_eq!(output_status, output_status_async);
819    }
820
821    #[cfg(all(feature = "blocking", feature = "async"))]
822    #[tokio::test]
823    async fn test_get_height() {
824        let (blocking_client, async_client) = setup_clients().await;
825        let block_height = blocking_client.get_height().unwrap();
826        let block_height_async = async_client.get_height().await.unwrap();
827        assert!(block_height > 0);
828        assert_eq!(block_height, block_height_async);
829    }
830
831    #[cfg(all(feature = "blocking", feature = "async"))]
832    #[tokio::test]
833    async fn test_get_tip_hash() {
834        let (blocking_client, async_client) = setup_clients().await;
835        let tip_hash = blocking_client.get_tip_hash().unwrap();
836        let tip_hash_async = async_client.get_tip_hash().await.unwrap();
837        assert_eq!(tip_hash, tip_hash_async);
838    }
839
840    #[cfg(all(feature = "blocking", feature = "async"))]
841    #[tokio::test]
842    async fn test_get_block_hash() {
843        let (blocking_client, async_client) = setup_clients().await;
844
845        let block_hash = BITCOIND
846            .client
847            .get_block_hash(21)
848            .unwrap()
849            .block_hash()
850            .unwrap();
851
852        let block_hash_blocking = blocking_client.get_block_hash(21).unwrap();
853        let block_hash_async = async_client.get_block_hash(21).await.unwrap();
854        assert_eq!(block_hash, block_hash_blocking);
855        assert_eq!(block_hash, block_hash_async);
856    }
857
858    #[cfg(all(feature = "blocking", feature = "async"))]
859    #[tokio::test]
860    async fn test_get_txid_at_block_index() {
861        let (blocking_client, async_client) = setup_clients().await;
862
863        let block_hash = BITCOIND
864            .client
865            .get_block_hash(23)
866            .unwrap()
867            .block_hash()
868            .unwrap();
869
870        let txid_at_block_index = blocking_client
871            .get_txid_at_block_index(&block_hash, 0)
872            .unwrap()
873            .unwrap();
874        let txid_at_block_index_async = async_client
875            .get_txid_at_block_index(&block_hash, 0)
876            .await
877            .unwrap()
878            .unwrap();
879        assert_eq!(txid_at_block_index, txid_at_block_index_async);
880    }
881
882    #[cfg(all(feature = "blocking", feature = "async"))]
883    #[tokio::test]
884    async fn test_get_fee_estimates() {
885        let (blocking_client, async_client) = setup_clients().await;
886        let fee_estimates = blocking_client.get_fee_estimates().unwrap();
887        let fee_estimates_async = async_client.get_fee_estimates().await.unwrap();
888        assert_eq!(fee_estimates.len(), fee_estimates_async.len());
889    }
890
891    #[cfg(all(feature = "blocking", feature = "async"))]
892    #[tokio::test]
893    async fn test_scripthash_txs() {
894        let (blocking_client, async_client) = setup_clients().await;
895
896        let address = BITCOIND
897            .client
898            .new_address_with_type(AddressType::Legacy)
899            .unwrap();
900        let txid = BITCOIND
901            .client
902            .send_to_address(&address, Amount::from_sat(1000))
903            .unwrap()
904            .txid()
905            .unwrap();
906        let _miner = MINER.lock().await;
907        generate_blocks_and_wait(1);
908
909        let expected_tx = BITCOIND
910            .client
911            .get_transaction(txid)
912            .unwrap()
913            .into_model()
914            .unwrap()
915            .tx;
916        let script = &expected_tx.output[0].script_pubkey;
917        let scripthash_txs_txids: Vec<Txid> = blocking_client
918            .scripthash_txs(script, None)
919            .unwrap()
920            .iter()
921            .map(|tx| tx.txid)
922            .collect();
923        let scripthash_txs_txids_async: Vec<Txid> = async_client
924            .scripthash_txs(script, None)
925            .await
926            .unwrap()
927            .iter()
928            .map(|tx| tx.txid)
929            .collect();
930        assert_eq!(scripthash_txs_txids, scripthash_txs_txids_async);
931    }
932
933    #[cfg(all(feature = "blocking", feature = "async"))]
934    #[tokio::test]
935    async fn test_get_block_info() {
936        let (blocking_client, async_client) = setup_clients().await;
937
938        // Genesis block `BlockHash` on regtest.
939        let blockhash_genesis =
940            BlockHash::from_str("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")
941                .unwrap();
942
943        let block_info_blocking = blocking_client.get_block_info(&blockhash_genesis).unwrap();
944        let block_info_async = async_client
945            .get_block_info(&blockhash_genesis)
946            .await
947            .unwrap();
948
949        assert_eq!(block_info_async, block_info_blocking);
950        assert_eq!(block_info_async.id, blockhash_genesis);
951        assert_eq!(block_info_async.height, 0);
952        assert_eq!(block_info_async.previousblockhash, None);
953    }
954
955    #[cfg(all(feature = "blocking", feature = "async"))]
956    #[tokio::test]
957    async fn test_get_block_txids() {
958        let (blocking_client, async_client) = setup_clients().await;
959
960        let address = BITCOIND
961            .client
962            .new_address_with_type(AddressType::Legacy)
963            .unwrap();
964
965        // Create 5 transactions and mine a block.
966        let txids: Vec<_> = (0..5)
967            .map(|_| {
968                BITCOIND
969                    .client
970                    .send_to_address(&address, Amount::from_sat(1000))
971                    .unwrap()
972                    .txid()
973                    .unwrap()
974            })
975            .collect();
976
977        let _miner = MINER.lock().await;
978        generate_blocks_and_wait(1);
979
980        // Get the block hash at the chain's tip.
981        let blockhash = blocking_client.get_tip_hash().unwrap();
982
983        let txids_async = async_client.get_block_txids(&blockhash).await.unwrap();
984        let txids_blocking = blocking_client.get_block_txids(&blockhash).unwrap();
985
986        assert_eq!(txids_async, txids_blocking);
987
988        // Compare expected and received (skipping the coinbase TXID).
989        for expected_txid in txids.iter() {
990            assert!(txids_async.contains(expected_txid));
991        }
992    }
993
994    #[cfg(all(feature = "blocking", feature = "async"))]
995    #[tokio::test]
996    async fn test_get_block_txs() {
997        let (blocking_client, async_client) = setup_clients().await;
998
999        let _miner = MINER.lock().await;
1000        let blockhash = blocking_client.get_tip_hash().unwrap();
1001
1002        let txs_blocking = blocking_client.get_block_txs(&blockhash, None).unwrap();
1003        let txs_async = async_client.get_block_txs(&blockhash, None).await.unwrap();
1004
1005        assert_ne!(txs_blocking.len(), 0);
1006        assert_eq!(txs_blocking.len(), txs_async.len());
1007    }
1008
1009    #[allow(deprecated)]
1010    #[cfg(all(feature = "blocking", feature = "async"))]
1011    #[tokio::test]
1012    async fn test_get_blocks() {
1013        let (blocking_client, async_client) = setup_clients().await;
1014        let start_height = BITCOIND.client.get_block_count().unwrap().0;
1015        let blocks1 = blocking_client.get_blocks(None).unwrap();
1016        let blocks_async1 = async_client.get_blocks(None).await.unwrap();
1017        assert_eq!(blocks1[0].time.height, start_height as u32);
1018        assert_eq!(blocks1, blocks_async1);
1019        generate_blocks_and_wait(10);
1020        let blocks2 = blocking_client.get_blocks(None).unwrap();
1021        let blocks_async2 = async_client.get_blocks(None).await.unwrap();
1022        assert_eq!(blocks2, blocks_async2);
1023        assert_ne!(blocks2, blocks1);
1024        let blocks3 = blocking_client
1025            .get_blocks(Some(start_height as u32))
1026            .unwrap();
1027        let blocks_async3 = async_client
1028            .get_blocks(Some(start_height as u32))
1029            .await
1030            .unwrap();
1031        assert_eq!(blocks3, blocks_async3);
1032        assert_eq!(blocks3[0].time.height, start_height as u32);
1033        assert_eq!(blocks3, blocks1);
1034        let blocks_genesis = blocking_client.get_blocks(Some(0)).unwrap();
1035        let blocks_genesis_async = async_client.get_blocks(Some(0)).await.unwrap();
1036        assert_eq!(blocks_genesis, blocks_genesis_async);
1037    }
1038
1039    #[cfg(all(feature = "blocking", feature = "async"))]
1040    #[tokio::test]
1041    async fn test_get_block_infos() {
1042        let (blocking_client, async_client) = setup_clients().await;
1043
1044        let start_height = BITCOIND.client.get_block_count().unwrap().0;
1045
1046        let blocks_blocking_0 = blocking_client.get_block_infos(None).unwrap();
1047        let blocks_async_0 = async_client.get_block_infos(None).await.unwrap();
1048        assert_eq!(blocks_blocking_0[0].height, start_height as u32);
1049        assert_eq!(blocks_blocking_0, blocks_async_0);
1050
1051        generate_blocks_and_wait(10);
1052
1053        let blocks_blocking_1 = blocking_client.get_block_infos(None).unwrap();
1054        let blocks_async_1 = async_client.get_block_infos(None).await.unwrap();
1055        assert_eq!(blocks_blocking_1, blocks_async_1);
1056        assert_ne!(blocks_blocking_0, blocks_blocking_1);
1057
1058        let blocks_blocking_2 = blocking_client
1059            .get_block_infos(Some(start_height as u32))
1060            .unwrap();
1061        let blocks_async_3 = async_client
1062            .get_block_infos(Some(start_height as u32))
1063            .await
1064            .unwrap();
1065        assert_eq!(blocks_blocking_2, blocks_async_3);
1066        assert_eq!(blocks_blocking_2[0].height, start_height as u32);
1067        assert_eq!(blocks_blocking_2, blocks_blocking_0);
1068
1069        let blocks_blocking_genesis = blocking_client.get_block_infos(Some(0)).unwrap();
1070        let blocks_async_genesis = async_client.get_block_infos(Some(0)).await.unwrap();
1071        assert_eq!(blocks_blocking_genesis, blocks_async_genesis);
1072    }
1073
1074    #[cfg(all(feature = "blocking", feature = "async"))]
1075    #[tokio::test]
1076    async fn test_get_tx_with_http_header() {
1077        let headers = [(
1078            "Authorization".to_string(),
1079            "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_string(),
1080        )]
1081        .into();
1082        let (blocking_client, async_client) = setup_clients_with_headers(headers).await;
1083
1084        let address = BITCOIND
1085            .client
1086            .new_address_with_type(AddressType::Legacy)
1087            .unwrap();
1088        let txid = BITCOIND
1089            .client
1090            .send_to_address(&address, Amount::from_sat(1000))
1091            .unwrap()
1092            .txid()
1093            .unwrap();
1094        let _miner = MINER.lock().await;
1095        generate_blocks_and_wait(1);
1096
1097        let tx = blocking_client.get_tx(&txid).unwrap();
1098        let tx_async = async_client.get_tx(&txid).await.unwrap();
1099        assert_eq!(tx, tx_async);
1100    }
1101
1102    #[cfg(all(feature = "blocking", feature = "async"))]
1103    #[tokio::test]
1104    async fn test_get_address_stats() {
1105        let (blocking_client, async_client) = setup_clients().await;
1106
1107        let address = BITCOIND
1108            .client
1109            .new_address_with_type(AddressType::Legacy)
1110            .unwrap();
1111
1112        let _txid = BITCOIND
1113            .client
1114            .send_to_address(&address, Amount::from_sat(1000))
1115            .unwrap()
1116            .txid()
1117            .unwrap();
1118
1119        let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1120        let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1121        assert_eq!(address_stats_blocking, address_stats_async);
1122        assert_eq!(address_stats_async.chain_stats.funded_txo_count, 0);
1123
1124        let _miner = MINER.lock().await;
1125        generate_blocks_and_wait(1);
1126
1127        let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1128        let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1129        assert_eq!(address_stats_blocking, address_stats_async);
1130        assert_eq!(address_stats_async.chain_stats.funded_txo_count, 1);
1131        assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000);
1132    }
1133
1134    #[cfg(all(feature = "blocking", feature = "async"))]
1135    #[tokio::test]
1136    async fn test_get_scripthash_stats() {
1137        let (blocking_client, async_client) = setup_clients().await;
1138
1139        // Create an address of each type.
1140        let address_legacy = BITCOIND
1141            .client
1142            .new_address_with_type(AddressType::Legacy)
1143            .unwrap();
1144        let address_p2sh_segwit = BITCOIND
1145            .client
1146            .new_address_with_type(AddressType::P2shSegwit)
1147            .unwrap();
1148        let address_bech32 = BITCOIND
1149            .client
1150            .new_address_with_type(AddressType::Bech32)
1151            .unwrap();
1152        let address_bech32m = BITCOIND
1153            .client
1154            .new_address_with_type(AddressType::Bech32m)
1155            .unwrap();
1156
1157        // Send a transaction to each address.
1158        let _txid = BITCOIND
1159            .client
1160            .send_to_address(&address_legacy, Amount::from_sat(1000))
1161            .unwrap()
1162            .txid()
1163            .unwrap();
1164        let _txid = BITCOIND
1165            .client
1166            .send_to_address(&address_p2sh_segwit, Amount::from_sat(1000))
1167            .unwrap()
1168            .txid()
1169            .unwrap();
1170        let _txid = BITCOIND
1171            .client
1172            .send_to_address(&address_bech32, Amount::from_sat(1000))
1173            .unwrap()
1174            .txid()
1175            .unwrap();
1176        let _txid = BITCOIND
1177            .client
1178            .send_to_address(&address_bech32m, Amount::from_sat(1000))
1179            .unwrap()
1180            .txid()
1181            .unwrap();
1182
1183        let _miner = MINER.lock().await;
1184        generate_blocks_and_wait(1);
1185
1186        // Derive each addresses script.
1187        let script_legacy = address_legacy.script_pubkey();
1188        let script_p2sh_segwit = address_p2sh_segwit.script_pubkey();
1189        let script_bech32 = address_bech32.script_pubkey();
1190        let script_bech32m = address_bech32m.script_pubkey();
1191
1192        // P2PKH
1193        let scripthash_stats_blocking_legacy = blocking_client
1194            .get_scripthash_stats(&script_legacy)
1195            .unwrap();
1196        let scripthash_stats_async_legacy = async_client
1197            .get_scripthash_stats(&script_legacy)
1198            .await
1199            .unwrap();
1200        assert_eq!(
1201            scripthash_stats_blocking_legacy,
1202            scripthash_stats_async_legacy
1203        );
1204        assert_eq!(
1205            scripthash_stats_blocking_legacy.chain_stats.funded_txo_sum,
1206            1000
1207        );
1208        assert_eq!(scripthash_stats_blocking_legacy.chain_stats.tx_count, 1);
1209
1210        // P2SH-P2WSH
1211        let scripthash_stats_blocking_p2sh_segwit = blocking_client
1212            .get_scripthash_stats(&script_p2sh_segwit)
1213            .unwrap();
1214        let scripthash_stats_async_p2sh_segwit = async_client
1215            .get_scripthash_stats(&script_p2sh_segwit)
1216            .await
1217            .unwrap();
1218        assert_eq!(
1219            scripthash_stats_blocking_p2sh_segwit,
1220            scripthash_stats_async_p2sh_segwit
1221        );
1222        assert_eq!(
1223            scripthash_stats_blocking_p2sh_segwit
1224                .chain_stats
1225                .funded_txo_sum,
1226            1000
1227        );
1228        assert_eq!(
1229            scripthash_stats_blocking_p2sh_segwit.chain_stats.tx_count,
1230            1
1231        );
1232
1233        // P2WPKH / P2WSH
1234        let scripthash_stats_blocking_bech32 = blocking_client
1235            .get_scripthash_stats(&script_bech32)
1236            .unwrap();
1237        let scripthash_stats_async_bech32 = async_client
1238            .get_scripthash_stats(&script_bech32)
1239            .await
1240            .unwrap();
1241        assert_eq!(
1242            scripthash_stats_blocking_bech32,
1243            scripthash_stats_async_bech32
1244        );
1245        assert_eq!(
1246            scripthash_stats_blocking_bech32.chain_stats.funded_txo_sum,
1247            1000
1248        );
1249        assert_eq!(scripthash_stats_blocking_bech32.chain_stats.tx_count, 1);
1250
1251        // P2TR
1252        let scripthash_stats_blocking_bech32m = blocking_client
1253            .get_scripthash_stats(&script_bech32m)
1254            .unwrap();
1255        let scripthash_stats_async_bech32m = async_client
1256            .get_scripthash_stats(&script_bech32m)
1257            .await
1258            .unwrap();
1259        assert_eq!(
1260            scripthash_stats_blocking_bech32m,
1261            scripthash_stats_async_bech32m
1262        );
1263        assert_eq!(
1264            scripthash_stats_blocking_bech32m.chain_stats.funded_txo_sum,
1265            1000
1266        );
1267        assert_eq!(scripthash_stats_blocking_bech32m.chain_stats.tx_count, 1);
1268    }
1269
1270    #[cfg(all(feature = "blocking", feature = "async"))]
1271    #[tokio::test]
1272    async fn test_get_address_txs() {
1273        let (blocking_client, async_client) = setup_clients().await;
1274
1275        let address = BITCOIND
1276            .client
1277            .new_address_with_type(AddressType::Legacy)
1278            .unwrap();
1279
1280        let txid = BITCOIND
1281            .client
1282            .send_to_address(&address, Amount::from_sat(1000))
1283            .unwrap()
1284            .txid()
1285            .unwrap();
1286
1287        let _miner = MINER.lock().await;
1288        generate_blocks_and_wait(1);
1289
1290        let address_txs_blocking = blocking_client.get_address_txs(&address, None).unwrap();
1291        let address_txs_async = async_client.get_address_txs(&address, None).await.unwrap();
1292
1293        assert_eq!(address_txs_blocking, address_txs_async);
1294        assert_eq!(address_txs_async[0].txid, txid);
1295    }
1296
1297    #[cfg(all(feature = "blocking", feature = "async"))]
1298    #[tokio::test]
1299    async fn test_get_address_utxos() {
1300        let (blocking_client, async_client) = setup_clients().await;
1301
1302        let address = BITCOIND
1303            .client
1304            .new_address_with_type(AddressType::Legacy)
1305            .unwrap();
1306
1307        let _txid = BITCOIND
1308            .client
1309            .send_to_address(&address, Amount::from_sat(21000))
1310            .unwrap()
1311            .txid()
1312            .unwrap();
1313
1314        let _miner = MINER.lock().await;
1315        generate_blocks_and_wait(1);
1316
1317        let address_utxos_blocking = blocking_client.get_address_utxos(&address).unwrap();
1318        let address_utxos_async = async_client.get_address_utxos(&address).await.unwrap();
1319
1320        assert_ne!(address_utxos_blocking.len(), 0);
1321        assert_ne!(address_utxos_async.len(), 0);
1322        assert_eq!(address_utxos_blocking, address_utxos_async);
1323    }
1324
1325    #[cfg(all(feature = "blocking", feature = "async"))]
1326    #[tokio::test]
1327    async fn test_get_scripthash_utxos() {
1328        let (blocking_client, async_client) = setup_clients().await;
1329
1330        let address = BITCOIND
1331            .client
1332            .new_address_with_type(AddressType::Legacy)
1333            .unwrap();
1334        let script = address.script_pubkey();
1335
1336        let _txid = BITCOIND
1337            .client
1338            .send_to_address(&address, Amount::from_sat(21000))
1339            .unwrap()
1340            .txid()
1341            .unwrap();
1342
1343        let _miner = MINER.lock().await;
1344        generate_blocks_and_wait(1);
1345
1346        let scripthash_utxos_blocking = blocking_client.get_scripthash_utxos(&script).unwrap();
1347        let scripthash_utxos_async = async_client.get_scripthash_utxos(&script).await.unwrap();
1348
1349        assert_ne!(scripthash_utxos_blocking.len(), 0);
1350        assert_ne!(scripthash_utxos_async.len(), 0);
1351        assert_eq!(scripthash_utxos_blocking, scripthash_utxos_async);
1352    }
1353
1354    #[cfg(all(feature = "blocking", feature = "async"))]
1355    #[tokio::test]
1356    async fn test_get_tx_outspends() {
1357        let (blocking_client, async_client) = setup_clients().await;
1358
1359        let address = BITCOIND
1360            .client
1361            .new_address_with_type(AddressType::Legacy)
1362            .unwrap();
1363
1364        let txid = BITCOIND
1365            .client
1366            .send_to_address(&address, Amount::from_sat(21000))
1367            .unwrap()
1368            .txid()
1369            .unwrap();
1370
1371        let _miner = MINER.lock().await;
1372        generate_blocks_and_wait(1);
1373
1374        let outspends_blocking = blocking_client.get_tx_outspends(&txid).unwrap();
1375        let outspends_async = async_client.get_tx_outspends(&txid).await.unwrap();
1376
1377        // Assert that there are 2 outputs: 21K sat and (coinbase - 21K sat).
1378        assert_eq!(outspends_blocking.len(), 2);
1379        assert_eq!(outspends_async.len(), 2);
1380        assert_eq!(outspends_blocking, outspends_async);
1381
1382        // Assert that both outputs are returned as unspent (spent == false).
1383        assert!(outspends_blocking.iter().all(|output| !output.spent));
1384    }
1385
1386    #[cfg(all(feature = "blocking", feature = "async"))]
1387    #[tokio::test]
1388    async fn test_mempool_methods() {
1389        let (blocking_client, async_client) = setup_clients().await;
1390
1391        let address = BITCOIND
1392            .client
1393            .new_address_with_type(AddressType::Legacy)
1394            .unwrap();
1395
1396        for _ in 0..5 {
1397            let _txid = BITCOIND
1398                .client
1399                .send_to_address(&address, Amount::from_sat(1000))
1400                .unwrap()
1401                .txid()
1402                .unwrap();
1403        }
1404
1405        // Wait for transactions to propagate to electrs' mempool.
1406        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
1407
1408        // Test `get_mempool_stats`
1409        let stats_blocking = blocking_client.get_mempool_stats().unwrap();
1410        let stats_async = async_client.get_mempool_stats().await.unwrap();
1411        assert_eq!(stats_blocking, stats_async);
1412        assert!(stats_blocking.count >= 5);
1413
1414        // Test `get_mempool_recent_txs`
1415        let recent_blocking = blocking_client.get_mempool_recent_txs().unwrap();
1416        let recent_async = async_client.get_mempool_recent_txs().await.unwrap();
1417        assert_eq!(recent_blocking, recent_async);
1418        assert!(recent_blocking.len() <= 10);
1419        assert!(!recent_blocking.is_empty());
1420
1421        // Test `get_mempool_txids`
1422        let txids_blocking = blocking_client.get_mempool_txids().unwrap();
1423        let txids_async = async_client.get_mempool_txids().await.unwrap();
1424        assert_eq!(txids_blocking, txids_async);
1425        assert!(txids_blocking.len() >= 5);
1426
1427        // Test `get_mempool_scripthash_txs`
1428        let script = address.script_pubkey();
1429        let scripthash_txs_blocking = blocking_client.get_mempool_scripthash_txs(&script).unwrap();
1430        let scripthash_txs_async = async_client
1431            .get_mempool_scripthash_txs(&script)
1432            .await
1433            .unwrap();
1434        assert_eq!(scripthash_txs_blocking, scripthash_txs_async);
1435        assert_eq!(scripthash_txs_blocking.len(), 5);
1436
1437        // Test `get_mempool_address_txs`
1438        let mempool_address_txs_blocking =
1439            blocking_client.get_mempool_address_txs(&address).unwrap();
1440        let mempool_address_txs_async = async_client
1441            .get_mempool_address_txs(&address)
1442            .await
1443            .unwrap();
1444        assert_eq!(mempool_address_txs_blocking, mempool_address_txs_async);
1445        assert_eq!(mempool_address_txs_blocking.len(), 5);
1446    }
1447}