Skip to content

Commit

Permalink
Add force flag to allow channel force-closure
Browse files Browse the repository at this point in the history
.. 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.
  • Loading branch information
tnull committed Mar 12, 2024
1 parent 71d6a7e commit 995109b
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ interface Node {
[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]
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/src/ldk_node/test_ldk_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
65 changes: 52 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,27 +1146,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(())
}
Expand Down
2 changes: 1 addition & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
assert_eq!(node_b.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat));

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);

Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests_cln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

0 comments on commit 995109b

Please sign in to comment.