14
14
use std:: collections:: HashMap ;
15
15
use std:: convert:: TryFrom ;
16
16
use std:: str:: FromStr ;
17
+ use std:: thread;
18
+ use std:: time:: Duration ;
17
19
18
20
#[ allow( unused_imports) ]
19
21
use log:: { debug, error, info, trace} ;
20
22
21
- use minreq:: { Proxy , Request } ;
23
+ use minreq:: { Proxy , Request , Response } ;
22
24
23
25
use bitcoin:: consensus:: { deserialize, serialize, Decodable } ;
24
26
use bitcoin:: hashes:: { sha256, Hash } ;
@@ -27,7 +29,10 @@ use bitcoin::{
27
29
block:: Header as BlockHeader , Block , BlockHash , MerkleBlock , Script , Transaction , Txid ,
28
30
} ;
29
31
30
- use crate :: { BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus } ;
32
+ use crate :: {
33
+ BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus ,
34
+ RETRYABLE_ERROR_CODES ,
35
+ } ;
31
36
32
37
#[ derive( Debug , Clone ) ]
33
38
pub struct BlockingClient {
@@ -39,6 +44,10 @@ pub struct BlockingClient {
39
44
pub timeout : Option < u64 > ,
40
45
/// HTTP headers to set on every request made to Esplora server
41
46
pub headers : HashMap < String , String > ,
47
+ /// Backoff
48
+ pub backoff : Duration ,
49
+ /// Max retries
50
+ pub max_retries : u32 ,
42
51
}
43
52
44
53
impl BlockingClient {
@@ -49,6 +58,8 @@ impl BlockingClient {
49
58
proxy : builder. proxy ,
50
59
timeout : builder. timeout ,
51
60
headers : builder. headers ,
61
+ backoff : builder. backoff ,
62
+ max_retries : builder. max_retries ,
52
63
}
53
64
}
54
65
@@ -80,20 +91,20 @@ impl BlockingClient {
80
91
}
81
92
82
93
fn get_opt_response < T : Decodable > ( & self , path : & str ) -> Result < Option < T > , Error > {
83
- match self . get_request ( path) ? . send ( ) {
94
+ match self . get_with_retry ( path) {
84
95
Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
85
96
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
86
97
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
87
98
let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
88
99
Err ( Error :: HttpResponse { status, message } )
89
100
}
90
101
Ok ( resp) => Ok ( Some ( deserialize :: < T > ( resp. as_bytes ( ) ) ?) ) ,
91
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
102
+ Err ( e) => Err ( e ) ,
92
103
}
93
104
}
94
105
95
106
fn get_opt_response_txid ( & self , path : & str ) -> Result < Option < Txid > , Error > {
96
- match self . get_request ( path) ? . send ( ) {
107
+ match self . get_with_retry ( path) {
97
108
Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
98
109
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
99
110
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
@@ -103,12 +114,12 @@ impl BlockingClient {
103
114
Ok ( resp) => Ok ( Some (
104
115
Txid :: from_str ( resp. as_str ( ) . map_err ( Error :: Minreq ) ?) . map_err ( Error :: HexToArray ) ?,
105
116
) ) ,
106
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
117
+ Err ( e) => Err ( e ) ,
107
118
}
108
119
}
109
120
110
121
fn get_opt_response_hex < T : Decodable > ( & self , path : & str ) -> Result < Option < T > , Error > {
111
- match self . get_request ( path) ? . send ( ) {
122
+ match self . get_with_retry ( path) {
112
123
Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
113
124
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
114
125
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
@@ -122,12 +133,12 @@ impl BlockingClient {
122
133
. map_err ( Error :: BitcoinEncoding )
123
134
. map ( |r| Some ( r) )
124
135
}
125
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
136
+ Err ( e) => Err ( e ) ,
126
137
}
127
138
}
128
139
129
140
fn get_response_hex < T : Decodable > ( & self , path : & str ) -> Result < T , Error > {
130
- match self . get_request ( path) ? . send ( ) {
141
+ match self . get_with_retry ( path) {
131
142
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
132
143
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
133
144
let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
@@ -138,51 +149,51 @@ impl BlockingClient {
138
149
let hex_vec = Vec :: from_hex ( hex_str) . unwrap ( ) ;
139
150
deserialize :: < T > ( & hex_vec) . map_err ( Error :: BitcoinEncoding )
140
151
}
141
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
152
+ Err ( e) => Err ( e ) ,
142
153
}
143
154
}
144
155
145
156
fn get_response_json < ' a , T : serde:: de:: DeserializeOwned > (
146
157
& ' a self ,
147
158
path : & ' a str ,
148
159
) -> Result < T , Error > {
149
- let response = self . get_request ( path) ? . send ( ) ;
160
+ let response = self . get_with_retry ( path) ;
150
161
match response {
151
162
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
152
163
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
153
164
let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
154
165
Err ( Error :: HttpResponse { status, message } )
155
166
}
156
167
Ok ( resp) => Ok ( resp. json :: < T > ( ) . map_err ( Error :: Minreq ) ?) ,
157
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
168
+ Err ( e) => Err ( e ) ,
158
169
}
159
170
}
160
171
161
172
fn get_opt_response_json < T : serde:: de:: DeserializeOwned > (
162
173
& self ,
163
174
path : & str ,
164
175
) -> Result < Option < T > , Error > {
165
- match self . get_request ( path) ? . send ( ) {
176
+ match self . get_with_retry ( path) {
166
177
Ok ( resp) if is_status_not_found ( resp. status_code ) => Ok ( None ) ,
167
178
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
168
179
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
169
180
let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
170
181
Err ( Error :: HttpResponse { status, message } )
171
182
}
172
183
Ok ( resp) => Ok ( Some ( resp. json :: < T > ( ) ?) ) ,
173
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
184
+ Err ( e) => Err ( e ) ,
174
185
}
175
186
}
176
187
177
188
fn get_response_str ( & self , path : & str ) -> Result < String , Error > {
178
- match self . get_request ( path) ? . send ( ) {
189
+ match self . get_with_retry ( path) {
179
190
Ok ( resp) if !is_status_ok ( resp. status_code ) => {
180
191
let status = u16:: try_from ( resp. status_code ) . map_err ( Error :: StatusCode ) ?;
181
192
let message = resp. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
182
193
Err ( Error :: HttpResponse { status, message } )
183
194
}
184
195
Ok ( resp) => Ok ( resp. as_str ( ) ?. to_string ( ) ) ,
185
- Err ( e) => Err ( Error :: Minreq ( e ) ) ,
196
+ Err ( e) => Err ( e ) ,
186
197
}
187
198
}
188
199
@@ -339,6 +350,28 @@ impl BlockingClient {
339
350
} ;
340
351
self . get_response_json ( & path)
341
352
}
353
+
354
+ /// Sends a GET request to the given `url`, retrying failed attempts
355
+ /// for retryable error codes until max retries hit.
356
+ pub fn get_with_retry ( & self , url : & str ) -> Result < Response , Error > {
357
+ let mut attempts = 0 ;
358
+ let mut delay = self . backoff ;
359
+
360
+ loop {
361
+ match self . get_request ( url) ?. send ( ) {
362
+ Ok ( resp)
363
+ if attempts < self . max_retries
364
+ && RETRYABLE_ERROR_CODES . contains ( & ( resp. status_code as u16 ) ) =>
365
+ {
366
+ thread:: sleep ( delay) ;
367
+ attempts += 1 ;
368
+ delay *= 2 ;
369
+ }
370
+ Ok ( resp) => return Ok ( resp) ,
371
+ Err ( e) => return Err ( Error :: Minreq ( e) ) ,
372
+ }
373
+ }
374
+ }
342
375
}
343
376
344
377
fn is_status_ok ( status : i32 ) -> bool {
0 commit comments