Skip to content

Commit 51bf78d

Browse files
committed
Include excess commitment transaction fees in dust exposure
Transaction fees on counterparty commitment transactions are ultimately not our money and thus are really "dust" from our PoV - they're funds that may be ours during off-chain updates but are not ours once we go on-chain. Thus, here, we count any such fees in excess of our own fee estimates towards dust exposure. We don't bother to make an inbound/outbound channel distinction here as in most cases users will use `MaxDustExposure::FeeRateMultiplier` which will scale with the fee we set on outbound channels anyway. Note that this also enables the dust exposure checks on anchor channels during feerate updates. We'd previously elided these as increases in the channel feerates do not change the HTLC dust exposure, but now do for the fee dust exposure.
1 parent 11c3d70 commit 51bf78d

File tree

3 files changed

+170
-56
lines changed

3 files changed

+170
-56
lines changed

lightning/src/ln/channel.rs

+107-33
Original file line numberDiff line numberDiff line change
@@ -2339,15 +2339,16 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
23392339
cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA)
23402340
}
23412341

2342-
pub fn get_max_dust_htlc_exposure_msat<F: Deref>(&self,
2343-
fee_estimator: &LowerBoundedFeeEstimator<F>) -> u64
2344-
where F::Target: FeeEstimator
2345-
{
2342+
fn get_dust_exposure_limiting_feerate<F: Deref>(&self,
2343+
fee_estimator: &LowerBoundedFeeEstimator<F>,
2344+
) -> u32 where F::Target: FeeEstimator {
2345+
fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::OnChainSweep)
2346+
}
2347+
2348+
pub fn get_max_dust_htlc_exposure_msat(&self, limiting_feerate_sat_per_kw: u32) -> u64 {
23462349
match self.config.options.max_dust_htlc_exposure {
23472350
MaxDustHTLCExposure::FeeRateMultiplier(multiplier) => {
2348-
let feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(
2349-
ConfirmationTarget::OnChainSweep) as u64;
2350-
feerate_per_kw.saturating_mul(multiplier)
2351+
(limiting_feerate_sat_per_kw as u64).saturating_mul(multiplier)
23512352
},
23522353
MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
23532354
}
@@ -2741,29 +2742,35 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
27412742
}
27422743

27432744
/// Returns a HTLCStats about pending htlcs
2744-
fn get_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>) -> HTLCStats {
2745+
fn get_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>, dust_exposure_limiting_feerate: u32) -> HTLCStats {
27452746
let context = self;
27462747
let uses_0_htlc_fee_anchors = self.get_channel_type().supports_anchors_zero_fee_htlc_tx();
27472748

2749+
let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update);
27482750
let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if uses_0_htlc_fee_anchors {
27492751
(0, 0)
27502752
} else {
2751-
let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update) as u64;
2752-
(dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000,
2753-
dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000)
2753+
(dust_buffer_feerate as u64 * htlc_timeout_tx_weight(context.get_channel_type()) / 1000,
2754+
dust_buffer_feerate as u64 * htlc_success_tx_weight(context.get_channel_type()) / 1000)
27542755
};
27552756

27562757
let mut on_holder_tx_dust_exposure_msat = 0;
27572758
let mut on_counterparty_tx_dust_exposure_msat = 0;
27582759

