Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip(feat): add new AsyncAnonymizedClient using arti-hyper
Browse files Browse the repository at this point in the history
- feat: add new async client, `AsyncAnonymizedClient`, that uses `arti-hyper`,
  and `arti-client` to connect and do requests over the Tor network.
- feat+test: add all methods and tests for `get_tx_..`, `Transaction`
  related endpoints.
oleonardolima committed Dec 21, 2023
1 parent ef1925e commit 86103c8
Showing 3 changed files with 306 additions and 7 deletions.
19 changes: 16 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -17,15 +17,24 @@ path = "src/lib.rs"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
bitcoin = { version = "0.30.0", features = ["serde", "std"], default-features = false }
# 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"] }

[target.'cfg(target_vendor="apple")'.dependencies]
tls-api-openssl = { version = "0.9.0", optional = true }

[dev-dependencies]
serde_json = "1.0"
tokio = { version = "1.20.1", features = ["full"] }
electrsd = { version = "0.24.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_22_0"] }
electrum-client = "0.16.0"
@@ -36,10 +45,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", "tls-api-openssl", "arti-hyper"]
async-arti-hyper-native = ["async-arti-hyper", "arti-hyper/native-tls"]
async-arti-hyper-rustls = ["async-arti-hyper", "arti-hyper/rustls"]
158 changes: 157 additions & 1 deletion src/async.rs
Original file line number Diff line number Diff line change
@@ -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};
@@ -22,10 +25,17 @@ use bitcoin::{
};
use bitcoin_internals::hex::display::DisplayHex;

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

use reqwest::{Client, StatusCode};
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
#[cfg(not(target_vendor = "apple"))]
use tls_api_native_tls::TlsConnector;
#[cfg(target_vendor = "apple")]
use tls_api_openssl::TlsConnector;
use tor_rtcompat::PreferredRuntime;

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

@@ -429,3 +439,149 @@ 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();

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 }
}

/// Get a [`Option<Transaction>`] given its [`Txid`]
pub async fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
let path = format!("{}/tx/{}/raw", self.url, txid);
let uri = Uri::from_str(&path).map_err(|_| Error::InvalidUri)?;

let resp = self.client.get(uri).await?;

if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}

if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: {
let body = resp.into_body();
let bytes = hyper::body::to_bytes(body).await?;
std::str::from_utf8(&bytes)
.map_err(|_| Error::ResponseDecoding)?
.to_string()
},
})
} else {
let body = resp.into_body();
let bytes = hyper::body::to_bytes(body).await?;
Ok(Some(deserialize(&bytes)?))
}
}

/// Get a [`Transaction`] given its [`Txid`].
pub async fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
match self.get_tx(txid).await {
Ok(Some(tx)) => Ok(tx),
Ok(None) => Err(Error::TransactionNotFound(*txid)),
Err(e) => Err(e),
}
}

/// Get a [`Txid`] of a transaction given its index in a block with a given hash.
pub async fn get_txid_at_block_index(
&self,
block_hash: &BlockHash,
index: usize,
) -> Result<Option<Txid>, Error> {
let path = format!("{}/block/{}/txid/{}", self.url, block_hash, index);
let uri = Uri::from_str(&path).map_err(|_| Error::InvalidUri)?;

let resp = self.client.get(uri).await?;

if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}

if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: {
let body = resp.into_body();
let bytes = hyper::body::to_bytes(body).await?;
std::str::from_utf8(&bytes)
.map_err(|_| Error::ResponseDecoding)?
.to_string()
},
})
} else {
let body = resp.into_body();
let bytes = hyper::body::to_bytes(body).await?;
Ok(Some(deserialize(&bytes)?))
}
}

/// Get the status of a [`Transaction`] given its [`Txid`].
pub async fn get_tx_status(&self, txid: &Txid) -> Result<TxStatus, Error> {
let path = format!("{}/tx/{}/status", self.url, txid);
let uri = Uri::from_str(&path).map_err(|_| Error::InvalidUri)?;

let resp = self.client.get(uri).await?;

if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: {
let body = resp.into_body();
let bytes = hyper::body::to_bytes(body).await?;
std::str::from_utf8(&bytes)
.map_err(|_| Error::ResponseDecoding)?
.to_string()
},
})
} else {
let body = resp.into_body();
let bytes = hyper::body::to_bytes(body).await?;
let tx_status =
serde_json::from_slice::<TxStatus>(&bytes).map_err(|_| Error::ResponseDecoding)?;
Ok(tx_status)
}
}

/// Get the underlying base URL.
pub fn url(&self) -> &str {
&self.url
}

/// Get the underlying [`hyper::Client`].
pub fn client(&self) -> &hyper::Client<ArtiHttpConnector<PreferredRuntime, TlsConnector>> {
&self.client
}
}
Loading

0 comments on commit 86103c8

Please sign in to comment.