Skip to content

Commit 22da16c

Browse files
committed
Refactor liquidity source to support multiple LSP nodes
Replace per-protocol single-LSP configuration `LSPS1Client, LSPS2Client` with a unified `Vec<LspNode>` model where users configure LSP nodes via `add_lsp()` and protocol support is discovered at runtime via LSPS0 `list_protocols`. - Replace separate `LSPS1Client/LSPS2Client` with global pending request maps keyed by `LSPSRequestId` - Add LSPS0 protocol discovery `discover_lsp_protocols` with event handling for `ListProtocolsResponse` - Update events to use is_lsps_node() for multi-LSP counterparty checks - Deprecate `set_liquidity_source_lsps1/lsps2` builder methods in favor of `add_lsp()` - LSPS2 JIT channels now query all LSPS2-capable LSPs and automatically select the cheapest fee offer across all of them - Add `request_channel_from_lsp()` for explicit LSPS1 LSP selection - Spawn background discovery task on `Node::start()`
1 parent d6c0952 commit 22da16c

File tree

12 files changed

+1974
-1478
lines changed

12 files changed

+1974
-1478
lines changed

bindings/ldk_node.udl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ interface Builder {
4343
void set_gossip_source_p2p();
4444
void set_gossip_source_rgs(string rgs_server_url);
4545
void set_pathfinding_scores_source(string url);
46-
void set_liquidity_source_lsps1(PublicKey node_id, SocketAddress address, string? token);
47-
void set_liquidity_source_lsps2(PublicKey node_id, SocketAddress address, string? token);
46+
void add_lsp(PublicKey node_id, SocketAddress address, string? token);
4847
void set_storage_dir_path(string storage_dir_path);
4948
void set_filesystem_logger(string? log_file_path, LogLevel? max_log_level);
5049
void set_log_facade_logger();
@@ -97,7 +96,7 @@ interface Node {
9796
SpontaneousPayment spontaneous_payment();
9897
OnchainPayment onchain_payment();
9998
UnifiedPayment unified_payment();
100-
LSPS1Liquidity lsps1_liquidity();
99+
Liquidity liquidity();
101100
[Throws=NodeError]
102101
void lnurl_auth(string lnurl);
103102
[Throws=NodeError]
@@ -165,7 +164,7 @@ interface FeeRate {
165164

166165
typedef interface UnifiedPayment;
167166

168-
typedef interface LSPS1Liquidity;
167+
typedef interface Liquidity;
169168

170169
[Error]
171170
enum NodeError {

src/builder.rs

Lines changed: 21 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ use crate::io::{
6565
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
6666
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
6767
};
68-
use crate::liquidity::{
69-
LSPS1ClientConfig, LSPS2ClientConfig, LSPS2ServiceConfig, LiquiditySourceBuilder,
70-
};
68+
use crate::liquidity::{LSPS2ServiceConfig, LiquiditySourceBuilder, LspConfig};
7169
use crate::lnurl_auth::LnurlAuth;
7270
use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger};
7371
use crate::message_handler::NodeCustomMessageHandler;
@@ -120,10 +118,8 @@ struct PathfindingScoresSyncConfig {
120118

121119
#[derive(Debug, Clone, Default)]
122120
struct LiquiditySourceConfig {
123-
// Act as an LSPS1 client connecting to the given service.
124-
lsps1_client: Option<LSPS1ClientConfig>,
125-
// Act as an LSPS2 client connecting to the given service.
126-
lsps2_client: Option<LSPS2ClientConfig>,
121+
// Acts for both LSPS1 and LSPS2 clients connecting to the given service.
122+
lsp_nodes: Vec<LspConfig>,
127123
// Act as an LSPS2 service.
128124
lsps2_service: Option<LSPS2ServiceConfig>,
129125
}
@@ -435,45 +431,24 @@ impl NodeBuilder {
435431
self
436432
}
437433

438-
/// Configures the [`Node`] instance to source inbound liquidity from the given
439-
/// [bLIP-51 / LSPS1] service.
440-
///
441-
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
442-
///
443-
/// The given `token` will be used by the LSP to authenticate the user.
444-
///
445-
/// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
446-
pub fn set_liquidity_source_lsps1(
447-
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
448-
) -> &mut Self {
449-
// Mark the LSP as trusted for 0conf
450-
self.config.trusted_peers_0conf.push(node_id.clone());
451-
452-
let liquidity_source_config =
453-
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
454-
let lsps1_client_config = LSPS1ClientConfig { node_id, address, token };
455-
liquidity_source_config.lsps1_client = Some(lsps1_client_config);
456-
self
457-
}
458-
459-
/// Configures the [`Node`] instance to source just-in-time inbound liquidity from the given
460-
/// [bLIP-52 / LSPS2] service.
434+
/// Configures the [`Node`] instance to source inbound liquidity from the given LSP, without specifying
435+
/// the exact protocol used (e.g., LSPS1 or LSPS2).
461436
///
462437
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
463438
///
464439
/// The given `token` will be used by the LSP to authenticate the user.
465-
///
466-
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
467-
pub fn set_liquidity_source_lsps2(
440+
/// This method is useful when the user wants to connect to an LSP but does not want to be concerned with
441+
/// the specific protocol used for liquidity provision. The node will automatically detect and use the
442+
/// appropriate protocol supported by the LSP.
443+
pub fn add_lsp(
468444
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
469445
) -> &mut Self {
470446
// Mark the LSP as trusted for 0conf
471-
self.config.trusted_peers_0conf.push(node_id.clone());
447+
self.config.trusted_peers_0conf.push(node_id);
472448

473449
let liquidity_source_config =
474450
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
475-
let lsps2_client_config = LSPS2ClientConfig { node_id, address, token };
476-
liquidity_source_config.lsps2_client = Some(lsps2_client_config);
451+
liquidity_source_config.lsp_nodes.push(LspConfig { node_id, address, token });
477452
self
478453
}
479454

@@ -956,32 +931,16 @@ impl ArcedNodeBuilder {
956931
self.inner.write().expect("lock").set_pathfinding_scores_source(url);
957932
}
958933

959-
/// Configures the [`Node`] instance to source inbound liquidity from the given
960-
/// [bLIP-51 / LSPS1] service.
934+
/// Configures the [`Node`] instance to source inbound liquidity from the given LSP.
961935
///
962936
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
963937
///
964938
/// The given `token` will be used by the LSP to authenticate the user.
965-
///
966-
/// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
967-
pub fn set_liquidity_source_lsps1(
968-
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
969-
) {
970-
self.inner.write().expect("lock").set_liquidity_source_lsps1(node_id, address, token);
971-
}
972-
973-
/// Configures the [`Node`] instance to source just-in-time inbound liquidity from the given
974-
/// [bLIP-52 / LSPS2] service.
975-
///
976-
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
977-
///
978-
/// The given `token` will be used by the LSP to authenticate the user.
979-
///
980-
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
981-
pub fn set_liquidity_source_lsps2(
982-
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
983-
) {
984-
self.inner.write().expect("lock").set_liquidity_source_lsps2(node_id, address, token);
939+
/// This method is useful when the user wants to connect to an LSP but does not want to be concerned with
940+
/// the specific protocol used for liquidity provision. The node will automatically detect and use the
941+
/// appropriate protocol supported by the LSP.
942+
pub fn add_lsp(&self, node_id: PublicKey, address: SocketAddress, token: Option<String>) {
943+
self.inner.write().expect("lock").add_lsp(node_id, address, token);
985944
}
986945

987946
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
@@ -1806,21 +1765,7 @@ fn build_with_store_internal(
18061765
Arc::clone(&logger),
18071766
);
18081767

1809-
lsc.lsps1_client.as_ref().map(|config| {
1810-
liquidity_source_builder.lsps1_client(
1811-
config.node_id,
1812-
config.address.clone(),
1813-
config.token.clone(),
1814-
)
1815-
});
1816-
1817-
lsc.lsps2_client.as_ref().map(|config| {
1818-
liquidity_source_builder.lsps2_client(
1819-
config.node_id,
1820-
config.address.clone(),
1821-
config.token.clone(),
1822-
)
1823-
});
1768+
liquidity_source_builder.set_lsp_nodes(lsc.lsp_nodes.clone());
18241769

18251770
let promise_secret = {
18261771
let lsps_xpriv = derive_xprv(
@@ -1889,7 +1834,9 @@ fn build_with_store_internal(
18891834
}
18901835
}));
18911836

1892-
liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::downgrade(&peer_manager)));
1837+
liquidity_source
1838+
.as_ref()
1839+
.map(|l| l.lsps2_service().set_peer_manager(Arc::downgrade(&peer_manager)));
18931840

18941841
let connection_manager = Arc::new(ConnectionManager::new(
18951842
Arc::clone(&peer_manager),

src/event.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -581,15 +581,15 @@ where
581581
Ok(final_tx) => {
582582
let needs_manual_broadcast =
583583
self.liquidity_source.as_ref().map_or(false, |ls| {
584-
ls.as_ref().lsps2_channel_needs_manual_broadcast(
584+
ls.as_ref().lsps2_service().lsps2_channel_needs_manual_broadcast(
585585
counterparty_node_id,
586586
user_channel_id,
587587
)
588588
});
589589

590590
let result = if needs_manual_broadcast {
591591
self.liquidity_source.as_ref().map(|ls| {
592-
ls.lsps2_store_funding_transaction(
592+
ls.lsps2_service().lsps2_store_funding_transaction(
593593
user_channel_id,
594594
counterparty_node_id,
595595
final_tx.clone(),
@@ -653,7 +653,8 @@ where
653653
},
654654
LdkEvent::FundingTxBroadcastSafe { user_channel_id, counterparty_node_id, .. } => {
655655
self.liquidity_source.as_ref().map(|ls| {
656-
ls.lsps2_funding_tx_broadcast_safe(user_channel_id, counterparty_node_id);
656+
ls.lsps2_service()
657+
.lsps2_funding_tx_broadcast_safe(user_channel_id, counterparty_node_id);
657658
});
658659
},
659660
LdkEvent::PaymentClaimable {
@@ -1162,7 +1163,10 @@ where
11621163
LdkEvent::ProbeFailed { .. } => {},
11631164
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
11641165
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
1165-
liquidity_source.handle_htlc_handling_failed(failure_type).await;
1166+
liquidity_source
1167+
.lsps2_service()
1168+
.handle_htlc_handling_failed(failure_type)
1169+
.await;
11661170
}
11671171
},
11681172
LdkEvent::SpendableOutputs { outputs, channel_id, counterparty_node_id } => {
@@ -1263,14 +1267,15 @@ where
12631267
.try_into()
12641268
.expect("slice is exactly 16 bytes"),
12651269
);
1266-
let allow_0conf = self.config.trusted_peers_0conf.contains(&counterparty_node_id);
1267-
let mut channel_override_config = None;
1268-
if let Some((lsp_node_id, _)) = self
1270+
let is_lsp_node = self
12691271
.liquidity_source
12701272
.as_ref()
1271-
.and_then(|ls| ls.as_ref().get_lsps2_lsp_details())
1272-
{
1273-
if lsp_node_id == counterparty_node_id {
1273+
.map_or(false, |ls| ls.as_ref().is_lsps_node(&counterparty_node_id));
1274+
let allow_0conf =
1275+
self.config.trusted_peers_0conf.contains(&counterparty_node_id) || is_lsp_node;
1276+
let mut channel_override_config = None;
1277+
if let Some(ls) = self.liquidity_source.as_ref() {
1278+
if ls.as_ref().is_lsps_node(&counterparty_node_id) {
12741279
// When we're an LSPS2 client, allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
12751280
// check that they don't take too much before claiming.
12761281
//
@@ -1416,6 +1421,7 @@ where
14161421
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
14171422
let skimmed_fee_msat = skimmed_fee_msat.unwrap_or(0);
14181423
liquidity_source
1424+
.lsps2_service()
14191425
.handle_payment_forwarded(Some(next_htlc.channel_id), skimmed_fee_msat)
14201426
.await;
14211427
}
@@ -1529,6 +1535,7 @@ where
15291535

15301536
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
15311537
liquidity_source
1538+
.lsps2_service()
15321539
.handle_channel_ready(user_channel_id, &channel_id, &counterparty_node_id)
15331540
.await;
15341541
}
@@ -1600,6 +1607,7 @@ where
16001607
} => {
16011608
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
16021609
liquidity_source
1610+
.lsps2_service()
16031611
.handle_htlc_intercepted(
16041612
requested_next_hop_scid,
16051613
intercept_id,

src/lib.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ use lightning_background_processor::process_events_async;
160160
pub use lightning_invoice;
161161
pub use lightning_liquidity;
162162
pub use lightning_types;
163-
use liquidity::{LSPS1Liquidity, LiquiditySource};
163+
use liquidity::LiquiditySource;
164164
use lnurl_auth::LnurlAuth;
165165
use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger};
166166
use payment::asynchronous::om_mailbox::OnionMessageMailbox;
@@ -180,6 +180,7 @@ use types::{
180180
pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, SyncAndAsyncKVStore, UserChannelId};
181181
pub use vss_client;
182182

183+
use crate::liquidity::Liquidity;
183184
use crate::scoring::setup_background_pathfinding_scores_sync;
184185
use crate::wallet::FundingAmount;
185186

@@ -679,7 +680,51 @@ impl Node {
679680
let mut stop_liquidity_handler = self.stop_sender.subscribe();
680681
let liquidity_handler = Arc::clone(&liquidity_source);
681682
let liquidity_logger = Arc::clone(&self.logger);
683+
let discovery_ls = Arc::clone(&liquidity_source);
684+
let discovery_cm = Arc::clone(&self.connection_manager);
682685
self.runtime.spawn_background_task(async move {
686+
let discovery_logger = Arc::clone(&liquidity_logger);
687+
tokio::spawn(async move {
688+
let mut set = tokio::task::JoinSet::new();
689+
for (node_id, address) in discovery_ls.get_all_lsp_details() {
690+
let cm = Arc::clone(&discovery_cm);
691+
let logger = Arc::clone(&discovery_logger);
692+
let ls = Arc::clone(&discovery_ls);
693+
set.spawn(async move {
694+
if let Err(e) =
695+
cm.connect_peer_if_necessary(node_id, address.clone()).await
696+
{
697+
log_error!(
698+
logger,
699+
"Failed to connect to LSP {} for protocol discovery: {}",
700+
node_id,
701+
e
702+
);
703+
return;
704+
}
705+
match ls.discover_lsp_protocols(&node_id).await {
706+
Ok(protocols) => {
707+
log_info!(
708+
logger,
709+
"Discovered protocols for LSP {}: {:?}",
710+
node_id,
711+
protocols
712+
);
713+
},
714+
Err(e) => {
715+
log_error!(
716+
logger,
717+
"Failed to discover protocols for LSP {}: {:?}",
718+
node_id,
719+
e
720+
);
721+
},
722+
}
723+
});
724+
}
725+
while set.join_next().await.is_some() {}
726+
});
727+
683728
loop {
684729
tokio::select! {
685730
_ = stop_liquidity_handler.changed() => {
@@ -1060,12 +1105,10 @@ impl Node {
10601105
})
10611106
}
10621107

1063-
/// Returns a liquidity handler allowing to request channels via the [bLIP-51 / LSPS1] protocol.
1064-
///
1065-
/// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
1108+
/// Returns a liquidity handler allowing to manage LSP connections and request channels.
10661109
#[cfg(not(feature = "uniffi"))]
1067-
pub fn lsps1_liquidity(&self) -> LSPS1Liquidity {
1068-
LSPS1Liquidity::new(
1110+
pub fn liquidity(&self) -> Liquidity {
1111+
Liquidity::new(
10691112
Arc::clone(&self.runtime),
10701113
Arc::clone(&self.wallet),
10711114
Arc::clone(&self.connection_manager),
@@ -1074,12 +1117,10 @@ impl Node {
10741117
)
10751118
}
10761119

1077-
/// Returns a liquidity handler allowing to request channels via the [bLIP-51 / LSPS1] protocol.
1078-
///
1079-
/// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
1120+
/// Returns a liquidity handler allowing to manage LSP connections and request channels.
10801121
#[cfg(feature = "uniffi")]
1081-
pub fn lsps1_liquidity(&self) -> Arc<LSPS1Liquidity> {
1082-
Arc::new(LSPS1Liquidity::new(
1122+
pub fn liquidity(&self) -> Arc<Liquidity> {
1123+
Arc::new(Liquidity::new(
10831124
Arc::clone(&self.runtime),
10841125
Arc::clone(&self.wallet),
10851126
Arc::clone(&self.connection_manager),

0 commit comments

Comments
 (0)