Skip to content

Commit 08efb3a

Browse files
authored
Merge pull request #956 from Camillarhi/multi-lsp-support-follow-up
Follow-up to #792 (multi-LSP support)
2 parents 0cea341 + 48104f7 commit 08efb3a

4 files changed

Lines changed: 103 additions & 55 deletions

File tree

src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ pub(crate) const HRN_RESOLUTION_TIMEOUT_SECS: u64 = 5;
129129
// The timeout after which we abort an LNURL-auth operation.
130130
pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15;
131131

132+
// The initial delay before retrying a failed liquidity protocol discovery operation.
133+
pub(crate) const LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY: Duration = Duration::from_secs(5);
134+
135+
// The maximum delay the discovery-retry backoff ramps up to, and the interval it keeps retrying at
136+
// thereafter until every configured LSP has been discovered.
137+
pub(crate) const LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY: Duration = Duration::from_secs(60 * 60);
138+
132139
#[derive(Debug, Clone)]
133140
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
134141
/// Represents the configuration of an [`Node`] instance.

src/event.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,14 +1375,15 @@ where
13751375
);
13761376
let mut allow_0conf =
13771377
self.config.trusted_peers_0conf.contains(&counterparty_node_id);
1378-
let mut channel_override_config = None;
13791378

13801379
// If the peer is a configured LSP node, additionally honor its trust_peer_0conf flag.
1381-
if let Some(lsp) =
1382-
self.liquidity_source.get_lsp_config(&counterparty_node_id, 2).await
1383-
{
1384-
allow_0conf = allow_0conf || lsp.trust_peer_0conf;
1380+
if self.liquidity_source.get_lsp_trust_0conf(&counterparty_node_id) == Some(true) {
1381+
allow_0conf = true;
1382+
}
1383+
1384+
let mut channel_override_config = None;
13851385

1386+
if self.liquidity_source.get_lsp_config(&counterparty_node_id, 2).await.is_some() {
13861387
// When we're an LSPS2 client, allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
13871388
// check that they don't take too much before claiming.
13881389
channel_override_config = Some(ChannelConfigOverrides {

src/lib.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ pub use types::{
191191
};
192192
pub use vss_client;
193193

194+
use crate::config::{LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY, LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY};
194195
use crate::ffi::maybe_wrap;
195196
use crate::liquidity::Liquidity;
196197
use crate::scoring::setup_background_pathfinding_scores_sync;
@@ -755,33 +756,7 @@ impl Node {
755756
let logger = Arc::clone(&discovery_logger);
756757
let ls = Arc::clone(&liquidity_handler);
757758
discovery_set.spawn(async move {
758-
if let Err(e) = cm.connect_peer_if_necessary(node_id, address.clone()).await {
759-
log_error!(
760-
logger,
761-
"Failed to connect to LSP {} for protocol discovery: {}",
762-
node_id,
763-
e
764-
);
765-
return;
766-
}
767-
match ls.discover_lsp_protocols(&node_id).await {
768-
Ok(protocols) => {
769-
log_info!(
770-
logger,
771-
"Discovered protocols for LSP {}: {:?}",
772-
node_id,
773-
protocols
774-
);
775-
},
776-
Err(e) => {
777-
log_error!(
778-
logger,
779-
"Failed to discover protocols for LSP {}: {:?}",
780-
node_id,
781-
e
782-
);
783-
},
784-
}
759+
connect_and_discover_lsp(&cm, &ls, &logger, node_id, address).await;
785760
});
786761
}
787762

@@ -811,6 +786,39 @@ impl Node {
811786
}
812787
});
813788

789+
// Retry protocol discovery for any LSPs that failed the startup batch, backing off up to a
790+
// cap and then retrying at that cap until every configured LSP has been discovered.
791+
let mut stop_retry = self.stop_sender.subscribe();
792+
let retry_ls = Arc::clone(&self.liquidity_source);
793+
let retry_logger = Arc::clone(&self.logger);
794+
let retry_cm = Arc::clone(&self.connection_manager);
795+
self.runtime.spawn_cancellable_background_task(async move {
796+
let mut backoff = LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY;
797+
loop {
798+
tokio::select! {
799+
_ = stop_retry.changed() => return,
800+
_ = tokio::time::sleep(backoff) => {},
801+
}
802+
803+
let undiscovered_lsps = retry_ls.get_undiscovered_lsps();
804+
if undiscovered_lsps.is_empty() {
805+
break;
806+
}
807+
808+
let mut discovery_set = tokio::task::JoinSet::new();
809+
for (node_id, address) in undiscovered_lsps {
810+
let cm = Arc::clone(&retry_cm);
811+
let ls = Arc::clone(&retry_ls);
812+
let logger = Arc::clone(&retry_logger);
813+
discovery_set.spawn(async move {
814+
connect_and_discover_lsp(&cm, &ls, &logger, node_id, address).await;
815+
});
816+
}
817+
discovery_set.join_all().await;
818+
backoff = (backoff * 2).min(LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY);
819+
}
820+
});
821+
814822
log_info!(self.logger, "Startup complete.");
815823
*is_running_lock = true;
816824
Ok(())
@@ -2485,6 +2493,23 @@ pub(crate) fn new_channel_anchor_reserve_sats(
24852493
}
24862494
}
24872495

2496+
async fn connect_and_discover_lsp(
2497+
connection_manager: &ConnectionManager<Arc<Logger>>,
2498+
liquidity_source: &LiquiditySource<Arc<Logger>>, logger: &Logger, node_id: PublicKey,
2499+
address: SocketAddress,
2500+
) {
2501+
if let Err(e) = connection_manager.connect_peer_if_necessary(node_id, address).await {
2502+
log_debug!(logger, "Failed to connect to LSP {} for protocol discovery: {}", node_id, e);
2503+
return;
2504+
}
2505+
match liquidity_source.discover_lsp_protocols(&node_id).await {
2506+
Ok(protocols) => {
2507+
log_info!(logger, "Discovered protocols for LSP {}: {:?}", node_id, protocols)
2508+
},
2509+
Err(e) => log_debug!(logger, "Protocol discovery failed for LSP {}: {:?}", node_id, e),
2510+
}
2511+
}
2512+
24882513
#[cfg(test)]
24892514
mod tests {
24902515
use lightning::util::ser::{Readable, Writeable};

src/liquidity/mod.rs

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ where
345345
lsps1_client: Arc<LSPS1Client<L>>,
346346
lsps2_client: Arc<LSPS2Client<L>>,
347347
lsps2_service: Arc<LSPS2ServiceLiquiditySource<L>>,
348-
pending_lsps0_discovery: Mutex<HashMap<PublicKey, oneshot::Sender<Vec<u16>>>>,
348+
pending_lsps0_discovery: Mutex<HashMap<PublicKey, Vec<oneshot::Sender<Vec<u16>>>>>,
349349
discovery_done_tx: tokio::sync::watch::Sender<bool>,
350350
discovery_done_rx: tokio::sync::watch::Receiver<bool>,
351351
liquidity_manager: Arc<LiquidityManager>,
@@ -383,21 +383,14 @@ where
383383
protocols,
384384
}) => {
385385
if self.is_lsps_node(&counterparty_node_id) {
386-
if let Some(sender) = self
386+
if let Some(senders) = self
387387
.pending_lsps0_discovery
388388
.lock()
389389
.expect("lock")
390390
.remove(&counterparty_node_id)
391391
{
392-
match sender.send(protocols) {
393-
Ok(()) => (),
394-
Err(_) => {
395-
log_error!(
396-
self.logger,
397-
"Failed to handle response for request {:?} from liquidity service",
398-
counterparty_node_id
399-
);
400-
},
392+
for sender in senders {
393+
let _ = sender.send(protocols.clone());
401394
}
402395
} else {
403396
log_error!(
@@ -438,24 +431,23 @@ where
438431
let lsps0_handler = self.liquidity_manager.lsps0_client_handler();
439432

440433
let (sender, receiver) = oneshot::channel();
441-
{
434+
let issued_request = {
442435
let mut pending_discovery = self.pending_lsps0_discovery.lock().expect("lock");
443436
match pending_discovery.entry(*node_id) {
444-
Entry::Occupied(_) => {
445-
log_error!(
446-
self.logger,
447-
"LSPS0 protocol discovery already in flight for {}",
448-
node_id
449-
);
450-
return Err(Error::LiquidityRequestFailed);
437+
Entry::Occupied(mut e) => {
438+
e.get_mut().push(sender);
439+
false
451440
},
452441
Entry::Vacant(v) => {
453-
v.insert(sender);
442+
v.insert(vec![sender]);
454443
lsps0_handler.list_protocols(node_id);
444+
true
455445
},
456446
}
457-
}
447+
};
458448

449+
// Only the request that issued the discovery may remove the entry; a follower removing it
450+
// would drop the in-flight request and all other waiters.
459451
let protocols =
460452
tokio::time::timeout(Duration::from_secs(LIQUIDITY_REQUEST_TIMEOUT_SECS), receiver)
461453
.await
@@ -466,7 +458,9 @@ where
466458
node_id,
467459
e
468460
);
469-
self.pending_lsps0_discovery.lock().expect("lock").remove(node_id);
461+
if issued_request {
462+
self.pending_lsps0_discovery.lock().expect("lock").remove(node_id);
463+
}
470464
Error::LiquidityRequestFailed
471465
})?
472466
.map_err(|e| {
@@ -476,7 +470,9 @@ where
476470
node_id,
477471
e
478472
);
479-
self.pending_lsps0_discovery.lock().expect("lock").remove(node_id);
473+
if issued_request {
474+
self.pending_lsps0_discovery.lock().expect("lock").remove(node_id);
475+
}
480476
Error::LiquidityRequestFailed
481477
})?;
482478

@@ -517,6 +513,25 @@ where
517513
select_lsps_for_protocol(&self.lsp_nodes, protocol, Some(node_id))
518514
}
519515

516+
pub(crate) fn get_undiscovered_lsps(&self) -> Vec<(PublicKey, SocketAddress)> {
517+
self.lsp_nodes
518+
.read()
519+
.expect("lock")
520+
.iter()
521+
.filter(|n| n.supported_protocols.is_none())
522+
.map(|n| (n.node_id, n.address.clone()))
523+
.collect()
524+
}
525+
526+
pub(crate) fn get_lsp_trust_0conf(&self, node_id: &PublicKey) -> Option<bool> {
527+
self.lsp_nodes
528+
.read()
529+
.expect("lock")
530+
.iter()
531+
.find(|n| &n.node_id == node_id)
532+
.map(|n| n.trust_peer_0conf)
533+
}
534+
520535
/// Flips the `discovery_done` watch to `true`.
521536
///
522537
/// Called once after the *initial* batch of LSPs configured at build time has been

0 commit comments

Comments
 (0)