Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

detect and act on network change #1133

Merged
merged 11 commits into from
Nov 30, 2024
26 changes: 13 additions & 13 deletions libs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ serde_json = "1.0"
strum = "0.25"
strum_macros = "0.25"
thiserror = "1.0.56"
tokio = { version = "1", features = ["full"] }
tokio = { version = "1.41", features = ["full"] }
tonic = "^0.8"
tonic-build = "^0.8"
uniffi = "0.23.0"
Expand Down
17 changes: 12 additions & 5 deletions libs/sdk-common/src/breez_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::grpc::support_client::SupportClient;
use crate::grpc::swapper_client::SwapperClient;
use crate::grpc::{ChainApiServersRequest, PingRequest};
use crate::prelude::{ServiceConnectivityError, ServiceConnectivityErrorKind};
use crate::tonic_wrap::with_connection_fallback;

pub static PRODUCTION_BREEZSERVER_URL: &str = "https://bs1.breez.technology:443";
pub static STAGING_BREEZSERVER_URL: &str = "https://bs1-st.breez.technology:443";
Expand Down Expand Up @@ -112,9 +113,11 @@ impl BreezServer {

pub async fn fetch_mempoolspace_urls(&self) -> Result<Vec<String>, ServiceConnectivityError> {
let mut client = self.get_information_client().await;

let chain_api_servers = client
.chain_api_servers(ChainApiServersRequest {})
let mut client_clone = client.clone();
let chain_api_servers =
with_connection_fallback(client.chain_api_servers(ChainApiServersRequest {}), || {
client_clone.chain_api_servers(ChainApiServersRequest {})
})
.await
.map_err(|e| {
ServiceConnectivityError::new(
Expand All @@ -138,9 +141,12 @@ impl BreezServer {

pub async fn fetch_boltz_swapper_urls(&self) -> Result<Vec<String>, ServiceConnectivityError> {
let mut client = self.get_information_client().await;
let mut client_clone = client.clone();

let chain_api_servers = client
.chain_api_servers(ChainApiServersRequest {})
let chain_api_servers =
with_connection_fallback(client.chain_api_servers(ChainApiServersRequest {}), || {
client_clone.chain_api_servers(ChainApiServersRequest {})
})
.await
.map_err(|e| {
ServiceConnectivityError::new(
Expand All @@ -163,6 +169,7 @@ impl BreezServer {
}
}

#[derive(Clone)]
pub struct ApiKeyInterceptor {
api_key_metadata: Option<MetadataValue<Ascii>>,
}
Expand Down
16 changes: 9 additions & 7 deletions libs/sdk-common/src/fiat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::collections::HashMap;

use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use tonic::Request;

use crate::grpc::RatesRequest;
use crate::prelude::BreezServer;
use crate::tonic_wrap::with_connection_fallback;

/// Trait covering fiat-related functionality
#[tonic::async_trait]
Expand Down Expand Up @@ -97,12 +97,14 @@ impl FiatAPI for BreezServer {

async fn fetch_fiat_rates(&self) -> Result<Vec<Rate>> {
let mut client = self.get_information_client().await;

let request = Request::new(RatesRequest {});
let response = client
.rates(request)
.await
.map_err(|e| anyhow!("Fetch rates request failed: {e}"))?;
let mut client_clone = client.clone();

let request = RatesRequest {};
let response = with_connection_fallback(client.rates(request.clone()), || {
client_clone.rates(request)
})
.await
.map_err(|e| anyhow!("Fetch rates request failed: {e}"))?;

let mut rates = response.into_inner().rates;
rates.sort_by(|a, b| a.coin.cmp(&b.coin));
Expand Down
1 change: 1 addition & 0 deletions libs/sdk-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod invoice;
pub mod liquid;
mod lnurl;
mod model;
pub mod tonic_wrap;
mod utils;

// Re-export commonly used crates, to make it easy for callers to use the specific versions we're using.
Expand Down
82 changes: 82 additions & 0 deletions libs/sdk-common/src/tonic_wrap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::{error::Error, fmt::Display, future::Future};

use log::debug;

pub struct Status(pub tonic::Status);

impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"status: {:?}, message: {:?}, details: {:?}, metadata: {:?}, source: {:?}",
self.0.code(),
self.0.message(),
self.0.details(),
self.0.metadata(),
self.0.source(),
)
}
}

pub struct TransportError(pub tonic::transport::Error);

impl Display for TransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"description: {:?}, source: {:?}",
self.0.to_string(),
self.0.source(),
)
}
}

pub async fn with_connection_fallback<T, M, F>(
main: M,
fallback: impl FnOnce() -> F,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need the "main" future or we can use the fallback also for the first attempt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this, if you want you can try yourself too.
I tried by passing in a client to the function and having a Fn that operates on the client. I got stuck on lifetimes. It would be the cleaner way though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use a macro here so we can use it like this:

retry! {
 client.lsp_list(request.clone()).await
}

I will give it a try

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this macro should do it:

#[macro_export]
macro_rules! retry_grpc {
    ($f:expr) => {{
      use std::error::Error;
      use log::debug;
      const BROKEN_CONNECTION_STRINGS: [&str; 3] = [
          "http2 error: keep-alive timed out",
          "connection error: address not available",
          "connection error: timed out",
      ];

      let res = $f;
      let result = match res {
          Ok(t) => Ok(t),
          Err(status) => {
            let mut retruned = Err(status.clone());
            if let Some(source) = status.source() {
              if let Some(error) = source.downcast_ref::<tonic::transport::Error>() {
                if error.to_string() == "transport error" {
                  if let Some(source) = error.source() {
                    if BROKEN_CONNECTION_STRINGS.contains(&source.to_string().as_str()) {
                      debug!("retry_grpc: initial call failed due to broken connection. Retrying.");
                      retruned = $f
                    }
                  }
                }
              }
            }
            retruned
          },
      };
      result
   }};
}

Then it can be used as:

retry_grpc! {
 client.lsp_list(request.clone()).await
}

) -> Result<T, tonic::Status>
where
M: Future<Output = Result<T, tonic::Status>>,
F: Future<Output = Result<T, tonic::Status>>,
T: std::fmt::Debug,
{
let res = main.await;
let status = match res {
Ok(t) => return Ok(t),
Err(s) => s,
};

debug!(
"with_connection_fallback: initial call failed with: {:?}",
status
);
let source = match status.source() {
Some(source) => source,
None => return Err(status),
};

let error: &tonic::transport::Error = match source.downcast_ref() {
Some(error) => error,
None => return Err(status),
};

if error.to_string() != "transport error" {
return Err(status);
}

let source = match error.source() {
Some(source) => source,
None => return Err(status),
};

if !source.to_string().contains("keep-alive timed out") {
return Err(status);
}

debug!(
"with_connection_fallback: initial call failed due to keepalive
timeout. Retrying fallback."
);

fallback().await
}
Loading
Loading