Skip to content

close_channel returns Ok when no channel matches the user_channel_id #1084

Description

@joostjager

AI, to be verified

Repository

ldk-node

Version

Observed on upstream/main at b1337d2f1665716313d2ccf849a04114f8b3ca19, Merge pull request #1077 from jkczyz/2026-08-bump-ldk-0.3-rc1.

The current Cargo.toml pins rust-lightning crates to 4219131b05e44663a1b417e4f6cf09c7740ead2a.

Problem

Node::close_channel returns Ok(()) when the supplied UserChannelId does not match any channel for the supplied counterparty. No call into LDK is made in that case, so no cooperative close is initiated even though the caller sees success.

Node::force_close_channel appears to have the same issue because it shares close_channel_internal: if list_channels_with_counterparty returns no channel whose user_channel_id matches, the helper falls through to Ok(()).

This can make callers treat a stale channel handle, mismatched counterparty, or otherwise incorrect close target as accepted while the channel remains open.

Expected Behavior

If no channel matches the supplied UserChannelId and counterparty, close_channel and force_close_channel should return an error, for example Err(NodeError::ChannelClosingFailed) or a more specific channel-not-found error.

Actual Behavior

close_channel returns Ok(()) even though no matching channel is found and the channel is not moved into shutdown.

Reproduction

Add UserChannelId to the existing import in tests/integration_tests_rust.rs:

use ldk_node::{
	BuildError, Builder, ChannelShutdownState, Event, Node, NodeError, ReserveType, UserChannelId,
};

Then add this test:

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn close_unknown_user_channel_id_errors() {
	let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
	let chain_source = random_chain_source(&bitcoind, &electrsd);
	let (node_a, node_b) = setup_two_nodes(&chain_source, false, false);

	let address_a = node_a.onchain_payment().new_address().unwrap();
	premine_and_distribute_funds(
		&bitcoind.client,
		&electrsd.client,
		vec![address_a],
		Amount::from_sat(5_000_000),
	)
	.await;
	node_a.sync_wallets().unwrap();

	open_channel(&node_a, &node_b, 4_000_000, false, &electrsd).await;
	generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
	node_a.sync_wallets().unwrap();
	node_b.sync_wallets().unwrap();

	let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id());
	let _user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id());
	let unknown_user_channel_id = UserChannelId(user_channel_id_a.0 ^ 1);

	assert_eq!(
		node_a.close_channel(&unknown_user_channel_id, node_b.node_id()),
		Err(NodeError::ChannelClosingFailed)
	);
}

Run:

CARGO_PROFILE_DEV_DEBUG=0 cargo test close_unknown_user_channel_id_errors --test integration_tests_rust -- --nocapture

Result:

thread 'close_unknown_user_channel_id_errors' panicked at tests/integration_tests_rust.rs:
assertion `left == right` failed
  left: Ok(())
 right: Err(ChannelClosingFailed)

Likely Cause

close_channel_internal only calls ChannelManager::close_channel or ChannelManager::force_close_broadcasting_latest_txn inside the if let Some(channel_details) branch. When no matching channel is found, execution skips the branch and returns Ok(()) unconditionally.

Related Searches

I did not find an open ldk-node issue covering this no-match success case. Existing rust-lightning issues around cooperative close and shutdown handling, including #4947 and #4950, cover different lower-level flows involving monitor-blocked HTLC updates. This ldk-node repro does not reach those lower-level paths because no LDK close method is called when the channel handle misses.

Suggested Fix

Return an error when close_channel_internal cannot find a matching channel for the supplied UserChannelId and counterparty. Since force_close_channel uses the same helper, the fix should cover both cooperative and force-close requests.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions