Skip to content

Commit 9bb8874

Browse files
Remove peerState if there are no pending requests with that peer
1 parent 5bc9017 commit 9bb8874

File tree

2 files changed

+122
-30
lines changed

2 files changed

+122
-30
lines changed

lightning-liquidity/src/lsps5/client.rs

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ impl PeerState {
5656
pending_remove_webhook_requests: BoundedMap::new(MAX_PENDING_REQUESTS),
5757
}
5858
}
59+
60+
fn is_empty(&self) -> bool {
61+
self.pending_set_webhook_requests.is_empty()
62+
&& self.pending_list_webhooks_requests.is_empty()
63+
&& self.pending_remove_webhook_requests.is_empty()
64+
}
5965
}
6066

6167
/// Client-side handler for the LSPS5 (bLIP-55) webhook registration protocol.
@@ -345,8 +351,26 @@ where
345351
}
346352
};
347353
self.with_peer_state(*counterparty_node_id, handle_response);
354+
355+
self.check_and_remove_empty_peer_state(counterparty_node_id);
356+
348357
result
349358
}
359+
360+
fn check_and_remove_empty_peer_state(&self, counterparty_node_id: &PublicKey) {
361+
let mut outer_state_lock = self.per_peer_state.write().unwrap();
362+
let should_remove =
363+
if let Some(peer_state_mutex) = outer_state_lock.get(counterparty_node_id) {
364+
let peer_state = peer_state_mutex.lock().unwrap();
365+
peer_state.is_empty()
366+
} else {
367+
false
368+
};
369+
370+
if should_remove {
371+
outer_state_lock.remove(counterparty_node_id);
372+
}
373+
}
350374
}
351375

352376
impl<ES: Deref> LSPSProtocolMessageHandler for LSPS5ClientHandler<ES>
@@ -461,36 +485,6 @@ mod tests {
461485
}
462486
}
463487

464-
#[test]
465-
fn test_handle_response_clears_pending_state() {
466-
let (client, _, _, peer, _) = setup_test_client();
467-
468-
let req_id = client
469-
.set_webhook(peer, "test-app".to_string(), "https://example.com/hook".to_string())
470-
.unwrap();
471-
472-
let response = LSPS5Response::SetWebhook(SetWebhookResponse {
473-
num_webhooks: 1,
474-
max_webhooks: 5,
475-
no_change: false,
476-
});
477-
let response_msg = LSPS5Message::Response(req_id.clone(), response);
478-
479-
{
480-
let outer_state_lock = client.per_peer_state.read().unwrap();
481-
let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap();
482-
assert!(peer_state.pending_set_webhook_requests.contains_key(&req_id));
483-
}
484-
485-
client.handle_message(response_msg, &peer).unwrap();
486-
487-
{
488-
let outer_state_lock = client.per_peer_state.read().unwrap();
489-
let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap();
490-
assert!(!peer_state.pending_set_webhook_requests.contains_key(&req_id));
491-
}
492-
}
493-
494488
#[test]
495489
fn test_unknown_request_id_handling() {
496490
let (client, _message_queue, _, peer, _) = setup_test_client();
@@ -552,4 +546,68 @@ mod tests {
552546
assert!(peer_state.pending_set_webhook_requests.contains_key(&new_req_id));
553547
}
554548
}
549+
550+
#[test]
551+
fn test_peer_state_cleanup_and_recreation() {
552+
let (client, _, _, peer, _) = setup_test_client();
553+
554+
let set_webhook_req_id = client
555+
.set_webhook(peer, "test-app".to_string(), "https://example.com/hook".to_string())
556+
.unwrap();
557+
558+
let list_webhooks_req_id = client.list_webhooks(peer);
559+
560+
{
561+
let state = client.per_peer_state.read().unwrap();
562+
assert!(state.contains_key(&peer));
563+
let peer_state = state.get(&peer).unwrap().lock().unwrap();
564+
assert!(peer_state.pending_set_webhook_requests.contains_key(&set_webhook_req_id));
565+
assert!(peer_state.pending_list_webhooks_requests.contains_key(&list_webhooks_req_id));
566+
}
567+
568+
let set_webhook_response = LSPS5Response::SetWebhook(SetWebhookResponse {
569+
num_webhooks: 1,
570+
max_webhooks: 5,
571+
no_change: false,
572+
});
573+
let response_msg = LSPS5Message::Response(set_webhook_req_id.clone(), set_webhook_response);
574+
// trigger cleanup but there is still a pending request
575+
// so the peer state should not be removed
576+
client.handle_message(response_msg, &peer).unwrap();
577+
578+
{
579+
let state = client.per_peer_state.read().unwrap();
580+
assert!(state.contains_key(&peer));
581+
let peer_state = state.get(&peer).unwrap().lock().unwrap();
582+
assert!(!peer_state.pending_set_webhook_requests.contains_key(&set_webhook_req_id));
583+
assert!(peer_state.pending_list_webhooks_requests.contains_key(&list_webhooks_req_id));
584+
}
585+
586+
let list_webhooks_response =
587+
LSPS5Response::ListWebhooks(crate::lsps5::msgs::ListWebhooksResponse {
588+
app_names: vec![],
589+
max_webhooks: 5,
590+
});
591+
let response_msg = LSPS5Message::Response(list_webhooks_req_id, list_webhooks_response);
592+
593+
// now the pending request is handled, so the peer state should be removed
594+
client.handle_message(response_msg, &peer).unwrap();
595+
596+
{
597+
let state = client.per_peer_state.read().unwrap();
598+
assert!(!state.contains_key(&peer));
599+
}
600+
601+
// check that it's possible to recreate the peer state by sending a new request
602+
let new_req_id = client
603+
.set_webhook(peer, "test-app-2".to_string(), "https://example.com/hook2".to_string())
604+
.unwrap();
605+
606+
{
607+
let state = client.per_peer_state.read().unwrap();
608+
assert!(state.contains_key(&peer));
609+
let peer_state = state.get(&peer).unwrap().lock().unwrap();
610+
assert!(peer_state.pending_set_webhook_requests.contains_key(&new_req_id));
611+
}
612+
}
555613
}

