Skip to content

Commit

Permalink
preliminary check whether lsp supports trampoline
Browse files Browse the repository at this point in the history
  • Loading branch information
JssDWt committed Jul 12, 2024
1 parent 2f85ca1 commit 8a68398
Show file tree
Hide file tree
Showing 14 changed files with 404 additions and 88 deletions.
11 changes: 10 additions & 1 deletion libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,19 @@ dictionary NodeState {
u64 max_receivable_msat;
u64 max_single_payment_amount_msat;
u64 max_chan_reserve_msats;
sequence<string> connected_peers;
sequence<ConnectedPeer> connected_peers;
u64 inbound_liquidity_msats;
};

dictionary ConnectedPeer {
string id;
PeerFeatures features;
};

dictionary PeerFeatures {
boolean trampoline;
};

dictionary ConfigureNodeRequest {
string? close_to_address;
};
Expand Down
12 changes: 6 additions & 6 deletions libs/sdk-bindings/src/uniffi_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ use breez_sdk_core::{
BackupFailedData, BackupStatus, BitcoinAddressData, BreezEvent, BreezServices,
BuyBitcoinProvider, BuyBitcoinRequest, BuyBitcoinResponse, ChannelState, CheckMessageRequest,
CheckMessageResponse, ClosedChannelPaymentDetails, Config, ConfigureNodeRequest,
ConnectRequest, CurrencyInfo, EnvironmentType, EventListener, FeeratePreset, FiatCurrency,
GreenlightCredentials, GreenlightDeviceCredentials, GreenlightNodeConfig, HealthCheckStatus,
InputType, InvoicePaidDetails, LNInvoice, ListPaymentsRequest, LnPaymentDetails,
LnUrlAuthError, LnUrlAuthRequestData, LnUrlCallbackStatus, LnUrlErrorData, LnUrlPayError,
LnUrlPayErrorData, LnUrlPayRequest, LnUrlPayRequestData, LnUrlWithdrawError,
ConnectRequest, ConnectedPeer, CurrencyInfo, EnvironmentType, EventListener, FeeratePreset,
FiatCurrency, GreenlightCredentials, GreenlightDeviceCredentials, GreenlightNodeConfig,
HealthCheckStatus, InputType, InvoicePaidDetails, LNInvoice, ListPaymentsRequest,
LnPaymentDetails, LnUrlAuthError, LnUrlAuthRequestData, LnUrlCallbackStatus, LnUrlErrorData,
LnUrlPayError, LnUrlPayErrorData, LnUrlPayRequest, LnUrlPayRequestData, LnUrlWithdrawError,
LnUrlWithdrawRequest, LnUrlWithdrawRequestData, LnUrlWithdrawResult, LnUrlWithdrawSuccessData,
LocaleOverrides, LocalizedName, LogEntry, LogStream, LspInformation,
MaxReverseSwapAmountResponse, MessageSuccessActionData, MetadataFilter, MetadataItem, Network,
NodeConfig, NodeCredentials, NodeState, OnchainPaymentLimitsResponse, OpenChannelFeeRequest,
OpenChannelFeeResponse, OpeningFeeParams, OpeningFeeParamsMenu, PayOnchainRequest,
PayOnchainResponse, Payment, PaymentDetails, PaymentFailedData, PaymentStatus, PaymentType,
PaymentTypeFilter, PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse,
PaymentTypeFilter, PeerFeatures, PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse,
PrepareRedeemOnchainFundsRequest, PrepareRedeemOnchainFundsResponse, PrepareRefundRequest,
PrepareRefundResponse, Rate, ReceiveOnchainRequest, ReceivePaymentRequest,
ReceivePaymentResponse, RecommendedFees, RedeemOnchainFundsRequest, RedeemOnchainFundsResponse,
Expand Down
128 changes: 79 additions & 49 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,23 +282,33 @@ impl BreezServices {
return Err(SendPaymentError::AlreadyPaid);
}

// If there is an lsp and the invoice route hint does not contain the
// lsp in the hint, attempt a trampoline payment.
let maybe_trampoline_id = match self.lsp_info().await {
Ok(lsp_info) => {
let lsp_pubkey = hex::encode(&lsp_info.lsp_pubkey);
match parsed_invoice.routing_hints.iter().any(|hint| {
hint.hops
.last()
.map(|hop| hop.src_node_id == lsp_pubkey)
.unwrap_or(false)
}) {
true => None,
false => Some(lsp_info.lsp_pubkey),
// If there is an lsp, the invoice route hint does not contain the
// lsp in the hint, and the lsp supports trampoline payments, attempt a
// trampoline payment.
let mut maybe_trampoline_id = None;
if let Some(lsp_pubkey) = self.persister.get_lsp_pubkey()? {
if !parsed_invoice.routing_hints.iter().any(|hint| {
hint.hops
.last()
.map(|hop| hop.src_node_id == lsp_pubkey)
.unwrap_or(false)
}) {
let node_state = self.node_info()?;
if let Some(peer) = node_state
.connected_peers
.iter()
.find(|peer| peer.id == lsp_pubkey)
{
if peer.features.trampoline {
maybe_trampoline_id = Some(hex::decode(lsp_pubkey).map_err(|_| {
SendPaymentError::Generic {
err: "failed to decode lsp pubkey".to_string(),
}
})?);
}
}
}
Err(_) => None,
};
}

self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;

Expand Down Expand Up @@ -704,19 +714,22 @@ impl BreezServices {

/// Select the LSP to be used and provide inbound liquidity
pub async fn connect_lsp(&self, lsp_id: String) -> SdkResult<()> {
match self.list_lsps().await?.iter().any(|lsp| lsp.id == lsp_id) {
true => {
self.persister.set_lsp_id(lsp_id)?;
self.sync().await?;
if let Some(webhook_url) = self.persister.get_webhook_url()? {
self.register_payment_notifications(webhook_url).await?
}
Ok(())
let lsp_pubkey = match self.list_lsps().await?.iter().find(|lsp| lsp.id == lsp_id) {
Some(lsp) => lsp.pubkey.clone(),
None => {
return Err(SdkError::Generic {
err: format!("Unknown LSP: {lsp_id}"),
})
}
false => Err(SdkError::Generic {
err: format!("Unknown LSP: {lsp_id}"),
}),
};

self.persister.set_lsp_id(lsp_id)?;
self.persister.set_lsp_pubkey(lsp_pubkey)?;
self.sync().await?;
if let Some(webhook_url) = self.persister.get_webhook_url()? {
self.register_payment_notifications(webhook_url).await?
}
Ok(())
}

/// Get the current LSP's ID
Expand Down Expand Up @@ -1187,32 +1200,46 @@ impl BreezServices {
/// If not or no LSP is selected, it selects the first LSP in [`list_lsps`].
async fn connect_lsp_peer(&self, node_pubkey: String) -> SdkResult<()> {
let lsps = self.lsp_api.list_lsps(node_pubkey).await?;
if let Some(lsp) = self
let lsp = match self
.persister
.get_lsp_id()?
.and_then(|lsp_id| lsps.clone().into_iter().find(|lsp| lsp.id == lsp_id))
.or_else(|| lsps.first().cloned())
.and_then(|lsp_id| lsps.iter().find(|lsp| lsp.id == lsp_id))
.or_else(|| lsps.first())
{
self.persister.set_lsp_id(lsp.id)?;
if let Ok(node_state) = self.node_info() {
let node_id = lsp.pubkey;
let address = lsp.host;
let lsp_connected = node_state
.connected_peers
.iter()
.any(|e| e == node_id.as_str());
if !lsp_connected {
debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
self.node_api
.connect_peer(node_id.clone(), address.clone())
.await
.map_err(|e| SdkError::ServiceConnectivity {
err: format!("(LSP: {node_id}) Failed to connect: {e}"),
})?;
}
debug!("connected to lsp {node_id}@{address}");
}
Some(lsp) => lsp.clone(),
None => return Ok(()),
};

self.persister.set_lsp_id(lsp.id)?;
self.persister.set_lsp_pubkey(lsp.pubkey.clone())?;
let mut node_state = match self.node_info() {
Ok(node_state) => node_state,
Err(_) => return Ok(()),
};

let node_id = lsp.pubkey;
let address = lsp.host;
let lsp_connected = node_state
.connected_peers
.iter()
.any(|peer| peer.id == node_id);
if !lsp_connected {
debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
let features = self
.node_api
.connect_peer(node_id.clone(), address.clone())
.await
.map_err(|e| SdkError::ServiceConnectivity {
err: format!("(LSP: {node_id}) Failed to connect: {e}"),
})?;
node_state.connected_peers.push(ConnectedPeer {
id: node_id.clone(),
features,
});
self.persister.set_node_state(&node_state)?;
debug!("connected to lsp {node_id}@{address}");
}

Ok(())
}

Expand Down Expand Up @@ -3206,7 +3233,10 @@ pub(crate) mod tests {
max_receivable_msat: 4_000_000_000,
max_single_payment_amount_msat: 1_000,
max_chan_reserve_msats: 0,
connected_peers: vec!["1111".to_string()],
connected_peers: vec![ConnectedPeer {
id: "1111".to_string(),
features: PeerFeatures::default(),
}],
inbound_liquidity_msats: 2_000,
}
}
Expand Down
30 changes: 30 additions & 0 deletions libs/sdk-core/src/bridge_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use crate::models::ClosedChannelPaymentDetails;
use crate::models::Config;
use crate::models::ConfigureNodeRequest;
use crate::models::ConnectRequest;
use crate::models::ConnectedPeer;
use crate::models::EnvironmentType;
use crate::models::GreenlightCredentials;
use crate::models::GreenlightDeviceCredentials;
Expand All @@ -66,6 +67,7 @@ use crate::models::PaymentDetails;
use crate::models::PaymentStatus;
use crate::models::PaymentType;
use crate::models::PaymentTypeFilter;
use crate::models::PeerFeatures;
use crate::models::PrepareOnchainPaymentRequest;
use crate::models::PrepareOnchainPaymentResponse;
use crate::models::PrepareRedeemOnchainFundsRequest;
Expand Down Expand Up @@ -1506,6 +1508,22 @@ impl rust2dart::IntoIntoDart<Config> for Config {
}
}

impl support::IntoDart for ConnectedPeer {
fn into_dart(self) -> support::DartAbi {
vec![
self.id.into_into_dart().into_dart(),
self.features.into_into_dart().into_dart(),
]
.into_dart()
}
}
impl support::IntoDartExceptPrimitive for ConnectedPeer {}
impl rust2dart::IntoIntoDart<ConnectedPeer> for ConnectedPeer {
fn into_into_dart(self) -> Self {
self
}
}

impl support::IntoDart for mirror_CurrencyInfo {
fn into_dart(self) -> support::DartAbi {
vec![
Expand Down Expand Up @@ -2238,6 +2256,18 @@ impl rust2dart::IntoIntoDart<PaymentType> for PaymentType {
}
}

impl support::IntoDart for PeerFeatures {
fn into_dart(self) -> support::DartAbi {
vec![self.trampoline.into_into_dart().into_dart()].into_dart()
}
}
impl support::IntoDartExceptPrimitive for PeerFeatures {}
impl rust2dart::IntoIntoDart<PeerFeatures> for PeerFeatures {
fn into_into_dart(self) -> Self {
self
}
}

impl support::IntoDart for PrepareOnchainPaymentResponse {
fn into_dart(self) -> support::DartAbi {
vec![
Expand Down
31 changes: 18 additions & 13 deletions libs/sdk-core/src/greenlight/node_api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cmp::{min, Reverse};
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::iter::Iterator;
use std::pin::Pin;
use std::str::FromStr;
Expand Down Expand Up @@ -456,7 +456,7 @@ impl Greenlight {
) -> NodeResult<(
Vec<cln::ListpeerchannelsChannels>,
Vec<cln::ListpeerchannelsChannels>,
Vec<String>,
Vec<ConnectedPeer>,
u64,
)> {
let (mut all_channels, mut opened_channels, mut connected_peers, mut channels_balance) =
Expand Down Expand Up @@ -491,24 +491,28 @@ impl Greenlight {
) -> NodeResult<(
Vec<cln::ListpeerchannelsChannels>,
Vec<cln::ListpeerchannelsChannels>,
Vec<String>,
Vec<ConnectedPeer>,
u64,
)> {
// list all channels
let peers = cln_client
.list_peers(cln::ListpeersRequest::default())
.await?
.into_inner();
let peerchannels = cln_client
.list_peer_channels(cln::ListpeerchannelsRequest::default())
.await?
.into_inner();

// filter only connected peers
let connected_peers: Vec<String> = peerchannels
.channels
let connected_peers: Vec<ConnectedPeer> = peers
.peers
.iter()
.filter(|channel| channel.peer_connected())
.filter_map(|channel| channel.peer_id.clone())
.map(hex::encode)
.collect::<HashSet<_>>()
.into_iter()
.filter(|peer| peer.connected)
.map(|peer| ConnectedPeer {
id: hex::encode(&peer.id),
features: peer.features().to_vec().into(),
})
.collect();

// filter only opened channels
Expand Down Expand Up @@ -1333,15 +1337,16 @@ impl NodeAPI for Greenlight {
}
}

async fn connect_peer(&self, id: String, addr: String) -> NodeResult<()> {
/// Connects to a remote node and returns the remote node's features.
async fn connect_peer(&self, id: String, addr: String) -> NodeResult<PeerFeatures> {
let mut client = self.get_node_client().await?;
let connect_req = cln::ConnectRequest {
id: format!("{id}@{addr}"),
host: None,
port: None,
};
client.connect_peer(connect_req).await?;
Ok(())
let resp = client.connect_peer(connect_req).await?.into_inner();
Ok(resp.features.into())
}

async fn sign_message(&self, message: &str) -> NodeResult<String> {
Expand Down
Loading

0 comments on commit 8a68398

Please sign in to comment.