From 1ba60a2fa065b574da0ace2a32ab8031a807b167 Mon Sep 17 00:00:00 2001 From: Yash Bhutwala Date: Thu, 29 Jan 2026 15:39:21 -0500 Subject: [PATCH 1/2] Expose `channel_reserve_satoshis` via `ChannelParameters` Add `channel_reserve_satoshis` field to `ChannelParameters` struct to expose the channel reserve value in `OpenChannelRequest` events. This allows users handling inbound channel requests to see the reserve requirement. For V1 channels, this is the explicit value from the `open_channel` message. For V2 (dual-funded) channels, this is `None` since the reserve is calculated based on total channel value which isn't known until both parties contribute. Also implements `channel_parameters()` on `OpenChannelMessageRef` to DRY up the code, as suggested in review. --- lightning/src/ln/channel_open_tests.rs | 145 +++++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 21 +++- lightning/src/ln/msgs.rs | 27 +++-- 3 files changed, 178 insertions(+), 15 deletions(-) diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index 059639330f8..e1706aede60 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -1706,6 +1706,151 @@ pub fn test_invalid_funding_tx() { mine_transaction(&nodes[1], &spend_tx); } +#[xtest(feature = "_externalize_tests")] +pub fn test_open_channel_request_channel_reserve_satoshis() { + // Test that the `channel_reserve_satoshis` field is correctly populated in the + // `OpenChannelRequest` event's `params` field for V1 channels. + let mut manually_accept_conf = UserConfig::default(); + manually_accept_conf.manually_accept_inbound_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, &[None, Some(manually_accept_conf.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Create channel with 100,000 sats + nodes[0] + .node + .create_channel(node_b_id, 100_000, 10_001, 42, None, Some(manually_accept_conf)) + .unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + // The channel_reserve_satoshis in the open_channel message is set by the opener + let expected_reserve = open_channel_msg.channel_reserve_satoshis; + + nodes[1].node.handle_open_channel(node_a_id, &open_channel_msg); + + // Verify the OpenChannelRequest event contains the correct channel_reserve_satoshis + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match &events[0] { + Event::OpenChannelRequest { temporary_channel_id, params, .. } => { + // For V1 channels, channel_reserve_satoshis should be Some with the value from the message + assert_eq!( + params.channel_reserve_satoshis, + Some(expected_reserve), + "channel_reserve_satoshis in OpenChannelRequest params should match the open_channel message" + ); + + // Verify other params fields are also correctly populated + assert_eq!( + params.dust_limit_satoshis, + open_channel_msg.common_fields.dust_limit_satoshis + ); + assert_eq!( + params.max_htlc_value_in_flight_msat, + open_channel_msg.common_fields.max_htlc_value_in_flight_msat + ); + assert_eq!(params.htlc_minimum_msat, open_channel_msg.common_fields.htlc_minimum_msat); + assert_eq!(params.to_self_delay, open_channel_msg.common_fields.to_self_delay); + assert_eq!( + params.max_accepted_htlcs, + open_channel_msg.common_fields.max_accepted_htlcs + ); + + // Accept the channel to clean up + nodes[1] + .node + .accept_inbound_channel(temporary_channel_id, &node_a_id, 0, None) + .unwrap(); + }, + _ => panic!("Expected OpenChannelRequest event"), + } + + // Clear the SendAcceptChannel message event generated by accepting the channel + nodes[1].node.get_and_clear_pending_msg_events(); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_open_channel_request_channel_reserve_satoshis_v2() { + // Test that the `channel_reserve_satoshis` field is `None` in the + // `OpenChannelRequest` event's `params` field for V2 (dual-funded) channels. + let mut manually_accept_conf = UserConfig::default(); + manually_accept_conf.manually_accept_inbound_channels = true; + manually_accept_conf.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(manually_accept_conf.clone()), Some(manually_accept_conf.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Get the open_channel message from node 0 to use as a template for the common fields + nodes[0] + .node + .create_channel(node_b_id, 100_000, 10_001, 42, None, Some(manually_accept_conf.clone())) + .unwrap(); + let open_channel_v1_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + // Create an OpenChannelV2 message using the common fields from V1 + let open_channel_v2_msg = msgs::OpenChannelV2 { + common_fields: open_channel_v1_msg.common_fields.clone(), + funding_feerate_sat_per_1000_weight: 1000, + locktime: 0, + second_per_commitment_point: open_channel_v1_msg.common_fields.first_per_commitment_point, + require_confirmed_inputs: None, + }; + + nodes[1].node.handle_open_channel_v2(node_a_id, &open_channel_v2_msg); + + // Verify the OpenChannelRequest event contains channel_reserve_satoshis = None for V2 channels + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match &events[0] { + Event::OpenChannelRequest { temporary_channel_id, params, .. } => { + // For V2 channels, channel_reserve_satoshis should be None + assert_eq!( + params.channel_reserve_satoshis, + None, + "channel_reserve_satoshis in OpenChannelRequest params should be None for V2 channels" + ); + + // Verify other params fields are correctly populated + assert_eq!( + params.dust_limit_satoshis, + open_channel_v2_msg.common_fields.dust_limit_satoshis + ); + assert_eq!( + params.max_htlc_value_in_flight_msat, + open_channel_v2_msg.common_fields.max_htlc_value_in_flight_msat + ); + assert_eq!(params.htlc_minimum_msat, open_channel_v2_msg.common_fields.htlc_minimum_msat); + assert_eq!(params.to_self_delay, open_channel_v2_msg.common_fields.to_self_delay); + assert_eq!( + params.max_accepted_htlcs, + open_channel_v2_msg.common_fields.max_accepted_htlcs + ); + + // Accept the channel to clean up + nodes[1] + .node + .accept_inbound_channel(temporary_channel_id, &node_a_id, 0, None) + .unwrap(); + }, + _ => panic!("Expected OpenChannelRequest event"), + } + + // Clear the SendAcceptChannelV2 message event generated by accepting the channel + nodes[1].node.get_and_clear_pending_msg_events(); +} + #[xtest(feature = "_externalize_tests")] pub fn test_coinbase_funding_tx() { // Miners are able to fund channels directly from coinbase transactions, however diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e840d705b8e..0d74098288b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -1700,6 +1700,25 @@ pub(super) enum OpenChannelMessageRef<'a> { V2(&'a msgs::OpenChannelV2), } +impl<'a> OpenChannelMessageRef<'a> { + pub(super) fn channel_parameters(&self) -> msgs::ChannelParameters { + let (common_fields, channel_reserve_satoshis) = match self { + Self::V1(msg) => (&msg.common_fields, Some(msg.channel_reserve_satoshis)), + Self::V2(msg) => (&msg.common_fields, None), + }; + msgs::ChannelParameters { + dust_limit_satoshis: common_fields.dust_limit_satoshis, + max_htlc_value_in_flight_msat: common_fields.max_htlc_value_in_flight_msat, + htlc_minimum_msat: common_fields.htlc_minimum_msat, + commitment_feerate_sat_per_1000_weight: common_fields + .commitment_feerate_sat_per_1000_weight, + to_self_delay: common_fields.to_self_delay, + max_accepted_htlcs: common_fields.max_accepted_htlcs, + 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 { @@ -10857,7 +10876,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: msg.channel_parameters(), }, None, )); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 67f7807a487..725515fba9d 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -244,20 +244,6 @@ pub struct CommonOpenChannelFields { pub channel_type: Option, } -impl CommonOpenChannelFields { - /// The [`ChannelParameters`] for this channel. - pub fn channel_parameters(&self) -> ChannelParameters { - ChannelParameters { - dust_limit_satoshis: self.dust_limit_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, - to_self_delay: self.to_self_delay, - max_accepted_htlcs: self.max_accepted_htlcs, - } - } -} - /// A subset of [`CommonOpenChannelFields`], containing various parameters which are set by the /// channel initiator and which are not part of the channel funding transaction. #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -277,6 +263,19 @@ pub struct ChannelParameters { pub to_self_delay: u16, /// The maximum number of pending HTLCs towards the channel initiator. pub max_accepted_htlcs: u16, + /// The minimum value unencumbered by HTLCs for the non-channel-initiator to keep in the channel. + /// + /// For V1 channels (`open_channel`), this is the explicit `channel_reserve_satoshis` value + /// from the channel initiator. + /// + /// For V2 channels (`open_channel2`), this is `None` at the time of [`Event::OpenChannelRequest`] + /// because the channel reserve is calculated as `max(1% of total_channel_value, dust_limit_satoshis)` + /// per the spec, where `total_channel_value` includes both the initiator's and acceptor's funding + /// contributions. Since the acceptor's contribution is not yet known when the event is generated, + /// the final reserve value cannot be determined at that point. + /// + /// [`Event::OpenChannelRequest`]: crate::events::Event::OpenChannelRequest + pub channel_reserve_satoshis: Option, } /// An [`open_channel`] message to be sent to or received from a peer. From aa3dd7260bdf736e6ec6d0e5f1139cad25305f19 Mon Sep 17 00:00:00 2001 From: Yash Bhutwala Date: Tue, 10 Feb 2026 15:39:14 -0500 Subject: [PATCH 2/2] Move `channel_reserve_satoshis` from `ChannelParameters` into `InboundChannelFunds` Per reviewer feedback, `channel_reserve_satoshis` is better placed in the `InboundChannelFunds` enum rather than as a separate `Option` on `ChannelParameters`. This is consistent with how V1 vs V2 channel differences are already handled via the enum. - Convert `InboundChannelFunds::PushMsat` from a tuple variant to a struct variant with `push_msat` and `channel_reserve_satoshis` fields - Remove `channel_reserve_satoshis` field from `ChannelParameters` - Restore `CommonOpenChannelFields::channel_parameters()` method - Remove `OpenChannelMessageRef::channel_parameters()` helper - Update tests to check reserve from the enum variant Co-Authored-By: Claude (claude.ai) --- lightning/src/events/mod.rs | 23 ++++- lightning/src/ln/channel_open_tests.rs | 83 +++++++++++-------- lightning/src/ln/channelmanager.rs | 32 ++----- lightning/src/ln/msgs.rs | 36 ++++---- ...cept-dual-funding-without-contributing.txt | 2 +- 5 files changed, 97 insertions(+), 79 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 3dfed10d5c8..7f1def5a61f 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -716,14 +716,28 @@ impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason, /// Used to indicate the kind of funding for this channel by the channel acceptor (us). /// /// Allows the differentiation between a request for a dual-funded and non-dual-funded channel. +/// For V1 channels, this also carries the `channel_reserve_satoshis` value set by the channel +/// initiator, since it is only available in V1 `open_channel` messages. #[derive(Clone, Debug, PartialEq, Eq)] pub enum InboundChannelFunds { - /// For a non-dual-funded channel, the `push_msat` value from the channel initiator to us. - PushMsat(u64), + /// For a non-dual-funded (V1) channel, the `push_msat` value and `channel_reserve_satoshis` + /// from the channel initiator. + PushMsat { + /// The amount, in millisatoshis, that the channel initiator is pushing to us. + push_msat: u64, + /// The minimum value unencumbered by HTLCs for the non-channel-initiator to keep in the + /// channel, as set by the channel initiator in the `open_channel` message. + channel_reserve_satoshis: u64, + }, /// Indicates the open request is for a dual funded channel. /// /// Note that these channels do not support starting with initial funds pushed from the counterparty, /// who is the channel opener in this case. + /// + /// For V2 channels, the channel reserve is calculated as + /// `max(1% of total_channel_value, dust_limit_satoshis)` per the spec, and is not known at + /// the time of [`Event::OpenChannelRequest`] because the acceptor's funding contribution has + /// not yet been determined. DualFunded, } @@ -1622,8 +1636,9 @@ pub enum Event { /// The channel value of the requested channel. funding_satoshis: u64, /// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to - /// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`, - /// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel. + /// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsat`, + /// indicating the `push_msat` value and `channel_reserve_satoshis` our peer set for a + /// non-dual-funded (V1) channel. channel_negotiation_type: InboundChannelFunds, /// The features that this channel will operate with. If you reject the channel, a /// well-behaved counterparty may automatically re-attempt the channel with a new set of diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index e1706aede60..c425d4178be 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -13,7 +13,7 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::channelmonitor::{self, ChannelMonitorUpdateStep}; use crate::chain::transaction::OutPoint; use crate::chain::{self, ChannelMonitorUpdateStatus}; -use crate::events::{ClosureReason, Event, FundingInfo}; +use crate::events::{ClosureReason, Event, FundingInfo, InboundChannelFunds}; use crate::ln::channel::{ get_holder_selected_channel_reserve_satoshis, ChannelError, InboundV1Channel, OutboundV1Channel, COINBASE_MATURITY, UNFUNDED_CHANNEL_AGE_LIMIT_TICKS, @@ -1709,24 +1709,17 @@ pub fn test_invalid_funding_tx() { #[xtest(feature = "_externalize_tests")] pub fn test_open_channel_request_channel_reserve_satoshis() { // Test that the `channel_reserve_satoshis` field is correctly populated in the - // `OpenChannelRequest` event's `params` field for V1 channels. - let mut manually_accept_conf = UserConfig::default(); - manually_accept_conf.manually_accept_inbound_channels = true; - + // `InboundChannelFunds::PushMsat` variant for V1 channels. 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, &[None, Some(manually_accept_conf.clone())]); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let node_a_id = nodes[0].node.get_our_node_id(); let node_b_id = nodes[1].node.get_our_node_id(); // Create channel with 100,000 sats - nodes[0] - .node - .create_channel(node_b_id, 100_000, 10_001, 42, None, Some(manually_accept_conf)) - .unwrap(); + nodes[0].node.create_channel(node_b_id, 100_000, 10_001, 42, None, None).unwrap(); let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); // The channel_reserve_satoshis in the open_channel message is set by the opener @@ -1738,15 +1731,24 @@ pub fn test_open_channel_request_channel_reserve_satoshis() { let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match &events[0] { - Event::OpenChannelRequest { temporary_channel_id, params, .. } => { - // For V1 channels, channel_reserve_satoshis should be Some with the value from the message - assert_eq!( - params.channel_reserve_satoshis, - Some(expected_reserve), - "channel_reserve_satoshis in OpenChannelRequest params should match the open_channel message" - ); + Event::OpenChannelRequest { + temporary_channel_id, + channel_negotiation_type, + params, + .. + } => { + // For V1 channels, channel_reserve_satoshis should be in the PushMsat variant + match channel_negotiation_type { + InboundChannelFunds::PushMsat { channel_reserve_satoshis, .. } => { + assert_eq!( + *channel_reserve_satoshis, expected_reserve, + "channel_reserve_satoshis in InboundChannelFunds::PushMsat should match the open_channel message" + ); + }, + _ => panic!("Expected InboundChannelFunds::PushMsat for V1 channel"), + } - // Verify other params fields are also correctly populated + // Verify params fields are correctly populated assert_eq!( params.dust_limit_satoshis, open_channel_msg.common_fields.dust_limit_satoshis @@ -1777,16 +1779,18 @@ pub fn test_open_channel_request_channel_reserve_satoshis() { #[xtest(feature = "_externalize_tests")] pub fn test_open_channel_request_channel_reserve_satoshis_v2() { - // Test that the `channel_reserve_satoshis` field is `None` in the - // `OpenChannelRequest` event's `params` field for V2 (dual-funded) channels. - let mut manually_accept_conf = UserConfig::default(); - manually_accept_conf.manually_accept_inbound_channels = true; - manually_accept_conf.enable_dual_funded_channels = true; + // Test that the `channel_negotiation_type` is `InboundChannelFunds::DualFunded` + // for V2 (dual-funded) channels, which does not carry a channel reserve field. + let mut dual_funded_conf = UserConfig::default(); + dual_funded_conf.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(manually_accept_conf.clone()), Some(manually_accept_conf.clone())]); + let node_chanmgrs = create_node_chanmgrs( + 2, + &node_cfgs, + &[Some(dual_funded_conf.clone()), Some(dual_funded_conf.clone())], + ); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let node_a_id = nodes[0].node.get_our_node_id(); @@ -1795,9 +1799,10 @@ pub fn test_open_channel_request_channel_reserve_satoshis_v2() { // Get the open_channel message from node 0 to use as a template for the common fields nodes[0] .node - .create_channel(node_b_id, 100_000, 10_001, 42, None, Some(manually_accept_conf.clone())) + .create_channel(node_b_id, 100_000, 10_001, 42, None, Some(dual_funded_conf.clone())) .unwrap(); - let open_channel_v1_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + let open_channel_v1_msg = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); // Create an OpenChannelV2 message using the common fields from V1 let open_channel_v2_msg = msgs::OpenChannelV2 { @@ -1814,15 +1819,20 @@ pub fn test_open_channel_request_channel_reserve_satoshis_v2() { let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match &events[0] { - Event::OpenChannelRequest { temporary_channel_id, params, .. } => { - // For V2 channels, channel_reserve_satoshis should be None + Event::OpenChannelRequest { + temporary_channel_id, + channel_negotiation_type, + params, + .. + } => { + // For V2 channels, channel_negotiation_type should be DualFunded (no reserve field) assert_eq!( - params.channel_reserve_satoshis, - None, - "channel_reserve_satoshis in OpenChannelRequest params should be None for V2 channels" + *channel_negotiation_type, + InboundChannelFunds::DualFunded, + "channel_negotiation_type should be DualFunded for V2 channels" ); - // Verify other params fields are correctly populated + // Verify params fields are correctly populated assert_eq!( params.dust_limit_satoshis, open_channel_v2_msg.common_fields.dust_limit_satoshis @@ -1831,7 +1841,10 @@ pub fn test_open_channel_request_channel_reserve_satoshis_v2() { params.max_htlc_value_in_flight_msat, open_channel_v2_msg.common_fields.max_htlc_value_in_flight_msat ); - assert_eq!(params.htlc_minimum_msat, open_channel_v2_msg.common_fields.htlc_minimum_msat); + assert_eq!( + params.htlc_minimum_msat, + open_channel_v2_msg.common_fields.htlc_minimum_msat + ); assert_eq!(params.to_self_delay, open_channel_v2_msg.common_fields.to_self_delay); assert_eq!( params.max_accepted_htlcs, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0d74098288b..cfa1a170e94 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -1700,25 +1700,6 @@ pub(super) enum OpenChannelMessageRef<'a> { V2(&'a msgs::OpenChannelV2), } -impl<'a> OpenChannelMessageRef<'a> { - pub(super) fn channel_parameters(&self) -> msgs::ChannelParameters { - let (common_fields, channel_reserve_satoshis) = match self { - Self::V1(msg) => (&msg.common_fields, Some(msg.channel_reserve_satoshis)), - Self::V2(msg) => (&msg.common_fields, None), - }; - msgs::ChannelParameters { - dust_limit_satoshis: common_fields.dust_limit_satoshis, - max_htlc_value_in_flight_msat: common_fields.max_htlc_value_in_flight_msat, - htlc_minimum_msat: common_fields.htlc_minimum_msat, - commitment_feerate_sat_per_1000_weight: common_fields - .commitment_feerate_sat_per_1000_weight, - to_self_delay: common_fields.to_self_delay, - max_accepted_htlcs: common_fields.max_accepted_htlcs, - 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 { @@ -9069,7 +9050,8 @@ impl< ComplFunc: FnOnce( Option, bool, - ) -> (Option, Option), + ) + -> (Option, Option), >( &self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage, payment_info: Option, attribution_data: Option, @@ -9107,7 +9089,8 @@ impl< ComplFunc: FnOnce( Option, bool, - ) -> (Option, Option), + ) + -> (Option, Option), >( &self, prev_hop: HTLCClaimSource, payment_preimage: PaymentPreimage, payment_info: Option, attribution_data: Option, @@ -10871,12 +10854,15 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ counterparty_node_id: *counterparty_node_id, funding_satoshis: common_fields.funding_satoshis, channel_negotiation_type: match msg { - OpenChannelMessageRef::V1(msg) => InboundChannelFunds::PushMsat(msg.push_msat), + OpenChannelMessageRef::V1(msg) => InboundChannelFunds::PushMsat { + push_msat: msg.push_msat, + channel_reserve_satoshis: msg.channel_reserve_satoshis, + }, OpenChannelMessageRef::V2(_) => InboundChannelFunds::DualFunded, }, channel_type, is_announced, - params: msg.channel_parameters(), + params: common_fields.channel_parameters(), }, None, )); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 725515fba9d..7e4500aff46 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -244,6 +244,20 @@ pub struct CommonOpenChannelFields { pub channel_type: Option, } +impl CommonOpenChannelFields { + /// The [`ChannelParameters`] for this channel. + pub fn channel_parameters(&self) -> ChannelParameters { + ChannelParameters { + dust_limit_satoshis: self.dust_limit_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, + to_self_delay: self.to_self_delay, + max_accepted_htlcs: self.max_accepted_htlcs, + } + } +} + /// A subset of [`CommonOpenChannelFields`], containing various parameters which are set by the /// channel initiator and which are not part of the channel funding transaction. #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -263,19 +277,6 @@ pub struct ChannelParameters { pub to_self_delay: u16, /// The maximum number of pending HTLCs towards the channel initiator. pub max_accepted_htlcs: u16, - /// The minimum value unencumbered by HTLCs for the non-channel-initiator to keep in the channel. - /// - /// For V1 channels (`open_channel`), this is the explicit `channel_reserve_satoshis` value - /// from the channel initiator. - /// - /// For V2 channels (`open_channel2`), this is `None` at the time of [`Event::OpenChannelRequest`] - /// because the channel reserve is calculated as `max(1% of total_channel_value, dust_limit_satoshis)` - /// per the spec, where `total_channel_value` includes both the initiator's and acceptor's funding - /// contributions. Since the acceptor's contribution is not yet known when the event is generated, - /// the final reserve value cannot be determined at that point. - /// - /// [`Event::OpenChannelRequest`]: crate::events::Event::OpenChannelRequest - pub channel_reserve_satoshis: Option, } /// An [`open_channel`] message to be sent to or received from a peer. @@ -3938,7 +3939,8 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPaylo used_aad, } => { if amt.is_some() - || cltv_value.is_some() || total_msat.is_some() + || cltv_value.is_some() + || total_msat.is_some() || keysend_preimage.is_some() || invoice_request.is_some() || used_aad @@ -3960,7 +3962,8 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPaylo used_aad, } => { if amt.is_some() - || cltv_value.is_some() || total_msat.is_some() + || cltv_value.is_some() + || total_msat.is_some() || keysend_preimage.is_some() || invoice_request.is_some() || !used_aad @@ -4106,7 +4109,8 @@ impl ReadableArgs<(Option, NS)> for InboundTrampoline used_aad, } => { if amt.is_some() - || cltv_value.is_some() || total_msat.is_some() + || cltv_value.is_some() + || total_msat.is_some() || keysend_preimage.is_some() || invoice_request.is_some() || used_aad diff --git a/pending_changelog/3137-accept-dual-funding-without-contributing.txt b/pending_changelog/3137-accept-dual-funding-without-contributing.txt index 5e1d0de2d86..312c7b8a478 100644 --- a/pending_changelog/3137-accept-dual-funding-without-contributing.txt +++ b/pending_changelog/3137-accept-dual-funding-without-contributing.txt @@ -6,7 +6,7 @@ * `Event::OpenChannelRequest::push_msat` has been replaced by the field `channel_negotiation_type` to differentiate between an inbound request for a dual-funded (V2) or non-dual-funded (V1) channel to be opened, with value being either of the enum variants `InboundChannelFunds::DualFunded` and - `InboundChannelFunds::PushMsat(u64)` corresponding to V2 and V1 channel open requests respectively. + `InboundChannelFunds::PushMsat { push_msat, channel_reserve_satoshis }` corresponding to V2 and V1 channel open requests respectively. * Similar to V1 channels, `ChannelManager::accept_inbound_channel()` can also be used to accept an inbound V2 channel. * 0conf dual-funded channels are not supported.