Skip to content

Commit c949532

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 a0cb334 commit c949532

2 files changed

Lines changed: 167 additions & 4 deletions

File tree

src/liquidity/client/lsps2/mod.rs

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,11 @@ where
354354
if let Some((lease, total_fee_msat, lsp)) =
355355
self.take_cached_fixed_lease(amount_msat, max_total_lsp_fee_limit_msat).await?
356356
{
357+
self.schedule_fixed_lease_refill(
358+
amount_msat,
359+
max_total_lsp_fee_limit_msat,
360+
connection_manager,
361+
);
357362
return Ok((lease, total_fee_msat, lsp, false));
358363
}
359364
let request_lock = self
@@ -365,9 +370,30 @@ where
365370
if let Some((lease, total_fee_msat, lsp)) =
366371
self.take_cached_fixed_lease(amount_msat, max_total_lsp_fee_limit_msat).await?
367372
{
373+
self.schedule_fixed_lease_refill(
374+
amount_msat,
375+
max_total_lsp_fee_limit_msat,
376+
connection_manager,
377+
);
368378
return Ok((lease, total_fee_msat, lsp, false));
369379
}
370380

381+
let (negotiated_lease, min_total_fee_msat, cheapest_lsp) = self
382+
.negotiate_fixed_lease(amount_msat, max_total_lsp_fee_limit_msat, connection_manager)
383+
.await?;
384+
let lease = self.consume_lease(&negotiated_lease.id).await?;
385+
self.schedule_fixed_lease_refill(
386+
amount_msat,
387+
max_total_lsp_fee_limit_msat,
388+
connection_manager,
389+
);
390+
Ok((lease, min_total_fee_msat, cheapest_lsp, true))
391+
}
392+
393+
async fn negotiate_fixed_lease(
394+
self: &Arc<Self>, amount_msat: u64, max_total_lsp_fee_limit_msat: Option<u64>,
395+
connection_manager: &Arc<ConnectionManager<L>>,
396+
) -> Result<(PaymentLease, u64, LspConfig), Error> {
371397
let all_offers = self.gather_lsps2_offers(connection_manager).await?;
372398
let (cheapest_lsp, min_total_fee_msat, min_opening_params) = all_offers
373399
.into_iter()
@@ -423,8 +449,7 @@ where
423449
Some(&cheapest_lsp.node_id),
424450
)
425451
.await?;
426-
let lease = self.consume_lease(&negotiated_lease.id).await?;
427-
Ok((lease, min_total_fee_msat, cheapest_lsp, true))
452+
Ok((negotiated_lease, min_total_fee_msat, cheapest_lsp))
428453
}
429454

430455
async fn acquire_variable_lease(
@@ -434,6 +459,10 @@ where
434459
if let Some((lease, proportional_fee, lsp)) =
435460
self.take_cached_variable_lease(max_proportional_lsp_fee_limit_ppm_msat).await?
436461
{
462+
self.schedule_variable_lease_refill(
463+
max_proportional_lsp_fee_limit_ppm_msat,
464+
connection_manager,
465+
);
437466
return Ok((lease, proportional_fee, lsp, false));
438467
}
439468
let request_lock = self
@@ -445,9 +474,28 @@ where
445474
if let Some((lease, proportional_fee, lsp)) =
446475
self.take_cached_variable_lease(max_proportional_lsp_fee_limit_ppm_msat).await?
447476
{
477+
self.schedule_variable_lease_refill(
478+
max_proportional_lsp_fee_limit_ppm_msat,
479+
connection_manager,
480+
);
448481
return Ok((lease, proportional_fee, lsp, false));
449482
}
450483

484+
let (negotiated_lease, min_prop_fee_ppm_msat, cheapest_lsp) = self
485+
.negotiate_variable_lease(max_proportional_lsp_fee_limit_ppm_msat, connection_manager)
486+
.await?;
487+
let lease = self.consume_lease(&negotiated_lease.id).await?;
488+
self.schedule_variable_lease_refill(
489+
max_proportional_lsp_fee_limit_ppm_msat,
490+
connection_manager,
491+
);
492+
Ok((lease, min_prop_fee_ppm_msat, cheapest_lsp, true))
493+
}
494+
495+
async fn negotiate_variable_lease(
496+
self: &Arc<Self>, max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
497+
connection_manager: &Arc<ConnectionManager<L>>,
498+
) -> Result<(PaymentLease, u64, LspConfig), Error> {
451499
let all_offers = self.gather_lsps2_offers(connection_manager).await?;
452500
let (cheapest_lsp, min_prop_fee_ppm_msat, min_opening_params) = all_offers
453501
.into_iter()
@@ -487,8 +535,85 @@ where
487535
let negotiated_lease = self
488536
.lsps2_send_buy_request(None, min_opening_params, Some(&cheapest_lsp.node_id))
489537
.await?;
490-
let lease = self.consume_lease(&negotiated_lease.id).await?;
491-
Ok((lease, min_prop_fee_ppm_msat, cheapest_lsp, true))
538+
Ok((negotiated_lease, min_prop_fee_ppm_msat, cheapest_lsp))
539+
}
540+
541+
fn schedule_fixed_lease_refill(
542+
self: &Arc<Self>, amount_msat: u64, max_total_lsp_fee_limit_msat: Option<u64>,
543+
connection_manager: &Arc<ConnectionManager<L>>,
544+
) {
545+
let client = Arc::clone(self);
546+
let connection_manager = Arc::clone(connection_manager);
547+
tokio::spawn(async move {
548+
if let Err(error) = client
549+
.cache_fixed_lease(amount_msat, max_total_lsp_fee_limit_msat, &connection_manager)
550+
.await
551+
{
552+
log_warn!(client.logger, "Failed refilling LSPS2 payment lease: {}", error);
553+
}
554+
});
555+
}
556+
557+
fn schedule_variable_lease_refill(
558+
self: &Arc<Self>, max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
559+
connection_manager: &Arc<ConnectionManager<L>>,
560+
) {
561+
let client = Arc::clone(self);
562+
let connection_manager = Arc::clone(connection_manager);
563+
tokio::spawn(async move {
564+
if let Err(error) = client
565+
.cache_variable_lease(max_proportional_lsp_fee_limit_ppm_msat, &connection_manager)
566+
.await
567+
{
568+
log_warn!(client.logger, "Failed refilling LSPS2 payment lease: {}", error);
569+
}
570+
});
571+
}
572+
573+
async fn cache_fixed_lease(
574+
self: &Arc<Self>, amount_msat: u64, max_total_lsp_fee_limit_msat: Option<u64>,
575+
connection_manager: &Arc<ConnectionManager<L>>,
576+
) -> Result<(), Error> {
577+
let request_lock = self
578+
.pending_lease_request_state
579+
.lock()
580+
.expect("lock")
581+
.request_lock(LeaseRequestKey::Fixed(amount_msat));
582+
let _request_guard = request_lock.lock().await;
583+
if self
584+
.lease_state
585+
.lock()
586+
.expect("lock")
587+
.has_fixed_amount(amount_msat, max_total_lsp_fee_limit_msat)
588+
{
589+
return Ok(());
590+
}
591+
self.negotiate_fixed_lease(amount_msat, max_total_lsp_fee_limit_msat, connection_manager)
592+
.await?;
593+
Ok(())
594+
}
595+
596+
async fn cache_variable_lease(
597+
self: &Arc<Self>, max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
598+
connection_manager: &Arc<ConnectionManager<L>>,
599+
) -> Result<(), Error> {
600+
let request_lock = self
601+
.pending_lease_request_state
602+
.lock()
603+
.expect("lock")
604+
.request_lock(LeaseRequestKey::Variable);
605+
let _request_guard = request_lock.lock().await;
606+
if self
607+
.lease_state
608+
.lock()
609+
.expect("lock")
610+
.has_variable_amount(max_proportional_lsp_fee_limit_ppm_msat)
611+
{
612+
return Ok(());
613+
}
614+
self.negotiate_variable_lease(max_proportional_lsp_fee_limit_ppm_msat, connection_manager)
615+
.await?;
616+
Ok(())
492617
}
493618

