Skip to content

Commit a386f6f

Browse files
committed
Improve prediction of commitment stats in can_accept_incoming_htlc
`ChannelContext::get_pending_htlc_stats` predicts that the set of HTLCs on the next commitment will be all the HTLCs in `ChannelContext.pending_inbound_htlcs`, and `ChannelContext.pending_outbound_htlcs`, as well as all the outbound HTLC adds in the holding cell. This is an overestimate: * Outbound HTLC removals which have been ACK'ed by the counterparty will certainly not be present in any *next* commitment, even though they remain in `pending_outbound_htlcs`. * Outbound HTLCs in the `RemoteRemoved` state, will not be present in the next *local* commitment. * Outbound HTLCs in the `LocalAnnounced` state have no guarantee that they were yet received by the counterparty. * Outbound `update_add_htlc`'s in the holding cell are certainly not known by the counterparty, and we will reevaluate their addition to the channel when freeing the holding cell. * Inbound HTLCs in the `LocalRemoved` state will not be present in the next *remote* commitment. This commit stops using `get_pending_htlc_stats` in favor of the newly added `ChannelContext::get_next_{local, remote}_commitment_stats` methods, and fixes the issues described above. The value of `remote_balance_before_fee_msat` remains *exactly* the same. `ChannelContext::next_remote_commit_tx_fee_msat` counts inbound HTLCs in the `LocalRemoved` state, as well as outbound HTLCs in the `LocalAnnounced` state. We now do not count them for the same reasons described above. Finally, we now always check holder dust exposure, whereas we previously would only do it if the incoming HTLC was dust on our own commitment transaction.
1 parent b7301de commit a386f6f

File tree

2 files changed

+30
-59
lines changed

2 files changed

+30
-59
lines changed

lightning/src/ln/channel.rs

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4488,78 +4488,49 @@ where
44884488

44894489
#[rustfmt::skip]
44904490
fn can_accept_incoming_htlc<L: Deref>(
4491-
&self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC,
4491+
&self, funding: &FundingScope,
44924492
dust_exposure_limiting_feerate: Option<u32>, logger: &L,
44934493
) -> Result<(), LocalHTLCFailureReason>
44944494
where
44954495
L::Target: Logger,
44964496
{
4497-
let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate);
4497+
// The fee spike buffer (an additional nondust HTLC) we keep for the remote if the channel
4498+
// is not zero fee. This deviates from the spec because the fee spike buffer requirement
4499+
// doesn't exist on the receiver's side, only on the sender's. Note that with anchor
4500+
// outputs we are no longer as sensitive to fee spikes, so we need to account for them.
4501+
let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
4502+
0
4503+
} else {
4504+
1
4505+
};
4506+
// Do not include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed by the counterparty (ie. LocalAnnounced HTLCs)
4507+
let do_not_include_counterparty_unknown_htlcs = false;
4508+
// A `None` `HTLCCandidate` is used as in this case because we're already accounting for
4509+
// the incoming HTLC as it has been fully committed by both sides.
4510+
let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, None, do_not_include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate);
4511+
let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, None, do_not_include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate);
4512+
44984513
let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
4499-
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat;
4500-
if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
4514+
if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
45014515
// Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction
45024516
log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
4503-
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
4517+
next_remote_commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat);
45044518
return Err(LocalHTLCFailureReason::DustLimitCounterparty)
45054519
}
4506-
let dust_buffer_feerate = self.get_dust_buffer_feerate(None);
4507-
let (htlc_success_tx_fee_sat, _) = second_stage_tx_fees_sat(
4508-
&funding.get_channel_type(), dust_buffer_feerate,
4509-
);
4510-
let exposure_dust_limit_success_sats = htlc_success_tx_fee_sat + self.holder_dust_limit_satoshis;
4511-
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
4512-
let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat;
4513-
if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
4514-
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
4515-
on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
4516-
return Err(LocalHTLCFailureReason::DustLimitHolder)
4517-
}
4520+
if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
4521+
// Note: We now always check holder dust exposure, whereas we previously would only
4522+
// do it if the incoming HTLC was dust on our own commitment transaction
4523+
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
4524+
next_local_commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat);
4525+
return Err(LocalHTLCFailureReason::DustLimitHolder)
45184526
}
45194527

