diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 1b5b376..b826197 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -22,6 +22,10 @@ jobs: features: - default - blocking + - blocking-https + - blocking-https-rustls + - blocking-https-native + - blocking-https-bundled - async - async-https - async-https-native @@ -53,7 +57,6 @@ jobs: run: | cargo update -p zstd-sys --precise "2.0.8+zstd.1.5.5" cargo update -p time --precise "0.3.20" - cargo update -p jobserver --precise "0.1.26" cargo update -p home --precise 0.5.5 - name: Build run: cargo build --features ${{ matrix.features }} --no-default-features diff --git a/Cargo.toml b/Cargo.toml index 54caef3..2fc234b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ serde = { version = "1.0", features = ["derive"] } bitcoin = { version = "0.31.0", features = ["serde", "std"], default-features = false } hex = { package = "hex-conservative", version = "*" } log = "^0.4" -ureq = { version = "2.5.0", features = ["json"], optional = true } +minreq = { version = "2.11.0", features = ["json-using-serde"], optional = true } reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] } [dev-dependencies] @@ -32,7 +32,11 @@ lazy_static = "1.4.0" [features] default = ["blocking", "async", "async-https"] -blocking = ["ureq", "ureq/socks-proxy"] +blocking = ["minreq", "minreq/proxy"] +blocking-https = ["blocking", "minreq/https"] +blocking-https-rustls = ["blocking", "minreq/https-rustls"] +blocking-https-native = ["blocking", "minreq/https-native"] +blocking-https-bundled = ["blocking", "minreq/https-bundled"] async = ["reqwest", "reqwest/socks"] async-https = ["async", "reqwest/default-tls"] async-https-native = ["async", "reqwest/native-tls"] diff --git a/README.md b/README.md index 84f62ff..cd94b31 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,4 @@ 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. - -To build with the MSRV you will need to pin dependencies as follows: - -```shell -cargo update -p jobserver --precise "0.1.26" -``` \ No newline at end of file +This library should compile with any combination of features with Rust 1.63.0. \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index 2e72337..c700a38 100644 --- a/src/api.rs +++ b/src/api.rs @@ -4,7 +4,9 @@ pub use bitcoin::consensus::{deserialize, serialize}; pub use bitcoin::hex::FromHex; -pub use bitcoin::{transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness}; +pub use bitcoin::{ + transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness, +}; use serde::Deserialize; diff --git a/src/async.rs b/src/async.rs index caa91fe..683bedb 100644 --- a/src/async.rs +++ b/src/async.rs @@ -15,8 +15,8 @@ use std::collections::HashMap; use std::str::FromStr; use bitcoin::consensus::{deserialize, serialize}; -use bitcoin::hex::{DisplayHex, FromHex}; use bitcoin::hashes::{sha256, Hash}; +use bitcoin::hex::{DisplayHex, FromHex}; use bitcoin::{ block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid, }; @@ -131,16 +131,6 @@ impl AsyncClient { } } - #[deprecated( - since = "0.2.0", - note = "Deprecated to improve alignment with Esplora API. Users should use `get_block_hash` and `get_header_by_hash` methods directly." - )] - /// Get a [`BlockHeader`] given a particular block height. - pub async fn get_header(&self, block_height: u32) -> Result { - let block_hash = self.get_block_hash(block_height).await?; - self.get_header_by_hash(&block_hash).await - } - /// Get a [`BlockHeader`] given a particular block hash. pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result { let resp = self diff --git a/src/blocking.rs b/src/blocking.rs index f05136d..bc581dd 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -9,22 +9,20 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! Esplora by way of `ureq` HTTP client. +//! Esplora by way of `minreq` HTTP client. use std::collections::HashMap; -use std::io; -use std::io::Read; +use std::convert::TryFrom; use std::str::FromStr; -use std::time::Duration; #[allow(unused_imports)] use log::{debug, error, info, trace}; -use ureq::{Agent, Proxy, Response}; +use minreq::{Proxy, Request}; -use bitcoin::consensus::{deserialize, serialize}; -use bitcoin::hex::{DisplayHex, FromHex}; +use bitcoin::consensus::{deserialize, serialize, Decodable}; use bitcoin::hashes::{sha256, Hash}; +use bitcoin::hex::{DisplayHex, FromHex}; use bitcoin::{ block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid, }; @@ -34,52 +32,149 @@ use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus #[derive(Debug, Clone)] pub struct BlockingClient { url: String, - agent: Agent, + /// The proxy is ignored when targeting `wasm32`. + pub proxy: Option, + /// Socket timeout. + pub timeout: Option, } impl BlockingClient { /// build a blocking client from a [`Builder`] - pub fn from_builder(builder: Builder) -> Result { - let mut agent_builder = ureq::AgentBuilder::new(); + pub fn from_builder(builder: Builder) -> Self { + Self { + url: builder.base_url, + proxy: builder.proxy, + timeout: builder.timeout, + } + } + + fn get_request(&self, path: &str) -> Result { + let mut request = minreq::get(format!("{}{}", self.url, path)); - if let Some(timeout) = builder.timeout { - agent_builder = agent_builder.timeout(Duration::from_secs(timeout)); + if let Some(proxy) = &self.proxy { + let proxy = Proxy::new(proxy.as_str())?; + request = request.with_proxy(proxy); } - if let Some(proxy) = &builder.proxy { - agent_builder = agent_builder.proxy(Proxy::new(proxy)?); + if let Some(timeout) = &self.timeout { + request = request.with_timeout(*timeout); } - Ok(Self::from_agent(builder.base_url, agent_builder.build())) + Ok(request) } - /// build a blocking client from an [`Agent`] - pub fn from_agent(url: String, agent: Agent) -> Self { - BlockingClient { url, agent } + fn get_opt_response(&self, path: &str) -> Result, Error> { + match self.get_request(path)?.send() { + Ok(resp) if is_status_not_found(resp.status_code) => Ok(None), + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => Ok(Some(deserialize::(resp.as_bytes())?)), + Err(e) => Err(Error::Minreq(e)), + } } - /// Get a [`Transaction`] option given its [`Txid`] - pub fn get_tx(&self, txid: &Txid) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/tx/{}/raw", self.url, txid)) - .call(); - - match resp { - Ok(resp) => Ok(Some(deserialize(&into_bytes(resp)?)?)), - Err(ureq::Error::Status(code, resp)) => { - if is_status_not_found(code) { - return Ok(None); - } - Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }) + fn get_opt_response_txid(&self, path: &str) -> Result, Error> { + match self.get_request(path)?.send() { + Ok(resp) if is_status_not_found(resp.status_code) => Ok(None), + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => Ok(Some( + Txid::from_str(resp.as_str().map_err(Error::Minreq)?).map_err(Error::HexToArray)?, + )), + Err(e) => Err(Error::Minreq(e)), + } + } + + fn get_opt_response_hex(&self, path: &str) -> Result, Error> { + match self.get_request(path)?.send() { + Ok(resp) if is_status_not_found(resp.status_code) => Ok(None), + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => { + let hex_str = resp.as_str().map_err(Error::Minreq)?; + let hex_vec = Vec::from_hex(hex_str).unwrap(); + deserialize::(&hex_vec) + .map_err(Error::BitcoinEncoding) + .map(|r| Some(r)) + } + Err(e) => Err(Error::Minreq(e)), + } + } + + fn get_response_hex(&self, path: &str) -> Result { + match self.get_request(path)?.send() { + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => { + let hex_str = resp.as_str().map_err(Error::Minreq)?; + let hex_vec = Vec::from_hex(hex_str).unwrap(); + deserialize::(&hex_vec).map_err(Error::BitcoinEncoding) } - Err(e) => Err(Error::Ureq(e)), + Err(e) => Err(Error::Minreq(e)), } } + fn get_response_json<'a, T: serde::de::DeserializeOwned>( + &'a self, + path: &'a str, + ) -> Result { + let response = self.get_request(path)?.send(); + match response { + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => Ok(resp.json::().map_err(Error::Minreq)?), + Err(e) => Err(Error::Minreq(e)), + } + } + + fn get_opt_response_json( + &self, + path: &str, + ) -> Result, Error> { + match self.get_request(path)?.send() { + Ok(resp) if is_status_not_found(resp.status_code) => Ok(None), + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => Ok(Some(resp.json::()?)), + Err(e) => Err(Error::Minreq(e)), + } + } + + fn get_response_str(&self, path: &str) -> Result { + match self.get_request(path)?.send() { + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(resp) => Ok(resp.as_str()?.to_string()), + Err(e) => Err(Error::Minreq(e)), + } + } + + /// Get a [`Transaction`] option given its [`Txid`] + pub fn get_tx(&self, txid: &Txid) -> Result, Error> { + self.get_opt_response(&format!("/tx/{}/raw", txid)) + } + /// Get a [`Transaction`] given its [`Txid`]. pub fn get_tx_no_opt(&self, txid: &Txid) -> Result { match self.get_tx(txid) { @@ -95,151 +190,37 @@ impl BlockingClient { block_hash: &BlockHash, index: usize, ) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/block/{}/txid/{}", self.url, block_hash, index)) - .call(); - - match resp { - Ok(resp) => Ok(Some(Txid::from_str(&resp.into_string()?)?)), - Err(ureq::Error::Status(code, resp)) => { - if is_status_not_found(code) { - return Ok(None); - } - Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }) - } - Err(e) => Err(Error::Ureq(e)), - } + self.get_opt_response_txid(&format!("/block/{}/txid/{}", block_hash, index)) } /// Get the status of a [`Transaction`] given its [`Txid`]. pub fn get_tx_status(&self, txid: &Txid) -> Result { - let resp = self - .agent - .get(&format!("{}/tx/{}/status", self.url, txid)) - .call(); - - match resp { - Ok(resp) => Ok(resp.into_json()?), - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), - } - } - - /// Get a [`BlockHeader`] given a particular block height. - #[deprecated( - since = "0.2.0", - note = "Deprecated to improve alignment with Esplora API. Users should use `get_block_hash` and `get_header_by_hash` methods directly." - )] - pub fn get_header(&self, block_height: u32) -> Result { - let block_hash = self.get_block_hash(block_height)?; - self.get_header_by_hash(&block_hash) + self.get_response_json(&format!("/tx/{}/status", txid)) } /// Get a [`BlockHeader`] given a particular block hash. pub fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result { - let resp = self - .agent - .get(&format!("{}/block/{}/header", self.url, block_hash)) - .call(); - - match resp { - Ok(resp) => Ok(deserialize(&Vec::from_hex(&resp.into_string()?)?)?), - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), - } + self.get_response_hex(&format!("/block/{}/header", block_hash)) } /// Get the [`BlockStatus`] given a particular [`BlockHash`]. pub fn get_block_status(&self, block_hash: &BlockHash) -> Result { - let resp = self - .agent - .get(&format!("{}/block/{}/status", self.url, block_hash)) - .call(); - - match resp { - Ok(resp) => Ok(resp.into_json()?), - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), - } + self.get_response_json(&format!("/block/{}/status", block_hash)) } /// Get a [`Block`] given a particular [`BlockHash`]. pub fn get_block_by_hash(&self, block_hash: &BlockHash) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/block/{}/raw", self.url, block_hash)) - .call(); - - match resp { - Ok(resp) => Ok(Some(deserialize(&into_bytes(resp)?)?)), - Err(ureq::Error::Status(code, resp)) => { - if is_status_not_found(code) { - return Ok(None); - } - Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }) - } - Err(e) => Err(Error::Ureq(e)), - } + self.get_opt_response(&format!("/block/{}/raw", block_hash)) } /// Get a merkle inclusion proof for a [`Transaction`] with the given [`Txid`]. pub fn get_merkle_proof(&self, txid: &Txid) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/tx/{}/merkle-proof", self.url, txid)) - .call(); - - match resp { - Ok(resp) => Ok(Some(resp.into_json()?)), - Err(ureq::Error::Status(code, resp)) => { - if is_status_not_found(code) { - return Ok(None); - } - Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }) - } - Err(e) => Err(Error::Ureq(e)), - } + self.get_opt_response_json(&format!("/tx/{}/merkle-proof", txid)) } /// Get a [`MerkleBlock`] inclusion proof for a [`Transaction`] with the given [`Txid`]. pub fn get_merkle_block(&self, txid: &Txid) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/tx/{}/merkleblock-proof", self.url, txid)) - .call(); - - match resp { - Ok(resp) => Ok(Some(deserialize(&Vec::from_hex(&resp.into_string()?)?)?)), - Err(ureq::Error::Status(code, resp)) => { - if is_status_not_found(code) { - return Ok(None); - } - Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }) - } - Err(e) => Err(Error::Ureq(e)), - } + self.get_opt_response_hex(&format!("/tx/{}/merkleblock-proof", txid)) } /// Get the spending status of an output given a [`Txid`] and the output index. @@ -248,118 +229,60 @@ impl BlockingClient { txid: &Txid, index: u64, ) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/tx/{}/outspend/{}", self.url, txid, index)) - .call(); - - match resp { - Ok(resp) => Ok(Some(resp.into_json()?)), - Err(ureq::Error::Status(code, resp)) => { - if is_status_not_found(code) { - return Ok(None); - } - Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }) - } - Err(e) => Err(Error::Ureq(e)), - } + self.get_opt_response_json(&format!("/tx/{}/outspend/{}", txid, index)) } /// Broadcast a [`Transaction`] to Esplora pub fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> { - let resp = self - .agent - .post(&format!("{}/tx", self.url)) - .send_string(&serialize(transaction).to_lower_hex_string()); - - match resp { - Ok(_) => Ok(()), // We do not return the txid? - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), + let mut request = minreq::post(format!("{}/tx", self.url)).with_body( + serialize(transaction) + .to_lower_hex_string() + .as_bytes() + .to_vec(), + ); + + if let Some(proxy) = &self.proxy { + let proxy = Proxy::new(proxy.as_str())?; + request = request.with_proxy(proxy); + } + + if let Some(timeout) = &self.timeout { + request = request.with_timeout(*timeout); + } + + match request.send() { + Ok(resp) if !is_status_ok(resp.status_code) => { + let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?; + let message = resp.as_str().unwrap_or_default().to_string(); + Err(Error::HttpResponse { status, message }) + } + Ok(_resp) => Ok(()), + Err(e) => Err(Error::Minreq(e)), } } /// Get the height of the current blockchain tip. pub fn get_height(&self) -> Result { - let resp = self - .agent - .get(&format!("{}/blocks/tip/height", self.url)) - .call(); - - match resp { - Ok(resp) => Ok(resp.into_string()?.parse()?), - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), - } + self.get_response_str("/blocks/tip/height") + .map(|s| u32::from_str(s.as_str()).map_err(Error::Parsing))? } /// Get the [`BlockHash`] of the current blockchain tip. pub fn get_tip_hash(&self) -> Result { - let resp = self - .agent - .get(&format!("{}/blocks/tip/hash", self.url)) - .call(); - - Self::process_block_result(resp) + self.get_response_str("/blocks/tip/hash") + .map(|s| BlockHash::from_str(s.as_str()).map_err(Error::HexToArray))? } /// Get the [`BlockHash`] of a specific block height pub fn get_block_hash(&self, block_height: u32) -> Result { - let resp = self - .agent - .get(&format!("{}/block-height/{}", self.url, block_height)) - .call(); - - if let Err(ureq::Error::Status(code, _)) = resp { - if is_status_not_found(code) { - return Err(Error::HeaderHeightNotFound(block_height)); - } - } - - Self::process_block_result(resp) - } - - fn process_block_result(response: Result) -> Result { - match response { - Ok(resp) => Ok(BlockHash::from_str(&resp.into_string()?)?), - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), - } + self.get_response_str(&format!("/block-height/{}", block_height)) + .map(|s| BlockHash::from_str(s.as_str()).map_err(Error::HexToArray))? } /// Get an map where the key is the confirmation target (in number of blocks) /// and the value is the estimated feerate (in sat/vB). pub fn get_fee_estimates(&self) -> Result, Error> { - let resp = self - .agent - .get(&format!("{}/fee-estimates", self.url,)) - .call(); - - let map = match resp { - Ok(resp) => { - let map: HashMap = resp.into_json()?; - Ok(map) - } - Err(ureq::Error::Status(code, resp)) => Err(Error::HttpResponse { - status: code, - message: resp.into_string()?, - }), - Err(e) => Err(Error::Ureq(e)), - }?; - - Ok(map) + self.get_response_json("/fee-estimates") } /// Get confirmed transaction history for the specified address/scripthash, @@ -371,14 +294,11 @@ impl BlockingClient { last_seen: Option, ) -> Result, Error> { let script_hash = sha256::Hash::hash(script.as_bytes()); - let url = match last_seen { - Some(last_seen) => format!( - "{}/scripthash/{:x}/txs/chain/{}", - self.url, script_hash, last_seen - ), - None => format!("{}/scripthash/{:x}/txs", self.url, script_hash), + let path = match last_seen { + Some(last_seen) => format!("/scripthash/{:x}/txs/chain/{}", script_hash, last_seen), + None => format!("/scripthash/{:x}/txs", script_hash), }; - Ok(self.agent.get(&url).call()?.into_json()?) + self.get_response_json(&path) } /// Gets some recent block summaries starting at the tip or at `height` if provided. @@ -386,57 +306,18 @@ impl BlockingClient { /// The maximum number of summaries returned depends on the backend itself: esplora returns `10` /// while [mempool.space](https://mempool.space/docs/api) returns `15`. pub fn get_blocks(&self, height: Option) -> Result, Error> { - let url = match height { - Some(height) => format!("{}/blocks/{}", self.url, height), - None => format!("{}/blocks", self.url), + let path = match height { + Some(height) => format!("/blocks/{}", height), + None => "/blocks".to_string(), }; - - Ok(self.agent.get(&url).call()?.into_json()?) - } - - /// Get the underlying base URL. - pub fn url(&self) -> &str { - &self.url + self.get_response_json(&path) } - - /// Get the underlying [`Agent`]. - pub fn agent(&self) -> &Agent { - &self.agent - } -} - -fn is_status_not_found(status: u16) -> bool { - status == 404 } -fn into_bytes(resp: Response) -> Result, io::Error> { - const BYTES_LIMIT: usize = 10 * 1_024 * 1_024; - - let mut buf: Vec = vec![]; - resp.into_reader() - .take((BYTES_LIMIT + 1) as u64) - .read_to_end(&mut buf)?; - if buf.len() > BYTES_LIMIT { - return Err(io::Error::new( - io::ErrorKind::Other, - "response too big for into_bytes", - )); - } - - Ok(buf) +fn is_status_ok(status: i32) -> bool { + status == 200 } -impl From for Error { - fn from(e: ureq::Error) -> Self { - match e { - ureq::Error::Status(code, resp) => match resp.into_string() { - Ok(msg) => Error::HttpResponse { - status: code, - message: msg, - }, - Err(e) => Error::Io(e), - }, - e => Error::Ureq(e), - } - } +fn is_status_not_found(status: i32) -> bool { + status == 404 } diff --git a/src/lib.rs b/src/lib.rs index d9adedb..b5db357 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! async Esplora client to query Esplora's backend. //! //! The library provides the possibility to build a blocking -//! client using [`ureq`] and an async client using [`reqwest`]. +//! client using [`minreq`] and an async client using [`reqwest`]. //! The library supports communicating to Esplora via a proxy //! and also using TLS (SSL) for secure communication. //! @@ -43,7 +43,15 @@ //! //! `esplora-client = { version = "*", default-features = false, features = ["blocking"] }` //! -//! * `blocking` enables [`ureq`], the blocking client with proxy and TLS (SSL) capabilities. +//! * `blocking` enables [`minreq`], the blocking client with proxy. +//! * `blocking-https` enables [`minreq`], the blocking client with proxy and TLS (SSL) +//! capabilities using the default [`minreq`] backend. +//! * `blocking-https-rustls` enables [`minreq`], the blocking client with proxy and TLS (SSL) +//! capabilities using the `rustls` backend. +//! * `blocking-https-native` enables [`minreq`], the blocking client with proxy and TLS (SSL) +//! capabilities using the platform's native TLS backend (likely OpenSSL). +//! * `blocking-https-bundled` enables [`minreq`], the blocking client with proxy and TLS (SSL) +//! capabilities using a bundled OpenSSL library backend. //! * `async` enables [`reqwest`], the async client with proxy capabilities. //! * `async-https` enables [`reqwest`], the async client with support for proxying and TLS (SSL) //! using the default [`reqwest`] TLS backend. @@ -61,7 +69,7 @@ use std::collections::HashMap; use std::fmt; -use std::io; +use std::num::TryFromIntError; use bitcoin::consensus; @@ -104,7 +112,7 @@ pub struct Builder { /// The string should be formatted as: `://:@host:`. /// /// Note that the format of this value and the supported protocols change slightly between the - /// blocking version of the client (using `ureq`) and the async version (using `reqwest`). For more + /// blocking version of the client (using `minreq`) and the async version (using `reqwest`). For more /// details check with the documentation of the two crates. Both of them are compiled with /// the `socks` feature enabled. /// @@ -138,7 +146,7 @@ impl Builder { /// build a blocking client from builder #[cfg(feature = "blocking")] - pub fn build_blocking(self) -> Result { + pub fn build_blocking(self) -> BlockingClient { BlockingClient::from_builder(self) } @@ -152,30 +160,24 @@ impl Builder { /// Errors that can happen during a sync with `Esplora` #[derive(Debug)] pub enum Error { - /// Error during ureq HTTP request + /// Error during `minreq` HTTP request #[cfg(feature = "blocking")] - Ureq(::ureq::Error), - /// Transport error during the ureq HTTP call - #[cfg(feature = "blocking")] - UreqTransport(::ureq::Transport), + Minreq(::minreq::Error), /// Error during reqwest HTTP request #[cfg(feature = "async")] Reqwest(::reqwest::Error), /// HTTP response error HttpResponse { status: u16, message: String }, - /// IO error during ureq response read - Io(io::Error), - /// No header found in ureq response - NoHeader, /// Invalid number returned Parsing(std::num::ParseIntError), + /// Invalid status code, unable to convert to `u16` + StatusCode(TryFromIntError), /// Invalid Bitcoin data returned BitcoinEncoding(bitcoin::consensus::encode::Error), /// Invalid hex data returned (attempting to create an array) HexToArray(bitcoin::hex::HexToArrayError), /// Invalid hex data returned (attempting to create a vector) HexToBytes(bitcoin::hex::HexToBytesError), - /// Transaction not found TransactionNotFound(Txid), /// Header height not found @@ -205,10 +207,9 @@ macro_rules! impl_error { impl std::error::Error for Error {} #[cfg(feature = "blocking")] -impl_error!(::ureq::Transport, UreqTransport, Error); +impl_error!(::minreq::Error, Minreq, Error); #[cfg(feature = "async")] impl_error!(::reqwest::Error, Reqwest, Error); -impl_error!(io::Error, Io, Error); impl_error!(std::num::ParseIntError, Parsing, Error); impl_error!(consensus::encode::Error, BitcoinEncoding, Error); impl_error!(bitcoin::hex::HexToArrayError, HexToArray, Error); @@ -273,7 +274,7 @@ mod test { let esplora_url = ELECTRSD.esplora_url.as_ref().unwrap(); let builder = Builder::new(&format!("http://{}", esplora_url)); - let blocking_client = builder.build_blocking().unwrap(); + let blocking_client = builder.build_blocking(); let builder_async = Builder::new(&format!("http://{}", esplora_url)); let async_client = builder_async.build_async().unwrap();