1414use std:: collections:: HashMap ;
1515use std:: convert:: TryFrom ;
1616use std:: str:: FromStr ;
17+ use std:: thread;
1718
1819#[ allow( unused_imports) ]
1920use log:: { debug, error, info, trace} ;
2021
21- use minreq:: { Proxy , Request } ;
22+ use minreq:: { Proxy , Request , Response } ;
2223
2324use bitcoin:: consensus:: { deserialize, serialize, Decodable } ;
2425use bitcoin:: hashes:: { sha256, Hash } ;
@@ -27,7 +28,10 @@ use bitcoin::{
2728 block:: Header as BlockHeader , Block , BlockHash , MerkleBlock , Script , Transaction , Txid ,
2829} ;
2930
30- use crate :: { BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus } ;
31+ use crate :: {
32+ BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus ,
33+ BASE_BACKOFF_MILLIS , RETRYABLE_ERROR_CODES ,
34+ } ;
3135
3236#[ derive( Debug , Clone ) ]
3337pub struct BlockingClient {
@@ -39,6 +43,8 @@ pub struct BlockingClient {
3943 pub timeout : Option < u64 > ,
4044 /// HTTP headers to set on every request made to Esplora server
4145 pub headers : HashMap < String , String > ,
46+ /// Number of times to retry a request
47+ pub max_retries : usize ,
4248}
4349
4450impl BlockingClient {
@@ -49,6 +55,7 @@ impl BlockingClient {
4955 proxy : builder. proxy ,
5056 timeout : builder. timeout ,
5157 headers : builder. headers ,
58+ max_retries : builder. max_retries ,
5259 }
5360 }
5461
@@ -80,20 +87,20 @@ impl BlockingClient {
8087 }
8188
8289 fn get_opt_response < T : Decodable > ( & self , path : & str ) -> Result < Option < T > , Error > {
83- match self . get_request ( path) ? . send ( ) {
90+ match self . get_with_retry ( path) {
8491 Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
8592 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
8693 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
8794 let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
8895 Err ( Error :: HttpResponse { status, message } )
8996 }
9097 Ok ( resp) => Ok ( Some ( deserialize :: < T > ( resp. as_bytes ( ) ) ?) ) ,
91- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
98+ Err ( e) => Err ( e ) ,
9299 }
93100 }
94101
95102 fn get_opt_response_txid ( & self , path : & str ) -> Result < Option < Txid > , Error > {
96- match self . get_request ( path) ? . send ( ) {
103+ match self . get_with_retry ( path) {
97104 Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
98105 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
99106 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
@@ -103,12 +110,12 @@ impl BlockingClient {
103110 Ok ( resp) => Ok ( Some (
104111 Txid :: from_str ( resp. as_str ( ) . map_err ( Error :: Minreq ) ?) . map_err ( Error :: HexToArray ) ?,
105112 ) ) ,
106- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
113+ Err ( e) => Err ( e ) ,
107114 }
108115 }
109116
110117 fn get_opt_response_hex < T : Decodable > ( & self , path : & str ) -> Result < Option < T > , Error > {
111- match self . get_request ( path) ? . send ( ) {
118+ match self . get_with_retry ( path) {
112119 Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
113120 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
114121 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
@@ -122,12 +129,12 @@ impl BlockingClient {
122129 . map_err ( Error :: BitcoinEncoding )
123130 . map ( |r| Some ( r) )
124131 }
125- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
132+ Err ( e) => Err ( e ) ,
126133 }
127134 }
128135
129136 fn get_response_hex < T : Decodable > ( & self , path : & str ) -> Result < T , Error > {
130- match self . get_request ( path) ? . send ( ) {
137+ match self . get_with_retry ( path) {
131138 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
132139 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
133140 let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
@@ -138,51 +145,51 @@ impl BlockingClient {
138145 let hex_vec = Vec :: from_hex ( hex_str) . unwrap ( ) ;
139146 deserialize :: < T > ( & hex_vec) . map_err ( Error :: BitcoinEncoding )
140147 }
141- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
148+ Err ( e) => Err ( e ) ,
142149 }
143150 }
144151
145152 fn get_response_json < ' a , T : serde:: de:: DeserializeOwned > (
146153 & ' a self ,
147154 path : & ' a str ,
148155 ) -> Result < T , Error > {
149- let response = self . get_request ( path) ? . send ( ) ;
156+ let response = self . get_with_retry ( path) ;
150157 match response {
151158 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
152159 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
153160 let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
154161 Err ( Error :: HttpResponse { status, message } )
155162 }
156163 Ok ( resp) => Ok ( resp. json :: < T > ( ) . map_err ( Error :: Minreq ) ?) ,
157- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
164+ Err ( e) => Err ( e ) ,
158165 }
159166 }
160167
161168 fn get_opt_response_json < T : serde:: de:: DeserializeOwned > (
162169 & self ,
163170 path : & str ,
164171 ) -> Result < Option < T > , Error > {
165- match self . get_request ( path) ? . send ( ) {
172+ match self . get_with_retry ( path) {
166173 Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
167174 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
168175 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
169176 let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
170177 Err ( Error :: HttpResponse { status, message } )
171178 }
172179 Ok ( resp) => Ok ( Some ( resp. json :: < T > ( ) ?) ) ,
173- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
180+ Err ( e) => Err ( e ) ,
174181 }
175182 }
176183
177184 fn get_response_str ( & self , path : & str ) -> Result < String , Error > {
178- match self . get_request ( path) ? . send ( ) {
185+ match self . get_with_retry ( path) {
179186 Ok ( resp) if !is_status_ok ( resp. status_code ) => {
180187 let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
181188 let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
182189 Err ( Error :: HttpResponse { status, message } )
183190 }
184191 Ok ( resp) => Ok ( resp. as_str ( ) ?. to_string ( ) ) ,
185- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
192+ Err ( e) => Err ( e ) ,
186193 }
187194 }
188195
@@ -339,6 +346,24 @@ impl BlockingClient {
339346 } ;
340347 self . get_response_json ( & path)
341348 }
349+
350+ /// Sends a GET request to the given `url`, retrying failed attempts
351+ /// for retryable error codes until max retries hit.
352+ pub fn get_with_retry ( & self , url : & str ) -> Result < Response , Error > {
353+ let mut delay = BASE_BACKOFF_MILLIS ;
354+ let mut attempts = 0 ;
355+
356+ loop {
357+ match self . get_request ( url) ?. send ( ) ? {
358+ resp if attempts < self . max_retries && is_status_retryable ( resp. status_code ) => {
359+ thread:: sleep ( delay) ;
360+ attempts += 1 ;
361+ delay *= 2 ;
362+ }
363+ resp => return Ok ( resp) ,
364+ }
365+ }
366+ }
342367}
343368
344369fn is_status_ok ( status : i32 ) -> bool {
@@ -348,3 +373,8 @@ fn is_status_ok(status: i32) -> bool {
348373fn is_status_not_found ( status : i32 ) -> bool {
349374 status == 404
350375}
376+
377+ fn is_status_retryable ( status : i32 ) -> bool {
378+ let status = status as u16 ;
379+ RETRYABLE_ERROR_CODES . contains ( & status)
380+ }
0 commit comments