Skip to content

Commit

Permalink
add peer features to connected peers
Browse files Browse the repository at this point in the history
currently only the trampoline feature is relevant. The features are added to the node state whenever the node state is fetched, or when connecting to a lsp. Existing node state is dropped from the cache, to ensure deserialization of cached node state is always possible.
  • Loading branch information
JssDWt committed Jul 12, 2024
1 parent 0c15baa commit 10d88bf
Show file tree
Hide file tree
Showing 13 changed files with 330 additions and 43 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
17 changes: 13 additions & 4 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1202,7 +1202,7 @@ impl BreezServices {

self.persister.set_lsp_id(lsp.id)?;
self.persister.set_lsp_pubkey(lsp.pubkey.clone())?;
let node_state = match self.node_info() {
let mut node_state = match self.node_info() {
Ok(node_state) => node_state,
Err(_) => return Ok(()),
};
Expand All @@ -1212,15 +1212,21 @@ impl BreezServices {
let lsp_connected = node_state
.connected_peers
.iter()
.any(|e| e == node_id.as_str());
.any(|peer| peer.id == node_id);
if !lsp_connected {
debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
self.node_api
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}");
}

Expand Down Expand Up @@ -3217,7 +3223,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
50 changes: 48 additions & 2 deletions libs/sdk-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ use crate::swap_out::error::{ReverseSwapError, ReverseSwapResult};
pub const SWAP_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60 * 24 * 2; // 2 days
pub const INVOICE_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60; // 60 minutes

const FEATURE_TRAMPOLINE: usize = 427;

/// Different types of supported payments
#[derive(Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize, Hash)]
pub enum PaymentType {
Expand Down Expand Up @@ -625,10 +627,40 @@ pub struct NodeState {
pub max_receivable_msat: u64,
pub max_single_payment_amount_msat: u64,
pub max_chan_reserve_msats: u64,
pub connected_peers: Vec<String>,
pub connected_peers: Vec<ConnectedPeer>,
pub inbound_liquidity_msats: u64,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct ConnectedPeer {
pub id: String,
pub features: PeerFeatures,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]
pub struct PeerFeatures {
pub trampoline: bool,
}

impl From<Vec<u8>> for PeerFeatures {
fn from(value: Vec<u8>) -> Self {
PeerFeatures {
trampoline: has_feature(&value, FEATURE_TRAMPOLINE),
}
}
}

/// Checks whether the given features vector contains the specified feature.
fn has_feature(features: &[u8], feature: usize) -> bool {
if features.is_empty() || features.len() * 8 - 1 < feature {
return false;
}

let byte_index = features.len() - feature / 8 - 1;
let bit_index = feature % 8;
((features[byte_index] >> bit_index) & 1) == 1
}

/// Internal response to a [crate::node_api::NodeAPI::pull_changed] call
pub struct SyncResponse {
pub node_state: NodeState,
Expand Down Expand Up @@ -1587,7 +1619,7 @@ mod tests {
use sdk_common::grpc;

use crate::test_utils::{get_test_ofp, rand_vec_u8};
use crate::{OpeningFeeParams, PaymentPath, PaymentPathEdge};
use crate::{OpeningFeeParams, PaymentPath, PaymentPathEdge, PeerFeatures};

#[test]
fn test_route_fees() -> Result<()> {
Expand Down Expand Up @@ -1747,4 +1779,18 @@ mod tests {
ofp.valid_until = "2023-08-03T00:30:35.117Z".to_string();
ofp.valid_until_date().map(|_| ())
}

#[test]
fn test_trampoline_feature_bit() {
let features_hex =
"08000000000000000000000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000";
let features = hex::decode(features_hex).unwrap();
let peer_features = PeerFeatures::from(features);
assert!(peer_features.trampoline);

let features = Vec::new();
let peer_features = PeerFeatures::from(features);
assert!(!peer_features.trampoline);
}
}
11 changes: 4 additions & 7 deletions libs/sdk-core/src/node_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ use tonic::Streaming;
use sdk_common::prelude::*;

use crate::{
bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey},
lightning_invoice::RawBolt11Invoice,
persist::error::PersistError,
CustomMessage, LspInformation, MaxChannelAmount, NodeCredentials, Payment, PaymentResponse,
PrepareRedeemOnchainFundsRequest, PrepareRedeemOnchainFundsResponse, RouteHint, RouteHintHop,
SyncResponse, TlvEntry, LnUrlAuthError
bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}, lightning_invoice::RawBolt11Invoice, persist::error::PersistError, CustomMessage, LnUrlAuthError, LspInformation, MaxChannelAmount, NodeCredentials, Payment, PaymentResponse, PeerFeatures, PrepareRedeemOnchainFundsRequest, PrepareRedeemOnchainFundsResponse, RouteHint, RouteHintHop, SyncResponse, TlvEntry
};

pub type NodeResult<T, E = NodeError> = Result<T, E>;
Expand Down Expand Up @@ -165,7 +160,9 @@ pub trait NodeAPI: Send + Sync {
) -> NodeResult<PrepareRedeemOnchainFundsResponse>;
async fn start_signer(&self, shutdown: mpsc::Receiver<()>);
async fn start_keep_alive(&self, shutdown: watch::Receiver<()>);
async fn connect_peer(&self, node_id: String, addr: String) -> NodeResult<()>;

/// Connects to a remote node and returns the remote node's features.
async fn connect_peer(&self, node_id: String, addr: String) -> NodeResult<PeerFeatures>;
fn sign_invoice(&self, invoice: RawBolt11Invoice) -> NodeResult<String>;
async fn close_peer_channels(&self, node_id: String) -> NodeResult<Vec<String>>;
async fn stream_incoming_payments(
Expand Down
3 changes: 2 additions & 1 deletion libs/sdk-core/src/persist/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,8 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
ALTER TABLE channels ADD COLUMN local_balance_msat INTEGER;
UPDATE channels SET local_balance_msat = spendable_msat;
",
"DELETE FROM cached_items WHERE key = 'gl_credentials'"
"DELETE FROM cached_items WHERE key = 'gl_credentials'",
"DELETE FROM cached_items WHERE key = 'node_state'"
]
}

Expand Down
6 changes: 3 additions & 3 deletions libs/sdk-core/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use crate::swap_out::boltzswap::{BoltzApiCreateReverseSwapResponse, BoltzApiReve
use crate::swap_out::error::{ReverseSwapError, ReverseSwapResult};
use crate::{
parse_invoice, Config, CustomMessage, LNInvoice, MaxChannelAmount, NodeCredentials,
OpeningFeeParamsMenu, PaymentResponse, PrepareRedeemOnchainFundsRequest,
OpeningFeeParamsMenu, PaymentResponse, PeerFeatures, PrepareRedeemOnchainFundsRequest,
PrepareRedeemOnchainFundsResponse, ReceivePaymentRequest, ReverseSwapPairInfo, RouteHint,
RouteHintHop, SwapInfo,
};
Expand Down Expand Up @@ -426,8 +426,8 @@ impl NodeAPI for MockNodeAPI {

async fn start_keep_alive(&self, _shutdown: watch::Receiver<()>) {}

async fn connect_peer(&self, _node_id: String, _addr: String) -> NodeResult<()> {
Ok(())
async fn connect_peer(&self, _node_id: String, _addr: String) -> NodeResult<PeerFeatures> {
Ok(PeerFeatures::default())
}

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

0 comments on commit 10d88bf

Please sign in to comment.