From 40a8ee7cf537065ac32f84cd684ad0e05e4f6b56 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 26 Feb 2024 15:04:09 +0100 Subject: [PATCH] Add `force` flag to allow channel force-closure .. which we somehow so far ommitted exposing in the API. We now introduce a `force` flag to `close_channel` and broadcast if the counterparty is not trusted. --- .../lightningdevkit/ldknode/LibraryTest.kt | 2 +- bindings/ldk_node.udl | 2 +- bindings/python/src/ldk_node/test_ldk_node.py | 2 +- src/lib.rs | 65 +++++++++++++++---- tests/common.rs | 2 +- tests/integration_tests_cln.rs | 2 +- 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index df6593b25..6fdaf8f2e 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -239,7 +239,7 @@ class LibraryTest { assert(node1.listPayments().size == 1) assert(node2.listPayments().size == 1) - node2.closeChannel(userChannelId, nodeId1) + node2.closeChannel(userChannelId, nodeId1, false) val channelClosedEvent1 = node1.waitNextEvent() println("Got event: $channelClosedEvent1") diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 52bdc919e..943220668 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -68,7 +68,7 @@ interface LDKNode { [Throws=NodeError] UserChannelId connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel); [Throws=NodeError] - void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); + void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, boolean force); [Throws=NodeError] void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config); [Throws=NodeError] diff --git a/bindings/python/src/ldk_node/test_ldk_node.py b/bindings/python/src/ldk_node/test_ldk_node.py index 864ef7b43..138ea135f 100644 --- a/bindings/python/src/ldk_node/test_ldk_node.py +++ b/bindings/python/src/ldk_node/test_ldk_node.py @@ -198,7 +198,7 @@ def test_channel_full_cycle(self): print("EVENT:", payment_received_event_2) node_2.event_handled() - node_2.close_channel(channel_ready_event_2.user_channel_id, node_id_1) + node_2.close_channel(channel_ready_event_2.user_channel_id, node_id_1, false) channel_closed_event_1 = node_1.wait_next_event() assert isinstance(channel_closed_event_1, Event.CHANNEL_CLOSED) diff --git a/src/lib.rs b/src/lib.rs index c9910959b..4b68e5100 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1054,27 +1054,66 @@ impl Node { } /// Close a previously opened channel. + /// + /// If `force` is set to `true`, we will force-close the channel, potentially broadcasting our + /// latest state. Note that in contrast to cooperative closure, force-closing will have the + /// channel funds time-locked, i.e., they will only be available after the counterparty had + /// time to contest our claim. Force-closing channels also more costly in terms of on-chain + /// fees. So cooperative closure should always be preferred (and tried first). + /// + /// Broadcasting the closing transactions will be omitted for Anchor channels if we trust the + /// counterparty to broadcast for us (see [`AnchorChannelsConfig::trusted_peers_no_reserve`] + /// for more information). pub fn close_channel( - &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, + &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, force: bool, ) -> Result<(), Error> { let open_channels = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0) { - match self - .channel_manager - .close_channel(&channel_details.channel_id, &counterparty_node_id) - { - Ok(_) => { - // Check if this was the last open channel, if so, forget the peer. - if open_channels.len() == 1 { - self.peer_store.remove_peer(&counterparty_node_id)?; - } - Ok(()) - }, - Err(_) => Err(Error::ChannelClosingFailed), + if force { + if self.config.anchor_channels_config.as_ref().map_or(false, |acc| { + acc.trusted_peers_no_reserve.contains(&counterparty_node_id) + }) { + self.channel_manager + .force_close_without_broadcasting_txn( + &channel_details.channel_id, + &counterparty_node_id, + ) + .map_err(|e| { + log_error!( + self.logger, + "Failed to force-close channel to trusted peer: {:?}", + e + ); + Error::ChannelClosingFailed + })?; + } else { + self.channel_manager + .force_close_broadcasting_latest_txn( + &channel_details.channel_id, + &counterparty_node_id, + ) + .map_err(|e| { + log_error!(self.logger, "Failed to force-close channel: {:?}", e); + Error::ChannelClosingFailed + })?; + } + } else { + self.channel_manager + .close_channel(&channel_details.channel_id, &counterparty_node_id) + .map_err(|e| { + log_error!(self.logger, "Failed to close channel: {:?}", e); + Error::ChannelClosingFailed + })?; } + + // Check if this was the last open channel, if so, forget the peer. + if open_channels.len() == 1 { + self.peer_store.remove_peer(&counterparty_node_id)?; + } + Ok(()) } else { Ok(()) } diff --git a/tests/common.rs b/tests/common.rs index 09de2ce8c..c56e67fa7 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -530,7 +530,7 @@ pub(crate) fn do_channel_full_cycle( ); println!("\nB close_channel"); - node_b.close_channel(&user_channel_id, node_a.node_id()).unwrap(); + node_b.close_channel(&user_channel_id, node_a.node_id(), false).unwrap(); expect_event!(node_a, ChannelClosed); expect_event!(node_b, ChannelClosed); diff --git a/tests/integration_tests_cln.rs b/tests/integration_tests_cln.rs index a82ec2dc7..dc4433733 100644 --- a/tests/integration_tests_cln.rs +++ b/tests/integration_tests_cln.rs @@ -111,7 +111,7 @@ fn test_cln() { cln_client.pay(&ldk_invoice.to_string(), Default::default()).unwrap(); common::expect_event!(node, PaymentReceived); - node.close_channel(&user_channel_id, cln_node_id).unwrap(); + node.close_channel(&user_channel_id, cln_node_id, false).unwrap(); common::expect_event!(node, ChannelClosed); node.stop().unwrap(); }