Skip to content

Commit 24e7cf3

Browse files
committed
lsps2: Refill consumed payment leases
Keep one usable lease ready after fixed or variable receive flows consume cached parameters. Foreground callers share the refill lock and reuse its result when it completes. Co-Authored-By: HAL 9000
1 parent 5838279 commit 24e7cf3

2 files changed

Lines changed: 129 additions & 4 deletions

File tree

src/liquidity/client/lsps2/mod.rs

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ where
274274
if let Some((lease, total_fee_msat, lsp)) =
275275
self.take_cached_fixed_lease(amount_msat).await?
276276
{
277+
self.schedule_fixed_lease_refill(amount_msat, connection_manager);
277278
return Ok((lease, total_fee_msat, lsp, false));
278279
}
279280
let request_lock = self
@@ -285,9 +286,20 @@ where
285286
if let Some((lease, total_fee_msat, lsp)) =
286287
self.take_cached_fixed_lease(amount_msat).await?
287288
{
289+
self.schedule_fixed_lease_refill(amount_msat, connection_manager);
288290
return Ok((lease, total_fee_msat, lsp, false));
289291
}
290292

293+
let (negotiated_lease, min_total_fee_msat, cheapest_lsp) =
294+
self.negotiate_fixed_lease(amount_msat, connection_manager).await?;
295+
let lease = self.consume_lease(&negotiated_lease.id).await?;
296+
self.schedule_fixed_lease_refill(amount_msat, connection_manager);
297+
Ok((lease, min_total_fee_msat, cheapest_lsp, true))
298+
}
299+
300+
async fn negotiate_fixed_lease(
301+
self: &Arc<Self>, amount_msat: u64, connection_manager: &Arc<ConnectionManager<L>>,
302+
) -> Result<(PaymentLease, u64, LspConfig), Error> {
291303
let all_offers = self.gather_lsps2_offers(connection_manager).await?;
292304
let (cheapest_lsp, min_total_fee_msat, min_opening_params) = all_offers
293305
.into_iter()
@@ -343,14 +355,14 @@ where
343355
Some(&cheapest_lsp.node_id),
344356
)
345357
.await?;
346-
let lease = self.consume_lease(&negotiated_lease.id).await?;
347-
Ok((lease, min_total_fee_msat, cheapest_lsp, true))
358+
Ok((negotiated_lease, min_total_fee_msat, cheapest_lsp))
348359
}
349360

350361
async fn acquire_variable_lease(
351362
self: &Arc<Self>, connection_manager: &Arc<ConnectionManager<L>>,
352363
) -> Result<(PaymentLease, u64, LspConfig, bool), Error> {
353364
if let Some((lease, proportional_fee, lsp)) = self.take_cached_variable_lease().await? {
365+
self.schedule_variable_lease_refill(connection_manager);
354366
return Ok((lease, proportional_fee, lsp, false));
355367
}
356368
let request_lock = self
@@ -360,9 +372,20 @@ where
360372
.request_lock(LeaseRequestKey::Variable);
361373
let _request_guard = request_lock.lock().await;
362374
if let Some((lease, proportional_fee, lsp)) = self.take_cached_variable_lease().await? {
375+
self.schedule_variable_lease_refill(connection_manager);
363376
return Ok((lease, proportional_fee, lsp, false));
364377
}
365378

379+
let (negotiated_lease, min_prop_fee_ppm_msat, cheapest_lsp) =
380+
self.negotiate_variable_lease(connection_manager).await?;
381+
let lease = self.consume_lease(&negotiated_lease.id).await?;
382+
self.schedule_variable_lease_refill(connection_manager);
383+
Ok((lease, min_prop_fee_ppm_msat, cheapest_lsp, true))
384+
}
385+
386+
async fn negotiate_variable_lease(
387+
self: &Arc<Self>, connection_manager: &Arc<ConnectionManager<L>>,
388+
) -> Result<(PaymentLease, u64, LspConfig), Error> {
366389
let all_offers = self.gather_lsps2_offers(connection_manager).await?;
367390
let mut rejected_for_fee = false;
368391
let (cheapest_lsp, min_prop_fee_ppm_msat, min_opening_params) = all_offers
@@ -404,8 +427,73 @@ where
404427
let negotiated_lease = self
405428
.lsps2_send_buy_request(None, min_opening_params, Some(&cheapest_lsp.node_id))
406429
.await?;
407-
let lease = self.consume_lease(&negotiated_lease.id).await?;
408-
Ok((lease, min_prop_fee_ppm_msat, cheapest_lsp, true))
430+
Ok((negotiated_lease, min_prop_fee_ppm_msat, cheapest_lsp))
431+
}
432+
433+
fn schedule_fixed_lease_refill(
434+
self: &Arc<Self>, amount_msat: u64, connection_manager: &Arc<ConnectionManager<L>>,
435+
) {
436+
let client = Arc::clone(self);
437+
let connection_manager = Arc::clone(connection_manager);
438+
tokio::spawn(async move {
439+
if let Err(error) = client.cache_fixed_lease(amount_msat, &connection_manager).await {
440+
log_warn!(client.logger, "Failed refilling LSPS2 payment lease: {}", error);
441+
}
442+
});
443+
}
444+
445+
fn schedule_variable_lease_refill(
446+
self: &Arc<Self>, connection_manager: &Arc<ConnectionManager<L>>,
447+
) {
448+
let client = Arc::clone(self);
449+
let connection_manager = Arc::clone(connection_manager);
450+
tokio::spawn(async move {
451+
if let Err(error) = client.cache_variable_lease(&connection_manager).await {
452+
log_warn!(client.logger, "Failed refilling LSPS2 payment lease: {}", error);
453+
}
454+
});
455+
}
456+
457+
async fn cache_fixed_lease(
458+
self: &Arc<Self>, amount_msat: u64, connection_manager: &Arc<ConnectionManager<L>>,
459+
) -> Result<(), Error> {
460+
let request_lock = self
461+
.pending_lease_request_state
462+
.lock()
463+
.expect("lock")
464+
.request_lock(LeaseRequestKey::Fixed(amount_msat));
465+
let _request_guard = request_lock.lock().await;
466+
if self
467+
.lease_state
468+
.lock()
469+
.expect("lock")
470+
.has_fixed_amount(amount_msat, self.config.lsps2_max_total_lsp_fee_limit_msat)
471+
{
472+
return Ok(());
473+
}
474+
self.negotiate_fixed_lease(amount_msat, connection_manager).await?;
475+
Ok(())
476+
}
477+
478+
async fn cache_variable_lease(
479+
self: &Arc<Self>, connection_manager: &Arc<ConnectionManager<L>>,
480+
) -> Result<(), Error> {
481+
let request_lock = self
482+
.pending_lease_request_state
483+
.lock()
484+
.expect("lock")
485+
.request_lock(LeaseRequestKey::Variable);
486+
let _request_guard = request_lock.lock().await;
487+
if self
488+
.lease_state
489+
.lock()
490+
.expect("lock")
491+
.has_variable_amount(self.config.lsps2_max_total_lsp_fee_limit_msat)
492+
{
493+
return Ok(());
494+
}
495+
self.negotiate_variable_lease(connection_manager).await?;
496+
Ok(())
409497
}
410498

