cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA)
}
- pub fn get_max_dust_htlc_exposure_msat<F: Deref>(&self,
- fee_estimator: &LowerBoundedFeeEstimator<F>) -> u64
- where F::Target: FeeEstimator
- {
+ fn get_dust_exposure_limiting_feerate<F: Deref>(&self,
+ fee_estimator: &LowerBoundedFeeEstimator<F>,
+ ) -> u32 where F::Target: FeeEstimator {
+ fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::OnChainSweep)
+ }
+
+ pub fn get_max_dust_htlc_exposure_msat(&self, limiting_feerate_sat_per_kw: u32) -> u64 {
match self.config.options.max_dust_htlc_exposure {
MaxDustHTLCExposure::FeeRateMultiplier(multiplier) => {
- let feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(
- ConfirmationTarget::OnChainSweep) as u64;
- feerate_per_kw.saturating_mul(multiplier)
+ (limiting_feerate_sat_per_kw as u64).saturating_mul(multiplier)
},
MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
}
}
/// Returns a HTLCStats about pending htlcs
- fn get_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>) -> HTLCStats {
+ fn get_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>, dust_exposure_limiting_feerate: u32) -> HTLCStats {
let context = self;
let uses_0_htlc_fee_anchors = self.get_channel_type().supports_anchors_zero_fee_htlc_tx();
+ let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update);
let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if uses_0_htlc_fee_anchors {
(0, 0)
} else {
- let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update) as u64;
- (dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000,
- dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000)
+ (dust_buffer_feerate as u64 * htlc_timeout_tx_weight(context.get_channel_type()) / 1000,
+ dust_buffer_feerate as u64 * htlc_success_tx_weight(context.get_channel_type()) / 1000)
};
let mut on_holder_tx_dust_exposure_msat = 0;
let mut on_counterparty_tx_dust_exposure_msat = 0;
+ let mut on_counterparty_tx_offered_nondust_htlcs = 0;
+ let mut on_counterparty_tx_accepted_nondust_htlcs = 0;
+
let mut pending_inbound_htlcs_value_msat = 0;
+
{
let counterparty_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.counterparty_dust_limit_satoshis;
let holder_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
pending_inbound_htlcs_value_msat += htlc.amount_msat;
if htlc.amount_msat / 1000 < counterparty_dust_limit_timeout_sat {
on_counterparty_tx_dust_exposure_msat += htlc.amount_msat;
+ } else {
+ on_counterparty_tx_offered_nondust_htlcs += 1;
}
if htlc.amount_msat / 1000 < holder_dust_limit_success_sat {
on_holder_tx_dust_exposure_msat += htlc.amount_msat;
pending_outbound_htlcs_value_msat += htlc.amount_msat;
if htlc.amount_msat / 1000 < counterparty_dust_limit_success_sat {
on_counterparty_tx_dust_exposure_msat += htlc.amount_msat;
+ } else {
+ on_counterparty_tx_accepted_nondust_htlcs += 1;
}
if htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat {
on_holder_tx_dust_exposure_msat += htlc.amount_msat;
outbound_holding_cell_msat += amount_msat;
if *amount_msat / 1000 < counterparty_dust_limit_success_sat {
on_counterparty_tx_dust_exposure_msat += amount_msat;
+ } else {
+ on_counterparty_tx_accepted_nondust_htlcs += 1;
}
if *amount_msat / 1000 < holder_dust_limit_timeout_sat {
on_holder_tx_dust_exposure_msat += amount_msat;
}
}
+ // Include any mining "excess" fees in the dust calculation
+ let excess_feerate_opt = outbound_feerate_update
+ .or(self.pending_update_fee.map(|(fee, _)| fee))
+ .unwrap_or(self.feerate_per_kw)
+ .checked_sub(dust_exposure_limiting_feerate);
+ if let Some(excess_feerate) = excess_feerate_opt {
+ let on_counterparty_tx_nondust_htlcs =
+ on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs;
+ on_counterparty_tx_dust_exposure_msat +=
+ commit_tx_fee_msat(excess_feerate, on_counterparty_tx_nondust_htlcs, &self.channel_type);
+ if !self.channel_type.supports_anchors_zero_fee_htlc_tx() {
+ on_counterparty_tx_dust_exposure_msat +=
+ on_counterparty_tx_accepted_nondust_htlcs as u64 * htlc_success_tx_weight(&self.channel_type)
+ * excess_feerate as u64 / 1000;
+ on_counterparty_tx_dust_exposure_msat +=
+ on_counterparty_tx_offered_nondust_htlcs as u64 * htlc_timeout_tx_weight(&self.channel_type)
+ * excess_feerate as u64 / 1000;
+ }
+ }
+
HTLCStats {
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
pending_outbound_htlcs,
where F::Target: FeeEstimator
{
let context = &self;
- // Note that we have to handle overflow due to the above case.
- let htlc_stats = context.get_pending_htlc_stats(None);
+ // Note that we have to handle overflow due to the case mentioned in the docs in general
+ // here.
+
+ let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator);
+ let htlc_stats = context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
let mut balance_msat = context.value_to_self_msat;
for ref htlc in context.pending_inbound_htlcs.iter() {
// send above the dust limit (as the router can always overpay to meet the dust limit).
let mut remaining_msat_below_dust_exposure_limit = None;
let mut dust_exposure_dust_limit_msat = 0;
- let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(context.counterparty_dust_limit_satoshis, context.holder_dust_limit_satoshis)
(context.counterparty_dust_limit_satoshis + dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000,
context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
};
- 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()) {
+
+ let excess_feerate_opt = self.feerate_per_kw.checked_sub(dust_exposure_limiting_feerate);
+ if let Some(excess_feerate) = excess_feerate_opt {
+ let htlc_dust_exposure_msat =
+ per_outbound_htlc_counterparty_commit_tx_fee_msat(excess_feerate, &context.channel_type);
+ let nondust_htlc_counterparty_tx_dust_exposure =
+ htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat);
+ if nondust_htlc_counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
+ // If adding an extra HTLC would put us over the dust limit in total fees, we cannot
+ // send any non-dust HTLCs.
+ available_capacity_msat = cmp::min(available_capacity_msat, htlc_success_dust_limit * 1000);
+ }
+ }
+
+ if htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_success_dust_limit * 1000) > max_dust_htlc_exposure_msat.saturating_add(1) {
+ // Note that we don't use the `counterparty_tx_dust_exposure` (with
+ // `htlc_dust_exposure_msat`) here as it only applies to non-dust HTLCs.
remaining_msat_below_dust_exposure_limit =
Some(max_dust_htlc_exposure_msat.saturating_sub(htlc_stats.on_counterparty_tx_dust_exposure_msat));
dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_success_dust_limit * 1000);
(commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000
}
+pub(crate) fn per_outbound_htlc_counterparty_commit_tx_fee_msat(feerate_per_kw: u32, channel_type_features: &ChannelTypeFeatures) -> u64 {
+ // Note that we need to divide before multiplying to round properly,
+ // since the lowest denomination of bitcoin on-chain is the satoshi.
+ let commitment_tx_fee = COMMITMENT_TX_WEIGHT_PER_HTLC * feerate_per_kw as u64 / 1000 * 1000;
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ commitment_tx_fee + htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
+ } else {
+ commitment_tx_fee
+ }
+}
+
/// Context for dual-funded channels.
#[cfg(any(dual_funding, splicing))]
pub(super) struct DualFundingChannelContext {
Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger))
}
- pub fn update_add_htlc(
+ pub fn update_add_htlc<F: Deref>(
&mut self, msg: &msgs::UpdateAddHTLC, pending_forward_status: PendingHTLCStatus,
- ) -> Result<(), ChannelError> {
+ fee_estimator: &LowerBoundedFeeEstimator<F>,
+ ) -> Result<(), ChannelError> where F::Target: FeeEstimator {
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
return Err(ChannelError::Close("Got add HTLC message when channel was not in an operational state".to_owned()));
}
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)));
}
- let htlc_stats = self.context.get_pending_htlc_stats(None);
+ let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
+ let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
if htlc_stats.pending_inbound_htlcs + 1 > self.context.holder_max_accepted_htlcs as usize {
return Err(ChannelError::Close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.context.holder_max_accepted_htlcs)));
}
}
// Before proposing a feerate update, check that we can actually afford the new fee.
- let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw));
+ let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
+ let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw), dust_exposure_limiting_feerate);
let keys = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number);
let commitment_stats = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &keys, true, true, logger);
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;
}
// Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`.
- let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw);
return None;
self.context.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced));
self.context.update_time_counter += 1;
// Check that we won't be pushed over our dust exposure limit by the feerate increase.
- if !self.context.channel_type.supports_anchors_zero_fee_htlc_tx() {
- let htlc_stats = self.context.get_pending_htlc_stats(None);
- let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
- if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
- 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)",
- msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat)));
- }
- if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
- 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)",
- msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat)));
- }
+ let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
+ let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
+ if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
+ 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)",
+ msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat)));
+ }
+ if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat {
+ 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)",
+ msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat)));
}
Ok(())
}
return Err(("Shutdown was already sent", 0x4000|8))
}
- let htlc_stats = self.context.get_pending_htlc_stats(None);
- let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
+ let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
return Err(("Exceeded our dust exposure limit on counterparty commitment tx", 0x1000|7))
}
+ } else {
+ let htlc_dust_exposure_msat =
+ per_outbound_htlc_counterparty_commit_tx_fee_msat(self.context.feerate_per_kw, &self.context.channel_type);
+ let counterparty_tx_dust_exposure =
+ htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat);
+ if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
+ log_info!(logger, "Cannot accept value that would put our exposure to tx fee dust at {} over the limit {} on counterparty commitment tx",
+ counterparty_tx_dust_exposure, max_dust_htlc_exposure_msat);
+ return Err(("Exceeded our tx fee dust exposure limit on counterparty commitment tx", 0x1000|7))
+ }
}
let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis;
AtUpdateFeeOutbound,
}
-fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool, multiplier_dust_limit: bool) {
+fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool, multiplier_dust_limit: bool, apply_excess_fee: bool) {
// Test that we properly reject dust HTLC violating our `max_dust_htlc_exposure_msat`
// policy.
//
let chanmon_cfgs = create_chanmon_cfgs(2);
let mut config = test_default_channel_config();
+
+ // We hard-code the feerate values here but they're re-calculated furter down and asserted.
+ // If the values ever change below these constants should simply be updated.
+ const AT_FEE_OUTBOUND_HTLCS: u64 = 20;
+ let nondust_htlc_count_in_limit =
+ if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
+ AT_FEE_OUTBOUND_HTLCS
+ } else { 0 };
+ let initial_feerate = if apply_excess_fee { 253 * 2 } else { 253 };
+ let expected_dust_buffer_feerate = initial_feerate + 2530;
+ let mut commitment_tx_cost = commit_tx_fee_msat(initial_feerate - 253, nondust_htlc_count_in_limit, &ChannelTypeFeatures::empty());
+ commitment_tx_cost +=
+ if on_holder_tx {
+ htlc_success_tx_weight(&ChannelTypeFeatures::empty())
+ } else {
+ htlc_timeout_tx_weight(&ChannelTypeFeatures::empty())
+ } * (initial_feerate as u64 - 253) / 1000 * nondust_htlc_count_in_limit;
+ {
+ let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
+ *feerate_lock = initial_feerate;
+ }
config.channel_config.max_dust_htlc_exposure = if multiplier_dust_limit {
// Default test fee estimator rate is 253 sat/kw, so we set the multiplier to 5_000_000 / 253
// to get roughly the same initial value as the default setting when this test was
// originally written.
- MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253)
- } else { MaxDustHTLCExposure::FixedLimitMsat(5_000_000) }; // initial default setting value
+ MaxDustHTLCExposure::FeeRateMultiplier((5_000_000 + commitment_tx_cost) / 253)
+ } else { MaxDustHTLCExposure::FixedLimitMsat(5_000_000 + commitment_tx_cost) };
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let (announcement, as_update, bs_update) = create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &channel_ready);
update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &as_update, &bs_update);
+ {
+ let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
+ *feerate_lock = 253;
+ }
+
// Fetch a route in advance as we will be unable to once we're unable to send.
let (mut route, payment_hash, _, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], 1000);
let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
let chan = chan_lock.channel_by_id.get(&channel_id).unwrap();
(chan.context().get_dust_buffer_feerate(None) as u64,
- chan.context().get_max_dust_htlc_exposure_msat(&LowerBoundedFeeEstimator(nodes[0].fee_estimator)))
+ chan.context().get_max_dust_htlc_exposure_msat(253))
};
+ assert_eq!(dust_buffer_feerate, expected_dust_buffer_feerate as u64);
let dust_outbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - 1) * 1000;
let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - if multiplier_dust_limit { 3 } else { 2 }) * 1000;
let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
+ // This test was written with a fixed dust value here, which we retain, but assert that it is,
+ // indeed, dust on both transactions.
let dust_htlc_on_counterparty_tx: u64 = 4;
- let dust_htlc_on_counterparty_tx_msat: u64 = max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
+ let dust_htlc_on_counterparty_tx_msat: u64 = 1_250_000;
+ let calcd_dust_htlc_on_counterparty_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - if multiplier_dust_limit { 3 } else { 2 }) * 1000;
+ assert!(dust_htlc_on_counterparty_tx_msat < dust_inbound_htlc_on_holder_tx_msat);
+ assert!(dust_htlc_on_counterparty_tx_msat < calcd_dust_htlc_on_counterparty_tx_msat);
if on_holder_tx {
if dust_outbound_balance {
// Outbound dust balance: 5200 sats
nodes[0].logger.assert_log("lightning::ln::channel",
format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
- dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 4,
+ dust_htlc_on_counterparty_tx_msat * dust_htlc_on_counterparty_tx + commitment_tx_cost + 4,
max_dust_htlc_exposure_msat), 1);
}
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
// For the multiplier dust exposure limit, since it scales with feerate,
// we need to add a lot of HTLCs that will become dust at the new feerate
// to cross the threshold.
- for _ in 0..20 {
+ for _ in 0..AT_FEE_OUTBOUND_HTLCS {
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(1_000), None);
nodes[0].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
added_monitors.clear();
}
-fn do_test_max_dust_htlc_exposure_by_threshold_type(multiplier_dust_limit: bool) {
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
+fn do_test_max_dust_htlc_exposure_by_threshold_type(multiplier_dust_limit: bool, apply_excess_fee: bool) {
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit, apply_excess_fee);
+ if !multiplier_dust_limit && !apply_excess_fee {
+ // Because non-dust HTLC transaction fees are included in the dust exposure, trying to
+ // increase the fee to hit a higher dust exposure with a
+ // `MaxDustHTLCExposure::FeeRateMultiplier` is no longer super practical, so we skip these
+ // in the `multiplier_dust_limit` case.
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit, apply_excess_fee);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit, apply_excess_fee);
+ }
}
#[test]
fn test_max_dust_htlc_exposure() {
- do_test_max_dust_htlc_exposure_by_threshold_type(false);
- do_test_max_dust_htlc_exposure_by_threshold_type(true);
+ do_test_max_dust_htlc_exposure_by_threshold_type(false, false);
+ do_test_max_dust_htlc_exposure_by_threshold_type(false, true);
+ do_test_max_dust_htlc_exposure_by_threshold_type(true, false);
+ do_test_max_dust_htlc_exposure_by_threshold_type(true, true);
}
#[test]