Skip to content

Commit

Permalink
wip(feat): add new AsyncAnonymizedClient using arti-hyper
Browse files Browse the repository at this point in the history
- add new async client, `AsyncAnonymizedClient`, that uses `arti-hyper`,
  and `arti-client` to connect and do requests over the Tor network.
  • Loading branch information
oleonardolima committed Dec 8, 2023
1 parent ef1925e commit 6c763b6
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 6 deletions.
14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ bitcoin = { version = "0.30.0", features = ["serde", "std"], default-features =
# Temporary dependency on internals until the rust-bitcoin devs release the hex-conservative crate.
bitcoin-internals = { version = "0.1.0", features = ["alloc"] }
log = "^0.4"
ureq = { version = "2.5.0", features = ["json"], optional = true }
ureq = { version = "2.5.0", optional = true, features = ["json"]}
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
hyper = { version = "0.14", optional = true, features = ["http1", "client", "runtime"], default-features = false }
arti-client = { version = "0.12.0", optional = true }
tor-rtcompat = { version = "0.9.6", optional = true, features = ["tokio"]}
tls-api = { version = "0.9.0", optional = true }
tls-api-native-tls = { version = "0.9.0", optional = true }
arti-hyper = { version = "0.12.0", optional = true, features = ["default"] }

[dev-dependencies]
serde_json = "1.0"
Expand All @@ -36,10 +42,14 @@ zip = "=0.6.3"
base64ct = "<1.6.0"

[features]
default = ["blocking", "async", "async-https"]
default = ["blocking", "async", "async-https", "async-arti-hyper"]
blocking = ["ureq", "ureq/socks-proxy"]
async = ["reqwest", "reqwest/socks"]
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"]
# TODO: (@leonardo) Should I rename it to async-anonymized ?
async-arti-hyper = ["hyper", "arti-client", "tor-rtcompat", "tls-api", "tls-api-native-tls", "arti-hyper"]
async-arti-hyper-native = ["async-arti-hyper", "arti-hyper/native-tls"]
async-arti-hyper-rustls = ["async-arti-hyper", "arti-hyper/rustls"]
48 changes: 47 additions & 1 deletion src/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
// You may not use this file except in accordance with one or both of these
// licenses.

//! Esplora by way of `reqwest` HTTP client.
//! Esplora by way of `reqwest`, and `arti-hyper` HTTP client.
use std::collections::HashMap;
use std::str::FromStr;

use arti_client::{TorClient, TorClientConfig};

use arti_hyper::ArtiHttpConnector;
use bitcoin::consensus::{deserialize, serialize};
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256, Hash};
Expand All @@ -22,10 +25,14 @@ use bitcoin::{
};
use bitcoin_internals::hex::display::DisplayHex;

use hyper::Body;
#[allow(unused_imports)]
use log::{debug, error, info, trace};

use reqwest::{Client, StatusCode};
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
use tls_api_native_tls::TlsConnector;
use tor_rtcompat::PreferredRuntime;

use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus};

Expand Down Expand Up @@ -429,3 +436,42 @@ impl AsyncClient {
&self.client
}
}

#[derive(Debug, Clone)]
pub struct AsyncAnonymizedClient {
url: String,
client: hyper::Client<ArtiHttpConnector<PreferredRuntime, TlsConnector>>,
}

