From f60708b6b4e8d5d8ebebe8adde07483eb8d89a47 Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Fri, 26 Dec 2025 23:54:32 +0530 Subject: [PATCH] docs: add example for EsploraClient with timeout configuration Add a new example demonstrating how to configure EsploraClient with custom timeout and retry settings. This is useful for developers working with slow or unreliable network connections. The example shows: - Setting socket timeout using Builder::timeout() - Configuring max retries using Builder::max_retries() - Handling timeout-related errors gracefully Closes #260 Signed-off-by: Eeshu-Yadav --- Cargo.toml | 3 + README.md | 1 + examples/esplora_with_timeout.rs | 109 +++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 examples/esplora_with_timeout.rs diff --git a/Cargo.toml b/Cargo.toml index eed208f9..fcc5ed76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,5 +72,8 @@ name = "esplora_async" [[example]] name = "esplora_blocking" +[[example]] +name = "esplora_with_timeout" + [[example]] name = "bitcoind_rpc" diff --git a/README.md b/README.md index 9d1d30eb..2ac0f13a 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ that the `Wallet` can use to update its view of the chain. * [`examples/esplora_async`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/esplora_async) * [`examples/esplora_blocking`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/esplora_blocking) +* [`examples/esplora_with_timeout`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/esplora_with_timeout) * [`examples/electrum`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/electrum) * [`examples/bitcoind_rpc`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/bitcoind_rpc) diff --git a/examples/esplora_with_timeout.rs b/examples/esplora_with_timeout.rs new file mode 100644 index 00000000..34d30dff --- /dev/null +++ b/examples/esplora_with_timeout.rs @@ -0,0 +1,109 @@ +//! Example demonstrating how to configure EsploraClient with custom timeout settings. + +use bdk_esplora::{esplora_client, EsploraExt}; +use bdk_wallet::{bitcoin::Network, rusqlite::Connection, KeychainKind, Wallet}; +use std::{collections::BTreeSet, io::Write}; + +const STOP_GAP: usize = 5; +const PARALLEL_REQUESTS: usize = 5; + +const DB_PATH: &str = "bdk-example-esplora-timeout.sqlite"; +const NETWORK: Network = Network::Testnet4; +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; +const ESPLORA_URL: &str = "https://mempool.space/testnet4/api"; + +const TIMEOUT_SECS: u64 = 30; +const MAX_RETRIES: usize = 3; + +fn main() -> Result<(), anyhow::Error> { + let mut db = Connection::open(DB_PATH)?; + let wallet_opt = Wallet::load() + .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + .extract_keys() + .check_network(NETWORK) + .load_wallet(&mut db)?; + let mut wallet = match wallet_opt { + Some(wallet) => wallet, + None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + .network(NETWORK) + .create_wallet(&mut db)?, + }; + + let address = wallet.next_unused_address(KeychainKind::External); + wallet.persist(&mut db)?; + println!( + "Next unused address: ({}) {}", + address.index, address.address + ); + + let balance = wallet.balance(); + println!("Wallet balance before syncing: {}", balance.total()); + + // Configure EsploraClient with custom timeout and retry settings. + // Available builder options: + // - timeout(secs): Socket timeout in seconds + // - max_retries(count): Retries on HTTP codes 408, 425, 429, 500, 502, 503, 504 + // - proxy(url): Proxy server URL + // - header(key, value): Custom HTTP header + println!( + "Creating Esplora client with {TIMEOUT_SECS}s timeout and {MAX_RETRIES} max retries..." + ); + let client = esplora_client::Builder::new(ESPLORA_URL) + .timeout(TIMEOUT_SECS) + .max_retries(MAX_RETRIES) + .build_blocking(); + + println!("Starting full sync..."); + let request = wallet.start_full_scan().inspect({ + let mut stdout = std::io::stdout(); + let mut once = BTreeSet::::new(); + move |keychain, spk_i, _| { + if once.insert(keychain) { + print!("\nScanning keychain [{keychain:?}] "); + } + print!(" {spk_i:<3}"); + stdout.flush().expect("must flush") + } + }); + + match client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS) { + Ok(update) => { + wallet.apply_update(update)?; + wallet.persist(&mut db)?; + println!(); + + let balance = wallet.balance(); + println!("Wallet balance after syncing: {}", balance.total()); + } + Err(e) => { + eprintln!("\nSync failed: {e}"); + eprintln!( + "Tips: increase timeout/max_retries, check connectivity, or try another server" + ); + return Err(e.into()); + } + } + + println!("\nFetching current block height..."); + match client.get_height() { + Ok(height) => println!("Current block height: {height}"), + Err(e) => eprintln!("Failed to get block height: {e}"), + } + + println!("\nFetching fee estimates..."); + match client.get_fee_estimates() { + Ok(estimates) => { + println!("Fee estimates (sat/vB):"); + for (target, rate) in estimates.iter().take(5) { + println!(" {} blocks: {:.1} sat/vB", target, rate); + } + } + Err(e) => { + eprintln!("Failed to get fee estimates: {e}"); + } + } + + Ok(()) +}