@@ -45,12 +45,17 @@ use bitcoincore_rpc::json::{
45
45
ListUnspentResultEntry , ScanningDetails ,
46
46
} ;
47
47
use bitcoincore_rpc:: jsonrpc:: serde_json:: { json, Value } ;
48
+ use bitcoincore_rpc:: jsonrpc:: {
49
+ self , simple_http:: SimpleHttpTransport , Error as JsonRpcError , Request , Response , Transport ,
50
+ } ;
48
51
use bitcoincore_rpc:: Auth as RpcAuth ;
49
52
use bitcoincore_rpc:: { Client , RpcApi } ;
50
53
use log:: { debug, info} ;
51
54
use serde:: { Deserialize , Serialize } ;
52
55
use std:: collections:: { HashMap , HashSet } ;
56
+ use std:: fmt;
53
57
use std:: path:: PathBuf ;
58
+ use std:: sync:: atomic:: { AtomicU8 , Ordering } ;
54
59
use std:: thread;
55
60
use std:: time:: Duration ;
56
61
@@ -80,6 +85,10 @@ pub struct RpcConfig {
80
85
pub wallet_name : String ,
81
86
/// Sync parameters
82
87
pub sync_params : Option < RpcSyncParams > ,
88
+ /// Max number of attempts before giving up and returning an error
89
+ ///
90
+ /// Set to `0` preserve the old behavior of erroring immediately
91
+ pub max_tries : u8 ,
83
92
}
84
93
85
94
/// Sync parameters for Bitcoin Core RPC.
@@ -195,6 +204,68 @@ impl WalletSync for RpcBlockchain {
195
204
}
196
205
}
197
206
207
+ struct SimpleHttpWithRetry {
208
+ inner : SimpleHttpTransport ,
209
+ attempts : AtomicU8 ,
210
+ limit : u8 ,
211
+ }
212
+
213
+ macro_rules! impl_inner {
214
+ ( $self: expr, $method: ident, $req: expr) => { {
215
+ while $self. attempts. load( Ordering :: Relaxed ) <= $self. limit {
216
+ match $self. inner. $method( $req. clone( ) ) {
217
+ Ok ( r) => {
218
+ $self. attempts. store( 0 , Ordering :: Relaxed ) ;
219
+ return Ok ( r) ;
220
+ }
221
+ Err ( JsonRpcError :: Transport ( e) ) => {
222
+ match e. downcast_ref:: <jsonrpc:: simple_http:: Error >( ) {
223
+ Some ( jsonrpc:: simple_http:: Error :: SocketError ( io) )
224
+ if io. kind( ) == std:: io:: ErrorKind :: WouldBlock =>
225
+ {
226
+ let attempt = $self. attempts. fetch_add( 1 , Ordering :: Relaxed ) ;
227
+ let delay = std:: cmp:: min( 1000 , 100 << attempt as u64 ) ;
228
+
229
+ debug!(
230
+ "Got a WouldBlock error at attempt {}, sleeping for {}ms" ,
231
+ attempt, delay
232
+ ) ;
233
+ std:: thread:: sleep( std:: time:: Duration :: from_millis( delay) ) ;
234
+
235
+ continue ;
236
+ }
237
+ _ => { }
238
+ }
239
+
240
+ $self. attempts. store( 0 , Ordering :: Relaxed ) ;
241
+ return Err ( JsonRpcError :: Transport ( e) ) ;
242
+ }
243
+ Err ( e) => {
244
+ $self. attempts. store( 0 , Ordering :: Relaxed ) ;
245
+ return Err ( e) ;
246
+ }
247
+ }
248
+ }
249
+
250
+ $self. attempts. store( 0 , Ordering :: Relaxed ) ;
251
+ Err ( JsonRpcError :: Transport ( "All attempts errored" . into( ) ) )
252
+ } } ;
253
+ }
254
+
255
+ impl Transport for SimpleHttpWithRetry {
256
+ fn send_request ( & self , req : Request ) -> Result < Response , JsonRpcError > {
257
+ impl_inner ! ( self , send_request, req)
258
+ }
259
+
260
+ fn send_batch ( & self , reqs : & [ Request ] ) -> Result < Vec < Response > , JsonRpcError > {
261
+ impl_inner ! ( self , send_batch, reqs)
262
+ }
263
+
264
+ fn fmt_target ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
265
+ self . inner . fmt_target ( f)
266
+ }
267
+ }
268
+
198
269
impl ConfigurableBlockchain for RpcBlockchain {
199
270
type Config = RpcConfig ;
200
271
@@ -203,7 +274,23 @@ impl ConfigurableBlockchain for RpcBlockchain {
203
274
fn from_config ( config : & Self :: Config ) -> Result < Self , Error > {
204
275
let wallet_url = format ! ( "{}/wallet/{}" , config. url, & config. wallet_name) ;
205
276
206
- let client = Client :: new ( wallet_url. as_str ( ) , config. auth . clone ( ) . into ( ) ) ?;
277
+ let mut builder = SimpleHttpTransport :: builder ( )
278
+ . url ( & wallet_url)
279
+ . map_err ( |e| bitcoincore_rpc:: Error :: JsonRpc ( e. into ( ) ) ) ?;
280
+
281
+ let ( user, pass) = bitcoincore_rpc:: Auth :: from ( config. auth . clone ( ) ) . get_user_pass ( ) ?;
282
+ if let Some ( user) = user {
283
+ builder = builder. auth ( user, pass) ;
284
+ }
285
+
286
+ let transport = SimpleHttpWithRetry {
287
+ inner : builder. build ( ) ,
288
+ attempts : AtomicU8 :: new ( 0 ) ,
289
+ limit : config. max_tries ,
290
+ } ;
291
+ let jsonrpc_client = jsonrpc:: client:: Client :: with_transport ( transport) ;
292
+
293
+ let client = Client :: from_jsonrpc ( jsonrpc_client) ;
207
294
let rpc_version = client. version ( ) ?;
208
295
209
296
info ! ( "connected to '{}' with auth: {:?}" , wallet_url, config. auth) ;
@@ -835,6 +922,10 @@ pub struct RpcBlockchainFactory {
835
922
pub default_skip_blocks : u32 ,
836
923
/// Sync parameters
837
924
pub sync_params : Option < RpcSyncParams > ,
925
+ /// Max number of attempts before giving up and returning an error
926
+ ///
927
+ /// Set to `0` preserve the old behavior of erroring immediately
928
+ pub max_tries : u8 ,
838
929
}
839
930
840
931
impl BlockchainFactory for RpcBlockchainFactory {
@@ -855,6 +946,7 @@ impl BlockchainFactory for RpcBlockchainFactory {
855
946
checksum
856
947
) ,
857
948
sync_params : self . sync_params . clone ( ) ,
949
+ max_tries : self . max_tries ,
858
950
} )
859
951
}
860
952
}
@@ -882,6 +974,7 @@ mod test {
882
974
network: Network :: Regtest ,
883
975
wallet_name: format!( "client-wallet-test-{}" , std:: time:: SystemTime :: now( ) . duration_since( std:: time:: UNIX_EPOCH ) . unwrap( ) . as_nanos( ) ) ,
884
976
sync_params: None ,
977
+ max_tries: 5 ,
885
978
} ;
886
979
RpcBlockchain :: from_config( & config) . unwrap( )
887
980
}
@@ -899,6 +992,7 @@ mod test {
899
992
wallet_name_prefix : Some ( "prefix-" . into ( ) ) ,
900
993
default_skip_blocks : 0 ,
901
994
sync_params : None ,
995
+ max_tries : 3 ,
902
996
} ;
903
997
904
998
( test_client, factory)
0 commit comments