Skip to content

Commit 3cda7f7

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 9e0a812 commit 3cda7f7

14 files changed

Lines changed: 2130 additions & 1599 deletions

File tree

bindings/ldk_node.udl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ interface Builder {
4545
void set_pathfinding_scores_source(string url);
4646
void set_liquidity_source_lsps1(PublicKey node_id, SocketAddress address, string? token);
4747
void set_liquidity_source_lsps2(PublicKey node_id, SocketAddress address, string? token);
48+
void add_lsp(PublicKey node_id, SocketAddress address, string? token);
4849
void set_storage_dir_path(string storage_dir_path);
4950
void set_filesystem_logger(string? log_file_path, LogLevel? max_log_level);
5051
void set_log_facade_logger();
@@ -138,6 +139,8 @@ interface Node {
138139
boolean verify_signature([ByRef]sequence<u8> msg, [ByRef]string sig, [ByRef]PublicKey pkey);
139140
[Throws=NodeError]
140141
bytes export_pathfinding_scores();
142+
[Throws=NodeError]
143+
void add_lsp(PublicKey node_id, SocketAddress address, string? token);
141144
};
142145

143146
typedef interface Bolt11Payment;

src/builder.rs

Lines changed: 44 additions & 36 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
}
@@ -440,17 +436,12 @@ impl NodeBuilder {
440436
/// The given `token` will be used by the LSP to authenticate the user.
441437
///
442438
/// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
439+
#[deprecated(note = "Use `add_lsp` instead")]
440+
#[allow(dead_code)]
443441
pub fn set_liquidity_source_lsps1(
444442
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
445443
) -> &mut Self {
446-
// Mark the LSP as trusted for 0conf
447-
self.config.trusted_peers_0conf.push(node_id.clone());
448-
449-
let liquidity_source_config =
450-
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
451-
let lsps1_client_config = LSPS1ClientConfig { node_id, address, token };
452-
liquidity_source_config.lsps1_client = Some(lsps1_client_config);
453-
self
444+
self.add_lsp(node_id, address, token)
454445
}
455446

456447
/// Configures the [`Node`] instance to source just-in-time inbound liquidity from the given
@@ -461,16 +452,32 @@ impl NodeBuilder {
461452
/// The given `token` will be used by the LSP to authenticate the user.
462453
///
463454
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
455+
#[deprecated(note = "Use `add_lsp` instead")]
456+
#[allow(dead_code)]
464457
pub fn set_liquidity_source_lsps2(
465458
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
459+
) -> &mut Self {
460+
self.add_lsp(node_id, address, token)
461+
}
462+
463+
/// Configures the [`Node`] instance to source inbound liquidity from the given LSP, without specifying
464+
/// the exact protocol used (e.g., LSPS1 or LSPS2).
465+
///
466+
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
467+
///
468+
/// The given `token` will be used by the LSP to authenticate the user.
469+
/// This method is useful when the user wants to connect to an LSP but does not want to be concerned with
470+
/// the specific protocol used for liquidity provision. The node will automatically detect and use the
471+
/// appropriate protocol supported by the LSP.
472+
pub fn add_lsp(
473+
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
466474
) -> &mut Self {
467475
// Mark the LSP as trusted for 0conf
468-
self.config.trusted_peers_0conf.push(node_id.clone());
476+
self.config.trusted_peers_0conf.write().unwrap().push(node_id);
469477

470478
let liquidity_source_config =
471479
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
472-
let lsps2_client_config = LSPS2ClientConfig { node_id, address, token };
473-
liquidity_source_config.lsps2_client = Some(lsps2_client_config);
480+
liquidity_source_config.lsp_nodes.push(LspConfig { node_id, address, token });
474481
self
475482
}
476483

@@ -964,7 +971,7 @@ impl ArcedNodeBuilder {
964971
pub fn set_liquidity_source_lsps1(
965972
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
966973
) {
967-
self.inner.write().unwrap().set_liquidity_source_lsps1(node_id, address, token);
974+
self.inner.write().unwrap().add_lsp(node_id, address, token);
968975
}
969976

970977
/// Configures the [`Node`] instance to source just-in-time inbound liquidity from the given
@@ -978,7 +985,20 @@ impl ArcedNodeBuilder {
978985
pub fn set_liquidity_source_lsps2(
979986
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
980987
) {
981-
self.inner.write().unwrap().set_liquidity_source_lsps2(node_id, address, token);
988+
self.inner.write().unwrap().add_lsp(node_id, address, token);
989+
}
990+
991+
/// Configures the [`Node`] instance to source inbound liquidity from the given LSP, without specifying
992+
/// the exact protocol used (e.g., LSPS1 or LSPS2).
993+
///
994+
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
995+
///
996+
/// The given `token` will be used by the LSP to authenticate the user.
997+
/// This method is useful when the user wants to connect to an LSP but does not want to be concerned with
998+
/// the specific protocol used for liquidity provision. The node will automatically detect and use the
999+
/// appropriate protocol supported by the LSP.
1000+
pub fn add_lsp(&self, node_id: PublicKey, address: SocketAddress, token: Option<String>) {
1001+
self.inner.write().unwrap().add_lsp(node_id, address, token);
9821002
}
9831003

9841004
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
@@ -1803,21 +1823,7 @@ fn build_with_store_internal(
18031823
Arc::clone(&logger),
18041824
);
18051825

1806-
lsc.lsps1_client.as_ref().map(|config| {
1807-
liquidity_source_builder.lsps1_client(
1808-
config.node_id,
1809-
config.address.clone(),
1810-
config.token.clone(),
1811-
)
1812-
});
1813-
1814-
lsc.lsps2_client.as_ref().map(|config| {
1815-
liquidity_source_builder.lsps2_client(
1816-
config.node_id,
1817-
config.address.clone(),
1818-
config.token.clone(),
1819-
)
1820-
});
1826+
liquidity_source_builder.set_lsp_nodes(lsc.lsp_nodes.clone());
18211827

18221828
let promise_secret = {
18231829
let lsps_xpriv = derive_xprv(
@@ -1886,7 +1892,9 @@ fn build_with_store_internal(
18861892
}
18871893
}));
18881894

1889-
liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::downgrade(&peer_manager)));
1895+
liquidity_source
1896+
.as_ref()
1897+
.map(|l| l.lsps2_service().set_peer_manager(Arc::downgrade(&peer_manager)));
18901898

18911899
let connection_manager = Arc::new(ConnectionManager::new(
18921900
Arc::clone(&peer_manager),

src/config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! Objects for configuring the node.
99
1010
use std::fmt;
11+
use std::sync::{Arc, RwLock};
1112
use std::time::Duration;
1213

1314
use bitcoin::secp256k1::PublicKey;
@@ -161,7 +162,7 @@ pub struct Config {
161162
/// **Note:** Allowing payments via zero-confirmation channels is potentially insecure if the
162163
/// funding transaction ends up never being confirmed on-chain. Zero-confirmation channels
163164
/// should therefore only be accepted from trusted peers.
164-
pub trusted_peers_0conf: Vec<PublicKey>,
165+
pub trusted_peers_0conf: Arc<RwLock<Vec<PublicKey>>>,
165166
/// The liquidity factor by which we filter the outgoing channels used for sending probes.
166167
///
167168
/// Channels with available liquidity less than the required amount times this value won't be
@@ -208,7 +209,7 @@ impl Default for Config {
208209
network: DEFAULT_NETWORK,
209210
listening_addresses: None,
210211
announcement_addresses: None,
211-
trusted_peers_0conf: Vec::new(),
212+
trusted_peers_0conf: Arc::new(RwLock::new(Vec::new())),
212213
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
213214
anchor_channels_config: Some(AnchorChannelsConfig::default()),
214215
tor_config: None,

src/event.rs

Lines changed: 15 additions & 11 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 {
@@ -1139,7 +1140,10 @@ where
11391140
LdkEvent::ProbeFailed { .. } => {},
11401141
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
11411142
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
1142-
liquidity_source.handle_htlc_handling_failed(failure_type).await;
1143+
liquidity_source
1144+
.lsps2_service()
1145+
.handle_htlc_handling_failed(failure_type)
1146+
.await;
11431147
}
11441148
},
11451149
LdkEvent::SpendableOutputs { outputs, channel_id, counterparty_node_id } => {
@@ -1238,14 +1242,11 @@ where
12381242
let user_channel_id: u128 = u128::from_ne_bytes(
12391243
self.keys_manager.get_secure_random_bytes()[..16].try_into().unwrap(),
12401244
);
1241-
let allow_0conf = self.config.trusted_peers_0conf.contains(&counterparty_node_id);
1245+
let allow_0conf =
1246+
self.config.trusted_peers_0conf.read().unwrap().contains(&counterparty_node_id);
12421247
let mut channel_override_config = None;
1243-
if let Some((lsp_node_id, _)) = self
1244-
.liquidity_source
1245-
.as_ref()
1246-
.and_then(|ls| ls.as_ref().get_lsps2_lsp_details())
1247-
{
1248-
if lsp_node_id == counterparty_node_id {
1248+
if let Some(ls) = self.liquidity_source.as_ref() {
1249+
if ls.as_ref().is_lsps_node(&counterparty_node_id) {
12491250
// When we're an LSPS2 client, allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
12501251
// check that they don't take too much before claiming.
12511252
//
@@ -1379,6 +1380,7 @@ where
13791380
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
13801381
let skimmed_fee_msat = skimmed_fee_msat.unwrap_or(0);
13811382
liquidity_source
1383+
.lsps2_service()
13821384
.handle_payment_forwarded(next_channel_id, skimmed_fee_msat)
13831385
.await;
13841386
}
@@ -1488,6 +1490,7 @@ where
14881490

14891491
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
14901492
liquidity_source
1493+
.lsps2_service()
14911494
.handle_channel_ready(user_channel_id, &channel_id, &counterparty_node_id)
14921495
.await;
14931496
}
@@ -1559,6 +1562,7 @@ where
15591562
} => {
15601563
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
15611564
liquidity_source
1565+
.lsps2_service()
15621566
.handle_htlc_intercepted(
15631567
requested_next_hop_scid,
15641568
intercept_id,

src/lib.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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::LspConfig;
183184
use crate::scoring::setup_background_pathfinding_scores_sync;
184185
use crate::wallet::FundingAmount;
185186

@@ -674,6 +675,29 @@ impl Node {
674675
});
675676
}
676677

678+
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
679+
let discovery_ls = Arc::clone(&liquidity_source);
680+
let discovery_cm = Arc::clone(&self.connection_manager);
681+
let discovery_logger = Arc::clone(&self.logger);
682+
self.runtime.spawn_background_task(async move {
683+
for (node_id, address) in discovery_ls.get_all_lsp_details() {
684+
if let Err(e) =
685+
discovery_cm.connect_peer_if_necessary(node_id, address.clone()).await
686+
{
687+
log_error!(
688+
discovery_logger,
689+
"Failed to connect to LSP {} for protocol discovery: {}",
690+
node_id,
691+
e
692+
);
693+
continue;
694+
}
695+
}
696+
697+
discovery_ls.discover_all_lsp_protocols().await;
698+
});
699+
}
700+
677701
log_info!(self.logger, "Startup complete.");
678702
*is_running_lock = true;
679703
Ok(())
@@ -1048,7 +1072,7 @@ impl Node {
10481072
Arc::clone(&self.runtime),
10491073
Arc::clone(&self.wallet),
10501074
Arc::clone(&self.connection_manager),
1051-
self.liquidity_source.clone(),
1075+
self.liquidity_source.as_ref().map(|ls| ls.lsps1_client()),
10521076
Arc::clone(&self.logger),
10531077
)
10541078
}
@@ -1062,7 +1086,7 @@ impl Node {
10621086
Arc::clone(&self.runtime),
10631087
Arc::clone(&self.wallet),
10641088
Arc::clone(&self.connection_manager),
1065-
self.liquidity_source.clone(),
1089+
self.liquidity_source.as_ref().map(|ls| ls.lsps1_client()),
10661090
Arc::clone(&self.logger),
10671091
))
10681092
}
@@ -1949,6 +1973,41 @@ impl Node {
19491973
Error::PersistenceFailed
19501974
})
19511975
}
1976+
1977+
/// Configures the [`Node`] instance to source inbound liquidity from the given LSP at runtime,
1978+
/// without specifying the exact protocol used (e.g., LSPS1 or LSPS2).
1979+
///
1980+
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
1981+
///
1982+
/// The given `token` will be used by the LSP to authenticate the user.
1983+
/// This method is useful when the user wants to connect to an LSP but does not want to be concerned with
1984+
/// the specific protocol used for liquidity provision. The node will automatically detect and use the
1985+
/// appropriate protocol supported by the LSP.
1986+
pub fn add_lsp(
1987+
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
1988+
) -> Result<(), Error> {
1989+
// Mark the LSP as trusted for 0conf
1990+
self.config.trusted_peers_0conf.write().unwrap().push(node_id);
1991+
let liquidity_source =
1992+
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
1993+
let lsp_config = LspConfig { node_id, address, token };
1994+
liquidity_source.add_lsp_node(lsp_config.clone())?;
1995+
1996+
let con_node_id = lsp_config.node_id;
1997+
let con_addr = lsp_config.address.clone();
1998+
let con_cm = Arc::clone(&self.connection_manager);
1999+
2000+
self.runtime.block_on(async move {
2001+
con_cm.connect_peer_if_necessary(con_node_id, con_addr).await
2002+
})?;
2003+
2004+
log_info!(self.logger, "Connected to LSP {}@{}. ", lsp_config.node_id, lsp_config.address);
2005+
2006+
let node_id = lsp_config.node_id;
2007+
self.runtime
2008+
.block_on(async move { liquidity_source.discover_lsp_protocols(&node_id).await })?;
2009+
Ok(())
2010+
}
19522011
}
19532012

19542013
impl Drop for Node {

0 commit comments

Comments
 (0)