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
104 changes: 82 additions & 22 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2556,12 +2556,15 @@ impl FundingScope {
.funding_pubkey = counterparty_funding_pubkey;

// New reserve values are based on the new channel value and are v2-specific
let counterparty_selected_channel_reserve_satoshis =
Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
let counterparty_selected_channel_reserve_satoshis = Some(
get_v2_channel_reserve_satoshis(post_channel_value, context.holder_dust_limit_satoshis)
.expect("We ran this exact function in `validate_splice_contributions`"),
);
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
post_channel_value,
context.counterparty_dust_limit_satoshis,
);
)
.expect("We ran this exact function in `validate_splice_contributions`");

Self {
channel_transaction_parameters: post_channel_transaction_parameters,
Expand Down Expand Up @@ -2985,6 +2988,9 @@ where
/// We use this to close if funding is never broadcasted.
pub(super) channel_creation_height: u32,

#[cfg(any(test, feature = "_test_utils"))]
pub(crate) counterparty_dust_limit_satoshis: u64,
#[cfg(not(any(test, feature = "_test_utils")))]
counterparty_dust_limit_satoshis: u64,

#[cfg(any(test, feature = "_test_utils"))]
Expand Down Expand Up @@ -6442,14 +6448,23 @@ fn get_holder_max_htlc_value_in_flight_msat(
///
/// This is used both for outbound and inbound channels and has lower bound
/// of `MIN_THEIR_CHAN_RESERVE_SATOSHIS`.
///
/// Returns `Err` if `channel_value_satoshis` is smaller than
/// `MIN_THEIR_CHAN_RESERVE_SATOSHIS`.
pub(crate) fn get_holder_selected_channel_reserve_satoshis(
channel_value_satoshis: u64, config: &UserConfig,
) -> u64 {
let counterparty_chan_reserve_prop_mil =
config.channel_handshake_config.their_channel_reserve_proportional_millionths as u64;
) -> Result<u64, ()> {
if channel_value_satoshis < MIN_THEIR_CHAN_RESERVE_SATOSHIS {
return Err(());
}
// As described in the `ChannelHandshakeConfig` docs, we cap this value at 1_000_000.
let counterparty_chan_reserve_prop_mil = cmp::min(
config.channel_handshake_config.their_channel_reserve_proportional_millionths as u64,
1_000_000,
);
let calculated_reserve =
channel_value_satoshis.saturating_mul(counterparty_chan_reserve_prop_mil) / 1_000_000;
cmp::min(channel_value_satoshis, cmp::max(calculated_reserve, MIN_THEIR_CHAN_RESERVE_SATOSHIS))
Ok(cmp::max(calculated_reserve, MIN_THEIR_CHAN_RESERVE_SATOSHIS))
}

/// This is for legacy reasons, present for forward-compatibility.
Expand All @@ -6466,14 +6481,21 @@ pub(crate) fn get_legacy_default_holder_selected_channel_reserve_satoshis(
/// Returns a minimum channel reserve value each party needs to maintain, fixed in the spec to a
/// default of 1% of the total channel value.
///
/// Guaranteed to return a value no larger than channel_value_satoshis
/// Guaranteed to return a value no larger than `channel_value_satoshis`
///
/// This is used both for outbound and inbound channels and has lower bound
/// of `dust_limit_satoshis`.
fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satoshis: u64) -> u64 {
///
/// Returns `Err` if `channel_value_satoshis` is smaller than `dust_limit_satoshis`.
fn get_v2_channel_reserve_satoshis(
channel_value_satoshis: u64, dust_limit_satoshis: u64,
) -> Result<u64, ()> {
if channel_value_satoshis < dust_limit_satoshis {
return Err(());
}
// Fixed at 1% of channel value by spec.
let (q, _) = channel_value_satoshis.overflowing_div(100);
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
Ok(cmp::max(q, dust_limit_satoshis))
}

fn check_splice_contribution_sufficient(
Expand Down Expand Up @@ -12163,12 +12185,23 @@ where
their_funding_contribution.to_sat(),
);
let counterparty_selected_channel_reserve = Amount::from_sat(
get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS),
get_v2_channel_reserve_satoshis(
post_channel_value,
self.context.holder_dust_limit_satoshis
).map_err(|()| format!(
"The post-splice channel value {post_channel_value} is smaller than our dust limit {}",
self.context.holder_dust_limit_satoshis,
))?
);
let holder_selected_channel_reserve = Amount::from_sat(
get_v2_channel_reserve_satoshis(
post_channel_value,
self.context.counterparty_dust_limit_satoshis,
).map_err(|()| format!(
"The post-splice channel value {post_channel_value} is smaller than their dust limit {}",
self.context.counterparty_dust_limit_satoshis,
))?
);
let holder_selected_channel_reserve = Amount::from_sat(get_v2_channel_reserve_satoshis(
post_channel_value,
self.context.counterparty_dust_limit_satoshis,
));

// We allow parties to draw from their previous reserve, as long as they satisfy their v2 reserve

Expand Down Expand Up @@ -13370,7 +13403,8 @@ where
F::Target: FeeEstimator,
L::Target: Logger,
{
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config);
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config)
.map_err(|()| APIError::APIMisuseError { err: format!("The channel value {channel_value_satoshis} is smaller than {MIN_THEIR_CHAN_RESERVE_SATOSHIS}")})?;
if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS {
// Protocol level safety check in place, although it should never happen because
// of `MIN_THEIR_CHAN_RESERVE_SATOSHIS`
Expand Down Expand Up @@ -13742,7 +13776,8 @@ where
// support this channel type.
let channel_type = channel_type_from_open_channel(&msg.common_fields, our_supported_features)?;

let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config);
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config)
.map_err(|()| ChannelError::close(format!("The channel value {} is smaller than {MIN_THEIR_CHAN_RESERVE_SATOSHIS}", msg.common_fields.funding_satoshis)))?;
let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.common_fields.funding_pubkey,
revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint),
Expand Down Expand Up @@ -13984,7 +14019,10 @@ where
});

