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 its the default root
65//!   certificates.
66
67#![allow(clippy::result_large_err)]
68
69use std::collections::HashMap;
70use std::fmt;
71use std::num::TryFromIntError;
72use std::time::Duration;
73
74#[cfg(feature = "async")]
75pub use r#async::Sleeper;
76
77pub mod api;
78#[cfg(feature = "async")]
79pub mod r#async;
80#[cfg(feature = "blocking")]
81pub mod blocking;
82
83pub use api::*;
84#[cfg(feature = "blocking")]
85pub use blocking::BlockingClient;
86#[cfg(feature = "async")]
87pub use r#async::AsyncClient;
88
89/// Response status codes for which the request may be retried.
90const RETRYABLE_ERROR_CODES: [u16; 3] = [
91    429, // TOO_MANY_REQUESTS
92    500, // INTERNAL_SERVER_ERROR
93    503, // SERVICE_UNAVAILABLE
94];
95
96/// Base backoff in milliseconds.
97const BASE_BACKOFF_MILLIS: Duration = Duration::from_millis(256);
98
99/// Default max retries.
100const DEFAULT_MAX_RETRIES: usize = 6;
101
102/// Get a fee value in sats/vbytes from the estimates
103/// that matches the confirmation target set as parameter.
104///
105/// Returns `None` if no feerate estimate is found at or below `target`
106/// confirmations.
107pub fn convert_fee_rate(target: usize, estimates: HashMap<u16, f64>) -> Option<f32> {
108    estimates
109        .into_iter()
110        .filter(|(k, _)| *k as usize <= target)
111        .max_by_key(|(k, _)| *k)
112        .map(|(_, v)| v as f32)
113}
114
115#[derive(Debug, Clone)]
116pub struct Builder {
117    /// The URL of the Esplora server.
118    pub base_url: String,
119    /// Optional URL of the proxy to use to make requests to the Esplora server
120    ///
121    /// The string should be formatted as:
122    /// `<protocol>://<user>:<password>@host:<port>`.
123    ///
124    /// Note that the format of this value and the supported protocols change
125    /// slightly between the blocking version of the client (using `minreq`)
126    /// and the async version (using `reqwest`). For more details check with
127    /// the documentation of the two crates. Both of them are compiled with
128    /// the `socks` feature enabled.
129    ///
130    /// The proxy is ignored when targeting `wasm32`.
131    pub proxy: Option<String>,
132    /// Socket timeout.
133    pub timeout: Option<u64>,
134    /// HTTP headers to set on every request made to Esplora server.
135    pub headers: HashMap<String, String>,
136    /// Max retries
137    pub max_retries: usize,
138}
139
140impl Builder {
141    /// Instantiate a new builder
142    pub fn new(base_url: &str) -> Self {
143        Builder {
144            base_url: base_url.to_string(),
145            proxy: None,
146            timeout: None,
147            headers: HashMap::new(),
148            max_retries: DEFAULT_MAX_RETRIES,
149        }
150    }
151
152    /// Set the proxy of the builder
153    pub fn proxy(mut self, proxy: &str) -> Self {
154        self.proxy = Some(proxy.to_string());
155        self
156    }
157
158    /// Set the timeout of the builder
159    pub fn timeout(mut self, timeout: u64) -> Self {
160        self.timeout = Some(timeout);
161        self
162    }
163
164    /// Add a header to set on each request
165    pub fn header(mut self, key: &str, value: &str) -> Self {
166        self.headers.insert(key.to_string(), value.to_string());
167        self
168    }
169
170    /// Set the maximum number of times to retry a request if the response status
171    /// is one of [`RETRYABLE_ERROR_CODES`].
172    pub fn max_retries(mut self, count: usize) -> Self {
173        self.max_retries = count;
174        self
175    }
176
177    /// Build a blocking client from builder
178    #[cfg(feature = "blocking")]
179    pub fn build_blocking(self) -> BlockingClient {
180        BlockingClient::from_builder(self)
181    }
182
183    /// Build an asynchronous client from builder
184    #[cfg(all(feature = "async", feature = "tokio"))]
185    pub fn build_async(self) -> Result<AsyncClient, Error> {
186        AsyncClient::from_builder(self)
187    }
188
189    /// Build an asynchronous client from builder where the returned client uses a
190    /// user-defined [`Sleeper`].
191    #[cfg(feature = "async")]
192    pub fn build_async_with_sleeper<S: Sleeper>(self) -> Result<AsyncClient<S>, Error> {
193        AsyncClient::from_builder(self)
194    }
195}
196
197/// Errors that can happen during a request to `Esplora` servers.
198#[derive(Debug)]
199pub enum Error {
200    /// Error during `minreq` HTTP request
201    #[cfg(feature = "blocking")]
202    Minreq(::minreq::Error),
203    /// Error during reqwest HTTP request
204    #[cfg(feature = "async")]
205    Reqwest(::reqwest::Error),
206    /// HTTP response error
207    HttpResponse { status: u16, message: String },
208    /// Invalid number returned
209    Parsing(std::num::ParseIntError),
210    /// Invalid status code, unable to convert to `u16`
211    StatusCode(TryFromIntError),
212    /// Invalid Bitcoin data returned
213    BitcoinEncoding(bitcoin::consensus::encode::Error),
214    /// Invalid hex data returned (attempting to create an array)
215    HexToArray(bitcoin::hex::HexToArrayError),
216    /// Invalid hex data returned (attempting to create a vector)
217    HexToBytes(bitcoin::hex::HexToBytesError),
218    /// Transaction not found
219    TransactionNotFound(Txid),
220    /// Block Header height not found
221    HeaderHeightNotFound(u32),
222    /// Block Header hash not found
223    HeaderHashNotFound(BlockHash),
224    /// Invalid HTTP Header name specified
225    InvalidHttpHeaderName(String),
226    /// Invalid HTTP Header value specified
227    InvalidHttpHeaderValue(String),
228    /// The server sent an invalid response
229    InvalidResponse,
230}
231
232impl fmt::Display for Error {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        write!(f, "{:?}", self)
235    }
236}
237
238macro_rules! impl_error {
239    ( $from:ty, $to:ident ) => {
240        impl_error!($from, $to, Error);
241    };
242    ( $from:ty, $to:ident, $impl_for:ty ) => {
243        impl std::convert::From<$from> for $impl_for {
244            fn from(err: $from) -> Self {
245                <$impl_for>::$to(err)
246            }
247        }
248    };
249}
250
251impl std::error::Error for Error {}
252#[cfg(feature = "blocking")]
253impl_error!(::minreq::Error, Minreq, Error);
254#[cfg(feature = "async")]
255impl_error!(::reqwest::Error, Reqwest, Error);
256impl_error!(std::num::ParseIntError, Parsing, Error);
257impl_error!(bitcoin::consensus::encode::Error, BitcoinEncoding, Error);
258impl_error!(bitcoin::hex::HexToArrayError, HexToArray, Error);
259impl_error!(bitcoin::hex::HexToBytesError, HexToBytes, Error);
260
261#[cfg(test)]
262mod test {
263    use super::*;
264    use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
265    use lazy_static::lazy_static;
266    use std::env;
267    use tokio::sync::Mutex;
268    #[cfg(all(feature = "blocking", feature = "async"))]
269    use {
270        bitcoin::hashes::Hash,
271        bitcoin::Amount,
272        electrsd::{
273            bitcoind::bitcoincore_rpc::json::AddressType, bitcoind::bitcoincore_rpc::RpcApi,
274            electrum_client::ElectrumApi,
275        },
276        std::time::Duration,
277        tokio::sync::OnceCell,
278    };
279
280    lazy_static! {
281        static ref BITCOIND: BitcoinD = {
282            let bitcoind_exe = env::var("BITCOIND_EXE")
283                .ok()
284                .or_else(|| bitcoind::downloaded_exe_path().ok())
285                .expect(
286                    "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
287                );
288            let conf = bitcoind::Conf::default();
289            BitcoinD::with_conf(bitcoind_exe, &conf).unwrap()
290        };
291        static ref ELECTRSD: ElectrsD = {
292            let electrs_exe = env::var("ELECTRS_EXE")
293                .ok()
294                .or_else(electrsd::downloaded_exe_path)
295                .expect(
296                    "you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
297                );
298            let mut conf = electrsd::Conf::default();
299            conf.http_enabled = true;
300            ElectrsD::with_conf(electrs_exe, &BITCOIND, &conf).unwrap()
301        };
302        static ref MINER: Mutex<()> = Mutex::new(());
303    }
304
305    #[cfg(all(feature = "blocking", feature = "async"))]
306    static PREMINE: OnceCell<()> = OnceCell::const_new();
307
308    #[cfg(all(feature = "blocking", feature = "async"))]
309    async fn setup_clients() -> (BlockingClient, AsyncClient) {
310        setup_clients_with_headers(HashMap::new()).await
311    }
312
313    #[cfg(all(feature = "blocking", feature = "async"))]
314    async fn setup_clients_with_headers(
315        headers: HashMap<String, String>,
316    ) -> (BlockingClient, AsyncClient) {
317        PREMINE
318            .get_or_init(|| async {
319                let _miner = MINER.lock().await;
320                generate_blocks_and_wait(101);
321            })
322            .await;
323
324        let esplora_url = ELECTRSD.esplora_url.as_ref().unwrap();
325
326        let mut builder = Builder::new(&format!("http://{}", esplora_url));
327        if !headers.is_empty() {
328            builder.headers = headers;
329        }
330
331        let blocking_client = builder.build_blocking();
332
333        let builder_async = Builder::new(&format!("http://{}", esplora_url));
334
335        #[cfg(feature = "tokio")]
336        let async_client = builder_async.build_async().unwrap();
337
338        #[cfg(not(feature = "tokio"))]
339        let async_client = builder_async
340            .build_async_with_sleeper::<r#async::DefaultSleeper>()
341            .unwrap();
342
343        (blocking_client, async_client)
344    }
345
346    #[cfg(all(feature = "blocking", feature = "async"))]
347    fn generate_blocks_and_wait(num: usize) {
348        let cur_height = BITCOIND.client.get_block_count().unwrap();
349        generate_blocks(num);
350        wait_for_block(cur_height as usize + num);
351    }
352
353    #[cfg(all(feature = "blocking", feature = "async"))]
354    fn generate_blocks(num: usize) {
355        let address = BITCOIND
356            .client
357            .get_new_address(Some("test"), Some(AddressType::Legacy))
358            .unwrap()
359            .assume_checked();
360        let _block_hashes = BITCOIND
361            .client
362            .generate_to_address(num as u64, &address)
363            .unwrap();
364    }
365
366    #[cfg(all(feature = "blocking", feature = "async"))]
367    fn wait_for_block(min_height: usize) {
368        let mut header = ELECTRSD.client.block_headers_subscribe().unwrap();
369        loop {
370            if header.height >= min_height {
371                break;
372            }
373            header = exponential_backoff_poll(|| {
374                ELECTRSD.trigger().unwrap();
375                ELECTRSD.client.ping().unwrap();
376                ELECTRSD.client.block_headers_pop().unwrap()
377            });
378        }
379    }
380
381    #[cfg(all(feature = "blocking", feature = "async"))]
382    fn exponential_backoff_poll<T, F>(mut poll: F) -> T
383    where
384        F: FnMut() -> Option<T>,
385    {
386        let mut delay = Duration::from_millis(64);
387        loop {
388            match poll() {
389                Some(data) => break data,
390                None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
391                None => {}
392            }
393
394            std::thread::sleep(delay);
395        }
396    }
397
398    #[test]
399    fn feerate_parsing() {
400        let esplora_fees = serde_json::from_str::<HashMap<u16, f64>>(
401            r#"{
402  "25": 1.015,
403  "5": 2.3280000000000003,
404  "12": 2.0109999999999997,
405  "15": 1.018,
406  "17": 1.018,
407  "11": 2.0109999999999997,
408  "3": 3.01,
409  "2": 4.9830000000000005,
410  "6": 2.2359999999999998,
411  "21": 1.018,
412  "13": 1.081,
413  "7": 2.2359999999999998,
414  "8": 2.2359999999999998,
415  "16": 1.018,
416  "20": 1.018,
417  "22": 1.017,
418  "23": 1.017,
419  "504": 1,
420  "9": 2.2359999999999998,
421  "14": 1.018,
422  "10": 2.0109999999999997,
423  "24": 1.017,
424  "1008": 1,
425  "1": 4.9830000000000005,
426  "4": 2.3280000000000003,
427  "19": 1.018,
428  "144": 1,
429  "18": 1.018
430}
431"#,
432        )
433        .unwrap();
434        assert!(convert_fee_rate(1, HashMap::new()).is_none());
435        assert_eq!(convert_fee_rate(6, esplora_fees.clone()).unwrap(), 2.236);
436        assert_eq!(
437            convert_fee_rate(26, esplora_fees.clone()).unwrap(),
438            1.015,
439            "should inherit from value for 25"
440        );
441        assert!(
442            convert_fee_rate(0, esplora_fees).is_none(),
443            "should not return feerate for 0 target"
444        );
445    }
446
447    #[cfg(all(feature = "blocking", feature = "async"))]
448    #[tokio::test]
449    async fn test_get_tx() {
450        let (blocking_client, async_client) = setup_clients().await;
451
452        let address = BITCOIND
453            .client
454            .get_new_address(Some("test"), Some(AddressType::Legacy))
455            .unwrap()
456            .assume_checked();
457        let txid = BITCOIND
458            .client
459            .send_to_address(
460                &address,
461                Amount::from_sat(1000),
462                None,
463                None,
464                None,
465                None,
466                None,
467                None,
468            )
469            .unwrap();
470        let _miner = MINER.lock().await;
471        generate_blocks_and_wait(1);
472
473        let tx = blocking_client.get_tx(&txid).unwrap();
474        let tx_async = async_client.get_tx(&txid).await.unwrap();
475        assert_eq!(tx, tx_async);
476    }
477
478    #[cfg(all(feature = "blocking", feature = "async"))]
479    #[tokio::test]
480    async fn test_get_tx_no_opt() {
481        let (blocking_client, async_client) = setup_clients().await;
482
483        let address = BITCOIND
484            .client
485            .get_new_address(Some("test"), Some(AddressType::Legacy))
486            .unwrap()
487            .assume_checked();
488        let txid = BITCOIND
489            .client
490            .send_to_address(
491                &address,
492                Amount::from_sat(1000),
493                None,
494                None,
495                None,
496                None,
497                None,
498                None,
499            )
500            .unwrap();
501        let _miner = MINER.lock().await;
502        generate_blocks_and_wait(1);
503
504        let tx_no_opt = blocking_client.get_tx_no_opt(&txid).unwrap();
505        let tx_no_opt_async = async_client.get_tx_no_opt(&txid).await.unwrap();
506        assert_eq!(tx_no_opt, tx_no_opt_async);
507    }
508
509    #[cfg(all(feature = "blocking", feature = "async"))]
510    #[tokio::test]
511    async fn test_get_tx_status() {
512        let (blocking_client, async_client) = setup_clients().await;
513
514        let address = BITCOIND
515            .client
516            .get_new_address(Some("test"), Some(AddressType::Legacy))
517            .unwrap()
518            .assume_checked();
519        let txid = BITCOIND
520            .client
521            .send_to_address(
522                &address,
523                Amount::from_sat(1000),
524                None,
525                None,
526                None,
527                None,
528                None,
529                None,
530            )
531            .unwrap();
532        let _miner = MINER.lock().await;
533        generate_blocks_and_wait(1);
534
535        let tx_status = blocking_client.get_tx_status(&txid).unwrap();
536        let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
537        assert_eq!(tx_status, tx_status_async);
538        assert!(tx_status.confirmed);
539
540        // Bogus txid returns a TxStatus with false, None, None, None
541        let txid = Txid::hash(b"ayyyy lmao");
542        let tx_status = blocking_client.get_tx_status(&txid).unwrap();
543        let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
544        assert_eq!(tx_status, tx_status_async);
545        assert!(!tx_status.confirmed);
546        assert!(tx_status.block_height.is_none());
547        assert!(tx_status.block_hash.is_none());
548        assert!(tx_status.block_time.is_none());
549    }
550
551    #[cfg(all(feature = "blocking", feature = "async"))]
552    #[tokio::test]
553    async fn test_get_tx_info() {
554        let (blocking_client, async_client) = setup_clients().await;
555
556        let address = BITCOIND
557            .client
558            .get_new_address(Some("test"), Some(AddressType::Legacy))
559            .unwrap()
560            .assume_checked();
561        let txid = BITCOIND
562            .client
563            .send_to_address(
564                &address,
565                Amount::from_sat(1000),
566                None,
567                None,
568                None,
569                None,
570                None,
571                None,
572            )
573            .unwrap();
574        let _miner = MINER.lock().await;
575        generate_blocks_and_wait(1);
576
577        let tx_res = BITCOIND.client.get_transaction(&txid, None).unwrap();
578        let tx_exp = tx_res.transaction().expect("must decode");
579
580        let tx_info = blocking_client
581            .get_tx_info(&txid)
582            .unwrap()
583            .expect("must get tx");
584        let tx_info_async = async_client
585            .get_tx_info(&txid)
586            .await
587            .unwrap()
588            .expect("must get tx");
589        assert_eq!(tx_info, tx_info_async);
590        assert_eq!(tx_info.txid, txid);
591        assert_eq!(tx_info.to_tx(), tx_exp);
592        assert_eq!(tx_info.size, tx_exp.total_size());
593        assert_eq!(tx_info.weight(), tx_exp.weight());
594        assert_eq!(tx_info.fee(), tx_res.fee.unwrap().unsigned_abs());
595        assert!(tx_info.status.confirmed);
596        assert_eq!(tx_info.status.block_height, tx_res.info.blockheight);
597        assert_eq!(tx_info.status.block_hash, tx_res.info.blockhash);
598        assert_eq!(tx_info.status.block_time, tx_res.info.blocktime);
599
600        let txid = Txid::hash(b"not exist");
601        assert_eq!(blocking_client.get_tx_info(&txid).unwrap(), None);
602        assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None);
603    }
604
605    #[cfg(all(feature = "blocking", feature = "async"))]
606    #[tokio::test]
607    async fn test_get_header_by_hash() {
608        let (blocking_client, async_client) = setup_clients().await;
609
610        let block_hash = BITCOIND.client.get_block_hash(23).unwrap();
611
612        let block_header = blocking_client.get_header_by_hash(&block_hash).unwrap();
613        let block_header_async = async_client.get_header_by_hash(&block_hash).await.unwrap();
614        assert_eq!(block_header, block_header_async);
615    }
616
617    #[cfg(all(feature = "blocking", feature = "async"))]
618    #[tokio::test]
619    async fn test_get_block_status() {
620        let (blocking_client, async_client) = setup_clients().await;
621
622        let block_hash = BITCOIND.client.get_block_hash(21).unwrap();
623        let next_block_hash = BITCOIND.client.get_block_hash(22).unwrap();
624
625        let expected = BlockStatus {
626            in_best_chain: true,
627            height: Some(21),
628            next_best: Some(next_block_hash),
629        };
630
631        let block_status = blocking_client.get_block_status(&block_hash).unwrap();
632        let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
633        assert_eq!(expected, block_status);
634        assert_eq!(expected, block_status_async);
635    }
636
637    #[cfg(all(feature = "blocking", feature = "async"))]
638    #[tokio::test]
639    async fn test_get_non_existing_block_status() {
640        // Esplora returns the same status for orphaned blocks as for non-existing
641        // blocks: non-existing: https://blockstream.info/api/block/0000000000000000000000000000000000000000000000000000000000000000/status
642        // orphaned: https://blockstream.info/api/block/000000000000000000181b1a2354620f66868a723c0c4d5b24e4be8bdfc35a7f/status
643        // (Here the block is cited as orphaned: https://bitcoinchain.com/block_explorer/block/000000000000000000181b1a2354620f66868a723c0c4d5b24e4be8bdfc35a7f/ )
644        // For this reason, we only test for the non-existing case here.
645
646        let (blocking_client, async_client) = setup_clients().await;
647
648        let block_hash = BlockHash::all_zeros();
649
650        let expected = BlockStatus {
651            in_best_chain: false,
652            height: None,
653            next_best: None,
654        };
655
656        let block_status = blocking_client.get_block_status(&block_hash).unwrap();
657        let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
658        assert_eq!(expected, block_status);
659        assert_eq!(expected, block_status_async);
660    }
661
662    #[cfg(all(feature = "blocking", feature = "async"))]
663    #[tokio::test]
664    async fn test_get_block_by_hash() {
665        let (blocking_client, async_client) = setup_clients().await;
666
667        let block_hash = BITCOIND.client.get_block_hash(21).unwrap();
668
669        let expected = Some(BITCOIND.client.get_block(&block_hash).unwrap());
670
671        let block = blocking_client.get_block_by_hash(&block_hash).unwrap();
672        let block_async = async_client.get_block_by_hash(&block_hash).await.unwrap();
673        assert_eq!(expected, block);
674        assert_eq!(expected, block_async);
675    }
676
677    #[cfg(all(feature = "blocking", feature = "async"))]
678    #[tokio::test]
679    async fn test_that_errors_are_propagated() {
680        let (blocking_client, async_client) = setup_clients().await;
681
682        let address = BITCOIND
683            .client
684            .get_new_address(Some("test"), Some(AddressType::Legacy))
685            .unwrap()
686            .assume_checked();
687        let txid = BITCOIND
688            .client
689            .send_to_address(
690                &address,
691                Amount::from_sat(1000),
692                None,
693                None,
694                None,
695                None,
696                None,
697                None,
698            )
699            .unwrap();
700        let _miner = MINER.lock().await;
701        generate_blocks_and_wait(1);
702
703        let tx = blocking_client.get_tx(&txid).unwrap();
704        let async_res = async_client.broadcast(tx.as_ref().unwrap()).await;
705        let blocking_res = blocking_client.broadcast(tx.as_ref().unwrap());
706        assert!(async_res.is_err());
707        assert_eq!(async_res.unwrap_err().to_string(),"HttpResponse { status: 400, message: \"sendrawtransaction RPC error: {\\\"code\\\":-27,\\\"message\\\":\\\"Transaction already in block chain\\\"}\" }");
708        assert!(blocking_res.is_err());
709        assert_eq!(blocking_res.unwrap_err().to_string(),"HttpResponse { status: 400, message: \"sendrawtransaction RPC error: {\\\"code\\\":-27,\\\"message\\\":\\\"Transaction already in block chain\\\"}\" }");
710    }
711
712    #[cfg(all(feature = "blocking", feature = "async"))]
713    #[tokio::test]
714    async fn test_get_block_by_hash_not_existing() {
715        let (blocking_client, async_client) = setup_clients().await;
716
717        let block = blocking_client
718            .get_block_by_hash(&BlockHash::all_zeros())
719            .unwrap();
720        let block_async = async_client
721            .get_block_by_hash(&BlockHash::all_zeros())
722            .await
723            .unwrap();
724        assert!(block.is_none());
725        assert!(block_async.is_none());
726    }
727
728    #[cfg(all(feature = "blocking", feature = "async"))]
729    #[tokio::test]
730    async fn test_get_merkle_proof() {
731        let (blocking_client, async_client) = setup_clients().await;
732
733        let address = BITCOIND
734            .client
735            .get_new_address(Some("test"), Some(AddressType::Legacy))
736            .unwrap()
737            .assume_checked();
738        let txid = BITCOIND
739            .client
740            .send_to_address(
741                &address,
742                Amount::from_sat(1000),
743                None,
744                None,
745                None,
746                None,
747                None,
748                None,
749            )
750            .unwrap();
751        let _miner = MINER.lock().await;
752        generate_blocks_and_wait(1);
753
754        let merkle_proof = blocking_client.get_merkle_proof(&txid).unwrap().unwrap();
755        let merkle_proof_async = async_client.get_merkle_proof(&txid).await.unwrap().unwrap();
756        assert_eq!(merkle_proof, merkle_proof_async);
757        assert!(merkle_proof.pos > 0);
758    }
759
760    #[cfg(all(feature = "blocking", feature = "async"))]
761    #[tokio::test]
762    async fn test_get_merkle_block() {
763        let (blocking_client, async_client) = setup_clients().await;
764
765        let address = BITCOIND
766            .client
767            .get_new_address(Some("test"), Some(AddressType::Legacy))
768            .unwrap()
769            .assume_checked();
770        let txid = BITCOIND
771            .client
772            .send_to_address(
773                &address,
774                Amount::from_sat(1000),
775                None,
776                None,
777                None,
778                None,
779                None,
780                None,
781            )
782            .unwrap();
783        let _miner = MINER.lock().await;
784        generate_blocks_and_wait(1);
785
786        let merkle_block = blocking_client.get_merkle_block(&txid).unwrap().unwrap();
787        let merkle_block_async = async_client.get_merkle_block(&txid).await.unwrap().unwrap();
788        assert_eq!(merkle_block, merkle_block_async);
789
790        let mut matches = vec![txid];
791        let mut indexes = vec![];
792        let root = merkle_block
793            .txn
794            .extract_matches(&mut matches, &mut indexes)
795            .unwrap();
796        assert_eq!(root, merkle_block.header.merkle_root);
797        assert_eq!(indexes.len(), 1);
798        assert!(indexes[0] > 0);
799    }
800
801    #[cfg(all(feature = "blocking", feature = "async"))]
802    #[tokio::test]
803    async fn test_get_output_status() {
804        let (blocking_client, async_client) = setup_clients().await;
805
806        let address = BITCOIND
807            .client
808            .get_new_address(Some("test"), Some(AddressType::Legacy))
809            .unwrap()
810            .assume_checked();
811        let txid = BITCOIND
812            .client
813            .send_to_address(
814                &address,
815                Amount::from_sat(1000),
816                None,
817                None,
818                None,
819                None,
820                None,
821                None,
822            )
823            .unwrap();
824        let _miner = MINER.lock().await;
825        generate_blocks_and_wait(1);
826
827        let output_status = blocking_client
828            .get_output_status(&txid, 1)
829            .unwrap()
830            .unwrap();
831        let output_status_async = async_client
832            .get_output_status(&txid, 1)
833            .await
834            .unwrap()
835            .unwrap();
836
837        assert_eq!(output_status, output_status_async);
838    }
839
840    #[cfg(all(feature = "blocking", feature = "async"))]
841    #[tokio::test]
842    async fn test_get_height() {
843        let (blocking_client, async_client) = setup_clients().await;
844        let block_height = blocking_client.get_height().unwrap();
845        let block_height_async = async_client.get_height().await.unwrap();
846        assert!(block_height > 0);
847        assert_eq!(block_height, block_height_async);
848    }
849
850    #[cfg(all(feature = "blocking", feature = "async"))]
851    #[tokio::test]
852    async fn test_get_tip_hash() {
853        let (blocking_client, async_client) = setup_clients().await;
854        let tip_hash = blocking_client.get_tip_hash().unwrap();
855        let tip_hash_async = async_client.get_tip_hash().await.unwrap();
856        assert_eq!(tip_hash, tip_hash_async);
857    }
858
859    #[cfg(all(feature = "blocking", feature = "async"))]
860    #[tokio::test]
861    async fn test_get_block_hash() {
862        let (blocking_client, async_client) = setup_clients().await;
863
864        let block_hash = BITCOIND.client.get_block_hash(21).unwrap();
865
866        let block_hash_blocking = blocking_client.get_block_hash(21).unwrap();
867        let block_hash_async = async_client.get_block_hash(21).await.unwrap();
868        assert_eq!(block_hash, block_hash_blocking);
869        assert_eq!(block_hash, block_hash_async);
870    }
871
872    #[cfg(all(feature = "blocking", feature = "async"))]
873    #[tokio::test]
874    async fn test_get_txid_at_block_index() {
875        let (blocking_client, async_client) = setup_clients().await;
876
877        let block_hash = BITCOIND.client.get_block_hash(23).unwrap();
878
879        let txid_at_block_index = blocking_client
880            .get_txid_at_block_index(&block_hash, 0)
881            .unwrap()
882            .unwrap();
883        let txid_at_block_index_async = async_client
884            .get_txid_at_block_index(&block_hash, 0)
885            .await
886            .unwrap()
887            .unwrap();
888        assert_eq!(txid_at_block_index, txid_at_block_index_async);
889    }
890
891    #[cfg(all(feature = "blocking", feature = "async"))]
892    #[tokio::test]
893    async fn test_get_fee_estimates() {
894        let (blocking_client, async_client) = setup_clients().await;
895        let fee_estimates = blocking_client.get_fee_estimates().unwrap();
896        let fee_estimates_async = async_client.get_fee_estimates().await.unwrap();
897        assert_eq!(fee_estimates.len(), fee_estimates_async.len());
898    }
899
900    #[cfg(all(feature = "blocking", feature = "async"))]
901    #[tokio::test]
902    async fn test_scripthash_txs() {
903        let (blocking_client, async_client) = setup_clients().await;
904
905        let address = BITCOIND
906            .client
907            .get_new_address(Some("test"), Some(AddressType::Legacy))
908            .unwrap()
909            .assume_checked();
910        let txid = BITCOIND
911            .client
912            .send_to_address(
913                &address,
914                Amount::from_sat(1000),
915                None,
916                None,
917                None,
918                None,
919                None,
920                None,
921            )
922            .unwrap();
923        let _miner = MINER.lock().await;
924        generate_blocks_and_wait(1);
925
926        let expected_tx = BITCOIND
927            .client
928            .get_transaction(&txid, None)
929            .unwrap()
930            .transaction()
931            .unwrap();
932        let script = &expected_tx.output[0].script_pubkey;
933        let scripthash_txs_txids: Vec<Txid> = blocking_client
934            .scripthash_txs(script, None)
935            .unwrap()
936            .iter()
937            .map(|tx| tx.txid)
938            .collect();
939        let scripthash_txs_txids_async: Vec<Txid> = async_client
940            .scripthash_txs(script, None)
941            .await
942            .unwrap()
943            .iter()
944            .map(|tx| tx.txid)
945            .collect();
946        assert_eq!(scripthash_txs_txids, scripthash_txs_txids_async);
947    }
948
949    #[cfg(all(feature = "blocking", feature = "async"))]
950    #[tokio::test]
951    async fn test_get_blocks() {
952        let (blocking_client, async_client) = setup_clients().await;
953        let start_height = BITCOIND.client.get_block_count().unwrap();
954        let blocks1 = blocking_client.get_blocks(None).unwrap();
955        let blocks_async1 = async_client.get_blocks(None).await.unwrap();
956        assert_eq!(blocks1[0].time.height, start_height as u32);
957        assert_eq!(blocks1, blocks_async1);
958        generate_blocks_and_wait(10);
959        let blocks2 = blocking_client.get_blocks(None).unwrap();
960        let blocks_async2 = async_client.get_blocks(None).await.unwrap();
961        assert_eq!(blocks2, blocks_async2);
962        assert_ne!(blocks2, blocks1);
963        let blocks3 = blocking_client
964            .get_blocks(Some(start_height as u32))
965            .unwrap();
966        let blocks_async3 = async_client
967            .get_blocks(Some(start_height as u32))
968            .await
969            .unwrap();
970        assert_eq!(blocks3, blocks_async3);
971        assert_eq!(blocks3[0].time.height, start_height as u32);
972        assert_eq!(blocks3, blocks1);
973        let blocks_genesis = blocking_client.get_blocks(Some(0)).unwrap();
974        let blocks_genesis_async = async_client.get_blocks(Some(0)).await.unwrap();
975        assert_eq!(blocks_genesis, blocks_genesis_async);
976    }
977
978    #[cfg(all(feature = "blocking", feature = "async"))]
979    #[tokio::test]
980    async fn test_get_tx_with_http_header() {
981        let headers = [(
982            "Authorization".to_string(),
983            "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_string(),
984        )]
985        .into();
986        let (blocking_client, async_client) = setup_clients_with_headers(headers).await;
987
988        let address = BITCOIND
989            .client
990            .get_new_address(Some("test"), Some(AddressType::Legacy))
991            .unwrap()
992            .assume_checked();
993        let txid = BITCOIND
994            .client
995            .send_to_address(
996                &address,
997                Amount::from_sat(1000),
998                None,
999                None,
1000                None,
1001                None,
1002                None,
1003                None,
1004            )
1005            .unwrap();
1006        let _miner = MINER.lock().await;
1007        generate_blocks_and_wait(1);
1008
1009        let tx = blocking_client.get_tx(&txid).unwrap();
1010        let tx_async = async_client.get_tx(&txid).await.unwrap();
1011        assert_eq!(tx, tx_async);
1012    }
1013
1014    #[cfg(all(feature = "blocking", feature = "async"))]
1015    #[tokio::test]
1016    async fn test_get_address_stats() {
1017        let (blocking_client, async_client) = setup_clients().await;
1018
1019        let address = BITCOIND
1020            .client
1021            .get_new_address(Some("test"), Some(AddressType::Legacy))
1022            .unwrap()
1023            .assume_checked();
1024
1025        let _txid = BITCOIND
1026            .client
1027            .send_to_address(
1028                &address,
1029                Amount::from_sat(1000),
1030                None,
1031                None,
1032                None,
1033                None,
1034                None,
1035                None,
1036            )
1037            .unwrap();
1038
1039        let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1040        let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1041        assert_eq!(address_stats_blocking, address_stats_async);
1042        assert_eq!(address_stats_async.chain_stats.funded_txo_count, 0);
1043
1044        let _miner = MINER.lock().await;
1045        generate_blocks_and_wait(1);
1046
1047        let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1048        let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1049        assert_eq!(address_stats_blocking, address_stats_async);
1050        assert_eq!(address_stats_async.chain_stats.funded_txo_count, 1);
1051        assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000);
1052    }
1053
1054    #[cfg(all(feature = "blocking", feature = "async"))]
1055    #[tokio::test]
1056    async fn test_get_address_txs() {
1057        let (blocking_client, async_client) = setup_clients().await;
1058
1059        let address = BITCOIND
1060            .client
1061            .get_new_address(Some("test"), Some(AddressType::Legacy))
1062            .unwrap()
1063            .assume_checked();
1064
1065        let txid = BITCOIND
1066            .client
1067            .send_to_address(
1068                &address,
1069                Amount::from_sat(1000),
1070                None,
1071                None,
1072                None,
1073                None,
1074                None,
1075                None,
1076            )
1077            .unwrap();
1078
1079        let _miner = MINER.lock().await;
1080        generate_blocks_and_wait(1);
1081
1082        let address_txs_blocking = blocking_client.get_address_txs(&address, None).unwrap();
1083        let address_txs_async = async_client.get_address_txs(&address, None).await.unwrap();
1084
1085        assert_eq!(address_txs_blocking, address_txs_async);
1086        assert_eq!(address_txs_async[0].txid, txid);
1087    }
1088}