Skip to content

Commit 7757974

Browse files
committed
Defer LSPS2 connections until needed
Read cached fee offers before connecting so cache hits do not open connections to every configured LSP. Connect only for cache misses and ensure the selected LSP is connected before sending a buy request. This commit was created with assistance from OpenAI Codex.
1 parent 90521f3 commit 7757974

1 file changed

Lines changed: 41 additions & 27 deletions

File tree

src/liquidity/client/lsps2.rs

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ where
6767
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
6868
payment_hash: Option<PaymentHash>, connection_manager: Arc<ConnectionManager<L>>,
6969
) -> Result<(Bolt11Invoice, LspConfig), Error> {
70-
// Connect to all candidate LSPs before querying fees.
70+
// Gather fee offers from all candidate LSPs.
7171
let all_offers = self.gather_lsps2_offers(&connection_manager, expiry_secs).await?;
7272
let (cheapest_lsp, min_total_fee_msat, min_opening_params) = all_offers
7373
.into_iter()
@@ -115,6 +115,9 @@ where
115115
cheapest_lsp.node_id,
116116
min_total_fee_msat
117117
);
118+
connection_manager
119+
.connect_peer_if_necessary(cheapest_lsp.node_id, cheapest_lsp.address.clone())
120+
.await?;
118121

119122
let buy_response = self
120123
.lsps2_send_buy_request(
@@ -147,7 +150,7 @@ where
147150
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
148151
connection_manager: Arc<ConnectionManager<L>>,
149152
) -> Result<(Bolt11Invoice, LspConfig), Error> {
150-
// Connect to all candidate LSPs before querying fees.
153+
// Gather fee offers from all candidate LSPs.
151154
let all_offers = self.gather_lsps2_offers(&connection_manager, expiry_secs).await?;
152155
let (cheapest_lsp, min_prop_fee_ppm_msat, min_opening_params) = all_offers
153156
.into_iter()
@@ -183,6 +186,9 @@ where
183186
cheapest_lsp.node_id,
184187
min_prop_fee_ppm_msat
185188
);
189+
connection_manager
190+
.connect_peer_if_necessary(cheapest_lsp.node_id, cheapest_lsp.address.clone())
191+
.await?;
186192

187193
let buy_response = self
188194
.lsps2_send_buy_request(None, min_opening_params, Some(&cheapest_lsp.node_id))
@@ -210,28 +216,25 @@ where
210216
) -> Result<Vec<(LspConfig, LSPS2FeeResponse)>, Error> {
211217
let lsps2_nodes = self.get_lsps2_nodes().await?;
212218

213-
// Connect to all candidate LSPs in parallel.
214-
let mut connect_set = JoinSet::new();
215-
for lsp_node in &lsps2_nodes {
216-
let cm = Arc::clone(connection_manager);
217-
let node_id = lsp_node.node_id;
218-
let addr = lsp_node.address.clone();
219-
let logger = self.logger.clone();
220-
connect_set.spawn(async move {
221-
if let Err(e) = cm.connect_peer_if_necessary(node_id, addr).await {
222-
log_warn!(logger, "Failed to connect to LSP {} for fee query: {}", node_id, e);
223-
}
224-
});
225-
}
226-
while connect_set.join_next().await.is_some() {}
227-
228219
let mut all_offers: Vec<(LspConfig, LSPS2FeeResponse)> =
229220
Vec::with_capacity(lsps2_nodes.len());
230221
let mut fee_set: JoinSet<(LspConfig, Result<LSPS2FeeResponse, Error>)> = JoinSet::new();
231222
for lsp_node in &lsps2_nodes {
232223
let lsp = lsp_node.clone();
233224
let client = Arc::clone(self);
225+
let cm = Arc::clone(connection_manager);
234226
fee_set.spawn(async move {
227+
if let Some(response) =
228+
client.lsps2_get_cached_opening_fee_params(&lsp, expiry_secs).await
229+
{
230+
return (lsp, Ok(response));
231+
}
232+
233+
if let Err(e) = cm.connect_peer_if_necessary(lsp.node_id, lsp.address.clone()).await
234+
{
235+
return (lsp, Err(e));
236+
}
237+
235238
let res =
236239
client.lsps2_request_opening_fee_params(Some(&lsp.node_id), expiry_secs).await;
237240
(lsp, res)
@@ -257,14 +260,10 @@ impl<L: Deref> LSPS2Client<L>
257260
where
258261
L::Target: LdkLogger,
259262
{
260-
async fn lsps2_request_opening_fee_params(
261-
&self, node_id: Option<&PublicKey>, invoice_expiry_secs: u32,
262-
) -> Result<LSPS2FeeResponse, Error> {
263-
let lsps2_node = select_lsps_for_protocol(&self.lsp_nodes, 2, node_id)
264-
.ok_or(Error::LiquiditySourceUnavailable)?;
265-
let required_validity = Duration::from_secs(
266-
u64::from(invoice_expiry_secs).saturating_add(LSPS2_GET_INFO_CACHE_EXPIRY_BUFFER_SECS),
267-
);
263+
async fn lsps2_get_cached_opening_fee_params(
264+
&self, lsps2_node: &LspConfig, invoice_expiry_secs: u32,
265+
) -> Option<LSPS2FeeResponse> {
266+
let required_validity = lsps2_fee_response_required_validity(invoice_expiry_secs);
268267
match self
269268
.fee_response_cache
270269
.get(&lsps2_node.node_id, lsps2_node.token.as_deref(), required_validity)
@@ -276,18 +275,27 @@ where
276275
"Using cached LSPS2 fee response from {}",
277276
lsps2_node.node_id
278277
);
279-
return Ok(response);
278+
Some(response)
280279
},
281-
Ok(None) => {},
280+
Ok(None) => None,
282281
Err(e) => {
283282
log_error!(
284283
self.logger,
285284
"Failed to read cached LSPS2 fee response from {}: {}",
286285
lsps2_node.node_id,
287286
e
288287
);
288+
None
289289
},
290290
}
291+
}
292+
293+
async fn lsps2_request_opening_fee_params(
294+
&self, node_id: Option<&PublicKey>, invoice_expiry_secs: u32,
295+
) -> Result<LSPS2FeeResponse, Error> {
296+
let lsps2_node = select_lsps_for_protocol(&self.lsp_nodes, 2, node_id)
297+
.ok_or(Error::LiquiditySourceUnavailable)?;
298+
let required_validity = lsps2_fee_response_required_validity(invoice_expiry_secs);
291299

292300
let client_handler = self.liquidity_manager.lsps2_client_handler().ok_or_else(|| {
293301
log_error!(self.logger, "Liquidity client was not configured.",);
@@ -697,6 +705,12 @@ pub(crate) struct LSPS2BuyResponse {
697705

698706
const LSPS2_GET_INFO_CACHE_EXPIRY_BUFFER_SECS: u64 = 60;
699707

708+
fn lsps2_fee_response_required_validity(invoice_expiry_secs: u32) -> Duration {
709+
Duration::from_secs(
710+
u64::from(invoice_expiry_secs).saturating_add(LSPS2_GET_INFO_CACHE_EXPIRY_BUFFER_SECS),
711+
)
712+
}
713+
700714
#[derive(Clone)]
701715
struct PersistedLSPS2FeeResponse {
702716
node_id: PublicKey,

0 commit comments

Comments
 (0)