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.
AI, to be verified
Repository
ldk-nodeVersion
Observed on
upstream/mainatb1337d2f1665716313d2ccf849a04114f8b3ca19,Merge pull request #1077 from jkczyz/2026-08-bump-ldk-0.3-rc1.The current
Cargo.tomlpins rust-lightning crates to4219131b05e44663a1b417e4f6cf09c7740ead2a.Problem
Node::close_channelreturnsOk(())when the suppliedUserChannelIddoes 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_channelappears to have the same issue because it sharesclose_channel_internal: iflist_channels_with_counterpartyreturns no channel whoseuser_channel_idmatches, the helper falls through toOk(()).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
UserChannelIdand counterparty,close_channelandforce_close_channelshould return an error, for exampleErr(NodeError::ChannelClosingFailed)or a more specific channel-not-found error.Actual Behavior
close_channelreturnsOk(())even though no matching channel is found and the channel is not moved into shutdown.Reproduction
Add
UserChannelIdto the existing import intests/integration_tests_rust.rs:Then add this test:
Run:
CARGO_PROFILE_DEV_DEBUG=0 cargo test close_unknown_user_channel_id_errors --test integration_tests_rust -- --nocaptureResult:
Likely Cause
close_channel_internalonly callsChannelManager::close_channelorChannelManager::force_close_broadcasting_latest_txninside theif let Some(channel_details)branch. When no matching channel is found, execution skips the branch and returnsOk(())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
shutdownhandling, including#4947and#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_internalcannot find a matching channel for the suppliedUserChannelIdand counterparty. Sinceforce_close_channeluses the same helper, the fix should cover both cooperative and force-close requests.