Skip to content

Commit 7609c26

Browse files
committed
event: remove peer from store on counterparty-initiated force-close
1 parent b24a905 commit 7609c26

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

src/event.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,44 @@ where
15851585
} => {
15861586
log_info!(self.logger, "Channel {} closed due to: {}", channel_id, reason);
15871587

1588+
// If the counterparty initiated closure of their last remaining channel
1589+
// with us, remove them from the peer store so we stop trying to reconnect.
1590+
//
1591+
// If we initiated the closure, keep them in the peer store so the
1592+
// background reconnection task fires and we can complete the
1593+
// channel_reestablish recovery flow. This matters especially for LND
1594+
// peers, which need us to reconnect to recover from force-closures.
1595+
//
1596+
// We exclude `channel_id` from the remaining-channel check because LDK
1597+
// fires ChannelClosed before removing the channel from its internal list,
1598+
// so list_channels_with_counterparty still includes the closing channel.
1599+
if let Some(counterparty_node_id) = counterparty_node_id {
1600+
let counterparty_initiated = matches!(
1601+
reason,
1602+
ClosureReason::CounterpartyForceClosed { .. }
1603+
| ClosureReason::CounterpartyInitiatedCooperativeClosure
1604+
);
1605+
1606+
if counterparty_initiated {
1607+
let has_other_channels = self
1608+
.channel_manager
1609+
.list_channels_with_counterparty(&counterparty_node_id)
1610+
.iter()
1611+
.any(|c| c.channel_id != channel_id);
1612+
1613+
if !has_other_channels {
1614+
if let Err(e) = self.peer_store.remove_peer(&counterparty_node_id) {
1615+
log_error!(
1616+
self.logger,
1617+
"Failed to remove peer {} from peer store: {}",
1618+
counterparty_node_id,
1619+
e
1620+
);
1621+
}
1622+
}
1623+
}
1624+
}
1625+
15881626
let event = Event::ChannelClosed {
15891627
channel_id,
15901628
user_channel_id: UserChannelId(user_channel_id),

src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,8 +1841,16 @@ impl Node {
18411841
})?;
18421842
}
18431843

1844-
// Check if this was the last open channel, if so, forget the peer.
1845-
if open_channels.len() == 1 {
1844+
// If this was the last open channel and we're closing cooperatively, forget the peer
1845+
// since we have no further reason to reconnect.
1846+
1847+
// For force-closes we intentionally keep the peer in the store so the background reconnection
1848+
// task keeps firing and can drive the channel_reestablish recovery flow.
1849+
// This is especially important against LND peers, which don't always handle force-closure error messages correctly.
1850+
1851+
//Note that this means a force-closed peer is retained until the user explicitly calls Node::disconnect.
1852+
1853+
if open_channels.len() == 1 && !force {
18461854
self.peer_store.remove_peer(&counterparty_node_id)?;
18471855
}
18481856
}

tests/common/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,20 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
15451545
node_b.sync_wallets().unwrap();
15461546
}
15471547

1548+
if force_close {
1549+
// Peer retained after local force-close to allow channel_reestablish recovery.
1550+
assert!(
1551+
node_a.list_peers().iter().any(|p| p.node_id == node_b.node_id() && p.is_persisted),
1552+
"node_b should remain persisted in node_a peer store after locally-initiated force-close"
1553+
);
1554+
} else {
1555+
// Peer removed after cooperative close — no further reason to reconnect.
1556+
assert!(
1557+
!node_a.list_peers().iter().any(|p| p.node_id == node_b.node_id() && p.is_persisted),
1558+
"node_b should be removed from node_a peer store after cooperative close"
1559+
);
1560+
}
1561+
15481562
let sum_of_all_payments_sat = (push_msat
15491563
+ invoice_amount_1_msat
15501564
+ overpaid_amount_msat

0 commit comments

Comments
 (0)