45204528
if !funding.is_outbound() {
4521-
let removed_outbound_total_msat: u64 = self.pending_outbound_htlcs
4522-
.iter()
4523-
.filter_map(|htlc| {
4524-
matches!(
4525-
htlc.state,
4526-
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_, _))
4527-
| OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_, _))
4528-
)
4529-
.then_some(htlc.amount_msat)
4530-
})
4531-
.sum();
4532-
let pending_value_to_self_msat =
4533-
funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat;
4534-
let pending_remote_value_msat =
4535-
funding.get_value_satoshis() * 1000 - pending_value_to_self_msat;
4536-
// Subtract any non-HTLC outputs from the local and remote balances
4537-
let (_, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs(
4538-
funding.is_outbound(),
4539-
pending_value_to_self_msat,
4540-
pending_remote_value_msat,
4541-
funding.get_channel_type()
4542-
);
4543-
4544-
// `Some(())` is for the fee spike buffer we keep for the remote if the channel is
4545-
// not zero fee. This deviates from the spec because the fee spike buffer requirement
4546-
// doesn't exist on the receiver's side, only on the sender's. Note that with anchor
4547-
// outputs we are no longer as sensitive to fee spikes, so we need to account for them.
4548-
//
4549-
// A `None` `HTLCCandidate` is used as in this case because we're already accounting for
4550-
// the incoming HTLC as it has been fully committed by both sides.
4551-
let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
4552-
None
4553-
} else {
4554-
Some(())
4555-
};
4556-
4557-
let mut remote_fee_cost_incl_stuck_buffer_msat = self.next_remote_commit_tx_fee_msat(
4558-
funding, None, fee_spike_buffer_htlc,
4559-
);
4529+
let mut remote_fee_cost_incl_stuck_buffer_msat = next_remote_commitment_stats.commit_tx_fee_sat * 1000;
45604530
if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
45614531
remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
45624532
}
4533+
let remote_balance_before_fee_msat = next_remote_commitment_stats.counterparty_balance_msat.unwrap_or(0);
45634534
if remote_balance_before_fee_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000) < remote_fee_cost_incl_stuck_buffer_msat {
45644535
log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.channel_id());
45654536
return Err(LocalHTLCFailureReason::FeeSpikeBuffer);
@@ -9520,7 +9491,7 @@ where
95209491
/// this function determines whether to fail the HTLC, or forward / claim it.
95219492
#[rustfmt::skip]
95229493
pub fn can_accept_incoming_htlc<F: Deref, L: Deref>(
9523-
&self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
9494+
&self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
95249495
) -> Result<(), LocalHTLCFailureReason>
95259496
where
95269497
F::Target: FeeEstimator,
@@ -9536,7 +9507,7 @@ where
95369507

95379508
core::iter::once(&self.funding)
95389509
.chain(self.pending_funding.iter())
9539-
.try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, msg, dust_exposure_limiting_feerate, &logger))
9510+
.try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, dust_exposure_limiting_feerate, &logger))
95409511
}
95419512

95429513
pub fn get_cur_holder_commitment_transaction_number(&self) -> u64 {

lightning/src/ln/channelmanager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6262,7 +6262,7 @@ where
62626262
&chan.context,
62636263
Some(update_add_htlc.payment_hash),
62646264
);
6265-
chan.can_accept_incoming_htlc(update_add_htlc, &self.fee_estimator, &logger)
6265+
chan.can_accept_incoming_htlc(&self.fee_estimator, &logger)
62666266
},
62676267
) {
62686268
Some(Ok(_)) => {},

0 commit comments

Comments
 (0)