From 6b681bbd1a10c6fb5caa814f3ed1200d739b7a81 Mon Sep 17 00:00:00 2001 From: LingX Date: Fri, 19 Jun 2026 14:12:46 -0400 Subject: [PATCH 1/2] Expose reserve on open channel requests --- lightning/src/ln/channel_open_tests.rs | 70 +++++++++++++++++-- lightning/src/ln/channelmanager.rs | 31 +++++++- lightning/src/ln/msgs.rs | 6 +- .../3909-open-channel-request-reserve.txt | 9 +++ 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 pending_changelog/3909-open-channel-request-reserve.txt diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index 2c048c9906c..175fe7d1bef 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -15,9 +15,9 @@ use crate::chain::transaction::OutPoint; use crate::chain::{self, ChannelMonitorUpdateStatus}; use crate::events::{ClosureReason, Event, FundingInfo}; use crate::ln::channel::{ - get_holder_selected_channel_reserve_satoshis, ChannelError, InboundV1Channel, - OutboundV1Channel, COINBASE_MATURITY, MIN_THEIR_CHAN_RESERVE_SATOSHIS, - UNFUNDED_CHANNEL_AGE_LIMIT_TICKS, + get_holder_selected_channel_reserve_satoshis, get_v2_channel_reserve_satoshis, ChannelError, + InboundV1Channel, OutboundV1Channel, COINBASE_MATURITY, MIN_CHAN_DUST_LIMIT_SATOSHIS, + MIN_THEIR_CHAN_RESERVE_SATOSHIS, UNFUNDED_CHANNEL_AGE_LIMIT_TICKS, }; use crate::ln::channelmanager::{ self, TrustedChannelFeatures, BREAKDOWN_TIMEOUT, MAX_UNFUNDED_CHANNEL_PEERS, @@ -233,15 +233,71 @@ fn do_test_manual_inbound_accept_with_override( nodes[2].node.handle_open_channel(node_a, &open_channel_msg); let events = nodes[2].node.get_and_clear_pending_events(); match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => nodes[2] - .node - .accept_inbound_channel(&temporary_channel_id, &node_a, 23, config_overrides) - .unwrap(), + Event::OpenChannelRequest { temporary_channel_id, ref params, .. } => { + assert_eq!(params.channel_reserve_satoshis, open_channel_msg.channel_reserve_satoshis); + nodes[2] + .node + .accept_inbound_channel(&temporary_channel_id, &node_a, 23, config_overrides) + .unwrap() + }, _ => panic!("Unexpected event"), } get_event_msg!(nodes[2], MessageSendEvent::SendAcceptChannel, node_a) } +#[test] +fn test_open_channel_request_exposes_v2_channel_reserve() { + let mut dual_fund_cfg = test_default_channel_config(); + dual_fund_cfg.enable_dual_funded_channels = true; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[Some(dual_fund_cfg.clone()), Some(dual_fund_cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a = nodes[0].node.get_our_node_id(); + let node_b = nodes[1].node.get_our_node_id(); + nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + let funding_feerate_sat_per_1000_weight = + open_channel.common_fields.commitment_feerate_sat_per_1000_weight; + let second_per_commitment_point = open_channel.common_fields.first_per_commitment_point; + let mut open_channel_v2 = msgs::OpenChannelV2 { + common_fields: open_channel.common_fields, + funding_feerate_sat_per_1000_weight, + locktime: 0, + second_per_commitment_point, + require_confirmed_inputs: None, + disable_channel_reserve: None, + }; + + nodes[1].node.handle_open_channel_v2(node_a, &open_channel_v2); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { ref params, .. } => { + assert_eq!( + params.channel_reserve_satoshis, + get_v2_channel_reserve_satoshis(100_000, MIN_CHAN_DUST_LIMIT_SATOSHIS, false) + .unwrap() + ); + }, + _ => panic!("Unexpected event"), + } + + open_channel_v2.common_fields.temporary_channel_id = + ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + open_channel_v2.disable_channel_reserve = Some(()); + nodes[1].node.handle_open_channel_v2(node_a, &open_channel_v2); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { ref params, .. } => { + assert_eq!(params.channel_reserve_satoshis, 0); + }, + _ => panic!("Unexpected event"), + } +} + #[test] fn test_anchors_zero_fee_htlc_tx_downgrade() { // Tests that if both nodes support anchors, but the remote node does not want to accept diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 49392264709..c1d768bf70e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -1910,6 +1910,34 @@ pub(super) enum OpenChannelMessageRef<'a> { V2(&'a msgs::OpenChannelV2), } +impl<'a> OpenChannelMessageRef<'a> { + fn channel_parameters(&self) -> Result { + match self { + OpenChannelMessageRef::V1(msg) => { + Ok(msg.common_fields.channel_parameters(msg.channel_reserve_satoshis)) + }, + OpenChannelMessageRef::V2(msg) => { + let channel_value_satoshis = msg.common_fields.funding_satoshis; + let channel_reserve_satoshis = channel::get_v2_channel_reserve_satoshis( + channel_value_satoshis, + channel::MIN_CHAN_DUST_LIMIT_SATOSHIS, + msg.disable_channel_reserve.is_some(), + ) + .map_err(|()| { + MsgHandleErrInternal::send_err_msg_no_close( + format!( + "The channel value {channel_value_satoshis} is smaller than our dust limit {}", + channel::MIN_CHAN_DUST_LIMIT_SATOSHIS + ), + msg.common_fields.temporary_channel_id, + ) + })?; + Ok(msg.common_fields.channel_parameters(channel_reserve_satoshis)) + }, + } + } +} + /// A not-yet-accepted inbound (from counterparty) channel. Once /// accepted, the parameters will be used to construct a channel. pub(super) struct InboundChannelRequest { @@ -11676,6 +11704,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut pending_events = self.pending_events.lock().unwrap(); let is_announced = (common_fields.channel_flags & 1) == 1; + let params = msg.channel_parameters()?; pending_events.push_back(( events::Event::OpenChannelRequest { temporary_channel_id: common_fields.temporary_channel_id, @@ -11687,7 +11716,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }, channel_type, is_announced, - params: common_fields.channel_parameters(), + params, }, None, )); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 5643bfd9498..e177568a835 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -238,9 +238,10 @@ pub struct CommonOpenChannelFields { impl CommonOpenChannelFields { /// The [`ChannelParameters`] for this channel. - pub fn channel_parameters(&self) -> ChannelParameters { + pub(crate) fn channel_parameters(&self, channel_reserve_satoshis: u64) -> ChannelParameters { ChannelParameters { dust_limit_satoshis: self.dust_limit_satoshis, + channel_reserve_satoshis, max_htlc_value_in_flight_msat: self.max_htlc_value_in_flight_msat, htlc_minimum_msat: self.htlc_minimum_msat, commitment_feerate_sat_per_1000_weight: self.commitment_feerate_sat_per_1000_weight, @@ -257,6 +258,9 @@ pub struct ChannelParameters { /// The threshold below which outputs on transactions broadcast by the channel initiator will be /// omitted. pub dust_limit_satoshis: u64, + /// The minimum value unencumbered by HTLCs for the non-channel-initiator to keep in the + /// channel. + pub channel_reserve_satoshis: u64, /// The maximum inbound HTLC value in flight towards channel initiator, in milli-satoshi pub max_htlc_value_in_flight_msat: u64, /// The minimum HTLC size for HTLCs towards the channel initiator, in milli-satoshi diff --git a/pending_changelog/3909-open-channel-request-reserve.txt b/pending_changelog/3909-open-channel-request-reserve.txt new file mode 100644 index 00000000000..57335b217cd --- /dev/null +++ b/pending_changelog/3909-open-channel-request-reserve.txt @@ -0,0 +1,9 @@ +# API Updates + + * `Event::OpenChannelRequest::params` now exposes the channel reserve in + `msgs::ChannelParameters::channel_reserve_satoshis`. For V1 channels this is + the reserve provided in `open_channel`; for V2 channels this is derived from + the V2 reserve rules and `disable_channel_reserve`. + `msgs::CommonOpenChannelFields::channel_parameters` is now crate-internal + because common open-channel fields alone do not contain enough information to + construct the full `ChannelParameters`. From e9276fe92dde42201021baa03106bf16cad6306d Mon Sep 17 00:00:00 2001 From: LingX Date: Fri, 19 Jun 2026 14:21:50 -0400 Subject: [PATCH 2/2] Document V2 channel reserve exposure timing --- lightning/src/ln/msgs.rs | 6 ++++++ pending_changelog/3909-open-channel-request-reserve.txt | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index e177568a835..ce32c930d09 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -260,6 +260,12 @@ pub struct ChannelParameters { pub dust_limit_satoshis: u64, /// The minimum value unencumbered by HTLCs for the non-channel-initiator to keep in the /// channel. + /// + /// For V2 channels, this is computed from the initiator's funding contribution known when + /// [`Event::OpenChannelRequest`] is generated. The finally enforced reserve may be higher if + /// the acceptor later contributes additional funds. + /// + /// [`Event::OpenChannelRequest`]: crate::events::Event::OpenChannelRequest pub channel_reserve_satoshis: u64, /// The maximum inbound HTLC value in flight towards channel initiator, in milli-satoshi pub max_htlc_value_in_flight_msat: u64, diff --git a/pending_changelog/3909-open-channel-request-reserve.txt b/pending_changelog/3909-open-channel-request-reserve.txt index 57335b217cd..4703159de97 100644 --- a/pending_changelog/3909-open-channel-request-reserve.txt +++ b/pending_changelog/3909-open-channel-request-reserve.txt @@ -3,7 +3,9 @@ * `Event::OpenChannelRequest::params` now exposes the channel reserve in `msgs::ChannelParameters::channel_reserve_satoshis`. For V1 channels this is the reserve provided in `open_channel`; for V2 channels this is derived from - the V2 reserve rules and `disable_channel_reserve`. + the initiator's funding contribution, the V2 reserve rules, and + `disable_channel_reserve`. For V2 channels the finally enforced reserve may + be higher if the acceptor later contributes additional funds. `msgs::CommonOpenChannelFields::channel_parameters` is now crate-internal because common open-channel fields alone do not contain enough information to construct the full `ChannelParameters`.