lightning-liquidity/src/utils/bounded_map.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ where
7474
pub(crate) fn contains_key(&self, key: &K) -> bool {
7575
self.map.contains_key(key)
7676
}
77+
78+
/// Check whether the map is empty.
79+
pub(crate) fn is_empty(&self) -> bool {
80+
self.map.is_empty()
81+
}
7782
}
7883

7984
#[cfg(test)]
@@ -87,21 +92,36 @@ mod tests {
8792
fn new_empty() {
8893
let m: BoundedMap<&str, i32> = BoundedMap::new(3);
8994
assert_eq!(m.len(), 0);
95+
assert!(m.is_empty());
9096
assert!(m.get(&"key").is_none());
9197
}
9298

9399
#[test]
94100
fn insert_within_capacity() {
95101
let mut m = BoundedMap::new(2);
102+
assert!(m.is_empty());
96103
m.insert("a", 1);
97104
m.insert("b", 2);
98105

106+
assert!(!m.is_empty());
99107
assert_eq!(m.len(), 2);
100108
assert!(m.contains_key(&"a"));
101109
assert_eq!(m.get(&"a"), Some(&1));
102110
assert_eq!(m.get(&"b"), Some(&2));
103111
}
104112

113+
#[test]
114+
fn is_empty() {
115+
let mut m: BoundedMap<&str, i32> = BoundedMap::new(3);
116+
assert!(m.is_empty());
117+
118+
m.insert("a", 1);
119+
assert!(!m.is_empty());
120+
121+
m.remove(&"a");
122+
assert!(m.is_empty());
123+
}
124+
105125
#[test]
106126
fn eviction_fifo() {
107127
let mut m = BoundedMap::new(2);
@@ -110,6 +130,7 @@ mod tests {
110130
m.insert("third", 3); // evicts "first"
111131

112132
assert_eq!(m.len(), 2);
133+
assert!(!m.is_empty());
113134
assert!(m.get(&"first").is_none());
114135
assert_eq!(m.get(&"second"), Some(&2));
115136
assert_eq!(m.get(&"third"), Some(&3));
@@ -123,6 +144,7 @@ mod tests {
123144
m.insert("old", 10); // update moves "old" to back
124145
m.insert("newer", 3); // should evict "new", not "old"
125146

147+
assert!(!m.is_empty());
126148
assert_eq!(m.get(&"old"), Some(&10));
127149
assert!(m.get(&"new").is_none());
128150
assert_eq!(m.get(&"newer"), Some(&3));
@@ -133,38 +155,50 @@ mod tests {
133155
let mut m = BoundedMap::new(2);
134156
m.insert("keep", 1);
135157
m.insert("remove", 2);
158+
assert!(!m.is_empty());
136159

137160
assert_eq!(m.remove(&"remove"), Some(2));
138161
assert_eq!(m.len(), 1);
162+
assert!(!m.is_empty());
139163
assert!(m.get(&"remove").is_none());
140164
assert_eq!(m.get(&"keep"), Some(&1));
165+
166+
m.remove(&"keep");
167+
assert!(m.is_empty());
141168
}
142169

143170
#[test]
144171
fn remove_nonexistent() {
145172
let mut m = BoundedMap::new(2);
146173
m.insert("exists", 1);
174+
assert!(!m.is_empty());
147175

148176
assert_eq!(m.remove(&"missing"), None);
149177
assert_eq!(m.len(), 1);
178+
assert!(!m.is_empty());
150179
}
151180

152181
#[test]
153182
fn zero_capacity() {
154183
let mut m = BoundedMap::new(0);
184+
assert!(m.is_empty());
155185
m.insert("any", 42);
156186

157187
assert_eq!(m.len(), 0);
188+
assert!(m.is_empty());
158189
assert!(m.get(&"any").is_none());
159190
}
160191

161192
#[test]
162193
fn capacity_one() {
163194
let mut m = BoundedMap::new(1);
195+
assert!(m.is_empty());
164196
m.insert("first", 1);
197+
assert!(!m.is_empty());
165198
assert_eq!(m.get(&"first"), Some(&1));
166199

167200
m.insert("second", 2);
201+
assert!(!m.is_empty());
168202
assert!(m.get(&"first").is_none());
169203
assert_eq!(m.get(&"second"), Some(&2));
170204
}

0 commit comments

Comments
 (0)