411499
async fn gather_lsps2_offers(

src/liquidity/client/lsps2/state.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,21 @@ impl LSPS2LeaseState {
406406
self.leases.remove(&id).map(|lease| (lease, fee_msat))
407407
}
408408

409+
pub(crate) fn has_fixed_amount(&self, amount_msat: u64, max_fee_msat: Option<u64>) -> bool {
410+
self.leases
411+
.values()
412+
.filter(|lease| lease.payment_size_msat == Some(amount_msat))
413+
.filter(|lease| is_lease_usable(lease))
414+
.filter_map(|lease| {
415+
compute_opening_fee(
416+
amount_msat,
417+
lease.params.min_fee_msat,
418+
lease.params.proportional as u64,
419+
)
420+
})
421+
.any(|fee_msat| max_fee_msat.map_or(true, |max| fee_msat <= max))
422+
}
423+
409424
/// Selects and removes a variable-amount lease in one operation.
410425
pub(crate) fn variable_amount(
411426
&mut self, max_total_fee_msat: Option<u64>,
@@ -423,6 +438,14 @@ impl LSPS2LeaseState {
423438
self.leases.remove(&id).map(|lease| (lease, proportional_fee))
424439
}
425440

441+
pub(crate) fn has_variable_amount(&self, max_total_fee_msat: Option<u64>) -> bool {
442+
self.leases
443+
.values()
444+
.filter(|lease| lease.payment_size_msat.is_none())
445+
.filter(|lease| is_lease_usable(lease))
446+
.any(|lease| max_total_fee_msat.map_or(true, |max| lease.params.min_fee_msat <= max))
447+
}
448+
426449
pub(crate) fn prune(&mut self) {
427450
self.leases.retain(|_, lease| is_lease_usable(lease));
428451
}
@@ -572,6 +595,20 @@ mod tests {
572595
assert_eq!(state.variable_amount(Some(50)).unwrap().0.id, variable.id);
573596
}
574597

598+
#[test]
599+
fn detects_cached_leases_for_refill() {
600+
let valid_until = now_secs() + MIN_LEASE_REMAINING_SECS + 60;
601+
let fixed = lease(2, 46, 100, Some(1_000), valid_until);
602+
let variable = lease(3, 47, 50, None, valid_until);
603+
let state = LSPS2LeaseState::from_leases(vec![fixed, variable]);
604+
605+
assert!(state.has_fixed_amount(1_000, Some(100)));
606+
assert!(!state.has_fixed_amount(1_000, Some(99)));
607+
assert!(!state.has_fixed_amount(2_000, None));
608+
assert!(state.has_variable_amount(Some(50)));
609+
assert!(!state.has_variable_amount(Some(49)));
610+
}
611+
575612
fn cache_target(
576613
id: LeaseCacheTargetId, absolute_expiry: Option<u64>, last_accessed: u64,
577614
) -> LeaseCacheTarget {

0 commit comments

Comments
 (0)