2760+
let mut on_counterparty_tx_offered_nondust_htlcs = 0;
2761+
let mut on_counterparty_tx_accepted_nondust_htlcs = 0;
2762+
27592763
let mut pending_inbound_htlcs_value_msat = 0;
2764+
27602765
{
27612766
let counterparty_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.counterparty_dust_limit_satoshis;
27622767
let holder_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
27632768
for ref htlc in context.pending_inbound_htlcs.iter() {
27642769
pending_inbound_htlcs_value_msat += htlc.amount_msat;
27652770
if htlc.amount_msat / 1000 < counterparty_dust_limit_timeout_sat {
27662771
on_counterparty_tx_dust_exposure_msat += htlc.amount_msat;
2772+
} else {
2773+
on_counterparty_tx_offered_nondust_htlcs += 1;
27672774
}
27682775
if htlc.amount_msat / 1000 < holder_dust_limit_success_sat {
27692776
on_holder_tx_dust_exposure_msat += htlc.amount_msat;
@@ -2782,6 +2789,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
27822789
pending_outbound_htlcs_value_msat += htlc.amount_msat;
27832790
if htlc.amount_msat / 1000 < counterparty_dust_limit_success_sat {
27842791
on_counterparty_tx_dust_exposure_msat += htlc.amount_msat;
2792+
} else {
2793+
on_counterparty_tx_accepted_nondust_htlcs += 1;
27852794
}
27862795
if htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat {
27872796
on_holder_tx_dust_exposure_msat += htlc.amount_msat;
@@ -2795,6 +2804,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
27952804
outbound_holding_cell_msat += amount_msat;
27962805
if *amount_msat / 1000 < counterparty_dust_limit_success_sat {
27972806
on_counterparty_tx_dust_exposure_msat += amount_msat;
2807+
} else {
2808+
on_counterparty_tx_accepted_nondust_htlcs += 1;
27982809
}
27992810
if *amount_msat / 1000 < holder_dust_limit_timeout_sat {
28002811
on_holder_tx_dust_exposure_msat += amount_msat;
@@ -2805,6 +2816,26 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
28052816
}
28062817
}
28072818

2819+
// Include any mining "excess" fees in the dust calculation
2820+
let excess_feerate_opt = outbound_feerate_update
2821+
.or(self.pending_update_fee.map(|(fee, _)| fee))
2822+
.unwrap_or(self.feerate_per_kw)
2823+
.checked_sub(dust_exposure_limiting_feerate);
2824+
if let Some(excess_feerate) = excess_feerate_opt {
2825+
let on_counterparty_tx_nondust_htlcs =
2826+
on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs;
2827+
on_counterparty_tx_dust_exposure_msat +=
2828+
commit_tx_fee_msat(excess_feerate, on_counterparty_tx_nondust_htlcs, &self.channel_type);
2829+
if !self.channel_type.supports_anchors_zero_fee_htlc_tx() {
2830+
on_counterparty_tx_dust_exposure_msat +=
2831+
on_counterparty_tx_accepted_nondust_htlcs as u64 * htlc_success_tx_weight(&self.channel_type)
2832+
* excess_feerate as u64 / 1000;
2833+
on_counterparty_tx_dust_exposure_msat +=
2834+
on_counterparty_tx_offered_nondust_htlcs as u64 * htlc_timeout_tx_weight(&self.channel_type)
2835+
* excess_feerate as u64 / 1000;
2836+
}
2837+
}
2838+
28082839
HTLCStats {
28092840
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
28102841
pending_outbound_htlcs,
@@ -2919,8 +2950,11 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
29192950
where F::Target: FeeEstimator
29202951
{
29212952
let context = &self;
2922-
// Note that we have to handle overflow due to the above case.
2923-
let htlc_stats = context.get_pending_htlc_stats(None);
2953+
// Note that we have to handle overflow due to the case mentioned in the docs in general
2954+
// here.
2955+
2956+
let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator);
2957+
let htlc_stats = context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
29242958

29252959
let mut balance_msat = context.value_to_self_msat;
29262960
for ref htlc in context.pending_inbound_htlcs.iter() {
@@ -3008,7 +3042,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
30083042
// send above the dust limit (as the router can always overpay to meet the dust limit).
30093043
let mut remaining_msat_below_dust_exposure_limit = None;
30103044
let mut dust_exposure_dust_limit_msat = 0;
3011-
let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(fee_estimator);
3045+
let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
30123046

30133047
let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
30143048
(context.counterparty_dust_limit_satoshis, context.holder_dust_limit_satoshis)
@@ -3017,7 +3051,23 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
30173051
(context.counterparty_dust_limit_satoshis + dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000,
30183052
context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
30193053
};
3020-
if htlc_stats.on_counterparty_tx_dust_exposure_msat as i64 + htlc_success_dust_limit as i64 * 1000 - 1 > max_dust_htlc_exposure_msat.try_into().unwrap_or(i64::max_value()) {
3054+
3055+
let excess_feerate_opt = self.feerate_per_kw.checked_sub(dust_exposure_limiting_feerate);
3056+
if let Some(excess_feerate) = excess_feerate_opt {
3057+
let htlc_dust_exposure_msat =
3058+
per_outbound_htlc_counterparty_commit_tx_fee_msat(excess_feerate, &context.channel_type);
3059+
let nondust_htlc_counterparty_tx_dust_exposure =
3060+
htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat);
3061+
if nondust_htlc_counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
3062+
// If adding an extra HTLC would put us over the dust limit in total fees, we cannot
3063+
// send any non-dust HTLCs.
3064+
available_capacity_msat = cmp::min(available_capacity_msat, htlc_success_dust_limit * 1000);
3065+
}
3066+
}
3067+
3068+
if htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_success_dust_limit * 1000) > max_dust_htlc_exposure_msat.saturating_add(1) {
3069+
// Note that we don't use the `counterparty_tx_dust_exposure` (with
3070+
// `htlc_dust_exposure_msat`) here as it only applies to non-dust HTLCs.
30213071
remaining_msat_below_dust_exposure_limit =
30223072
Some(max_dust_htlc_exposure_msat.saturating_sub(htlc_stats.on_counterparty_tx_dust_exposure_msat));
30233073
dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_success_dust_limit * 1000);
@@ -3517,6 +3567,17 @@ pub(crate) fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_
35173567
(commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000
35183568
}
35193569

3570+
pub(crate) fn per_outbound_htlc_counterparty_commit_tx_fee_msat(feerate_per_kw: u32, channel_type_features: &ChannelTypeFeatures) -> u64 {
3571+
// Note that we need to divide before multiplying to round properly,
3572+
// since the lowest denomination of bitcoin on-chain is the satoshi.
3573+
let commitment_tx_fee = COMMITMENT_TX_WEIGHT_PER_HTLC * feerate_per_kw as u64 / 1000 * 1000;
3574+
if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
3575+
commitment_tx_fee + htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
3576+
} else {
3577+
commitment_tx_fee
3578+
}
3579+
}
3580+
35203581
/// Context for dual-funded channels.
35213582
#[cfg(any(dual_funding, splicing))]
35223583
pub(super) struct DualFundingChannelContext {
@@ -4114,9 +4175,10 @@ impl<SP: Deref> Channel<SP> where
41144175
Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger))
41154176
}
41164177

