Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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
Expand Down
160 changes: 159 additions & 1 deletion lightning/src/ln/channel_open_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1706,6 +1706,164 @@ 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
// `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, 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, 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
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,
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 params fields are 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_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(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();
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(dual_funded_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,
channel_negotiation_type,
params,
..
} => {
// For V2 channels, channel_negotiation_type should be DualFunded (no reserve field)
assert_eq!(
*channel_negotiation_type,
InboundChannelFunds::DualFunded,
"channel_negotiation_type should be DualFunded for V2 channels"
);

// Verify 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
Expand Down
11 changes: 8 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9050,7 +9050,8 @@ impl<
ComplFunc: FnOnce(
Option<u64>,
bool,
) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
)
-> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
>(
&self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage,
payment_info: Option<PaymentClaimDetails>, attribution_data: Option<AttributionData>,
Expand Down Expand Up @@ -9088,7 +9089,8 @@ impl<
ComplFunc: FnOnce(
Option<u64>,
bool,
) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
)
-> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
>(
&self, prev_hop: HTLCClaimSource, payment_preimage: PaymentPreimage,
payment_info: Option<PaymentClaimDetails>, attribution_data: Option<AttributionData>,
Expand Down Expand Up @@ -10852,7 +10854,10 @@ 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,
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3939,7 +3939,8 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, 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
Expand All @@ -3961,7 +3962,8 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, 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
Expand Down Expand Up @@ -4107,7 +4109,8 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading