@@ -34,7 +34,7 @@ use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum};
3434use lightning_liquidity:: lsps2:: utils:: compute_opening_fee;
3535use lightning_types:: payment:: { PaymentHash , PaymentPreimage } ;
3636
37- use crate :: config:: { may_announce_channel, Config } ;
37+ use crate :: config:: { may_announce_channel, Config , PEER_RECONNECTION_INTERVAL } ;
3838use crate :: connection:: ConnectionManager ;
3939use crate :: data_store:: DataStoreUpdateResult ;
4040use crate :: fee_estimator:: ConfirmationTarget ;
@@ -583,6 +583,68 @@ where
583583 }
584584 }
585585
586+ fn remove_peer_after_reconnect ( & self , peer_info : PeerInfo , closed_channel_id : ChannelId ) {
587+ let channel_manager = Arc :: clone ( & self . channel_manager ) ;
588+ let connection_manager = Arc :: clone ( & self . connection_manager ) ;
589+ let peer_store = Arc :: clone ( & self . peer_store ) ;
590+ let logger = self . logger . clone ( ) ;
591+ self . runtime . spawn_cancellable_background_task ( async move {
592+ let has_other_channels = || {
593+ channel_manager
594+ . list_channels_with_counterparty ( & peer_info. node_id )
595+ . iter ( )
596+ . any ( |c| c. channel_id != closed_channel_id)
597+ } ;
598+
599+ if peer_store. get_peer ( & peer_info. node_id ) . is_none ( ) || has_other_channels ( ) {
600+ return ;
601+ }
602+
603+ // Ensure a connected peer cannot be mistaken for a completed recovery reconnect.
604+ // With no other channels left, reconnecting once gives `channel_reestablish` a chance
605+ // to retransmit the force-close error before we stop persisting the peer.
606+ connection_manager. disconnect_peer ( peer_info. node_id ) ;
607+
608+ loop {
609+ if peer_store. get_peer ( & peer_info. node_id ) . is_none ( ) || has_other_channels ( ) {
610+ return ;
611+ }
612+
613+ match connection_manager
614+ . connect_peer_if_necessary ( peer_info. node_id , peer_info. address . clone ( ) )
615+ . await
616+ {
617+ Ok ( ( ) ) => {
618+ if peer_store. get_peer ( & peer_info. node_id ) . is_none ( ) || has_other_channels ( )
619+ {
620+ return ;
621+ }
622+ if let Err ( e) = peer_store. remove_peer ( & peer_info. node_id ) . await {
623+ log_error ! (
624+ logger,
625+ "Failed to remove peer {} from peer store: {}" ,
626+ peer_info. node_id,
627+ e
628+ ) ;
629+ } else {
630+ return ;
631+ }
632+ } ,
633+ Err ( e) => {
634+ log_debug ! (
635+ logger,
636+ "Failed to reconnect peer {} before removing from peer store: {}" ,
637+ peer_info. node_id,
638+ e
639+ ) ;
640+ } ,
641+ }
642+
643+ tokio:: time:: sleep ( PEER_RECONNECTION_INTERVAL ) . await ;
644+ }
645+ } ) ;
646+ }
647+
586648 async fn fail_claimable_payment (
587649 & self , payment_id : PaymentId , payment_hash : & PaymentHash ,
588650 ) -> Result < ( ) , ReplayEvent > {
@@ -1627,25 +1689,24 @@ where
16271689 let counterparty_node_id = counterparty_node_id
16281690 . expect ( "counterparty_node_id is always set since LDK 0.0.117" ) ;
16291691
1630- // Drop the peer once its last channel with us has reached a terminal state
1631- // that reconnection cannot recover. Every closure reason is terminal except
1632- // `HolderForceClosed`: when *we* force-close, we keep reconnecting so that
1633- // `channel_reestablish` can drive recovery (see `Node::close_channel_internal`).
1692+ // Drop the peer once its last channel with us has reached a terminal state.
1693+ // For `HolderForceClosed`, retain it through one recovery reconnect so that
1694+ // `channel_reestablish` can retransmit the force-close error before cleanup.
16341695 // This also cleans up peers persisted for a channel that closed before funding
16351696 // (e.g. `CounterpartyCoopClosedUnfundedChannel`), which would otherwise be
16361697 // retried forever.
16371698 // We exclude `channel_id` from the count because LDK emits `ChannelClosed`
16381699 // before removing it from its internal list.
1639- let dont_reconnect = ! matches ! ( reason , ClosureReason :: HolderForceClosed { .. } ) ;
1640-
1641- if dont_reconnect {
1642- let has_other_channels = self
1643- . channel_manager
1644- . list_channels_with_counterparty ( & counterparty_node_id )
1645- . iter ( )
1646- . any ( |c| c . channel_id != channel_id ) ;
1647-
1648- if !has_other_channels {
1700+ let has_other_channels = self
1701+ . channel_manager
1702+ . list_channels_with_counterparty ( & counterparty_node_id )
1703+ . iter ( )
1704+ . any ( |c| c . channel_id != channel_id ) ;
1705+
1706+ let peer_to_reconnect = if !has_other_channels {
1707+ if matches ! ( reason , ClosureReason :: HolderForceClosed { .. } ) {
1708+ self . peer_store . get_peer ( & counterparty_node_id )
1709+ } else {
16491710 if let Err ( e) = self . peer_store . remove_peer ( & counterparty_node_id) . await {
16501711 log_error ! (
16511712 self . logger,
@@ -1655,8 +1716,11 @@ where
16551716 ) ;
16561717 return Err ( ReplayEvent ( ) ) ;
16571718 }
1719+ None
16581720 }
1659- }
1721+ } else {
1722+ None
1723+ } ;
16601724
16611725 let event = Event :: ChannelClosed {
16621726 channel_id,
@@ -1672,6 +1736,10 @@ where
16721736 return Err ( ReplayEvent ( ) ) ;
16731737 } ,
16741738 } ;
1739+
1740+ if let Some ( peer_info) = peer_to_reconnect {
1741+ self . remove_peer_after_reconnect ( peer_info, channel_id) ;
1742+ }
16751743 } ,
16761744 LdkEvent :: DiscardFunding { channel_id, funding_info } => {
16771745 if let FundingInfo :: Contribution { inputs : _, outputs } = funding_info {
0 commit comments