4117-
pub fn update_add_htlc(
4178+
pub fn update_add_htlc<F: Deref>(
41184179
&mut self, msg: &msgs::UpdateAddHTLC, pending_forward_status: PendingHTLCStatus,
4119-
) -> Result<(), ChannelError> {
4180+
fee_estimator: &LowerBoundedFeeEstimator<F>,
4181+
) -> Result<(), ChannelError> where F::Target: FeeEstimator {
41204182
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
41214183
return Err(ChannelError::Close("Got add HTLC message when channel was not in an operational state".to_owned()));
41224184
}
@@ -4137,7 +4199,8 @@ impl<SP: Deref> Channel<SP> where
41374199
return Err(ChannelError::Close(format!("Remote side tried to send less than our minimum HTLC value. Lower limit: ({}). Actual: ({})", self.context.holder_htlc_minimum_msat, msg.amount_msat)));
41384200
}
41394201

4140-
let htlc_stats = self.context.get_pending_htlc_stats(None);
4202+
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
4203+
let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
41414204
if htlc_stats.pending_inbound_htlcs + 1 > self.context.holder_max_accepted_htlcs as usize {
41424205
return Err(ChannelError::Close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.context.holder_max_accepted_htlcs)));
41434206
}
@@ -4989,7 +5052,8 @@ impl<SP: Deref> Channel<SP> where
49895052
}
49905053

49915054
// Before proposing a feerate update, check that we can actually afford the new fee.
4992-
let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw));
5055+
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
5056+
let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw), dust_exposure_limiting_feerate);
49935057
let keys = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number);
49945058
let commitment_stats = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &keys, true, true, logger);
49955059
let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.num_nondust_htlcs + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000;
@@ -5001,7 +5065,7 @@ impl<SP: Deref> Channel<SP> where
50015065
}
50025066

50035067
// Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`.
5004-
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
5068+
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
50055069
if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
50065070
log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw);
50075071
return None;
@@ -5239,17 +5303,16 @@ impl<SP: Deref> Channel<SP> where
52395303
self.context.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced));
52405304
self.context.update_time_counter += 1;
52415305
// Check that we won't be pushed over our dust exposure limit by the feerate increase.
5242-
if !self.context.channel_type.supports_anchors_zero_fee_htlc_tx() {
5243-
let htlc_stats = self.context.get_pending_htlc_stats(None);
5244-
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
5245-
if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
5246-
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)",
5247-
msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat)));
5248-
}
5249-
if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
5250-
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)",
5251-
msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat)));
5252-
}
5306+
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
5307+
let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
5308+
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
5309+
if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
5310+
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)",
5311+
msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat)));
5312+
}
5313+
if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
5314+
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)",
5315+
msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat)));
52535316
}
52545317
Ok(())
52555318
}
@@ -6093,8 +6156,9 @@ impl<SP: Deref> Channel<SP> where
60936156
return Err(("Shutdown was already sent", 0x4000|8))
60946157
}
60956158

6096-
let htlc_stats = self.context.get_pending_htlc_stats(None);
6097-
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
6159+
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
6160+
let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
6161+
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
60986162
let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
60996163
(0, 0)
61006164
} else {
@@ -6110,6 +6174,16 @@ impl<SP: Deref> Channel<SP> where
61106174
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
61116175
return Err(("Exceeded our dust exposure limit on counterparty commitment tx", 0x1000|7))
61126176
}
6177+
} else {
6178+
let htlc_dust_exposure_msat =
6179+
per_outbound_htlc_counterparty_commit_tx_fee_msat(self.context.feerate_per_kw, &self.context.channel_type);
6180+
let counterparty_tx_dust_exposure =
6181+
htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat);
6182+
if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
6183+
log_info!(logger, "Cannot accept value that would put our exposure to tx fee dust at {} over the limit {} on counterparty commitment tx",
6184+
counterparty_tx_dust_exposure, max_dust_htlc_exposure_msat);
6185+
return Err(("Exceeded our tx fee dust exposure limit on counterparty commitment tx", 0x1000|7))
6186+
}
61136187
}
61146188

61156189
let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis;

lightning/src/ln/channelmanager.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7673,7 +7673,7 @@ where
76737673
}
76747674
}
76757675
}
7676-
try_chan_phase_entry!(self, chan.update_add_htlc(&msg, pending_forward_info), chan_phase_entry);
7676+
try_chan_phase_entry!(self, chan.update_add_htlc(&msg, pending_forward_info, &self.fee_estimator), chan_phase_entry);
76777677
} else {
76787678
return try_chan_phase_entry!(self, Err(ChannelError::Close(
76797679
"Got an update_add_htlc message for an unfunded channel!".into())), chan_phase_entry);

0 commit comments

Comments
 (0)