Skip to content

Commit 3218db1

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. `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. Inbound `LocalRemoved` HTLCs that were **not** successful are now credited to `remote_balance_before_fee_msat` as they will certainly not be on the next remote commitment. We previously debited these from the remote balance to arrive at `remote_balance_before_fee_msat`. 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. Furthermore, dust exposure calculations now take a buffer from the currently committed feerate, and ignore any fee updates in `ChannelContext.pending_update_fee`.
1 parent 48b412a commit 3218db1

File tree

2 files changed

+32
-61
lines changed

2 files changed

+32
-61
lines changed

lightning/src/ln/channel.rs

Lines changed: 31 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4483,79 +4483,50 @@ where
44834483

44844484
#[rustfmt::skip]
44854485
fn can_accept_incoming_htlc<L: Deref>(
4486-
&self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC,
4486+
&self, funding: &FundingScope,
44874487
dust_exposure_limiting_feerate: Option<u32>, logger: &L,
44884488
) -> Result<(), LocalHTLCFailureReason>
44894489
where
44904490
L::Target: Logger,
44914491
{
4492-
let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate);
4492+
// The fee spike buffer (an additional nondust HTLC) we keep for the remote if the channel
4493+
// is not zero fee. This deviates from the spec because the fee spike buffer requirement
4494+
// doesn't exist on the receiver's side, only on the sender's.
4495+
let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
4496+
0
4497+
} else {
4498+
1
4499+
};
4500+
// 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)
4501+
let include_counterparty_unknown_htlcs = false;
4502+
// A `None` `HTLCCandidate` is used as in this case because we're already accounting for
4503+
// the incoming HTLC as it has been fully committed by both sides.
4504+
let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, None, include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate);
4505+
let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, None, include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate);
4506+
44934507
let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
4494-
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat;
4495-
if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
4508+
if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
44964509
// Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction
44974510
log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
4498-
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
4511+
next_remote_commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat);
44994512
return Err(LocalHTLCFailureReason::DustLimitCounterparty)
45004513
}
4501-
let dust_buffer_feerate = self.get_dust_buffer_feerate(None);
4502-
let (htlc_success_tx_fee_sat, _) = second_stage_tx_fees_sat(
4503-
&funding.get_channel_type(), dust_buffer_feerate,
4504-
);
4505-
let exposure_dust_limit_success_sats = htlc_success_tx_fee_sat + self.holder_dust_limit_satoshis;
4506-
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
4507-
let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat;
4508-
if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
4509-
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
4510-
on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
4511-
return Err(LocalHTLCFailureReason::DustLimitHolder)
4512-
}
4514+
if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
4515+
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
4516+
next_local_commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat);
4517+
return Err(LocalHTLCFailureReason::DustLimitHolder)
45134518
}
45144519

45154520
if !funding.is_outbound() {
4516-
let removed_outbound_total_msat: u64 = self.pending_outbound_htlcs
4517-
.iter()
4518-
.filter_map(|htlc| {
4519-
matches!(
4520-
htlc.state,
4521-
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_, _))
4522-
| OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_, _))
4523-
)
4524-
.then_some(htlc.amount_msat)
4525-
})
4526-
.sum();
4527-
let pending_value_to_self_msat =
4528-
funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat;
4529-
let pending_remote_value_msat =
4530-
funding.get_value_satoshis() * 1000 - pending_value_to_self_msat;
4531-
// Subtract any non-HTLC outputs from the local and remote balances
4532-
let (_, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs(
4533-
funding.is_outbound(),
4534-
pending_value_to_self_msat,
4535-
pending_remote_value_msat,
4536-
funding.get_channel_type()
4537-
);
4538-
4539-
// `Some(())` is for the fee spike buffer we keep for the remote if the channel is
4540-
// not zero fee. This deviates from the spec because the fee spike buffer requirement
4541-
// doesn't exist on the receiver's side, only on the sender's. Note that with anchor
4542-
// outputs we are no longer as sensitive to fee spikes, so we need to account for them.
4543-
//
4544-
// A `None` `HTLCCandidate` is used as in this case because we're already accounting for
4545-
// the incoming HTLC as it has been fully committed by both sides.
4546-
let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
4547-
None
4548-
} else {
4549-
Some(())
4550-
};
4551-
4552-
let mut remote_fee_cost_incl_stuck_buffer_msat = self.next_remote_commit_tx_fee_msat(
4553-
funding, None, fee_spike_buffer_htlc,
4554-
);
4521+
let mut remote_fee_incl_fee_spike_buffer_htlc_msat = next_remote_commitment_stats.commit_tx_fee_sat * 1000;
4522+
// Note that with anchor outputs we are no longer as sensitive to fee spikes, so we don't need to account for them.
45554523
if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
4556-
remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
4524+
remote_fee_incl_fee_spike_buffer_htlc_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
45574525
}
4558-
if remote_balance_before_fee_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000) < remote_fee_cost_incl_stuck_buffer_msat {
4526+
// We unwrap here; if the HTLC exhausts the counterparty's balance, we should have rejected it at `update_add_htlc`, here the HTLC is already
4527+
// irrevocably committed to the channel.
4528+
let remote_balance_before_fee_msat = next_remote_commitment_stats.counterparty_balance_before_fee_msat.expect("The counterparty's balance before fees should never underflow");
4529+
if remote_balance_before_fee_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000) < remote_fee_incl_fee_spike_buffer_htlc_msat {
45594530
log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.channel_id());
45604531
return Err(LocalHTLCFailureReason::FeeSpikeBuffer);
45614532
}
@@ -9744,7 +9715,7 @@ where
97449715
/// this function determines whether to fail the HTLC, or forward / claim it.
97459716
#[rustfmt::skip]
97469717
pub fn can_accept_incoming_htlc<F: Deref, L: Deref>(
9747-
&self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
9718+
&self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
97489719
) -> Result<(), LocalHTLCFailureReason>
97499720
where
97509721
F::Target: FeeEstimator,
@@ -9760,7 +9731,7 @@ where
97609731

97619732
core::iter::once(&self.funding)
97629733
.chain(self.pending_funding.iter())
9763-
.try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, msg, dust_exposure_limiting_feerate, &logger))
9734+
.try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, dust_exposure_limiting_feerate, &logger))
97649735
}
97659736

97669737
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
@@ -6392,7 +6392,7 @@ where
63926392
&chan.context,
63936393
Some(update_add_htlc.payment_hash),
63946394
);
6395-
chan.can_accept_incoming_htlc(update_add_htlc, &self.fee_estimator, &logger)
6395+
chan.can_accept_incoming_htlc(&self.fee_estimator, &logger)
63966396
},
63976397
) {
63986398
Some(Ok(_)) => {},

0 commit comments

Comments
 (0)