Skip to content

Commit cd475ee

Browse files
committed
Expose channel_reserve_satoshis via ChannelParameters
Add `channel_reserve_satoshis: Option<u64>` to `ChannelParameters` so users can access the counterparty's channel reserve when handling the `OpenChannelRequest` event. For V1 channels (`open_channel`), this returns `Some(value)` with the explicit reserve from the message. For V2 channels (`open_channel2`), this returns `None` because the reserve is calculated as `max(1% of total_channel_value, dust_limit)` per spec, where total_channel_value includes both parties' funding. Since the acceptor's contribution is unknown at `OpenChannelRequest` time, the final reserve cannot be determined. Move `channel_parameters()` from `CommonOpenChannelFields` to separate implementations on `OpenChannel` and `OpenChannelV2` to handle the V1/V2 difference correctly. Fixes #3909
1 parent 4281e49 commit cd475ee

3 files changed

Lines changed: 117 additions & 15 deletions

File tree

lightning/src/ln/channel_open_tests.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,6 +1750,66 @@ pub fn test_invalid_funding_tx() {
17501750
mine_transaction(&nodes[1], &spend_tx);
17511751
}
17521752

1753+
#[xtest(feature = "_externalize_tests")]
1754+
pub fn test_open_channel_request_channel_reserve_satoshis() {
1755+
// Test that the `channel_reserve_satoshis` field is correctly populated in the
1756+
// `OpenChannelRequest` event's `params` field for V1 channels.
1757+
let mut manually_accept_conf = UserConfig::default();
1758+
manually_accept_conf.manually_accept_inbound_channels = true;
1759+
1760+
let chanmon_cfgs = create_chanmon_cfgs(2);
1761+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1762+
let node_chanmgrs =
1763+
create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]);
1764+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1765+
1766+
let node_a_id = nodes[0].node.get_our_node_id();
1767+
let node_b_id = nodes[1].node.get_our_node_id();
1768+
1769+
// Create channel with 100,000 sats
1770+
nodes[0]
1771+
.node
1772+
.create_channel(node_b_id, 100_000, 10_001, 42, None, Some(manually_accept_conf))
1773+
.unwrap();
1774+
let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id);
1775+
1776+
// The channel_reserve_satoshis in the open_channel message is set by the opener
1777+
let expected_reserve = open_channel_msg.channel_reserve_satoshis;
1778+
1779+
nodes[1].node.handle_open_channel(node_a_id, &open_channel_msg);
1780+
1781+
// Verify the OpenChannelRequest event contains the correct channel_reserve_satoshis
1782+
let events = nodes[1].node.get_and_clear_pending_events();
1783+
assert_eq!(events.len(), 1);
1784+
match &events[0] {
1785+
Event::OpenChannelRequest { temporary_channel_id, params, .. } => {
1786+
// For V1 channels, channel_reserve_satoshis should be Some with the value from the message
1787+
assert_eq!(
1788+
params.channel_reserve_satoshis,
1789+
Some(expected_reserve),
1790+
"channel_reserve_satoshis in OpenChannelRequest params should match the open_channel message"
1791+
);
1792+
1793+
// Verify other params fields are also correctly populated
1794+
assert_eq!(params.dust_limit_satoshis, open_channel_msg.common_fields.dust_limit_satoshis);
1795+
assert_eq!(params.max_htlc_value_in_flight_msat, open_channel_msg.common_fields.max_htlc_value_in_flight_msat);
1796+
assert_eq!(params.htlc_minimum_msat, open_channel_msg.common_fields.htlc_minimum_msat);
1797+
assert_eq!(params.to_self_delay, open_channel_msg.common_fields.to_self_delay);
1798+
assert_eq!(params.max_accepted_htlcs, open_channel_msg.common_fields.max_accepted_htlcs);
1799+
1800+
// Accept the channel to clean up
1801+
nodes[1]
1802+
.node
1803+
.accept_inbound_channel(temporary_channel_id, &node_a_id, 0, None)
1804+
.unwrap();
1805+
},
1806+
_ => panic!("Expected OpenChannelRequest event"),
1807+
}
1808+
1809+
// Clear the SendAcceptChannel message event generated by accepting the channel
1810+
nodes[1].node.get_and_clear_pending_msg_events();
1811+
}
1812+
17531813
#[xtest(feature = "_externalize_tests")]
17541814
pub fn test_coinbase_funding_tx() {
17551815
// Miners are able to fund channels directly from coinbase transactions, however

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10624,7 +10624,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1062410624
},
1062510625
channel_type,
1062610626
is_announced,
10627-
params: common_fields.channel_parameters(),
10627+
params: match msg {
10628+
OpenChannelMessageRef::V1(msg) => msg.channel_parameters(),
10629+
OpenChannelMessageRef::V2(msg) => msg.channel_parameters(),
10630+
},
1062810631
}, None));
1062910632
peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest {
1063010633
open_channel_msg: match msg {

lightning/src/ln/msgs.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -244,20 +244,6 @@ pub struct CommonOpenChannelFields {
244244
pub channel_type: Option<ChannelTypeFeatures>,
245245
}
246246

247-
impl CommonOpenChannelFields {
248-
/// The [`ChannelParameters`] for this channel.
249-
pub fn channel_parameters(&self) -> ChannelParameters {
250-
ChannelParameters {
251-
dust_limit_satoshis: self.dust_limit_satoshis,
252-
max_htlc_value_in_flight_msat: self.max_htlc_value_in_flight_msat,
253-
htlc_minimum_msat: self.htlc_minimum_msat,
254-
commitment_feerate_sat_per_1000_weight: self.commitment_feerate_sat_per_1000_weight,
255-
to_self_delay: self.to_self_delay,
256-
max_accepted_htlcs: self.max_accepted_htlcs,
257-
}
258-
}
259-
}
260-
261247
/// A subset of [`CommonOpenChannelFields`], containing various parameters which are set by the
262248
/// channel initiator and which are not part of the channel funding transaction.
263249
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -277,6 +263,19 @@ pub struct ChannelParameters {
277263
pub to_self_delay: u16,
278264
/// The maximum number of pending HTLCs towards the channel initiator.
279265
pub max_accepted_htlcs: u16,
266+
/// The minimum value unencumbered by HTLCs for the counterparty to keep in the channel.
267+
///
268+
/// For V1 channels (`open_channel`), this is the explicit `channel_reserve_satoshis` value
269+
/// from the counterparty.
270+
///
271+
/// For V2 channels (`open_channel2`), this is `None` at the time of [`Event::OpenChannelRequest`]
272+
/// because the channel reserve is calculated as `max(1% of total_channel_value, dust_limit_satoshis)`
273+
/// per the spec, where `total_channel_value` includes both the initiator's and acceptor's funding
274+
/// contributions. Since the acceptor's contribution is not yet known when the event is generated,
275+
/// the final reserve value cannot be determined at that point.
276+
///
277+
/// [`Event::OpenChannelRequest`]: crate::events::Event::OpenChannelRequest
278+
pub channel_reserve_satoshis: Option<u64>,
280279
}
281280

282281
/// An [`open_channel`] message to be sent to or received from a peer.
@@ -294,6 +293,23 @@ pub struct OpenChannel {
294293
pub channel_reserve_satoshis: u64,
295294
}
296295

296+
impl OpenChannel {
297+
/// The [`ChannelParameters`] for this V1 channel.
298+
pub fn channel_parameters(&self) -> ChannelParameters {
299+
ChannelParameters {
300+
dust_limit_satoshis: self.common_fields.dust_limit_satoshis,
301+
max_htlc_value_in_flight_msat: self.common_fields.max_htlc_value_in_flight_msat,
302+
htlc_minimum_msat: self.common_fields.htlc_minimum_msat,
303+
commitment_feerate_sat_per_1000_weight: self
304+
.common_fields
305+
.commitment_feerate_sat_per_1000_weight,
306+
to_self_delay: self.common_fields.to_self_delay,
307+
max_accepted_htlcs: self.common_fields.max_accepted_htlcs,
308+
channel_reserve_satoshis: Some(self.channel_reserve_satoshis),
309+
}
310+
}
311+
}
312+
297313
/// An [`open_channel2`] message to be sent by or received from the channel initiator.
298314
///
299315
/// Used in V2 channel establishment
@@ -313,6 +329,29 @@ pub struct OpenChannelV2 {
313329
pub require_confirmed_inputs: Option<()>,
314330
}
315331

332+
impl OpenChannelV2 {
333+
/// The [`ChannelParameters`] for this V2 channel.
334+
///
335+
/// Note that `channel_reserve_satoshis` will be `None` because for V2 channels the reserve
336+
/// is calculated as `max(1% of total_channel_value, dust_limit_satoshis)` per the spec,
337+
/// where `total_channel_value` includes both parties' funding contributions. Since the
338+
/// acceptor's contribution is not yet known at this point, the final reserve cannot be
339+
/// determined.
340+
pub fn channel_parameters(&self) -> ChannelParameters {
341+
ChannelParameters {
342+
dust_limit_satoshis: self.common_fields.dust_limit_satoshis,
343+
max_htlc_value_in_flight_msat: self.common_fields.max_htlc_value_in_flight_msat,
344+
htlc_minimum_msat: self.common_fields.htlc_minimum_msat,
345+
commitment_feerate_sat_per_1000_weight: self
346+
.common_fields
347+
.commitment_feerate_sat_per_1000_weight,
348+
to_self_delay: self.common_fields.to_self_delay,
349+
max_accepted_htlcs: self.common_fields.max_accepted_htlcs,
350+
channel_reserve_satoshis: None,
351+
}
352+
}
353+
}
354+
316355
/// Contains fields that are both common to [`accept_channel`] and [`accept_channel2`] messages.
317356
///
318357
/// [`accept_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel-message

0 commit comments

Comments
 (0)