Skip to content

Commit c7b80b8

Browse files
committed
fix(close_channel): error when no matching channel found
Closes #1084 Previously close_channel and force_close_channel returned Ok(()) when no matching UserChannelId was found for the counterparty, silently succeeding without initiating a close. Now returns Err(ChannelClosingFailed), matching update_channel_config. AI-assisted: generated by Sera (Hermes Agent), verified manually. Tests compile; integration test requires bitcoind/electrs binaries (architecturally incompatible electrs binary in this cron environment, same failure affects all integration tests) Signed-off-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
1 parent c54d971 commit c7b80b8

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,9 +2123,11 @@ impl Node {
21232123
// dropping it. This lets `channel_reestablish` drive the recovery flow, which is
21242124
// especially important against LND peers that don't always handle force-closure
21252125
// error messages correctly.
2126-
}
21272126

2128-
Ok(())
2127+
Ok(())
2128+
} else {
2129+
Err(Error::ChannelClosingFailed)
2130+
}
21292131
}
21302132

21312133
/// Update the config for a previously opened channel.

tests/integration_tests_rust.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use ldk_node::payment::{
4343
ConfirmationStatus, PayerProofOptions, PaymentDetails, PaymentDirection, PaymentKind,
4444
PaymentStatus, TransactionType, UnifiedPaymentResult,
4545
};
46-
use ldk_node::{BuildError, Builder, Event, Node, NodeError, ReserveType};
46+
use ldk_node::{BuildError, Builder, Event, Node, NodeError, ReserveType, UserChannelId};
4747
use lightning::ln::channelmanager::PaymentId;
4848
use lightning::routing::gossip::{NodeAlias, NodeId};
4949
use lightning::routing::router::RouteParametersConfig;
@@ -544,6 +544,47 @@ async fn peer_removed_when_counterparty_force_closes_last_channel() {
544544
);
545545
}
546546

547+
/// Regression test for issue #1084: `Node::close_channel` and
548+
/// `Node::force_close_channel` returned `Ok(())` when the supplied
549+
/// `UserChannelId` did not match any channel for the counterparty, silently
550+
/// succeeding without initiating a close.
551+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
552+
async fn close_unknown_user_channel_id_errors() {
553+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
554+
let chain_source = random_chain_source(&bitcoind, &electrsd);
555+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, false);
556+
557+
let address_a = node_a.onchain_payment().new_address().unwrap();
558+
premine_and_distribute_funds(
559+
&bitcoind.client,
560+
&electrsd.client,
561+
vec![address_a],
562+
Amount::from_sat(5_000_000),
563+
)
564+
.await;
565+
node_a.sync_wallets().unwrap();
566+
567+
open_channel(&node_a, &node_b, 4_000_000, false, &electrsd).await;
568+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
569+
node_a.sync_wallets().unwrap();
570+
node_b.sync_wallets().unwrap();
571+
572+
let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id());
573+
let _user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id());
574+
let unknown_user_channel_id = UserChannelId(user_channel_id_a.0 ^ 1);
575+
576+
assert_eq!(
577+
node_a.close_channel(&unknown_user_channel_id, node_b.node_id()),
578+
Err(NodeError::ChannelClosingFailed)
579+
);
580+
581+
// force_close_channel shares close_channel_internal and should also error.
582+
assert_eq!(
583+
node_a.force_close_channel(&unknown_user_channel_id, node_b.node_id(), None),
584+
Err(NodeError::ChannelClosingFailed)
585+
);
586+
}
587+
547588
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
548589
async fn channel_full_cycle_0conf() {
549590
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)