Skip to content

Commit

Permalink
Extract BreezServer to sdk-common (#1015)
Browse files Browse the repository at this point in the history
* Extract BreezServer to sdk-common

* Simplify imports and sdk_common::prelude to minimize diff to main

* Fix tests clippy

* BreezServer: fix return type of methods that don't need to return Result
  • Loading branch information
ok300 authored Jun 19, 2024
1 parent a5ae46d commit 02a4612
Show file tree
Hide file tree
Showing 27 changed files with 251 additions and 224 deletions.
4 changes: 3 additions & 1 deletion libs/Cargo.lock

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

3 changes: 3 additions & 0 deletions libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ lightning-invoice = "=0.24.0" # Same version as used in gl-client
log = "0.4"
mockito = "1"
once_cell = "1"
prost = "^0.11"
regex = "1.8.1"
reqwest = { version = "=0.11.20", features = ["json"] }
rusqlite = { version = "0.29", features = [
Expand All @@ -43,5 +44,7 @@ strum = "0.25"
strum_macros = "0.25"
thiserror = "1.0.56"
tokio = { version = "1", features = ["full"] }
tonic = "^0.8"
tonic-build = "^0.8"
uniffi = "0.23.0"
uniffi_macros = "0.23.0"
2 changes: 1 addition & 1 deletion libs/sdk-bindings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ log = { workspace = true }
once_cell = { workspace = true }
flutter_rust_bridge = "=1.82.6"
tiny-bip39 = "*"
tonic = { version = "^0.8", features = [
tonic = { workspace = true, features = [
"tls",
"tls-roots",
"tls-webpki-roots",
Expand Down
7 changes: 6 additions & 1 deletion libs/sdk-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ lightning = { workspace = true }
lightning-invoice = { workspace = true }
log = { workspace = true }
querystring = "1"
prost = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true }
rusqlite = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
tonic = { workspace = true }

[dev-dependencies]
bitcoin = { workspace = true, features = ["rand"] }
mockito = { workspace = true }
tokio = { workspace = true }
once_cell = { workspace = true }
once_cell = { workspace = true }

[build-dependencies]
tonic-build = { workspace = true }
4 changes: 4 additions & 0 deletions libs/sdk-common/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("src/grpc/proto/breez.proto")?;
Ok(())
}
139 changes: 139 additions & 0 deletions libs/sdk-common/src/breez_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use anyhow::Result;
use log::trace;
use tonic::codegen::InterceptedService;
use tonic::metadata::errors::InvalidMetadataValue;
use tonic::metadata::{Ascii, MetadataValue};
use tonic::service::Interceptor;
use tonic::transport::{Channel, Endpoint};
use tonic::{Request, Status};

use crate::grpc::channel_opener_client::ChannelOpenerClient;
use crate::grpc::information_client::InformationClient;
use crate::grpc::payment_notifier_client::PaymentNotifierClient;
use crate::grpc::signer_client::SignerClient;
use crate::grpc::support_client::SupportClient;
use crate::grpc::swapper_client::SwapperClient;
use crate::grpc::{ChainApiServersRequest, PingRequest};
use crate::prelude::ServiceConnectivityError;

pub struct BreezServer {
grpc_channel: Channel,
api_key: Option<String>,
}

impl BreezServer {
pub fn new(server_url: String, api_key: Option<String>) -> Result<Self> {
Ok(Self {
grpc_channel: Endpoint::from_shared(server_url)?.connect_lazy(),
api_key,
})
}

fn api_key_metadata(&self) -> Result<Option<MetadataValue<Ascii>>, ServiceConnectivityError> {
match &self.api_key {
Some(key) => Ok(Some(format!("Bearer {key}").parse().map_err(
|e: InvalidMetadataValue| {
ServiceConnectivityError::new(&format!(
"(Breez: {:?}) Failed parse API key: {e}",
self.api_key
))
},
)?)),
_ => Ok(None),
}
}

pub async fn get_channel_opener_client(
&self,
) -> Result<
ChannelOpenerClient<InterceptedService<Channel, ApiKeyInterceptor>>,
ServiceConnectivityError,
> {
let api_key_metadata = self.api_key_metadata()?;
let with_interceptor = ChannelOpenerClient::with_interceptor(
self.grpc_channel.clone(),
ApiKeyInterceptor { api_key_metadata },
);
Ok(with_interceptor)
}

pub async fn get_payment_notifier_client(&self) -> PaymentNotifierClient<Channel> {
PaymentNotifierClient::new(self.grpc_channel.clone())
}

pub async fn get_information_client(&self) -> InformationClient<Channel> {
InformationClient::new(self.grpc_channel.clone())
}

pub async fn get_signer_client(&self) -> SignerClient<Channel> {
SignerClient::new(self.grpc_channel.clone())
}

pub async fn get_support_client(
&self,
) -> Result<
SupportClient<InterceptedService<Channel, ApiKeyInterceptor>>,
ServiceConnectivityError,
> {
let api_key_metadata = self.api_key_metadata()?;
Ok(SupportClient::with_interceptor(
self.grpc_channel.clone(),
ApiKeyInterceptor { api_key_metadata },
))
}

pub async fn get_swapper_client(&self) -> SwapperClient<Channel> {
SwapperClient::new(self.grpc_channel.clone())
}

pub async fn ping(&self) -> Result<String> {
let request = Request::new(PingRequest {});
let response = self
.get_information_client()
.await
.ping(request)
.await?
.into_inner()
.version;
Ok(response)
}

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(&format!(
"(Breez: {e:?}) Failed to fetch ChainApiServers"
))
})?
.into_inner()
.servers;
trace!("Received chain_api_servers: {chain_api_servers:?}");

let mempoolspace_urls = chain_api_servers
.iter()
.filter(|s| s.server_type == "MEMPOOL_SPACE")
.map(|s| s.server_base_url.clone())
.collect();
trace!("Received mempoolspace_urls: {mempoolspace_urls:?}");

Ok(mempoolspace_urls)
}
}

pub struct ApiKeyInterceptor {
api_key_metadata: Option<MetadataValue<Ascii>>,
}

impl Interceptor for ApiKeyInterceptor {
fn call(&mut self, mut req: Request<()>) -> Result<Request<()>, Status> {
if self.api_key_metadata.clone().is_some() {
req.metadata_mut()
.insert("authorization", self.api_key_metadata.clone().unwrap());
}
Ok(req)
}
}
21 changes: 21 additions & 0 deletions libs/sdk-common/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use thiserror::Error;

#[derive(Debug, Error)]
#[error("{err}")]
pub struct ServiceConnectivityError {
pub err: String,
}
impl ServiceConnectivityError {
pub fn new(err: &str) -> Self {
ServiceConnectivityError {
err: err.to_string(),
}
}
}
impl From<reqwest::Error> for ServiceConnectivityError {
fn from(err: reqwest::Error) -> Self {
Self {
err: err.to_string(),
}
}
}
File renamed without changes.
File renamed without changes.
6 changes: 6 additions & 0 deletions libs/sdk-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
mod breez_server;
mod error;
pub mod grpc;
pub mod input_parser;
pub mod invoice;
mod lnurl;
mod model;
mod utils;

// We don't include grpc::* in the prelude exports, to force callers to use the grpc path prefix.
#[rustfmt::skip]
pub mod prelude {
pub use crate::*;
pub use crate::breez_server::*;
pub use crate::error::*;
pub use crate::input_parser::*;
pub use crate::invoice::*;
pub use crate::lnurl::error::*;
Expand Down
23 changes: 3 additions & 20 deletions libs/sdk-common/src/utils/rest_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,8 @@ use std::time::Duration;

use log::*;
use reqwest::StatusCode;
use thiserror::Error;

#[derive(Debug, Error)]
#[error("{err}")]
pub struct ServiceConnectivityError {
pub err: String,
}
impl ServiceConnectivityError {
pub fn new(err: String) -> Self {
ServiceConnectivityError { err }
}
}
impl From<reqwest::Error> for ServiceConnectivityError {
fn from(err: reqwest::Error) -> Self {
Self {
err: err.to_string(),
}
}
}
use crate::error::ServiceConnectivityError;

/// Creates an HTTP client with a built-in connection timeout
pub fn get_reqwest_client() -> Result<reqwest::Client, ServiceConnectivityError> {
Expand Down Expand Up @@ -83,8 +66,8 @@ where
if enforce_status_check && !status.is_success() {
let err = format!("GET request {url} failed with status: {status}");
error!("{err}");
return Err(ServiceConnectivityError::new(err));
return Err(ServiceConnectivityError::new(&err));
}

serde_json::from_str::<T>(&raw_body).map_err(|e| ServiceConnectivityError::new(e.to_string()))
serde_json::from_str::<T>(&raw_body).map_err(|e| ServiceConnectivityError::new(&e.to_string()))
}
6 changes: 2 additions & 4 deletions libs/sdk-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ ripemd = "0.1"
rand = "0.8"
tiny-bip39 = "1"
tokio = { workspace = true }
prost = "^0.11"
prost = { workspace = true }
rusqlite = { workspace = true }
rusqlite_migration = "1.0"
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sdk-common = { path = "../sdk-common" }
tonic = { version = "^0.8", features = [
tonic = { workspace = true, features = [
"tls",
"transport",
"tls-roots",
Expand All @@ -57,5 +57,3 @@ regex = { workspace = true }
[dev-dependencies]
mockito = { workspace = true }

[build-dependencies]
tonic-build = "^0.8"
1 change: 0 additions & 1 deletion libs/sdk-core/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("src/grpc/proto/breez.proto")?;
set_git_revision_hash();
Ok(())
}
Expand Down
Loading

0 comments on commit 02a4612

Please sign in to comment.