@@ -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,51 @@ 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) ) if & e. to_string( ) == "Would block" => {
222
+ let attempt = $self. attempts. fetch_add( 1 , Ordering :: Relaxed ) ;
223
+ let delay = std:: cmp:: min( 1000 , 100 << attempt as u64 ) ;
224
+ std:: thread:: sleep( std:: time:: Duration :: from_millis( delay) ) ;
225
+ }
226
+ Err ( e) => {
227
+ $self. attempts. store( 0 , Ordering :: Relaxed ) ;
228
+ return Err ( e) ;
229
+ }
230
+ }
231
+ }
232
+
233
+ $self. attempts. store( 0 , Ordering :: Relaxed ) ;
234
+ Err ( JsonRpcError :: Transport ( "All attempts errored" . into( ) ) )
235
+ } } ;
236
+ }
237
+
238
+ impl Transport for SimpleHttpWithRetry {
239
+ fn send_request ( & self , req : Request ) -> Result < Response , JsonRpcError > {
240
+ impl_inner ! ( self , send_request, req)
241
+ }
242
+
243
+ fn send_batch ( & self , reqs : & [ Request ] ) -> Result < Vec < Response > , JsonRpcError > {
244
+ impl_inner ! ( self , send_batch, reqs)
245
+ }
246
+
247
+ fn fmt_target ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
248
+ self . inner . fmt_target ( f)
249
+ }
250
+ }
251
+
198
252
impl ConfigurableBlockchain for RpcBlockchain {
199
253
type Config = RpcConfig ;
200
254
@@ -203,7 +257,23 @@ impl ConfigurableBlockchain for RpcBlockchain {
203
257
fn from_config ( config : & Self :: Config ) -> Result < Self , Error > {
204
258
let wallet_url = format ! ( "{}/wallet/{}" , config. url, & config. wallet_name) ;
205
259
206
- let client = Client :: new ( wallet_url. as_str ( ) , config. auth . clone ( ) . into ( ) ) ?;
260
+ let mut builder = SimpleHttpTransport :: builder ( )
261
+ . url ( & wallet_url)
262
+ . map_err ( |e| bitcoincore_rpc:: Error :: JsonRpc ( e. into ( ) ) ) ?;
263
+
264
+ let ( user, pass) = bitcoincore_rpc:: Auth :: from ( config. auth . clone ( ) ) . get_user_pass ( ) ?;
265
+ if let Some ( user) = user {
266
+ builder = builder. auth ( user, pass) ;
267
+ }
268
+
269
+ let transport = SimpleHttpWithRetry {
270
+ inner : builder. build ( ) ,
271
+ attempts : AtomicU8 :: new ( 0 ) ,
272
+ limit : config. max_tries ,
273
+ } ;
274
+ let jsonrpc_client = jsonrpc:: client:: Client :: with_transport ( transport) ;
275
+
276
+ let client = Client :: from_jsonrpc ( jsonrpc_client) ;
207
277
let rpc_version = client. version ( ) ?;
208
278
209
279
info ! ( "connected to '{}' with auth: {:?}" , wallet_url, config. auth) ;
@@ -835,6 +905,10 @@ pub struct RpcBlockchainFactory {
835
905
pub default_skip_blocks : u32 ,
836
906
/// Sync parameters
837
907
pub sync_params : Option < RpcSyncParams > ,
908
+ /// Max number of attempts before giving up and returning an error
909
+ ///
910
+ /// Set to `0` preserve the old behavior of erroring immediately
911
+ pub max_tries : u8 ,
838
912
}
839
913
840
914
impl BlockchainFactory for RpcBlockchainFactory {
@@ -855,6 +929,7 @@ impl BlockchainFactory for RpcBlockchainFactory {
855
929
checksum
856
930
) ,
857
931
sync_params : self . sync_params . clone ( ) ,
932
+ max_tries : self . max_tries ,
858
933
} )
859
934
}
860
935
}
@@ -882,6 +957,7 @@ mod test {
882
957
network: Network :: Regtest ,
883
958
wallet_name: format!( "client-wallet-test-{}" , std:: time:: SystemTime :: now( ) . duration_since( std:: time:: UNIX_EPOCH ) . unwrap( ) . as_nanos( ) ) ,
884
959
sync_params: None ,
960
+ max_tries: 5 ,
885
961
} ;
886
962
RpcBlockchain :: from_config( & config) . unwrap( )
887
963
}
@@ -899,6 +975,7 @@ mod test {
899
975
wallet_name_prefix : Some ( "prefix-" . into ( ) ) ,
900
976
default_skip_blocks : 0 ,
901
977
sync_params : None ,
978
+ max_tries : 3 ,
902
979
} ;
903
980
904
981
( test_client, factory)
0 commit comments