impl AsyncAnonymizedClient {
/// build an async [`TorClient`] with default Tor configuration
async fn create_tor_client() -> Result<TorClient<PreferredRuntime>, arti_client::Error> {
let config = TorClientConfig::default();
TorClient::create_bootstrapped(config).await
}

/// build an [`AsyncAnonymizedClient`] from a [`Builder`]
pub async fn from_builder(builder: Builder) -> Result<Self, Error> {
let tor_client = Self::create_tor_client().await?.isolated_client();

// TODO: (@leonardo) how to improve this error handling/propagation ?
let tls_conn: TlsConnector = TlsConnector::builder()
.map_err(|_| Error::TlsConnector)?
.build()
.map_err(|_| Error::TlsConnector)?;

let connector = ArtiHttpConnector::new(tor_client, tls_conn);

// TODO: (@leonardo) how to handle/pass the timeout option ?
let client = hyper::Client::builder().build::<_, Body>(connector);
Ok(Self::from_client(builder.base_url, client))
}

/// build an async client from the base url and [`Client`]
pub fn from_client(
url: String,
client: hyper::Client<ArtiHttpConnector<PreferredRuntime, TlsConnector>>,
) -> Self {
AsyncAnonymizedClient { url, client }
}
}
41 changes: 38 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
//! 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`].
//! The library supports communicating to Esplora via a proxy
//! client using [`ureq`], an async client using [`reqwest`],
//! and an anonymized async client using [`arti-hyper`].
//! The library supports communicating to Esplora via a Tor, proxy,
//! and also using TLS (SSL) for secure communication.
//!
//!
Expand Down Expand Up @@ -35,6 +36,19 @@
//! # }
//! ```
//!
//! Here is an example of how to create an anonymized asynchronous client.
//!
//! ```no_run
//! # #[cfg(feature = "arti-hyper")]
//! # {
//! use esplora_client::Builder;
//! let builder = Builder::new("http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/testnet/api");
//! let async_client = builder.build_async_anonymized();
//! # Ok::<(), esplora_client::Error>(());
//! # }
//! ```
//!
//!
//! ## Features
//!
//! By default the library enables all features. To specify
Expand All @@ -54,6 +68,12 @@
//! * `async-https-rustls-manual-roots` enables [`reqwest`], the async client with support for
//! proxying and TLS (SSL) using the `rustls` TLS backend without using its the default root
//! certificates.
//! * `async-arti-hyper` enables [`arti-hyper`], the async anonymized client support for TLS (SSL) over Tor,
//! using the default [`arti-hyper`] TLS backend.
//! * `async-arti-hyper-native` enables [`arti-hyper`], the async anonymized client support for TLS (SSL) over Tor,
//! using the platform's native TLS backend (likely OpenSSL).
//! * `async-arti-hyper-rustls` enables [`arti-hyper`], the async anonymized client support for TLS (SSL) over Tor,
//! using the `rustls` TLS backend without using its the default root certificates.
//!
//!
Expand All @@ -77,6 +97,8 @@ pub use api::*;
pub use blocking::BlockingClient;
#[cfg(feature = "async")]
pub use r#async::AsyncClient;
#[cfg(feature = "async-arti-hyper")]
pub use r#async::AsyncAnonymizedClient;

/// Get a fee value in sats/vbytes from the estimates
/// that matches the confirmation target set as parameter.
Expand Down Expand Up @@ -109,7 +131,7 @@ pub struct Builder {
/// the `socks` feature enabled.
///
/// The proxy is ignored when targeting `wasm32`.
pub proxy: Option<String>,
pub proxy: Option<String>, // TODO: (@leonardo) should this be available for `async-arti-hyper`
/// Socket timeout.
pub timeout: Option<u64>,
}
Expand Down Expand Up @@ -147,6 +169,11 @@ impl Builder {
pub fn build_async(self) -> Result<AsyncClient, Error> {
AsyncClient::from_builder(self)
}

// build an asynchronous anonymized (Tor) client from builder
pub async fn build_async_anonymized(self) -> Result<AsyncAnonymizedClient, Error> {
AsyncAnonymizedClient::from_builder(self).await
}
}

/// Errors that can happen during a sync with `Esplora`
Expand All @@ -161,6 +188,12 @@ pub enum Error {
/// Error during reqwest HTTP request
#[cfg(feature = "async")]
Reqwest(::reqwest::Error),
/// Error during Tor client creation
#[cfg(feature = "async-arti-hyper")]
ArtiClient(::arti_client::Error),
/// Error during TlsConnector building
#[cfg(feature = "async-arti-hyper")]
TlsConnector,
/// HTTP response error
HttpResponse { status: u16, message: String },
/// IO error during ureq response read
Expand Down Expand Up @@ -206,6 +239,8 @@ impl std::error::Error for Error {}
impl_error!(::ureq::Transport, UreqTransport, Error);
#[cfg(feature = "async")]
impl_error!(::reqwest::Error, Reqwest, Error);
#[cfg(feature = "async-arti-hyper")]
impl_error!(::arti_client::Error, ArtiClient, Error);
impl_error!(io::Error, Io, Error);
impl_error!(std::num::ParseIntError, Parsing, Error);
impl_error!(consensus::encode::Error, BitcoinEncoding, Error);
Expand Down

0 comments on commit 6c763b6

Please sign in to comment.