494619
async fn gather_lsps2_offers(

src/liquidity/client/lsps2/state.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,21 @@ impl LSPS2LeaseState {
367367
self.leases.get(&id).cloned().map(|lease| (lease, fee_msat))
368368
}
369369

370+
pub(crate) fn has_fixed_amount(&self, amount_msat: u64, max_fee_msat: Option<u64>) -> bool {
371+
self.leases
372+
.values()
373+
.filter(|lease| lease.payment_size_msat == Some(amount_msat))
374+
.filter(|lease| is_lease_usable(lease))
375+
.filter_map(|lease| {
376+
compute_opening_fee(
377+
amount_msat,
378+
lease.params.min_fee_msat,
379+
lease.params.proportional as u64,
380+
)
381+
})
382+
.any(|fee_msat| max_fee_msat.map_or(true, |max| fee_msat <= max))
383+
}
384+
370385
pub(crate) fn variable_amount(
371386
&self, max_proportional_fee_ppm_msat: Option<u64>,
372387
) -> Option<(PaymentLease, u64)> {
@@ -381,6 +396,15 @@ impl LSPS2LeaseState {
381396
self.leases.get(&id).cloned().map(|lease| (lease, proportional_fee))
382397
}
383398

399+
pub(crate) fn has_variable_amount(&self, max_proportional_fee_ppm_msat: Option<u64>) -> bool {
400+
self.leases
401+
.values()
402+
.filter(|lease| lease.payment_size_msat.is_none())
403+
.filter(|lease| is_lease_usable(lease))
404+
.map(|lease| lease.params.proportional as u64)
405+
.any(|fee| max_proportional_fee_ppm_msat.map_or(true, |max| fee <= max))
406+
}
407+
384408
pub(crate) fn prune(&mut self) {
385409
self.leases.retain(|_, lease| is_lease_usable(lease));
386410
}
@@ -466,6 +490,20 @@ mod tests {
466490
assert_eq!(remaining.id, expensive.id);
467491
}
468492

493+
#[test]
494+
fn detects_cached_leases_for_refill() {
495+
let valid_until = now_secs() + MIN_LEASE_REMAINING_SECS + 60;
496+
let fixed = lease(2, 46, 100, Some(1_000), valid_until);
497+
let variable = lease(3, 47, 50, None, valid_until);
498+
let state = LSPS2LeaseState::from_leases(vec![fixed, variable]);
499+
500+
assert!(state.has_fixed_amount(1_000, Some(100)));
501+
assert!(!state.has_fixed_amount(1_000, Some(99)));
502+
assert!(!state.has_fixed_amount(2_000, None));
503+
assert!(state.has_variable_amount(Some(1)));
504+
assert!(!state.has_variable_amount(Some(0)));
505+
}
506+
469507
fn pending_offer(
470508
id_byte: u8, amount: PendingOfferAmount, absolute_expiry: Option<u64>, last_accessed: u64,
471509
) -> PendingOffer {

0 commit comments

Comments
 (0)