Skip to content

Commit 8f53a15

Browse files
committed
lsps2: Serialize lease negotiation
Make same-amount and variable callers wait for one in-flight LSPS2 request, then recheck the shared cache before negotiating. Co-Authored-By: HAL 9000
1 parent 506a5a1 commit 8f53a15

3 files changed

Lines changed: 67 additions & 3 deletions

File tree

src/liquidity/client/lsps2/mod.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use crate::{Config, Error};
3939
use self::router::LSPS2LeaseParameters;
4040
use self::state::{
4141
now_secs, LSPS2LeaseState, LeaseCacheTarget, LeaseCacheTargetId, LeaseCacheTargetStore,
42-
PaymentLease, PaymentLeaseId, PaymentLeaseStore,
42+
LeaseRequestKey, PaymentLease, PaymentLeaseId, PaymentLeaseStore, PendingLeaseRequestState,
4343
};
4444

4545
async fn consume_after_persisted_removal<T, E, RF, CF, Fut>(
@@ -102,6 +102,7 @@ where
102102
pub(crate) lease_store: Arc<PaymentLeaseStore<L>>,
103103
pub(crate) lease_state: Mutex<LSPS2LeaseState>,
104104
pub(crate) cache_target_store: Arc<LeaseCacheTargetStore<L>>,
105+
pub(crate) pending_lease_request_state: Mutex<PendingLeaseRequestState>,
105106
pub(crate) channel_manager: Arc<ChannelManager>,
106107
pub(crate) keys_manager: Arc<KeysManager>,
107108
pub(crate) discovery_done_rx: tokio::sync::watch::Receiver<bool>,
@@ -275,6 +276,17 @@ where
275276
{
276277
return Ok((lease, total_fee_msat, lsp, false));
277278
}
279+
let request_lock = self
280+
.pending_lease_request_state
281+
.lock()
282+
.expect("lock")
283+
.request_lock(LeaseRequestKey::Fixed(amount_msat));
284+
let _request_guard = request_lock.lock().await;
285+
if let Some((lease, total_fee_msat, lsp)) =
286+
self.take_cached_fixed_lease(amount_msat).await?
287+
{
288+
return Ok((lease, total_fee_msat, lsp, false));
289+
}
278290

279291
let all_offers = self.gather_lsps2_offers(connection_manager).await?;
280292
let (cheapest_lsp, min_total_fee_msat, min_opening_params) = all_offers
@@ -341,6 +353,15 @@ where
341353
if let Some((lease, proportional_fee, lsp)) = self.take_cached_variable_lease().await? {
342354
return Ok((lease, proportional_fee, lsp, false));
343355
}
356+
let request_lock = self
357+
.pending_lease_request_state
358+
.lock()
359+
.expect("lock")
360+
.request_lock(LeaseRequestKey::Variable);
361+
let _request_guard = request_lock.lock().await;
362+
if let Some((lease, proportional_fee, lsp)) = self.take_cached_variable_lease().await? {
363+
return Ok((lease, proportional_fee, lsp, false));
364+
}
344365

345366
let all_offers = self.gather_lsps2_offers(connection_manager).await?;
346367
let mut rejected_for_fee = false;

src/liquidity/client/lsps2/state.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
22
use std::ops::Deref;
3-
use std::sync::{Arc, Mutex};
3+
use std::sync::{Arc, Mutex, Weak};
44
use std::time::{Duration, SystemTime, UNIX_EPOCH};
55

66
use bitcoin::secp256k1::PublicKey;
@@ -9,6 +9,7 @@ use lightning::util::ser::{Readable, Writeable};
99
use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum};
1010
use lightning_liquidity::lsps2::msgs::LSPS2OpeningFeeParams;
1111
use lightning_liquidity::lsps2::utils::compute_opening_fee;
12+
use tokio::sync::Mutex as AsyncMutex;
1213

1314
use crate::data_store::{DataStore, StorableObject, StorableObjectId, StorableObjectUpdate};
1415
use crate::hex_utils;
@@ -261,6 +262,33 @@ fn merge_absolute_expiry(current: Option<u64>, new: Option<u64>) -> Option<u64>
261262
}
262263
}
263264

265+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
266+
pub(crate) enum LeaseRequestKey {
267+
Fixed(u64),
268+
Variable,
269+
}
270+
271+
#[derive(Default)]
272+
pub(crate) struct PendingLeaseRequestState {
273+
locks: HashMap<LeaseRequestKey, Weak<AsyncMutex<()>>>,
274+
}
275+
276+
impl PendingLeaseRequestState {
277+
pub(crate) fn request_lock(&mut self, key: LeaseRequestKey) -> Arc<AsyncMutex<()>> {
278+
self.prune();
279+
if let Some(lock) = self.locks.get(&key).and_then(Weak::upgrade) {
280+
return lock;
281+
}
282+
let lock = Arc::new(AsyncMutex::new(()));
283+
self.locks.insert(key, Arc::downgrade(&lock));
284+
lock
285+
}
286+
287+
pub(crate) fn prune(&mut self) {
288+
self.locks.retain(|_, lock| lock.strong_count() > 0);
289+
}
290+
}
291+
264292
fn is_cache_target_expired(target: &LeaseCacheTarget, now: u64) -> bool {
265293
target.absolute_expiry.is_some_and(|expiry| expiry <= now)
266294
}
@@ -649,4 +677,18 @@ mod tests {
649677
state.register(cache_target(id, None, 3));
650678
assert_eq!(state.targets().first().unwrap().absolute_expiry, None);
651679
}
680+
681+
#[test]
682+
fn lease_requests_are_serialized_per_key() {
683+
let mut state = PendingLeaseRequestState::default();
684+
685+
let fixed = state.request_lock(LeaseRequestKey::Fixed(1_000));
686+
let same_fixed = state.request_lock(LeaseRequestKey::Fixed(1_000));
687+
let other_fixed = state.request_lock(LeaseRequestKey::Fixed(2_000));
688+
let variable = state.request_lock(LeaseRequestKey::Variable);
689+
690+
assert!(Arc::ptr_eq(&fixed, &same_fixed));
691+
assert!(!Arc::ptr_eq(&fixed, &other_fixed));
692+
assert!(!Arc::ptr_eq(&fixed, &variable));
693+
}
652694
}

src/liquidity/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use crate::io::{
3838
use crate::liquidity::client::lsps1::LSPS1Client;
3939
use crate::liquidity::client::lsps2::state::{
4040
is_lease_usable, now_secs, read_lease_cache_targets, LSPS2LeaseState, LeaseCacheTargetState,
41-
LeaseCacheTargetStore, PaymentLeaseStore,
41+
LeaseCacheTargetStore, PaymentLeaseStore, PendingLeaseRequestState,
4242
};
4343
use crate::liquidity::client::lsps2::LSPS2Client;
4444
use crate::liquidity::service::lsps2::{LSPS2Service, LSPS2ServiceLiquiditySource};
@@ -361,6 +361,7 @@ where
361361
lease_store,
362362
lease_state: Mutex::new(lease_state),
363363
cache_target_store,
364+
pending_lease_request_state: Mutex::new(PendingLeaseRequestState::default()),
364365
channel_manager: self.channel_manager.clone(),
365366
keys_manager: self.keys_manager.clone(),
366367
discovery_done_rx: discovery_done_rx.clone(),

0 commit comments

Comments
 (0)