let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS);
funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS
).map_err(|()| APIError::APIMisuseError { err: format!(
"The channel value {funding_satoshis} is smaller than their dust limit {MIN_CHAN_DUST_LIMIT_SATOSHIS}",
)})?;

let funding_feerate_sat_per_1000_weight = fee_estimator.bounded_sat_per_1000_weight(funding_confirmation_target);
let funding_tx_locktime = LockTime::from_height(current_chain_height)
Expand Down Expand Up @@ -14131,9 +14169,15 @@ where
let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS).map_err(|()| ChannelError::close(format!(
"The channel value {channel_value_satoshis} is smaller than our dust limit {MIN_CHAN_DUST_LIMIT_SATOSHIS}"
)))?;
let their_dust_limit_satoshis = msg.common_fields.dust_limit_satoshis;
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS);
channel_value_satoshis, their_dust_limit_satoshis
).map_err(|()| ChannelError::close(format!(
"The channel value {channel_value_satoshis} is smaller than their dust limit {their_dust_limit_satoshis}"
)))?;

let channel_type = channel_type_from_open_channel(&msg.common_fields, our_supported_features)?;

Expand Down Expand Up @@ -16187,6 +16231,10 @@ mod tests {
// to channel value
test_self_and_counterparty_channel_reserve(10_000_000, 0.50, 0.50);
test_self_and_counterparty_channel_reserve(10_000_000, 0.60, 0.50);

// Make sure we correctly handle reserves greater than the channel value
test_self_and_counterparty_channel_reserve(100_000, 1.1, 0.30);
test_self_and_counterparty_channel_reserve(100_000, 0.30, 1.1);
}

#[rustfmt::skip]
Expand All @@ -16206,7 +16254,19 @@ mod tests {
outbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (outbound_selected_channel_reserve_perc * 1_000_000.0) as u32;
let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), channel_value_satoshis, 100_000, 42, &outbound_node_config, 0, 42, None, &logger).unwrap();

let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * outbound_selected_channel_reserve_perc) as u64);
let outbound_capped_reserve_perc = if outbound_selected_channel_reserve_perc.lt(&1.0) {
outbound_selected_channel_reserve_perc
} else {
1.0
};

