Skip to content

Commit fb98674

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 7bb1191 commit fb98674

13 files changed

Lines changed: 1634 additions & 1133 deletions

File tree

bindings/ldk_node.udl

Lines changed: 4 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_liquidity_source(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 {
@@ -229,6 +228,7 @@ enum NodeError {
229228
"LnurlAuthFailed",
230229
"LnurlAuthTimeout",
231230
"InvalidLnurl",
231+
"DuplicateLspNode",
232232
};
233233

234234
typedef dictionary NodeStatus;

src/builder.rs

Lines changed: 26 additions & 77 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_liquidity_source(
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

@@ -483,12 +458,12 @@ impl NodeBuilder {
483458
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
484459
///
485460
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
486-
pub fn set_liquidity_provider_lsps2(
487-
&mut self, service_config: LSPS2ServiceConfig,
461+
pub fn enable_liquidity_provider(
462+
&mut self, lsps2_service_config: LSPS2ServiceConfig,
488463
) -> &mut Self {
489464
let liquidity_source_config =
490465
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
491-
liquidity_source_config.lsps2_service = Some(service_config);
466+
liquidity_source_config.lsps2_service = Some(lsps2_service_config);
492467
self
493468
}
494469

@@ -956,32 +931,18 @@ 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(
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_liquidity_source(
968943
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
969944
) {
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);
945+
self.inner.write().expect("lock").add_liquidity_source(node_id, address, token);
985946
}
986947

987948
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
@@ -990,8 +951,8 @@ impl ArcedNodeBuilder {
990951
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
991952
///
992953
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
993-
pub fn set_liquidity_provider_lsps2(&self, service_config: LSPS2ServiceConfig) {
994-
self.inner.write().expect("lock").set_liquidity_provider_lsps2(service_config);
954+
pub fn enable_liquidity_provider(&self, lsps2_service_config: LSPS2ServiceConfig) {
955+
self.inner.write().expect("lock").enable_liquidity_provider(lsps2_service_config);
995956
}
996957

997958
/// Sets the used storage directory path.
@@ -1806,21 +1767,7 @@ fn build_with_store_internal(
18061767
Arc::clone(&logger),
18071768
);
18081769

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-
});
1770+
liquidity_source_builder.set_lsp_nodes(lsc.lsp_nodes.clone());
18241771

18251772
let promise_secret = {
18261773
let lsps_xpriv = derive_xprv(
@@ -1889,7 +1836,9 @@ fn build_with_store_internal(
18891836
}
18901837
}));
18911838

1892-
liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::downgrade(&peer_manager)));
1839+
liquidity_source
1840+
.as_ref()
1841+
.map(|l| l.lsps2_service().set_peer_manager(Arc::downgrade(&peer_manager)));
18931842

18941843
let connection_manager = Arc::new(ConnectionManager::new(
18951844
Arc::clone(&peer_manager),

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ pub enum Error {
137137
LnurlAuthTimeout,
138138
/// The provided lnurl is invalid.
139139
InvalidLnurl,
140+
/// An LSP with the given node id has already been added.
141+
DuplicateLspNode,
140142
}
141143

142144
impl fmt::Display for Error {
@@ -222,6 +224,9 @@ impl fmt::Display for Error {
222224
Self::LnurlAuthFailed => write!(f, "LNURL-auth authentication failed."),
223225
Self::LnurlAuthTimeout => write!(f, "LNURL-auth authentication timed out."),
224226
Self::InvalidLnurl => write!(f, "The provided lnurl is invalid."),
227+
Self::DuplicateLspNode => {
228+
write!(f, "An LSP with the given node id has already been added.")
229+
},
225230
}
226231
}
227232
}

src/event.rs

Lines changed: 33 additions & 27 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,31 +1267,30 @@ 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 {
1274-
// When we're an LSPS2 client, allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
1275-
// check that they don't take too much before claiming.
1276-
//
1277-
// We also set maximum allowed inbound HTLC value in flight
1278-
// to 100%. We should eventually be able to set this on a per-channel basis, but for
1279-
// now we just bump the default for all channels.
1280-
channel_override_config = Some(ChannelConfigOverrides {
1281-
handshake_overrides: Some(ChannelHandshakeConfigUpdate {
1282-
max_inbound_htlc_value_in_flight_percent_of_channel: Some(100),
1283-
..Default::default()
1284-
}),
1285-
update_overrides: Some(ChannelConfigUpdate {
1286-
accept_underpaying_htlcs: Some(true),
1287-
..Default::default()
1288-
}),
1289-
});
1290-
}
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 is_lsp_node {
1278+
// When we're an LSPS2 client, allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
1279+
// check that they don't take too much before claiming.
1280+
//
1281+
// We also set maximum allowed inbound HTLC value in flight
1282+
// to 100%. We should eventually be able to set this on a per-channel basis, but for
1283+
// now we just bump the default for all channels.
1284+
channel_override_config = Some(ChannelConfigOverrides {
1285+
handshake_overrides: Some(ChannelHandshakeConfigUpdate {
1286+
max_inbound_htlc_value_in_flight_percent_of_channel: Some(100),
1287+
..Default::default()
1288+
}),
1289+
update_overrides: Some(ChannelConfigUpdate {
1290+
accept_underpaying_htlcs: Some(true),
1291+
..Default::default()
1292+
}),
1293+
});
12911294
}
12921295
let res = if allow_0conf {
12931296
self.channel_manager.accept_inbound_channel_from_trusted_peer(
@@ -1416,6 +1419,7 @@ where
14161419
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
14171420
let skimmed_fee_msat = skimmed_fee_msat.unwrap_or(0);
14181421
liquidity_source
1422+
.lsps2_service()
14191423
.handle_payment_forwarded(Some(next_htlc.channel_id), skimmed_fee_msat)
14201424
.await;
14211425
}
@@ -1529,6 +1533,7 @@ where
15291533

15301534
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
15311535
liquidity_source
1536+
.lsps2_service()
15321537
.handle_channel_ready(user_channel_id, &channel_id, &counterparty_node_id)
15331538
.await;
15341539
}
@@ -1600,6 +1605,7 @@ where
16001605
} => {
16011606
if let Some(liquidity_source) = self.liquidity_source.as_ref() {
16021607
liquidity_source
1608+
.lsps2_service()
16031609
.handle_htlc_intercepted(
16041610
requested_next_hop_scid,
16051611
intercept_id,

0 commit comments

Comments
 (0)