Skip to content

Commit c4b07e1

Browse files
committed
Retry LSP protocol discovery after transient failures
- Add a background task that retries discovery for any LSP whose protocols are still undiscovered, using exponential backoff (5s up to 1h). - Expose `Liquidity::retry_discovery(node_id)` so users can trigger re-discovery on demand, both to recover a stuck LSP and to pick up newly supported protocols after an LSP upgrade.
1 parent f2e44fd commit c4b07e1

3 files changed

Lines changed: 120 additions & 0 deletions

File tree

src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ pub(crate) const HRN_RESOLUTION_TIMEOUT_SECS: u64 = 5;
111111
// The timeout after which we abort an LNURL-auth operation.
112112
pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15;
113113

114+
// The initial delay before retrying a failed liquidity protocol discovery operation.
115+
pub(crate) const LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY_SECS: u64 = 5;
116+
117+
// The maximum delay before retrying a failed liquidity protocol discovery operation.
118+
pub(crate) const LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY_SECS: u64 = 3600;
119+
114120
#[derive(Debug, Clone)]
115121
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
116122
/// Represents the configuration of an [`Node`] instance.

src/lib.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ pub use types::{
184184
};
185185
pub use vss_client;
186186

187+
use crate::config::{
188+
LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY_SECS, LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY_SECS,
189+
};
187190
use crate::ffi::maybe_wrap;
188191
use crate::liquidity::Liquidity;
189192
use crate::scoring::setup_background_pathfinding_scores_sync;
@@ -748,6 +751,62 @@ impl Node {
748751
}
749752
});
750753

754+
// Background retry for LSPs that failed the initial protocol-discovery batch, so a
755+
// transient startup failure doesn't leave a configured LSP permanently unusable.
756+
let mut stop_retry = self.stop_sender.subscribe();
757+
let retry_ls = Arc::clone(&self.liquidity_source);
758+
let retry_logger = Arc::clone(&self.logger);
759+
let retry_cm = Arc::clone(&self.connection_manager);
760+
self.runtime.spawn_background_task(async move {
761+
let mut backoff = LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY_SECS;
762+
loop {
763+
let undiscovered_lsps = retry_ls.get_undiscovered_lsps();
764+
if undiscovered_lsps.is_empty() {
765+
tokio::select! {
766+
_ = stop_retry.changed() => return,
767+
_ = tokio::time::sleep(Duration::from_secs(
768+
LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY_SECS,
769+
)) => continue,
770+
}
771+
}
772+
773+
tokio::select! {
774+
_ = stop_retry.changed() => return,
775+
_ = tokio::time::sleep(Duration::from_secs(backoff)) => {},
776+
}
777+
778+
for (node_id, address) in undiscovered_lsps {
779+
if let Err(e) =
780+
retry_cm.connect_peer_if_necessary(node_id, address.clone()).await
781+
{
782+
log_debug!(
783+
retry_logger,
784+
"Discovery retry: failed to connect to LSP {}: {}",
785+
node_id,
786+
e
787+
);
788+
continue;
789+
}
790+
match retry_ls.discover_lsp_protocols(&node_id).await {
791+
Ok(protocols) => log_info!(
792+
retry_logger,
793+
"Discovery retry: discovered protocols for LSP {}: {:?}",
794+
node_id,
795+
protocols
796+
),
797+
Err(e) => log_debug!(
798+
retry_logger,
799+
"Discovery retry: failed for LSP {}: {:?}",
800+
node_id,
801+
e
802+
),
803+
}
804+
}
805+
806+
backoff = (backoff * 2).min(LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY_SECS);
807+
}
808+
});
809+
751810
log_info!(self.logger, "Startup complete.");
752811
*is_running_lock = true;
753812
Ok(())

src/liquidity/mod.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,40 @@ impl Liquidity {
164164
Ok(())
165165
}
166166

167+
/// Re-runs bLIP-50 / LSPS0 protocol discovery for an already-configured LSP.
168+
///
169+
/// Use this to recover an LSP that failed protocol discovery at startup or to
170+
/// pick up newly supported protocols after the LSP has rolled out an upgrade.
171+
///
172+
/// The `node_id` must belong to an LSP configured at build time or added via
173+
/// [`Liquidity::add_liquidity_source`]; otherwise [`Error::LiquiditySourceUnavailable`]
174+
/// is returned.
175+
pub fn retry_discovery(&self, node_id: PublicKey) -> Result<(), Error> {
176+
let (_, address) = self
177+
.liquidity_source
178+
.get_single_lsp_details(&node_id)
179+
.ok_or(Error::LiquiditySourceUnavailable)?;
180+
181+
let con_cm = Arc::clone(&self.connection_manager);
182+
let connect_addr = address.clone();
183+
self.runtime.block_on(async move {
184+
con_cm.connect_peer_if_necessary(node_id, connect_addr).await
185+
})?;
186+
log_info!(
187+
self.logger,
188+
"Connected to LSP {}@{} for protocol re-discovery.",
189+
node_id,
190+
address
191+
);
192+
193+
let protocols = self
194+
.runtime
195+
.block_on(async { self.liquidity_source.discover_lsp_protocols(&node_id).await })?;
196+
log_info!(self.logger, "Re-discovered protocols for LSP {}: {:?}", node_id, protocols);
197+
198+
Ok(())
199+
}
200+
167201
/// Returns a liquidity handler allowing to request channels via the [bLIP-51 / LSPS1] protocol.
168202
///
169203
/// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
@@ -432,6 +466,17 @@ where
432466
.collect()
433467
}
434468

469+
pub(crate) fn get_single_lsp_details(
470+
&self, node_id: &PublicKey,
471+
) -> Option<(PublicKey, SocketAddress)> {
472+
self.lsp_nodes
473+
.read()
474+
.expect("lock")
475+
.iter()
476+
.find(|n| &n.node_id == node_id)
477+
.map(|n| (n.node_id, n.address.clone()))
478+
}
479+
435480
pub(crate) async fn discover_lsp_protocols(
436481
&self, node_id: &PublicKey,
437482
) -> Result<Vec<u16>, Error> {
@@ -517,6 +562,16 @@ where
517562
select_lsps_for_protocol(&self.lsp_nodes, protocol, Some(node_id))
518563
}
519564

565+
pub(crate) fn get_undiscovered_lsps(&self) -> Vec<(PublicKey, SocketAddress)> {
566+
self.lsp_nodes
567+
.read()
568+
.expect("lock")
569+
.iter()
570+
.filter(|n| n.supported_protocols.is_none())
571+
.map(|n| (n.node_id, n.address.clone()))
572+
.collect()
573+
}
574+
520575
/// Flips the `discovery_done` watch to `true`.
521576
///
522577
/// Called once after the *initial* batch of LSPs configured at build time has been

0 commit comments

Comments
 (0)