99// You may not use this file except in accordance with one or both of these
1010// licenses.
1111
12- //! Esplora by way of `reqwest` HTTP client.
12+ //! Esplora by way of `reqwest`, and `arti-hyper` HTTP client.
1313
1414use std:: collections:: HashMap ;
1515use std:: str:: FromStr ;
1616
17+ use arti_client:: { TorClient , TorClientConfig } ;
18+
19+ use arti_hyper:: ArtiHttpConnector ;
1720use bitcoin:: consensus:: { deserialize, serialize} ;
1821use bitcoin:: hashes:: hex:: FromHex ;
1922use bitcoin:: hashes:: { sha256, Hash } ;
@@ -22,10 +25,17 @@ use bitcoin::{
2225} ;
2326use bitcoin_internals:: hex:: display:: DisplayHex ;
2427
28+ use hyper:: { Body , Uri } ;
2529#[ allow( unused_imports) ]
2630use log:: { debug, error, info, trace} ;
2731
2832use reqwest:: { Client , StatusCode } ;
33+ use tls_api:: { TlsConnector as TlsConnectorTrait , TlsConnectorBuilder } ;
34+ #[ cfg( not( target_vendor = "apple" ) ) ]
35+ use tls_api_native_tls:: TlsConnector ;
36+ #[ cfg( target_vendor = "apple" ) ]
37+ use tls_api_openssl:: TlsConnector ;
38+ use tor_rtcompat:: PreferredRuntime ;
2939
3040use crate :: { BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus } ;
3141
@@ -429,3 +439,149 @@ impl AsyncClient {
429439 & self . client
430440 }
431441}
442+
443+ #[ derive( Debug , Clone ) ]
444+ pub struct AsyncAnonymizedClient {
445+ url : String ,
446+ client : hyper:: Client < ArtiHttpConnector < PreferredRuntime , TlsConnector > > ,
447+ }
448+
449+ impl AsyncAnonymizedClient {
450+ /// build an async [`TorClient`] with default Tor configuration
451+ async fn create_tor_client ( ) -> Result < TorClient < PreferredRuntime > , arti_client:: Error > {
452+ let config = TorClientConfig :: default ( ) ;
453+ TorClient :: create_bootstrapped ( config) . await
454+ }
455+
456+ /// build an [`AsyncAnonymizedClient`] from a [`Builder`]
457+ pub async fn from_builder ( builder : Builder ) -> Result < Self , Error > {
458+ let tor_client = Self :: create_tor_client ( ) . await ?. isolated_client ( ) ;
459+
460+ let tls_conn: TlsConnector = TlsConnector :: builder ( )
461+ . map_err ( |_| Error :: TlsConnector ) ?
462+ . build ( )
463+ . map_err ( |_| Error :: TlsConnector ) ?;
464+
465+ let connector = ArtiHttpConnector :: new ( tor_client, tls_conn) ;
466+
467+ // TODO: (@leonardo) how to handle/pass the timeout option ?
468+ let client = hyper:: Client :: builder ( ) . build :: < _ , Body > ( connector) ;
469+ Ok ( Self :: from_client ( builder. base_url , client) )
470+ }
471+
472+ /// build an async client from the base url and [`Client`]
473+ pub fn from_client (
474+ url : String ,
475+ client : hyper:: Client < ArtiHttpConnector < PreferredRuntime , TlsConnector > > ,
476+ ) -> Self {
477+ AsyncAnonymizedClient { url, client }
478+ }
479+
480+ /// Get a [`Option<Transaction>`] given its [`Txid`]
481+ pub async fn get_tx ( & self , txid : & Txid ) -> Result < Option < Transaction > , Error > {
482+ let path = format ! ( "{}/tx/{}/raw" , self . url, txid) ;
483+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
484+
485+ let resp = self . client . get ( uri) . await ?;
486+
487+ if let StatusCode :: NOT_FOUND = resp. status ( ) {
488+ return Ok ( None ) ;
489+ }
490+
491+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
492+ Err ( Error :: HttpResponse {
493+ status : resp. status ( ) . as_u16 ( ) ,
494+ message : {
495+ let body = resp. into_body ( ) ;
496+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
497+ std:: str:: from_utf8 ( & bytes)
498+ . map_err ( |_| Error :: ResponseDecoding ) ?
499+ . to_string ( )
500+ } ,
501+ } )
502+ } else {
503+ let body = resp. into_body ( ) ;
504+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
505+ Ok ( Some ( deserialize ( & bytes) ?) )
506+ }
507+ }
508+
509+ /// Get a [`Transaction`] given its [`Txid`].
510+ pub async fn get_tx_no_opt ( & self , txid : & Txid ) -> Result < Transaction , Error > {
511+ match self . get_tx ( txid) . await {
512+ Ok ( Some ( tx) ) => Ok ( tx) ,
513+ Ok ( None ) => Err ( Error :: TransactionNotFound ( * txid) ) ,
514+ Err ( e) => Err ( e) ,
515+ }
516+ }
517+
518+ /// Get a [`Txid`] of a transaction given its index in a block with a given hash.
519+ pub async fn get_txid_at_block_index (
520+ & self ,
521+ block_hash : & BlockHash ,
522+ index : usize ,
523+ ) -> Result < Option < Txid > , Error > {
524+ let path = format ! ( "{}/block/{}/txid/{}" , self . url, block_hash, index) ;
525+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
526+
527+ let resp = self . client . get ( uri) . await ?;
528+
529+ if let StatusCode :: NOT_FOUND = resp. status ( ) {
530+ return Ok ( None ) ;
531+ }
532+
533+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
534+ Err ( Error :: HttpResponse {
535+ status : resp. status ( ) . as_u16 ( ) ,
536+ message : {
537+ let body = resp. into_body ( ) ;
538+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
539+ std:: str:: from_utf8 ( & bytes)
540+ . map_err ( |_| Error :: ResponseDecoding ) ?
541+ . to_string ( )
542+ } ,
543+ } )
544+ } else {
545+ let body = resp. into_body ( ) ;
546+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
547+ Ok ( Some ( deserialize ( & bytes) ?) )
548+ }
549+ }
550+
551+ /// Get the status of a [`Transaction`] given its [`Txid`].
552+ pub async fn get_tx_status ( & self , txid : & Txid ) -> Result < TxStatus , Error > {
553+ let path = format ! ( "{}/tx/{}/status" , self . url, txid) ;
554+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
555+
556+ let resp = self . client . get ( uri) . await ?;
557+
558+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
559+ Err ( Error :: HttpResponse {
560+ status : resp. status ( ) . as_u16 ( ) ,
561+ message : {
562+ let body = resp. into_body ( ) ;
563+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
564+ std:: str:: from_utf8 ( & bytes)
565+ . map_err ( |_| Error :: ResponseDecoding ) ?
566+ . to_string ( )
567+ } ,
568+ } )
569+ } else {
570+ let body = resp. into_body ( ) ;
571+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
572+ let tx_status =
573+ serde_json:: from_slice :: < TxStatus > ( & bytes) . map_err ( |_| Error :: ResponseDecoding ) ?;
574+ Ok ( tx_status)
575+ }
576+ }
577+
578+ /// Get the underlying base URL.
579+ pub fn url ( & self ) -> & str {
580+ & self . url
581+ }
582+
583+ /// Get the underlying [`hyper::Client`].
584+ pub fn client ( & self ) -> & hyper:: Client < ArtiHttpConnector < PreferredRuntime , TlsConnector > > {
585+ & self . client
586+ }
587+ }
0 commit comments