diff --git a/mutiny-core/src/auth.rs b/mutiny-core/src/auth.rs index 3151d226b..b3caed607 100644 --- a/mutiny-core/src/auth.rs +++ b/mutiny-core/src/auth.rs @@ -4,6 +4,7 @@ use crate::{ lnurlauth::{make_lnurl_auth_connection, AuthManager}, logging::MutinyLogger, networking::websocket::{SimpleWebSocket, WebSocketImpl}, + utils, }; use jwt_compact::UntrustedToken; use lightning::util::logger::*; @@ -101,10 +102,11 @@ impl MutinyAuthClient { request = request.json(&json); } - request - .send() - .await - .map_err(|_| MutinyError::ConnectionFailed) + utils::fetch_with_timeout( + &self.http_client, + request.build().expect("should build req"), + ) + .await } async fn retrieve_new_jwt(&self) -> Result { diff --git a/mutiny-core/src/fees.rs b/mutiny-core/src/fees.rs index b7404cab3..0bcc89dd2 100644 --- a/mutiny-core/src/fees.rs +++ b/mutiny-core/src/fees.rs @@ -87,15 +87,15 @@ struct MempoolFees { impl MutinyFeeEstimator { async fn get_mempool_recommended_fees(&self) -> anyhow::Result> { - let fees = self - .esplora - .client() - .get(&format!("{}/v1/fees/recommended", self.esplora.url())) - .send() + let client = self.esplora.client(); + let request = client + .get(format!("{}/v1/fees/recommended", self.esplora.url())) + .build()?; + + let fees_response = utils::fetch_with_timeout(&client, request) .await? - .error_for_status()? - .json::() - .await?; + .error_for_status()?; + let fees = fees_response.json::().await?; // convert to hashmap of num blocks -> fee rate let mut fee_estimates = HashMap::new(); diff --git a/mutiny-core/src/gossip.rs b/mutiny-core/src/gossip.rs index e289d1988..042f23167 100644 --- a/mutiny-core/src/gossip.rs +++ b/mutiny-core/src/gossip.rs @@ -241,12 +241,13 @@ async fn fetch_updated_gossip( let http_client = Client::builder() .build() .map_err(|_| MutinyError::RapidGossipSyncError)?; - let rgs_response = http_client - .get(rgs_url) - .send() - .await + + let request = http_client + .get(&rgs_url) + .build() .map_err(|_| MutinyError::RapidGossipSyncError)?; + let rgs_response = utils::fetch_with_timeout(&http_client, request).await?; let rgs_data = rgs_response .bytes() .await diff --git a/mutiny-core/src/lspclient.rs b/mutiny-core/src/lspclient.rs index 9a6f495f7..3f154fdee 100644 --- a/mutiny-core/src/lspclient.rs +++ b/mutiny-core/src/lspclient.rs @@ -2,7 +2,7 @@ use bitcoin::secp256k1::PublicKey; use reqwest::Client; use serde::{Deserialize, Serialize}; -use crate::error::MutinyError; +use crate::{error::MutinyError, utils}; #[derive(Clone, Debug)] pub(crate) struct LspClient { @@ -76,11 +76,13 @@ const FEE_PATH: &str = "/api/v1/fee"; impl LspClient { pub async fn new(url: &str) -> Result { let http_client = Client::new(); - let get_info_response: GetInfoResponse = http_client + let request = http_client .get(format!("{}{}", url, GET_INFO_PATH)) - .send() - .await - .map_err(|_| MutinyError::LspGenericError)? + .build() + .map_err(|_| MutinyError::LspGenericError)?; + let response: reqwest::Response = utils::fetch_with_timeout(&http_client, request).await?; + + let get_info_response: GetInfoResponse = response .json() .await .map_err(|_| MutinyError::LspGenericError)?; @@ -127,14 +129,15 @@ impl LspClient { port: None, }; - let response: reqwest::Response = self + let request = self .http_client .post(format!("{}{}", &self.url, PROPOSAL_PATH)) .json(&payload) - .send() - .await + .build() .map_err(|_| MutinyError::LspGenericError)?; + let response: reqwest::Response = + utils::fetch_with_timeout(&self.http_client, request).await?; let status = response.status().as_u16(); if (200..300).contains(&status) { let proposal_response: ProposalResponse = response @@ -170,13 +173,16 @@ impl LspClient { &self, fee_request: FeeRequest, ) -> Result { - let fee_response: FeeResponse = self + let request = self .http_client .post(format!("{}{}", &self.url, FEE_PATH)) .json(&fee_request) - .send() - .await - .map_err(|_| MutinyError::LspGenericError)? + .build() + .map_err(|_| MutinyError::LspGenericError)?; + let response: reqwest::Response = + utils::fetch_with_timeout(&self.http_client, request).await?; + + let fee_response: FeeResponse = response .json() .await .map_err(|_| MutinyError::LspGenericError)?; diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index c396e86d2..67c50523d 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -2262,12 +2262,12 @@ impl NodeManager { .build() .map_err(|_| MutinyError::BitcoinPriceError)?; - let resp = client + let request = client .get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd") - .send() - .await + .build() .map_err(|_| MutinyError::BitcoinPriceError)?; + let resp: reqwest::Response = utils::fetch_with_timeout(&client, request).await?; let response: CoingeckoResponse = resp .error_for_status() .map_err(|_| MutinyError::BitcoinPriceError)? diff --git a/mutiny-core/src/utils.rs b/mutiny-core/src/utils.rs index 8356be61e..50a6bda6a 100644 --- a/mutiny-core/src/utils.rs +++ b/mutiny-core/src/utils.rs @@ -1,11 +1,19 @@ +use crate::error::MutinyError; use bitcoin::Network; use core::cell::{RefCell, RefMut}; use core::ops::{Deref, DerefMut}; use core::time::Duration; +use futures::{ + future::{self, Either}, + pin_mut, +}; use lightning::routing::scoring::LockableScore; use lightning::routing::scoring::Score; use lightning::util::ser::Writeable; use lightning::util::ser::Writer; +use reqwest::Client; + +pub const FETCH_TIMEOUT: i32 = 30_000; pub(crate) fn min_lightning_amount(network: Network) -> u64 { match network { @@ -28,7 +36,7 @@ pub async fn sleep(millis: i32) { } #[cfg(not(target_arch = "wasm32"))] { - std::thread::sleep(Duration::from_millis(millis.try_into().unwrap())); + tokio::time::sleep(Duration::from_millis(millis.try_into().unwrap())).await; } } @@ -44,6 +52,32 @@ pub fn now() -> Duration { .unwrap(); } +pub async fn fetch_with_timeout( + client: &Client, + req: reqwest::Request, +) -> Result { + let fetch_future = fetch(client, req); + let timeout_future = async { + sleep(FETCH_TIMEOUT).await; + Err(MutinyError::ConnectionFailed) + }; + + pin_mut!(fetch_future); + pin_mut!(timeout_future); + + match future::select(fetch_future, timeout_future).await { + Either::Left((ok, _)) => ok, + Either::Right((err, _)) => err, + } +} + +async fn fetch(client: &Client, req: reqwest::Request) -> Result { + client + .execute(req) + .await + .map_err(|_| MutinyError::ConnectionFailed) +} + pub type LockResult = Result; pub struct Mutex {