Skip to content
109 changes: 71 additions & 38 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3777,6 +3777,14 @@ impl<SP: SignerProvider> ChannelContext<SP> {
"Funding must be smaller than the total bitcoin supply. It was {channel_value_satoshis}"
)));
}
if !channel_type.supports_anchors_zero_fee_htlc_tx()
&& !channel_type.supports_anchor_zero_fee_commitments()
&& (msg_channel_reserve_satoshis == 0 || holder_selected_channel_reserve_satoshis == 0)
{
return Err(ChannelError::close(format!(
"0-reserve is not allowed on legacy channels"
)));
}
if msg_channel_reserve_satoshis > channel_value_satoshis {
return Err(ChannelError::close(format!(
"Bogus channel_reserve_satoshis ({msg_channel_reserve_satoshis}). Must be no greater than channel_value_satoshis: {channel_value_satoshis}"
Expand Down Expand Up @@ -4198,6 +4206,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
addl_nondust_htlc_count,
channel_context.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand Down Expand Up @@ -4267,6 +4276,14 @@ impl<SP: SignerProvider> ChannelContext<SP> {
}

let channel_type = get_initial_channel_type(&config, their_features);
if !channel_type.supports_anchors_zero_fee_htlc_tx()
&& !channel_type.supports_anchor_zero_fee_commitments()
&& holder_selected_channel_reserve_satoshis == 0
{
return Err(APIError::APIMisuseError {
err: format!("0-reserve is not allowed on legacy channels"),
});
}
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type
.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));
Expand Down Expand Up @@ -4494,6 +4511,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
addl_nondust_htlc_count,
channel_context.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| APIError::APIMisuseError {
Expand Down Expand Up @@ -4812,6 +4830,15 @@ impl<SP: SignerProvider> ChannelContext<SP> {
}

let channel_type = funding.get_channel_type();
if !channel_type.supports_anchors_zero_fee_htlc_tx()
&& !channel_type.supports_anchor_zero_fee_commitments()
&& (channel_reserve_satoshis == 0
|| funding.holder_selected_channel_reserve_satoshis == 0)
{
return Err(ChannelError::close(
"0-reserve is not allowed on legacy channels".to_owned(),
));
}
if common_fields.max_accepted_htlcs > max_htlcs(channel_type) {
return Err(ChannelError::close(format!(
"max_accepted_htlcs was {}. It must not be larger than {}",
Expand Down Expand Up @@ -5284,7 +5311,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
fn get_next_local_commitment_stats(
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
feerate_per_kw: u32, dust_exposure_limiting_feerate: Option<u32>,
feerate_per_kw: u32, assuming_future_increased_feerate: bool,
dust_exposure_limiting_feerate: Option<u32>,
) -> Result<(ChannelStats, Vec<HTLCAmountDirection>), ()> {
let next_commitment_htlcs = self.get_next_commitment_htlcs(
true,
Expand All @@ -5306,6 +5334,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
&next_commitment_htlcs,
addl_nondust_htlc_count,
feerate_per_kw,
assuming_future_increased_feerate,
dust_exposure_limiting_feerate,
max_dust_htlc_exposure_msat,
channel_constraints,
Expand All @@ -5330,6 +5359,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
&next_commitment_htlcs,
0,
feerate_per_kw,
false,
dust_exposure_limiting_feerate,
max_dust_htlc_exposure_msat,
channel_constraints,
Expand All @@ -5351,7 +5381,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
fn get_next_remote_commitment_stats(
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
feerate_per_kw: u32, dust_exposure_limiting_feerate: Option<u32>,
feerate_per_kw: u32, assuming_future_increased_feerate: bool,
dust_exposure_limiting_feerate: Option<u32>,
) -> Result<(ChannelStats, Vec<HTLCAmountDirection>), ()> {
let next_commitment_htlcs = self.get_next_commitment_htlcs(
false,
Expand All @@ -5373,6 +5404,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
&next_commitment_htlcs,
addl_nondust_htlc_count,
feerate_per_kw,
assuming_future_increased_feerate,
dust_exposure_limiting_feerate,
max_dust_htlc_exposure_msat,
channel_constraints,
Expand All @@ -5397,6 +5429,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
&next_commitment_htlcs,
0,
feerate_per_kw,
false,
dust_exposure_limiting_feerate,
max_dust_htlc_exposure_msat,
channel_constraints,
Expand Down Expand Up @@ -5439,6 +5472,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
fee_spike_buffer_htlc,
self.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand Down Expand Up @@ -5497,6 +5531,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
fee_spike_buffer_htlc,
self.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand All @@ -5523,6 +5558,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
0,
new_feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand All @@ -5544,6 +5580,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
0,
new_feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand Down Expand Up @@ -5724,6 +5761,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize,
feerate_per_kw,
false,
dust_exposure_limiting_feerate,
) {
stats
Expand Down Expand Up @@ -5763,6 +5801,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize,
feerate_per_kw,
false,
dust_exposure_limiting_feerate,
) {
stats
Expand Down Expand Up @@ -5810,6 +5849,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
fee_spike_buffer_htlc,
feerate,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand All @@ -5826,6 +5866,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
fee_spike_buffer_htlc,
feerate,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand Down Expand Up @@ -5862,21 +5903,14 @@ impl<SP: SignerProvider> ChannelContext<SP> {
if !funding.is_outbound() {
// Note that with anchor outputs we are no longer as sensitive to fee spikes, so we don't need
// to account for them.
let fee_spike_multiple =
if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32
} else {
1
};
// Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
let spiked_feerate = feerate.saturating_mul(fee_spike_multiple);
let (remote_stats, _remote_htlcs) = self
.get_next_remote_commitment_stats(
funding,
None,
include_counterparty_unknown_htlcs,
fee_spike_buffer_htlc,
spiked_feerate,
feerate,
true,
dust_exposure_limiting_feerate,
)
.map_err(|()| {
Expand Down Expand Up @@ -6231,6 +6265,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
addl_nondust_htlc_count,
self.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map(|(remote_stats, _)| remote_stats.available_balances)?;
Expand All @@ -6252,6 +6287,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
include_counterparty_unknown_htlcs,
addl_nondust_htlc_count,
self.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.unwrap();
Expand Down Expand Up @@ -6473,17 +6509,15 @@ impl<SP: SignerProvider> ChannelContext<SP> {
/// If we receive an error message when attempting to open a channel, it may only be a rejection
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
#[rustfmt::skip]
pub(crate) fn maybe_downgrade_channel_features<F: FeeEstimator>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()> {
if !funding.is_outbound() ||
!matches!(
if !funding.is_outbound()
|| !matches!(
self.channel_state, ChannelState::NegotiatingFunding(flags)
if flags == NegotiatingFundingFlags::OUR_INIT_SENT
)
{
) {
return Err(());
}
if funding.get_channel_type() == &ChannelTypeFeatures::only_static_remote_key() {
Expand Down Expand Up @@ -6514,11 +6548,17 @@ impl<SP: SignerProvider> ChannelContext<SP> {
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);
if !next_channel_type.supports_anchors_zero_fee_htlc_tx()
&& !next_channel_type.supports_anchor_zero_fee_commitments()
&& funding.holder_selected_channel_reserve_satoshis == 0
{
// 0-reserve is not allowed on legacy channels
return Err(());
}

self.feerate_per_kw = selected_commitment_sat_per_1000_weight(
&fee_estimator, &next_channel_type,
);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
self.feerate_per_kw =
selected_commitment_sat_per_1000_weight(&fee_estimator, &next_channel_type);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;

Ok(())
}
Expand Down Expand Up @@ -13372,16 +13412,6 @@ where
// We are not interested in dust exposure
let dust_exposure_limiting_feerate = None;

// Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
let feerate_per_kw = if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
// Similar to HTLC additions, require the funder to have enough funds reserved for
// fees such that the feerate can jump without rendering the channel useless.
let spike_mul = FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
self.context.feerate_per_kw.saturating_mul(spike_mul)
} else {
self.context.feerate_per_kw
};

// Different dust limits on the local and remote commitments cause the commitment
// transaction fee to be different depending on the commitment, so we grab the floor
// of both balances across both commitments here.
Expand All @@ -13399,7 +13429,8 @@ where
None, // htlc_candidate
include_counterparty_unknown_htlcs,
addl_nondust_htlc_count,
feerate_per_kw,
self.context.feerate_per_kw,
true,
dust_exposure_limiting_feerate,
)
.map_err(|()| "Balance exhausted on local commitment")?;
Expand All @@ -13411,7 +13442,8 @@ where
None, // htlc_candidate
include_counterparty_unknown_htlcs,
addl_nondust_htlc_count,
feerate_per_kw,
self.context.feerate_per_kw,
true,
dust_exposure_limiting_feerate,
)
.map_err(|()| "Balance exhausted on remote commitment")?;
Expand Down Expand Up @@ -13451,6 +13483,7 @@ where
include_counterparty_unknown_htlcs,
0,
self.context.feerate_per_kw,
false,
dust_exposure_limiting_feerate,
)
.map_err(|()| "Balance exhausted on remote commitment")?;
Expand Down Expand Up @@ -17186,7 +17219,7 @@ mod tests {
// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
// the dust limit check.
let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amount_msat, outbound: true };
let local_commit_tx_fee = node_a_chan.context.get_next_local_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), false, 0, node_a_chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let local_commit_tx_fee = node_a_chan.context.get_next_local_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), false, 0, node_a_chan.context.feerate_per_kw, false, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let local_commit_fee_0_htlcs = commit_tx_fee_sat(node_a_chan.context.feerate_per_kw, 0, node_a_chan.funding.get_channel_type()) * 1000;
assert_eq!(local_commit_tx_fee, local_commit_fee_0_htlcs);

Expand All @@ -17195,7 +17228,7 @@ mod tests {
node_a_chan.funding.channel_transaction_parameters.is_outbound_from_holder = false;
let remote_commit_fee_3_htlcs = commit_tx_fee_sat(node_a_chan.context.feerate_per_kw, 3, node_a_chan.funding.get_channel_type()) * 1000;
let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amount_msat, outbound: true };
let remote_commit_tx_fee = node_a_chan.context.get_next_remote_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), false, 0, node_a_chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let remote_commit_tx_fee = node_a_chan.context.get_next_remote_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), false, 0, node_a_chan.context.feerate_per_kw, false, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
assert_eq!(remote_commit_tx_fee, remote_commit_fee_3_htlcs);
}

Expand Down Expand Up @@ -17230,27 +17263,27 @@ mod tests {
// counted as dust when it shouldn't be.
let htlc_amt_above_timeout = (htlc_timeout_tx_fee_sat + chan.context.holder_dust_limit_satoshis + 1) * 1000;
let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amt_above_timeout, outbound: true };
let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, false, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc);

// If swapped: this HTLC would be counted as non-dust when it shouldn't be.
let dust_htlc_amt_below_success = (htlc_success_tx_fee_sat + chan.context.holder_dust_limit_satoshis - 1) * 1000;
let htlc_candidate = HTLCAmountDirection { amount_msat: dust_htlc_amt_below_success, outbound: false };
let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, false, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs);

chan.funding.channel_transaction_parameters.is_outbound_from_holder = false;

// If swapped: this HTLC would be counted as non-dust when it shouldn't be.
let dust_htlc_amt_above_timeout = (htlc_timeout_tx_fee_sat + chan.context.counterparty_dust_limit_satoshis + 1) * 1000;
let htlc_candidate = HTLCAmountDirection { amount_msat: dust_htlc_amt_above_timeout, outbound: true };
let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, false, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs);

// If swapped: this HTLC would be counted as dust when it shouldn't be.
let htlc_amt_below_success = (htlc_success_tx_fee_sat + chan.context.counterparty_dust_limit_satoshis - 1) * 1000;
let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amt_below_success, outbound: false };
let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, false, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000;
assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc);
}

Expand Down
Loading