From de0d443e360def588976613929bccee601bac7e3 Mon Sep 17 00:00:00 2001 From: Einherjar Date: Sun, 8 Oct 2023 09:06:05 -0300 Subject: [PATCH] fix(wallet_esplora): Gracefully handle 429s errors --- Cargo.toml | 19 +++++++++++++++---- src/async.rs | 17 +++++++++++++---- src/blocking.rs | 26 +++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 589a0af..87e2e20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,17 +17,28 @@ path = "src/lib.rs" [dependencies] serde = { version = "1.0", features = ["derive"] } -bitcoin = { version = "0.30.0", features = ["serde", "std"], default-features = false } +bitcoin = { version = "0.30.0", features = [ + "serde", + "std", +], default-features = false } # Temporary dependency on internals until the rust-bitcoin devs release the hex-conservative crate. bitcoin-internals = { version = "0.1.0", features = ["alloc"] } log = "^0.4" ureq = { version = "2.5.0", features = ["json"], optional = true } -reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] } +reqwest = { version = "0.11", optional = true, default-features = false, features = [ + "json", +] } +tokio = { version = "1", features = ["time"], optional = true } +async-recursion = { version = "1", optional = true } [dev-dependencies] serde_json = "1.0" tokio = { version = "1.20.1", features = ["full"] } -electrsd = { version = "0.24.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_22_0"] } +electrsd = { version = "0.24.0", features = [ + "legacy", + "esplora_a33e97e1", + "bitcoind_22_0", +] } electrum-client = "0.16.0" lazy_static = "1.4.0" # zip versions after 0.6.3 don't work with our MSRV 1.57.0 @@ -38,7 +49,7 @@ base64ct = "<1.6.0" [features] default = ["blocking", "async", "async-https"] blocking = ["ureq", "ureq/socks-proxy"] -async = ["reqwest", "reqwest/socks"] +async = ["reqwest", "reqwest/socks", "tokio/time", "async-recursion"] async-https = ["async", "reqwest/default-tls"] async-https-native = ["async", "reqwest/native-tls"] async-https-rustls = ["async", "reqwest/rustls-tls"] diff --git a/src/async.rs b/src/async.rs index fcdf23e..dfcefaa 100644 --- a/src/async.rs +++ b/src/async.rs @@ -25,7 +25,9 @@ use bitcoin_internals::hex::display::DisplayHex; #[allow(unused_imports)] use log::{debug, error, info, trace}; +use async_recursion::async_recursion; use reqwest::{Client, StatusCode}; +use tokio::time::{sleep, Duration}; use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus}; @@ -352,6 +354,7 @@ impl AsyncClient { /// Get confirmed transaction history for the specified address/scripthash, /// sorted with newest first. Returns 25 transactions per page. /// More can be requested by specifying the last txid seen by the previous query. + #[async_recursion] pub async fn scripthash_txs( &self, script: &Script, @@ -369,10 +372,16 @@ impl AsyncClient { let resp = self.client.get(url).send().await?; if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) + if resp.status() == StatusCode::TOO_MANY_REQUESTS { + eprintln!("Too many requests, sleeping for 500 ms"); + sleep(Duration::from_millis(500)).await; + self.scripthash_txs(script, last_seen).await + } else { + Err(Error::HttpResponse { + status: resp.status().as_u16(), + message: resp.text().await?, + }) + } } else { Ok(resp.json::>().await?) } diff --git a/src/blocking.rs b/src/blocking.rs index bc8ca83..2cd54fd 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -15,6 +15,7 @@ use std::collections::HashMap; use std::io; use std::io::Read; use std::str::FromStr; +use std::thread::sleep; use std::time::Duration; #[allow(unused_imports)] @@ -380,7 +381,26 @@ impl BlockingClient { ), None => format!("{}/scripthash/{:x}/txs", self.url, script_hash), }; - Ok(self.agent.get(&url).call()?.into_json()?) + let resp = self.agent.get(&url).call(); //?.into_json(); + match resp { + Ok(resp) => { + let resp_json = resp.into_json()?; + Ok(resp_json) + } + Err(ureq::Error::Status(code, resp)) => { + if is_status_too_many_requests(code) { + eprintln!("Too many requests, sleeping for 500 ms"); + sleep(Duration::from_millis(500)); + self.scripthash_txs(script, last_seen) + } else { + Err(Error::HttpResponse { + status: code, + message: resp.into_string()?, + }) + } + } + Err(e) => Err(Error::Ureq(e)), + } } /// Gets some recent block summaries starting at the tip or at `height` if provided. @@ -411,6 +431,10 @@ fn is_status_not_found(status: u16) -> bool { status == 404 } +fn is_status_too_many_requests(status: u16) -> bool { + status == 429 +} + fn into_bytes(resp: Response) -> Result, io::Error> { const BYTES_LIMIT: usize = 10 * 1_024 * 1_024;