Skip to content

Commit 2ffb12d

Browse files
committed
Add deregistration cleanup for OnionMessageInterceptor in LSPS2
When intercept SCIDs are removed during cleanup, also clean up the handler-level `peer_by_intercept_scid` map and deregister the peer from onion message interception if they have no remaining active SCIDs. Cleanup is added in all relevant paths: - `prune_expired_request_state()` now returns pruned SCIDs - `peer_disconnected()` cleans up after pruning - `htlc_intercepted()` error path (fixes existing TODO) - `channel_open_abandoned()` cleans up after removing the SCID - `persist()` cleans up both `peer_by_intercept_scid` and `peer_by_channel_id` when removing a peer entry entirely Co-Authored-By: HAL 9000
1 parent 89655a5 commit 2ffb12d

1 file changed

Lines changed: 79 additions & 6 deletions

File tree

lightning-liquidity/src/lsps2/service.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -632,17 +632,20 @@ impl PeerState {
632632
});
633633
}
634634

635-
fn prune_expired_request_state(&mut self) {
635+
fn prune_expired_request_state(&mut self) -> Vec<u64> {
636+
let mut pruned_scids = Vec::new();
636637
self.outbound_channels_by_intercept_scid.retain(|intercept_scid, entry| {
637638
if entry.is_prunable() {
638639
// We abort the flow, and prune any data kept.
639640
self.intercept_scid_by_channel_id.retain(|_, iscid| intercept_scid != iscid);
640641
self.intercept_scid_by_user_channel_id.retain(|_, iscid| intercept_scid != iscid);
641642
self.needs_persist |= true;
643+
pruned_scids.push(*intercept_scid);
642644
return false;
643645
}
644646
true
645647
});
648+
pruned_scids
646649
}
647650

648651
fn pending_requests_and_channels(&self) -> usize {
@@ -788,6 +791,29 @@ where
788791
&self.config
789792
}
790793

794+
/// Cleans up `peer_by_intercept_scid` entries for the given SCIDs, and deregisters the peer
795+
/// from onion message interception if they have no remaining active intercept SCIDs.
796+
fn cleanup_intercept_scids(
797+
&self, counterparty_node_id: &PublicKey, pruned_scids: &[u64], has_remaining_channels: bool,
798+
) {
799+
if pruned_scids.is_empty() {
800+
return;
801+
}
802+
803+
{
804+
let mut peer_by_intercept_scid = self.peer_by_intercept_scid.write().unwrap();
805+
for scid in pruned_scids {
806+
peer_by_intercept_scid.remove(scid);
807+
}
808+
}
809+
810+
if !has_remaining_channels {
811+
if let Some(ref interceptor) = self.onion_message_interceptor {
812+
interceptor.deregister_peer_for_interception(counterparty_node_id);
813+
}
814+
}
815+
}
816+
791817
/// Returns whether the peer has any active LSPS2 requests.
792818
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
793819
let outer_state_lock = self.per_peer_state.read().unwrap();
@@ -1067,7 +1093,15 @@ where
10671093
peer_state
10681094
.outbound_channels_by_intercept_scid
10691095
.remove(&intercept_scid);
1070-
// TODO: cleanup peer_by_intercept_scid
1096+
let has_remaining =
1097+
!peer_state.outbound_channels_by_intercept_scid.is_empty();
1098+
drop(peer_state);
1099+
drop(outer_state_lock);
1100+
self.cleanup_intercept_scids(
1101+
counterparty_node_id,
1102+
&[intercept_scid],
1103+
has_remaining,
1104+
);
10711105
return Err(APIError::APIMisuseError { err: e.err });
10721106
},
10731107
}
@@ -1286,7 +1320,7 @@ where
12861320
pub async fn channel_open_abandoned(
12871321
&self, counterparty_node_id: &PublicKey, user_channel_id: u128,
12881322
) -> Result<(), APIError> {
1289-
{
1323+
let (intercept_scid, has_remaining) = {
12901324
let outer_state_lock = self.per_peer_state.read().unwrap();
12911325
let inner_state_lock = outer_state_lock.get(counterparty_node_id).ok_or_else(|| {
12921326
APIError::APIMisuseError {
@@ -1333,7 +1367,11 @@ where
13331367
peer_state.outbound_channels_by_intercept_scid.remove(&intercept_scid);
13341368
peer_state.intercept_scid_by_channel_id.retain(|_, &mut scid| scid != intercept_scid);
13351369
peer_state.needs_persist |= true;
1336-
}
1370+
let has_remaining = !peer_state.outbound_channels_by_intercept_scid.is_empty();
1371+
(intercept_scid, has_remaining)
1372+
};
1373+
1374+
self.cleanup_intercept_scids(counterparty_node_id, &[intercept_scid], has_remaining);
13371375

13381376
self.persist_peer_state(*counterparty_node_id).await.map_err(|e| {
13391377
APIError::APIMisuseError {
@@ -1817,17 +1855,32 @@ where
18171855
{
18181856
// First build a list of peers to persist and prune with the read lock. This allows
18191857
// us to avoid the write lock unless we actually need to remove a node.
1858+
let mut all_pruned_scids = Vec::new();
18201859
let outer_state_lock = self.per_peer_state.read().unwrap();
18211860
for (counterparty_node_id, inner_state_lock) in outer_state_lock.iter() {
18221861
let mut peer_state_lock = inner_state_lock.lock().unwrap();
1823-
peer_state_lock.prune_expired_request_state();
1862+
let pruned_scids = peer_state_lock.prune_expired_request_state();
1863+
if !pruned_scids.is_empty() {
1864+
let has_remaining =
1865+
!peer_state_lock.outbound_channels_by_intercept_scid.is_empty();
1866+
all_pruned_scids.push((*counterparty_node_id, pruned_scids, has_remaining));
1867+
}
18241868
let is_prunable = peer_state_lock.is_prunable();
18251869
if is_prunable {
18261870
need_remove.push(*counterparty_node_id);
18271871
} else if peer_state_lock.needs_persist {
18281872
need_persist.push(*counterparty_node_id);
18291873
}
18301874
}
1875+
drop(outer_state_lock);
1876+
1877+
for (counterparty_node_id, pruned_scids, has_remaining) in all_pruned_scids {
1878+
self.cleanup_intercept_scids(
1879+
&counterparty_node_id,
1880+
&pruned_scids,
1881+
has_remaining,
1882+
);
1883+
}
18311884
}
18321885

18331886
for counterparty_node_id in need_persist.into_iter() {
@@ -1838,6 +1891,7 @@ where
18381891

18391892
for counterparty_node_id in need_remove {
18401893
let mut future_opt = None;
1894+
let mut was_removed = false;
18411895
{
18421896
// We need to take the `per_peer_state` write lock to remove an entry, but also
18431897
// have to hold it until after the `remove` call returns (but not through
@@ -1849,6 +1903,7 @@ where
18491903
let state = entry.get_mut().get_mut().unwrap();
18501904
if state.is_prunable() {
18511905
entry.remove();
1906+
was_removed = true;
18521907
let key = counterparty_node_id.to_string();
18531908
future_opt = Some(self.kv_store.remove(
18541909
LIQUIDITY_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
@@ -1866,6 +1921,20 @@ where
18661921
debug_assert!(false);
18671922
}
18681923
}
1924+
if was_removed {
1925+
// Clean up handler-level maps for the removed peer.
1926+
self.peer_by_intercept_scid
1927+
.write()
1928+
.unwrap()
1929+
.retain(|_, node_id| *node_id != counterparty_node_id);
1930+
self.peer_by_channel_id
1931+
.write()
1932+
.unwrap()
1933+
.retain(|_, node_id| *node_id != counterparty_node_id);
1934+
if let Some(ref interceptor) = self.onion_message_interceptor {
1935+
interceptor.deregister_peer_for_interception(&counterparty_node_id);
1936+
}
1937+
}
18691938
if let Some(future) = future_opt {
18701939
future.await?;
18711940
did_persist = true;
@@ -1893,7 +1962,11 @@ where
18931962
// We clean up the peer state, but leave removing the peer entry to the prune logic in
18941963
// `persist` which removes it from the store.
18951964
peer_state_lock.prune_pending_requests();
1896-
peer_state_lock.prune_expired_request_state();
1965+
let pruned_scids = peer_state_lock.prune_expired_request_state();
1966+
let has_remaining = !peer_state_lock.outbound_channels_by_intercept_scid.is_empty();
1967+
drop(peer_state_lock);
1968+
drop(outer_state_lock);
1969+
self.cleanup_intercept_scids(&counterparty_node_id, &pruned_scids, has_remaining);
18971970
}
18981971
}
18991972

0 commit comments

Comments
 (0)