Skip to content

Commit 5bc9017

Browse files
Add BoundedMap to limit LSPS5/client pending requests
Replaces HashMap with BoundedMap (HashMap + FIFO eviction) since requests no longer expire by time. This requires minimal client code changes, provides HashMap ergonomics with O(1) operations and VecDeque-like capacity limits.
1 parent d2c7c66 commit 5bc9017

File tree

3 files changed

+376
-13
lines changed

3 files changed

+376
-13
lines changed

lightning-liquidity/src/lsps5/client.rs

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::lsps5::msgs::{
2121
use crate::message_queue::MessageQueue;
2222
use crate::prelude::{new_hash_map, HashMap};
2323
use crate::sync::{Arc, Mutex, RwLock};
24+
use crate::utils::bounded_map::BoundedMap;
2425
use crate::utils::generate_request_id;
2526

2627
use super::msgs::{LSPS5AppName, LSPS5Error, LSPS5WebhookUrl};
@@ -40,17 +41,19 @@ use core::ops::Deref;
4041
pub struct LSPS5ClientConfig {}
4142

4243
struct PeerState {
43-
pending_set_webhook_requests: HashMap<LSPSRequestId, (LSPS5AppName, LSPS5WebhookUrl)>,
44-
pending_list_webhooks_requests: HashMap<LSPSRequestId, ()>,
45-
pending_remove_webhook_requests: HashMap<LSPSRequestId, LSPS5AppName>,
44+
pending_set_webhook_requests: BoundedMap<LSPSRequestId, (LSPS5AppName, LSPS5WebhookUrl)>,
45+
pending_list_webhooks_requests: BoundedMap<LSPSRequestId, ()>,
46+
pending_remove_webhook_requests: BoundedMap<LSPSRequestId, LSPS5AppName>,
4647
}
4748

49+
const MAX_PENDING_REQUESTS: usize = 5;
50+
4851
impl PeerState {
4952
fn new() -> Self {
5053
Self {
51-
pending_set_webhook_requests: new_hash_map(),
52-
pending_list_webhooks_requests: new_hash_map(),
53-
pending_remove_webhook_requests: new_hash_map(),
54+
pending_set_webhook_requests: BoundedMap::new(MAX_PENDING_REQUESTS),
55+
pending_list_webhooks_requests: BoundedMap::new(MAX_PENDING_REQUESTS),
56+
pending_remove_webhook_requests: BoundedMap::new(MAX_PENDING_REQUESTS),
5457
}
5558
}
5659
}
@@ -364,19 +367,31 @@ where
364367
mod tests {
365368

366369
use super::*;
367-
use crate::{
368-
lsps0::ser::LSPSRequestId, lsps5::msgs::SetWebhookResponse, tests::utils::TestEntropy,
369-
};
370+
use crate::{lsps0::ser::LSPSRequestId, lsps5::msgs::SetWebhookResponse};
370371
use bitcoin::{key::Secp256k1, secp256k1::SecretKey};
372+
use core::sync::atomic::{AtomicU64, Ordering};
373+
374+
struct UniqueTestEntropy {
375+
counter: AtomicU64,
376+
}
377+
378+
impl EntropySource for UniqueTestEntropy {
379+
fn get_secure_random_bytes(&self) -> [u8; 32] {
380+
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
381+
let mut bytes = [0u8; 32];
382+
bytes[0..8].copy_from_slice(&counter.to_be_bytes());
383+
bytes
384+
}
385+
}
371386

372387
fn setup_test_client() -> (
373-
LSPS5ClientHandler<Arc<TestEntropy>>,
388+
LSPS5ClientHandler<Arc<UniqueTestEntropy>>,
374389
Arc<MessageQueue>,
375390
Arc<EventQueue>,
376391
PublicKey,
377392
PublicKey,
378393
) {
379-
let test_entropy_source = Arc::new(TestEntropy {});
394+
let test_entropy_source = Arc::new(UniqueTestEntropy { counter: AtomicU64::new(2) });
380395
let message_queue = Arc::new(MessageQueue::new());
381396
let event_queue = Arc::new(EventQueue::new());
382397
let client = LSPS5ClientHandler::new(
@@ -497,4 +512,44 @@ mod tests {
497512
let error = result.unwrap_err();
498513
assert!(error.err.to_lowercase().contains("unknown request id"));
499514
}
515+
516+
#[test]
517+
fn test_pending_request_eviction() {
518+
let (client, _, _, peer, _) = setup_test_client();
519+
520+
let mut request_ids = Vec::new();
521+
for i in 0..MAX_PENDING_REQUESTS {
522+
let req_id = client
523+
.set_webhook(peer, format!("app-{}", i), format!("https://example.com/hook{}", i))
524+
.unwrap();
525+
request_ids.push(req_id);
526+
}
527+
528+
{
529+
let outer_state_lock = client.per_peer_state.read().unwrap();
530+
let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap();
531+
for req_id in &request_ids {
532+
assert!(peer_state.pending_set_webhook_requests.contains_key(req_id));
533+
}
534+
assert_eq!(peer_state.pending_set_webhook_requests.len(), MAX_PENDING_REQUESTS);
535+
}
536+
537+
let new_req_id = client
538+
.set_webhook(peer, "app-new".to_string(), "https://example.com/hook-new".to_string())
539+
.unwrap();
540+
541+
{
542+
let outer_state_lock = client.per_peer_state.read().unwrap();
543+
let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap();
544+
assert_eq!(peer_state.pending_set_webhook_requests.len(), MAX_PENDING_REQUESTS);
545+
546+
assert!(!peer_state.pending_set_webhook_requests.contains_key(&request_ids[0]));
547+
548+
for req_id in &request_ids[1..] {
549+
assert!(peer_state.pending_set_webhook_requests.contains_key(req_id));
550+
}
551+
552+
assert!(peer_state.pending_set_webhook_requests.contains_key(&new_req_id));
553+
}
554+
}
500555
}

0 commit comments

Comments
 (0)