let inbound_capped_reserve_perc = if inbound_selected_channel_reserve_perc.lt(&1.0) {
inbound_selected_channel_reserve_perc
} else {
1.0
};

let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * outbound_capped_reserve_perc) as u64);
assert_eq!(chan.funding.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve);

let chan_open_channel_msg = chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap();
Expand All @@ -16216,7 +16276,7 @@ mod tests {
if outbound_selected_channel_reserve_perc + inbound_selected_channel_reserve_perc < 1.0 {
let chan_inbound_node = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false).unwrap();

let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * inbound_selected_channel_reserve_perc) as u64);
let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * inbound_capped_reserve_perc) as u64);

assert_eq!(chan_inbound_node.funding.holder_selected_channel_reserve_satoshis, expected_inbound_selected_chan_reserve);
assert_eq!(chan_inbound_node.funding.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve);
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/channel_open_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ pub fn test_insane_channel_opens() {
// funding satoshis
let channel_value_sat = 31337; // same as funding satoshis
let channel_reserve_satoshis =
get_holder_selected_channel_reserve_satoshis(channel_value_sat, &cfg);
get_holder_selected_channel_reserve_satoshis(channel_value_sat, &cfg).unwrap();
let push_msat = (channel_value_sat - channel_reserve_satoshis) * 1000;

// Have node0 initiate a channel to node1 with aforementioned parameters
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ pub fn test_inbound_outbound_capacity_is_not_zero() {
assert_eq!(channels0.len(), 1);
assert_eq!(channels1.len(), 1);

let reserve = get_holder_selected_channel_reserve_satoshis(100_000, &default_config);
let reserve = get_holder_selected_channel_reserve_satoshis(100_000, &default_config).unwrap();
assert_eq!(channels0[0].inbound_capacity_msat, 95000000 - reserve * 1000);
assert_eq!(channels1[0].outbound_capacity_msat, 95000000 - reserve * 1000);

Expand Down
12 changes: 8 additions & 4 deletions lightning/src/ln/htlc_reserve_unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) {
push_amt -= feerate_per_kw as u64
* (commitment_tx_base_weight(&channel_type_features) + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC)
/ 1000 * 1000;
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
push_amt -=
get_holder_selected_channel_reserve_satoshis(100_000, &default_config).unwrap() * 1000;

let push = if send_from_initiator { 0 } else { push_amt };
let temp_channel_id =
Expand Down Expand Up @@ -993,7 +994,8 @@ pub fn test_chan_reserve_violation_outbound_htlc_inbound_chan() {
&channel_type_features,
);

push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
push_amt -=
get_holder_selected_channel_reserve_satoshis(100_000, &default_config).unwrap() * 1000;

let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, push_amt);

Expand Down Expand Up @@ -1035,7 +1037,8 @@ pub fn test_chan_reserve_violation_inbound_htlc_outbound_channel() {
MIN_AFFORDABLE_HTLC_COUNT as u64,
&channel_type_features,
);
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
push_amt -=
get_holder_selected_channel_reserve_satoshis(100_000, &default_config).unwrap() * 1000;
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, push_amt);

// Send four HTLCs to cover the initial push_msat buffer we're required to include
Expand Down Expand Up @@ -1111,7 +1114,8 @@ pub fn test_chan_reserve_dust_inbound_htlcs_outbound_chan() {
MIN_AFFORDABLE_HTLC_COUNT as u64,
&channel_type_features,
);
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
push_amt -=
get_holder_selected_channel_reserve_satoshis(100_000, &default_config).unwrap() * 1000;
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt);

let (htlc_success_tx_fee_sat, _) =
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4951,7 +4951,7 @@ fn test_htlc_forward_considers_anchor_outputs_value() {
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, CHAN_AMT, PUSH_MSAT);

let channel_reserve_msat =
get_holder_selected_channel_reserve_satoshis(CHAN_AMT, &config) * 1000;
get_holder_selected_channel_reserve_satoshis(CHAN_AMT, &config).unwrap() * 1000;
let commitment_fee_msat = chan_utils::commit_tx_fee_sat(
*nodes[1].fee_estimator.sat_per_kw.lock().unwrap(),
2,
Expand Down
Loading