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 , Response , 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,204 @@ 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 : Self :: text ( resp) . await ?,
495+ } )
496+ } else {
497+ let body = resp. into_body ( ) ;
498+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
499+ Ok ( Some ( deserialize ( & bytes) ?) )
500+ }
501+ }
502+
503+ /// Get a [`Transaction`] given its [`Txid`].
504+ pub async fn get_tx_no_opt ( & self , txid : & Txid ) -> Result < Transaction , Error > {
505+ match self . get_tx ( txid) . await {
506+ Ok ( Some ( tx) ) => Ok ( tx) ,
507+ Ok ( None ) => Err ( Error :: TransactionNotFound ( * txid) ) ,
508+ Err ( e) => Err ( e) ,
509+ }
510+ }
511+
512+ /// Get a [`Txid`] of a transaction given its index in a block with a given hash.
513+ pub async fn get_txid_at_block_index (
514+ & self ,
515+ block_hash : & BlockHash ,
516+ index : usize ,
517+ ) -> Result < Option < Txid > , Error > {
518+ let path = format ! ( "{}/block/{}/txid/{}" , self . url, block_hash, index) ;
519+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
520+
521+ let resp = self . client . get ( uri) . await ?;
522+
523+ if let StatusCode :: NOT_FOUND = resp. status ( ) {
524+ return Ok ( None ) ;
525+ }
526+
527+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
528+ Err ( Error :: HttpResponse {
529+ status : resp. status ( ) . as_u16 ( ) ,
530+ message : Self :: text ( resp) . await ?,
531+ } )
532+ } else {
533+ let text = Self :: text ( resp) . await ?;
534+ let txid = Txid :: from_str ( & text) ?;
535+ Ok ( Some ( txid) )
536+ }
537+ }
538+
539+ /// Get the status of a [`Transaction`] given its [`Txid`].
540+ pub async fn get_tx_status ( & self , txid : & Txid ) -> Result < TxStatus , Error > {
541+ let path = format ! ( "{}/tx/{}/status" , self . url, txid) ;
542+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
543+
544+ let resp = self . client . get ( uri) . await ?;
545+
546+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
547+ Err ( Error :: HttpResponse {
548+ status : resp. status ( ) . as_u16 ( ) ,
549+ message : Self :: text ( resp) . await ?,
550+ } )
551+ } else {
552+ let body = resp. into_body ( ) ;
553+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
554+ let tx_status =
555+ serde_json:: from_slice :: < TxStatus > ( & bytes) . map_err ( |_| Error :: ResponseDecoding ) ?;
556+ Ok ( tx_status)
557+ }
558+ }
559+
560+ /// Get a [`BlockHeader`] given a particular block hash.
561+ pub async fn get_header_by_hash ( & self , block_hash : & BlockHash ) -> Result < BlockHeader , Error > {
562+ let path = format ! ( "{}/block/{}/header" , self . url, block_hash) ;
563+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
564+
565+ let resp = self . client . get ( uri) . await ?;
566+
567+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
568+ Err ( Error :: HttpResponse {
569+ status : resp. status ( ) . as_u16 ( ) ,
570+ message : Self :: text ( resp) . await ?,
571+ } )
572+ } else {
573+ let text = Self :: text ( resp) . await ?;
574+ let block_header = deserialize ( & Vec :: from_hex ( & text) ?) ?;
575+ Ok ( block_header)
576+ }
577+ }
578+
579+ /// Get the [`BlockStatus`] given a particular [`BlockHash`].
580+ pub async fn get_block_status ( & self , block_hash : & BlockHash ) -> Result < BlockStatus , Error > {
581+ let path = & format ! ( "{}/block/{}/status" , self . url, block_hash) ;
582+ let uri = Uri :: from_str ( path) . map_err ( |_| Error :: InvalidUri ) ?;
583+ let resp = self . client . get ( uri) . await ?;
584+
585+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
586+ Err ( Error :: HttpResponse {
587+ status : resp. status ( ) . as_u16 ( ) ,
588+ message : Self :: text ( resp) . await ?,
589+ } )
590+ } else {
591+ let body = resp. into_body ( ) ;
592+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
593+
594+ let block_status = serde_json:: from_slice :: < BlockStatus > ( & bytes)
595+ . map_err ( |_| Error :: ResponseDecoding ) ?;
596+ Ok ( block_status)
597+ }
598+ }
599+
600+ /// Get a [`Block`] given a particular [`BlockHash`].
601+ pub async fn get_block_by_hash ( & self , block_hash : & BlockHash ) -> Result < Option < Block > , Error > {
602+ let path = format ! ( "{}/block/{}/raw" , self . url, block_hash) ;
603+ let uri = Uri :: from_str ( & path) . map_err ( |_| Error :: InvalidUri ) ?;
604+ let resp = self . client . get ( uri) . await ?;
605+
606+ if let StatusCode :: NOT_FOUND = resp. status ( ) {
607+ return Ok ( None ) ;
608+ }
609+
610+ if resp. status ( ) . is_server_error ( ) || resp. status ( ) . is_client_error ( ) {
611+ Err ( Error :: HttpResponse {
612+ status : resp. status ( ) . as_u16 ( ) ,
613+ message : Self :: text ( resp) . await ?,
614+ } )
615+ } else {
616+ let body = resp. into_body ( ) ;
617+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
618+ Ok ( Some ( deserialize ( & bytes) ?) )
619+ }
620+ }
621+
622+ /// Get the underlying base URL.
623+ pub fn url ( & self ) -> & str {
624+ & self . url
625+ }
626+
627+ /// Get the underlying [`hyper::Client`].
628+ pub fn client ( & self ) -> & hyper:: Client < ArtiHttpConnector < PreferredRuntime , TlsConnector > > {
629+ & self . client
630+ }
631+
632+ /// Get the given [`Response<Body>`] as [`String`].
633+ async fn text ( response : Response < Body > ) -> Result < String , Error > {
634+ let body = response. into_body ( ) ;
635+ let bytes = hyper:: body:: to_bytes ( body) . await ?;
636+
637+ match std:: str:: from_utf8 ( & bytes) {
638+ Ok ( text) => Ok ( text. to_string ( ) ) ,
639+ Err ( _) => Err ( Error :: ResponseDecoding ) ,
640+ }
641+ }
642+ }
0 commit comments