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
47 changes: 24 additions & 23 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::with_connection_retry;

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,18 +113,17 @@ 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 {})
.await
.map_err(|e| {
ServiceConnectivityError::new(
ServiceConnectivityErrorKind::Other,
format!("(Breez: {e:?}) Failed to fetch ChainApiServers"),
)
})?
.into_inner()
.servers;
let chain_api_servers =
with_connection_retry!(client.chain_api_servers(ChainApiServersRequest {}))
.await
.map_err(|e| {
ServiceConnectivityError::new(
ServiceConnectivityErrorKind::Other,
format!("(Breez: {e:?}) Failed to fetch ChainApiServers"),
)
})?
.into_inner()
.servers;
trace!("Received chain_api_servers: {chain_api_servers:?}");

let mempoolspace_urls = chain_api_servers
Expand All @@ -139,17 +139,17 @@ impl BreezServer {
pub async fn fetch_boltz_swapper_urls(&self) -> Result<Vec<String>, ServiceConnectivityError> {
let mut client = self.get_information_client().await;

let chain_api_servers = client
.chain_api_servers(ChainApiServersRequest {})
.await
.map_err(|e| {
ServiceConnectivityError::new(
ServiceConnectivityErrorKind::Other,
format!("(Breez: {e:?}) Failed to fetch ChainApiServers"),
)
})?
.into_inner()
.servers;
let chain_api_servers =
with_connection_retry!(client.chain_api_servers(ChainApiServersRequest {}))
.await
.map_err(|e| {
ServiceConnectivityError::new(
ServiceConnectivityErrorKind::Other,
format!("(Breez: {e:?}) Failed to fetch ChainApiServers"),
)
})?
.into_inner()
.servers;
trace!("Received chain_api_servers: {chain_api_servers:?}");

let boltz_swapper_urls = chain_api_servers
Expand All @@ -163,6 +163,7 @@ impl BreezServer {
}
}

#[derive(Clone)]
pub struct ApiKeyInterceptor {
api_key_metadata: Option<MetadataValue<Ascii>>,
}
Expand Down
7 changes: 3 additions & 4 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::with_connection_retry;

/// Trait covering fiat-related functionality
#[tonic::async_trait]
Expand Down Expand Up @@ -98,9 +98,8 @@ 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)
let request = RatesRequest {};
let response = with_connection_retry!(client.rates(request.clone()))
.await
.map_err(|e| anyhow!("Fetch rates request failed: {e}"))?;

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
90 changes: 90 additions & 0 deletions libs/sdk-common/src/tonic_wrap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::{error::Error, fmt::Display};

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(),
)
}
}

/// Executes the given grpc call function. If an error is returned that
/// indicates the connection broke, the call is tried again.
#[macro_export]
macro_rules! with_connection_retry {
($f:expr) => {{
use log::debug;
use std::error::Error;
const BROKEN_CONNECTION_STRINGS: [&str; 4] = [
"http2 error: keep-alive timed out",
"connection error: address not available",
"connection error: timed out",
"connection error: unexpected end of file",
];

async {
let res = $f.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),
};

// It's a bit of a guess which errors can occur here. hyper Io errors start
// with 'connection error'. These are some of the errors seen before.
if !BROKEN_CONNECTION_STRINGS.contains(&source.to_string().as_str()) {
debug!("transport error string is: '{}'", source.to_string());
return Err(status);
}

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

$f.await
}
}};
}
Loading
Loading