9
9
// You may not use this file except in accordance with one or both of these
10
10
// licenses.
11
11
12
- //! Esplora by way of `reqwest` HTTP client.
12
+ //! Esplora by way of `reqwest`, and `arti-hyper` HTTP client.
13
13
14
14
use std:: collections:: HashMap ;
15
15
use std:: str:: FromStr ;
16
16
17
+ use arti_client:: { TorClient , TorClientConfig } ;
18
+
19
+ use arti_hyper:: ArtiHttpConnector ;
17
20
use bitcoin:: consensus:: { deserialize, serialize} ;
18
21
use bitcoin:: hashes:: hex:: FromHex ;
19
22
use bitcoin:: hashes:: { sha256, Hash } ;
@@ -22,10 +25,17 @@ use bitcoin::{
22
25
} ;
23
26
use bitcoin_internals:: hex:: display:: DisplayHex ;
24
27
28
+ use hyper:: { Body , Response , Uri } ;
25
29
#[ allow( unused_imports) ]
26
30
use log:: { debug, error, info, trace} ;
27
31
28
32
use 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 ;
29
39
30
40
use crate :: { BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus } ;
31
41
@@ -429,3 +439,204 @@ impl AsyncClient {
429
439
& self . client
430
440
}
431
441
}
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