diff --git a/.clippy.toml b/.clippy.toml index 69478ce..e0ec8dd 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1 @@ -msrv="1.63.0" +msrv="1.71.0" diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 861d2ea..1e4b13e 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -17,7 +17,7 @@ jobs: matrix: rust: - version: stable # STABLE - - version: 1.63.0 # MSRV + - version: 1.71.0 # MSRV features: - default - blocking @@ -30,6 +30,10 @@ jobs: - async-https-native - async-https-rustls - async-https-rustls-manual-roots + - async-minreq + - async-minreq-https + - async-minreq-https-native + - async-minreq-https-rustls steps: - name: Checkout uses: actions/checkout@v4 @@ -52,13 +56,13 @@ jobs: - name: Update toolchain run: rustup update - name: Pin dependencies for MSRV - if: matrix.rust.version == '1.63.0' + if: matrix.rust.version == '1.71.0' run: | cargo update -p reqwest --precise "0.12.4" cargo update -p minreq --precise "2.13.2" cargo update -p home --precise "0.5.5" cargo update -p url --precise "2.5.0" - cargo update -p tokio --precise "1.38.1" + cargo update -p tokio --precise "1.44.0" cargo update -p security-framework-sys --precise "2.11.1" cargo update -p native-tls --precise "0.2.13" cargo update -p ring --precise "0.17.12" diff --git a/Cargo.toml b/Cargo.toml index 8438b5c..ecdd375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ documentation = "https://docs.rs/esplora-client/" description = "Bitcoin Esplora API client library. Supports plaintext, TLS and Onion servers. Blocking or async" keywords = ["bitcoin", "esplora"] readme = "README.md" -rust-version = "1.63.0" +rust-version = "1.71.0" [lib] name = "esplora_client" @@ -22,6 +22,7 @@ bitcoin = { version = "0.32", features = ["serde", "std"], default-features = fa hex = { version = "0.2", package = "hex-conservative" } log = "^0.4" minreq = { version = "2.11.0", features = ["json-using-serde"], optional = true } +async_minreq = { version = "0.1.0", default-features = false, features = ["json-using-serde"], optional = true } reqwest = { version = "0.12", features = ["json"], default-features = false, optional = true } # default async runtime @@ -47,3 +48,9 @@ async-https = ["async", "reqwest/default-tls"] async-https-native = ["async", "reqwest/native-tls"] async-https-rustls = ["async", "reqwest/rustls-tls"] async-https-rustls-manual-roots = ["async", "reqwest/rustls-tls-manual-roots"] +async-minreq = ["async_minreq", "async_minreq/proxy", "tokio?/time"] +async-minreq-https = ["async-minreq", "async_minreq/https"] +async-minreq-https-native = ["async-minreq", "async_minreq/https-native"] +async-minreq-https-rustls = ["async-minreq", "async_minreq/https-rustls"] +# TODO async-mineq does not support it for now +# async-minreq-https-rustls-manual-roots = ["async-minreq"] diff --git a/README.md b/README.md index dd1acdf..0caec5f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Bitcoin Esplora API client library. Supports plaintext, TLS and Onion servers. B ## Minimum Supported Rust Version (MSRV) -This library should compile with any combination of features with Rust 1.63.0. +This library should compile with any combination of features with Rust 1.71.0. To build with the MSRV you will need to pin dependencies as follows: @@ -36,3 +36,16 @@ cargo update -p lock_api --precise "0.4.12" cargo update -p socket2@0.6.0 --precise "0.5.10" cargo update -p webpki-roots@1.0.2 --precise "1.0.1" ``` + +## Experimental Features using [async-minreq](https://crates.io/crates/async_minreq) + +async-minreq features are currently experimental but may become the only async client In the future once it's more mature. + +Currently Supported features + +```shell +async-minreq +async-minreq-https +async-minreq-https-native +async-minreq-https-rustls +``` \ No newline at end of file diff --git a/src/async.rs b/src/async.rs index a5175b9..12ee445 100644 --- a/src/async.rs +++ b/src/async.rs @@ -9,11 +9,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! Esplora by way of `reqwest` HTTP client. - -use std::collections::HashMap; -use std::marker::PhantomData; -use std::str::FromStr; +//! Esplora by way of `reqwest` or `async-minreq` HTTP client. use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable}; use bitcoin::hashes::{sha256, Hash}; @@ -22,32 +18,44 @@ use bitcoin::Address; use bitcoin::{ block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid, }; - -#[allow(unused_imports)] -use log::{debug, error, info, trace}; - -use reqwest::{header, Client, Response}; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::str::FromStr; use crate::api::AddressStats; use crate::{ BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES, }; +#[cfg(all(feature = "async-minreq", not(feature = "async")))] +use async_minreq::{Method, Request, Response}; +#[cfg(feature = "async")] +use reqwest::{header, Client, Response}; + +#[allow(unused_imports)] +use log::{debug, error, info, trace}; + +#[cfg(all(feature = "async-minreq", not(feature = "async")))] +/// Valid HTTP code +const VALID_HTTP_CODE: i32 = 299; #[derive(Debug, Clone)] pub struct AsyncClient { /// The URL of the Esplora Server. url: String, - /// The inner [`reqwest::Client`] to make HTTP requests. - client: Client, - /// Number of times to retry a request + /// Number of times to retry a request. max_retries: usize, - - /// Marker for the type of sleeper used + #[cfg(feature = "async")] + client: Client, + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + /// Default headers (applied to every request). + headers: HashMap, + /// Marker for the sleeper. marker: PhantomData, } impl AsyncClient { + #[cfg(feature = "async")] /// Build an async client from a builder pub fn from_builder(builder: Builder) -> Result { let mut client_builder = Client::builder(); @@ -82,6 +90,18 @@ impl AsyncClient { }) } + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + /// Build an async client from a builder + pub fn from_builder(builder: Builder) -> Result { + Ok(AsyncClient { + url: builder.base_url, + max_retries: builder.max_retries, + headers: builder.headers, + marker: PhantomData, + }) + } + + #[cfg(feature = "async")] pub fn from_client(url: String, client: Client) -> Self { AsyncClient { url, @@ -90,6 +110,15 @@ impl AsyncClient { marker: PhantomData, } } + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + pub fn from_client(url: String, headers: HashMap) -> Self { + AsyncClient { + url, + headers, + max_retries: crate::DEFAULT_MAX_RETRIES, + marker: PhantomData, + } + } /// Make an HTTP GET request to given URL, deserializing to any `T` that /// implement [`bitcoin::consensus::Decodable`]. @@ -106,14 +135,32 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.get_with_retry(&url).await?; - if !response.status().is_success() { - return Err(Error::HttpResponse { - status: response.status().as_u16(), - message: response.text().await?, - }); + #[cfg(feature = "async")] + { + if !response.status().is_success() { + return Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }); + } + + Ok(deserialize::(&response.bytes().await?)?) } - Ok(deserialize::(&response.bytes().await?)?) + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + { + if response.status_code > VALID_HTTP_CODE { + return Err(Error::HttpResponse { + status: response.status_code as u16, + message: match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }, + }); + } + + return Ok(deserialize::(response.as_bytes())?); + } } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -146,14 +193,31 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.get_with_retry(&url).await?; - if !response.status().is_success() { - return Err(Error::HttpResponse { - status: response.status().as_u16(), - message: response.text().await?, - }); + #[cfg(feature = "async")] + { + if !response.status().is_success() { + return Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }); + } + + response.json::().await.map_err(Error::Reqwest) } + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + { + if response.status_code > VALID_HTTP_CODE { + return Err(Error::HttpResponse { + status: response.status_code as u16, + message: match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }, + }); + } - response.json::().await.map_err(Error::Reqwest) + return response.json().map_err(Error::AsyncMinreq); + } } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -188,15 +252,38 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.get_with_retry(&url).await?; - if !response.status().is_success() { - return Err(Error::HttpResponse { - status: response.status().as_u16(), - message: response.text().await?, - }); + #[cfg(feature = "async")] + { + if !response.status().is_success() { + return Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }); + } + + let hex_str = response.text().await?; + Ok(deserialize(&Vec::from_hex(&hex_str)?)?) } - let hex_str = response.text().await?; - Ok(deserialize(&Vec::from_hex(&hex_str)?)?) + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + { + if response.status_code > VALID_HTTP_CODE { + return Err(Error::HttpResponse { + status: response.status_code as u16, + message: match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }, + }); + } + + let hex_str = match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }; + + return Ok(deserialize(&Vec::from_hex(&hex_str)?)?); + } } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -225,14 +312,35 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.get_with_retry(&url).await?; - if !response.status().is_success() { - return Err(Error::HttpResponse { - status: response.status().as_u16(), - message: response.text().await?, - }); + #[cfg(feature = "async")] + { + if !response.status().is_success() { + return Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }); + } + + Ok(response.text().await?) } - Ok(response.text().await?) + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + { + if response.status_code > VALID_HTTP_CODE { + return Err(Error::HttpResponse { + status: response.status_code as u16, + message: match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }, + }); + } + + return Ok(match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }); + } } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -263,15 +371,36 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let body = serialize::(&body).to_lower_hex_string(); - let response = self.client.post(url).body(body).send().await?; + #[cfg(feature = "async")] + { + let response = self.client.post(url).body(body).send().await?; - if !response.status().is_success() { - return Err(Error::HttpResponse { - status: response.status().as_u16(), - message: response.text().await?, - }); - } + if !response.status().is_success() { + return Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }); + } + + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + { + let mut request = Request::new(Method::Post, &url).with_body(body); + for (key, value) in &self.headers { + request = request.with_header(key, value); + } + let response = request.send().await.map_err(Error::AsyncMinreq)?; + if response.status_code > VALID_HTTP_CODE { + return Err(Error::HttpResponse { + status: response.status_code as u16, + message: match response.as_str() { + Ok(resp) => resp.to_string(), + Err(_) => return Err(Error::InvalidResponse), + }, + }); + } + } + } Ok(()) } @@ -454,6 +583,7 @@ impl AsyncClient { &self.url } + #[cfg(feature = "async")] /// Get the underlying [`Client`]. pub fn client(&self) -> &Client { &self.client @@ -465,6 +595,7 @@ impl AsyncClient { let mut delay = BASE_BACKOFF_MILLIS; let mut attempts = 0; + #[cfg(feature = "async")] loop { match self.client.get(url).send().await? { resp if attempts < self.max_retries && is_status_retryable(resp.status()) => { @@ -475,13 +606,40 @@ impl AsyncClient { resp => return Ok(resp), } } + + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + { + loop { + let mut request = Request::new(Method::Get, url); + for (key, value) in &self.headers { + request = request.with_header(key, value); + } + + match request.send().await? { + resp if attempts < self.max_retries + && is_status_retryable(resp.status_code) => + { + S::sleep(delay).await; + attempts += 1; + delay *= 2; + } + resp => return Ok(resp), + } + } + } } } +#[cfg(feature = "async")] fn is_status_retryable(status: reqwest::StatusCode) -> bool { RETRYABLE_ERROR_CODES.contains(&status.as_u16()) } +#[cfg(all(feature = "async-minreq", not(feature = "async")))] +fn is_status_retryable(status: i32) -> bool { + RETRYABLE_ERROR_CODES.contains(&(status as u16)) +} + pub trait Sleeper: 'static { type Sleep: std::future::Future; fn sleep(dur: std::time::Duration) -> Self::Sleep; diff --git a/src/lib.rs b/src/lib.rs index d8091b6..a78a892 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,8 @@ //! async Esplora client to query Esplora's backend. //! //! The library provides the possibility to build a blocking -//! client using [`minreq`] and an async client using [`reqwest`]. -//! The library supports communicating to Esplora via a proxy +//! client using [`minreq`] and an async client using [`reqwest`] or [`async_minreq`] +//! (experimental). The library supports communicating to Esplora via a proxy //! and also using TLS (SSL) for secure communication. //! //! @@ -64,9 +64,19 @@ //! proxying and TLS (SSL) using the `rustls` TLS backend without using its the default root //! certificates. //! -//! [`dont remove this line or cargo doc will break`]: https://example.com -#![cfg_attr(not(feature = "minreq"), doc = "[`minreq`]: https://docs.rs/minreq")] -#![cfg_attr(not(feature = "reqwest"), doc = "[`reqwest`]: https://docs.rs/reqwest")] +//! Below Features are currently experimental: +//! * `async-minreq` enables [`async_minreq`], the async client with proxy capabilities. +//! * `async-minreq-https` enables [`async_minreq`], the async client with support for proxying and +//! TLS (SSL) using the default [`async_minreq`] TLS backend. +//! * `async-minreq-https-native` enables [`async_minreq`], the async client with support for +//! proxying and TLS (SSL) using the platform's native TLS backend (likely OpenSSL). +//! * `async-minreq-https-rustls` enables [`async_minreq`], the async client with support for +//! proxying and TLS (SSL) using the `rustls` TLS backend. +//! +//! [`minreq`]: https://docs.rs/minreq +//! [`async_minreq`]: https://docs.rs/async-minreq +//! [`reqwest`]: https://docs.rs/reqwest + #![allow(clippy::result_large_err)] use std::collections::HashMap; @@ -74,11 +84,11 @@ use std::fmt; use std::num::TryFromIntError; use std::time::Duration; -#[cfg(feature = "async")] +#[cfg(any(feature = "async", feature = "async-minreq"))] pub use r#async::Sleeper; pub mod api; -#[cfg(feature = "async")] +#[cfg(any(feature = "async", feature = "async-minreq"))] pub mod r#async; #[cfg(feature = "blocking")] pub mod blocking; @@ -86,7 +96,7 @@ pub mod blocking; pub use api::*; #[cfg(feature = "blocking")] pub use blocking::BlockingClient; -#[cfg(feature = "async")] +#[cfg(any(feature = "async", feature = "async-minreq"))] pub use r#async::AsyncClient; /// Response status codes for which the request may be retried. @@ -126,7 +136,7 @@ pub struct Builder { /// /// Note that the format of this value and the supported protocols change /// slightly between the blocking version of the client (using `minreq`) - /// and the async version (using `reqwest`). For more details check with + /// and the async version (using `reqwest` or `async-minreq`). For more details check with /// the documentation of the two crates. Both of them are compiled with /// the `socks` feature enabled. /// @@ -184,14 +194,14 @@ impl Builder { } /// Build an asynchronous client from builder - #[cfg(all(feature = "async", feature = "tokio"))] + #[cfg(all(any(feature = "async", feature = "async-minreq"), feature = "tokio"))] pub fn build_async(self) -> Result { AsyncClient::from_builder(self) } /// Build an asynchronous client from builder where the returned client uses a /// user-defined [`Sleeper`]. - #[cfg(feature = "async")] + #[cfg(any(feature = "async", feature = "async-minreq"))] pub fn build_async_with_sleeper(self) -> Result, Error> { AsyncClient::from_builder(self) } @@ -203,7 +213,9 @@ pub enum Error { /// Error during `minreq` HTTP request #[cfg(feature = "blocking")] Minreq(::minreq::Error), - /// Error during reqwest HTTP request + /// Error during async_minreq HTTP request + #[cfg(all(feature = "async-minreq", not(feature = "async")))] + AsyncMinreq(async_minreq::Error), #[cfg(feature = "async")] Reqwest(::reqwest::Error), /// HTTP response error @@ -250,10 +262,11 @@ macro_rules! impl_error { } }; } - impl std::error::Error for Error {} #[cfg(feature = "blocking")] impl_error!(::minreq::Error, Minreq, Error); +#[cfg(all(feature = "async-minreq", not(feature = "async")))] +impl_error!(::async_minreq::Error, AsyncMinreq, Error); #[cfg(feature = "async")] impl_error!(::reqwest::Error, Reqwest, Error); impl_error!(std::num::ParseIntError, Parsing, Error); @@ -268,7 +281,7 @@ mod test { use lazy_static::lazy_static; use std::env; use tokio::sync::Mutex; - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] use { bitcoin::{hashes::Hash, Amount}, corepc_node::AddressType, @@ -302,15 +315,15 @@ mod test { static ref MINER: Mutex<()> = Mutex::new(()); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] static PREMINE: OnceCell<()> = OnceCell::const_new(); - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] async fn setup_clients() -> (BlockingClient, AsyncClient) { setup_clients_with_headers(HashMap::new()).await } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] async fn setup_clients_with_headers( headers: HashMap, ) -> (BlockingClient, AsyncClient) { @@ -343,14 +356,14 @@ mod test { (blocking_client, async_client) } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] fn generate_blocks_and_wait(num: usize) { let cur_height = BITCOIND.client.get_block_count().unwrap().0; generate_blocks(num); wait_for_block(cur_height as usize + num); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] fn generate_blocks(num: usize) { let address = BITCOIND .client @@ -359,7 +372,7 @@ mod test { let _block_hashes = BITCOIND.client.generate_to_address(num, &address).unwrap(); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] fn wait_for_block(min_height: usize) { let mut header = ELECTRSD.client.block_headers_subscribe().unwrap(); loop { @@ -374,7 +387,7 @@ mod test { } } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] fn exponential_backoff_poll(mut poll: F) -> T where F: FnMut() -> Option, @@ -440,7 +453,7 @@ mod test { ); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_tx() { let (blocking_client, async_client) = setup_clients().await; @@ -463,7 +476,7 @@ mod test { assert_eq!(tx, tx_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_tx_no_opt() { let (blocking_client, async_client) = setup_clients().await; @@ -486,7 +499,7 @@ mod test { assert_eq!(tx_no_opt, tx_no_opt_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_tx_status() { let (blocking_client, async_client) = setup_clients().await; @@ -520,7 +533,7 @@ mod test { assert!(tx_status.block_time.is_none()); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_tx_info() { let (blocking_client, async_client) = setup_clients().await; @@ -581,7 +594,7 @@ mod test { assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_header_by_hash() { let (blocking_client, async_client) = setup_clients().await; @@ -598,7 +611,7 @@ mod test { assert_eq!(block_header, block_header_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_block_status() { let (blocking_client, async_client) = setup_clients().await; @@ -628,7 +641,7 @@ mod test { assert_eq!(expected, block_status_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_non_existing_block_status() { // Esplora returns the same status for orphaned blocks as for non-existing @@ -653,7 +666,7 @@ mod test { assert_eq!(expected, block_status_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_block_by_hash() { let (blocking_client, async_client) = setup_clients().await; @@ -673,7 +686,7 @@ mod test { assert_eq!(expected, block_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_that_errors_are_propagated() { let (blocking_client, async_client) = setup_clients().await; @@ -706,7 +719,7 @@ mod test { )); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_block_by_hash_not_existing() { let (blocking_client, async_client) = setup_clients().await; @@ -722,7 +735,7 @@ mod test { assert!(block_async.is_none()); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_merkle_proof() { let (blocking_client, async_client) = setup_clients().await; @@ -746,7 +759,7 @@ mod test { assert!(merkle_proof.pos > 0); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_merkle_block() { let (blocking_client, async_client) = setup_clients().await; @@ -779,7 +792,7 @@ mod test { assert!(indexes[0] > 0); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_output_status() { let (blocking_client, async_client) = setup_clients().await; @@ -810,7 +823,7 @@ mod test { assert_eq!(output_status, output_status_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_height() { let (blocking_client, async_client) = setup_clients().await; @@ -820,7 +833,7 @@ mod test { assert_eq!(block_height, block_height_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_tip_hash() { let (blocking_client, async_client) = setup_clients().await; @@ -829,7 +842,7 @@ mod test { assert_eq!(tip_hash, tip_hash_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_block_hash() { let (blocking_client, async_client) = setup_clients().await; @@ -847,7 +860,7 @@ mod test { assert_eq!(block_hash, block_hash_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_txid_at_block_index() { let (blocking_client, async_client) = setup_clients().await; @@ -871,7 +884,7 @@ mod test { assert_eq!(txid_at_block_index, txid_at_block_index_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_fee_estimates() { let (blocking_client, async_client) = setup_clients().await; @@ -880,7 +893,7 @@ mod test { assert_eq!(fee_estimates.len(), fee_estimates_async.len()); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_scripthash_txs() { let (blocking_client, async_client) = setup_clients().await; @@ -922,7 +935,7 @@ mod test { assert_eq!(scripthash_txs_txids, scripthash_txs_txids_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_blocks() { let (blocking_client, async_client) = setup_clients().await; @@ -951,7 +964,7 @@ mod test { assert_eq!(blocks_genesis, blocks_genesis_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_tx_with_http_header() { let headers = [( @@ -979,7 +992,7 @@ mod test { assert_eq!(tx, tx_async); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_address_stats() { let (blocking_client, async_client) = setup_clients().await; @@ -1011,7 +1024,7 @@ mod test { assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000); } - #[cfg(all(feature = "blocking", feature = "async"))] + #[cfg(all(feature = "blocking", any(feature = "async", feature = "async-minreq")))] #[tokio::test] async fn test_get_address_txs() { let (blocking_client, async_client) = setup_clients().await;