Skip to content

Commit ccb951a

Browse files
committed
Retry failed RPC calls on error
Whenever the transport errors with "WouldBlock" we wait a bit and retry the call. Related to #650, should at least fix the errors with RPC
1 parent 9f9ffd0 commit ccb951a

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

src/blockchain/rpc.rs

+97-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
//! network: bdk::bitcoin::Network::Testnet,
2828
//! wallet_name: "wallet_name".to_string(),
2929
//! sync_params: None,
30+
//! max_tries: 3,
3031
//! };
3132
//! let blockchain = RpcBlockchain::from_config(&config);
3233
//! ```
@@ -45,12 +46,17 @@ use bitcoincore_rpc::json::{
4546
ListUnspentResultEntry, ScanningDetails,
4647
};
4748
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+
};
4852
use bitcoincore_rpc::Auth as RpcAuth;
4953
use bitcoincore_rpc::{Client, RpcApi};
5054
use log::{debug, info};
5155
use serde::{Deserialize, Serialize};
5256
use std::collections::{HashMap, HashSet};
57+
use std::fmt;
5358
use std::path::PathBuf;
59+
use std::sync::atomic::{AtomicU8, Ordering};
5460
use std::thread;
5561
use std::time::Duration;
5662

@@ -80,6 +86,10 @@ pub struct RpcConfig {
8086
pub wallet_name: String,
8187
/// Sync parameters
8288
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,
8393
}
8494

8595
/// Sync parameters for Bitcoin Core RPC.
@@ -195,6 +205,68 @@ impl WalletSync for RpcBlockchain {
195205
}
196206
}
197207

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+
198270
impl ConfigurableBlockchain for RpcBlockchain {
199271
type Config = RpcConfig;
200272

@@ -203,7 +275,23 @@ impl ConfigurableBlockchain for RpcBlockchain {
203275
fn from_config(config: &Self::Config) -> Result<Self, Error> {
204276
let wallet_url = format!("{}/wallet/{}", config.url, &config.wallet_name);
205277

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);
207295
let rpc_version = client.version()?;
208296

209297
info!("connected to '{}' with auth: {:?}", wallet_url, config.auth);
@@ -816,6 +904,7 @@ fn descriptor_from_script_pubkey(script: &Script) -> String {
816904
/// wallet_name_prefix: Some("prefix-".to_string()),
817905
/// default_skip_blocks: 100_000,
818906
/// sync_params: None,
907+
/// max_tries: 3,
819908
/// };
820909
/// let main_wallet_blockchain = factory.build("main_wallet", Some(200_000))?;
821910
/// # Ok(())
@@ -835,6 +924,10 @@ pub struct RpcBlockchainFactory {
835924
pub default_skip_blocks: u32,
836925
/// Sync parameters
837926
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,
838931
}
839932

840933
impl BlockchainFactory for RpcBlockchainFactory {
@@ -855,6 +948,7 @@ impl BlockchainFactory for RpcBlockchainFactory {
855948
checksum
856949
),
857950
sync_params: self.sync_params.clone(),
951+
max_tries: self.max_tries,
858952
})
859953
}
860954
}
@@ -882,6 +976,7 @@ mod test {
882976
network: Network::Regtest,
883977
wallet_name: format!("client-wallet-test-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() ),
884978
sync_params: None,
979+
max_tries: 5,
885980
};
886981
RpcBlockchain::from_config(&config).unwrap()
887982
}
@@ -899,6 +994,7 @@ mod test {
899994
wallet_name_prefix: Some("prefix-".into()),
900995
default_skip_blocks: 0,
901996
sync_params: None,
997+
max_tries: 3,
902998
};
903999

9041000
(test_client, factory)

0 commit comments

Comments
 (0)