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