Skip to content

Commit c24d052

Browse files
committed
Use multiplier in dust exposure threshold calculation
This commit makes use of the added config knob to calculate the dust exposure threshold based on the current fee rate. This also updates tests to ensure it works as intended.
1 parent ab9fe62 commit c24d052

File tree

5 files changed

+65
-24
lines changed

5 files changed

+65
-24
lines changed

lightning/src/ln/channel.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -4966,7 +4966,10 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
49664966
}
49674967

49684968
pub fn get_max_dust_htlc_exposure_msat(&self) -> u64 {
4969-
self.config.options.max_dust_htlc_exposure_msat
4969+
match (self.config.options.max_dust_htlc_exposure_multiplier_thousandths, self.high_priority_feerate_per_kw) {
4970+
(Some(multiplier_thousandths), Some(feerate_per_kw)) => feerate_per_kw as u64 * multiplier_thousandths,
4971+
_ => self.config.options.max_dust_htlc_exposure_msat,
4972+
}
49704973
}
49714974

49724975
/// Sets the channel's high priority feerate per kw.

lightning/src/ln/functional_test_utils.rs

+5
Original file line numberDiff line numberDiff line change
@@ -2528,7 +2528,12 @@ pub fn test_default_channel_config() -> UserConfig {
25282528
default_config.channel_handshake_config.our_htlc_minimum_msat = 1000;
25292529
// When most of our tests were written, we didn't have the notion of a `max_dust_htlc_exposure_msat`,
25302530
// It now defaults to 5_000_000 msat; to avoid interfering with tests we bump it to 50_000_000 msat.
2531+
// This is less relevant now as it's overriden by max_dust_htlc_exposure_multiplier_thousandths
2532+
// by default.
25312533
default_config.channel_config.max_dust_htlc_exposure_msat = 50_000_000;
2534+
// Similarly to the previous threshold, this was added after most of our tests were written, so
2535+
// we bump the default value to avoid interference.
2536+
default_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = Some(50_000_000 / 253);
25322537
default_config
25332538
}
25342539

lightning/src/ln/functional_tests.rs

+44-23
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use crate::chain;
1515
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
16-
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
16+
use crate::chain::chaininterface::{LowerBoundedFeeEstimator, FeeEstimator, ConfirmationTarget};
1717
use crate::chain::channelmonitor;
1818
use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
1919
use crate::chain::transaction::OutPoint;
@@ -9565,7 +9565,7 @@ enum ExposureEvent {
95659565
AtUpdateFeeOutbound,
95669566
}
95679567

9568-
fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool) {
9568+
fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool, multiplier_dust_limit: bool) {
95699569
// Test that we properly reject dust HTLC violating our `max_dust_htlc_exposure_msat`
95709570
// policy.
95719571
//
@@ -9581,6 +9581,9 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
95819581
let chanmon_cfgs = create_chanmon_cfgs(2);
95829582
let mut config = test_default_channel_config();
95839583
config.channel_config.max_dust_htlc_exposure_msat = 5_000_000; // default setting value
9584+
// Default test fee estimator rate is 253 sat/kw, so we set the multiplier to 5_000_000 / 253
9585+
// to get roughly the same initial value as the default setting.
9586+
config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = if multiplier_dust_limit { Some(5_000_000 / 253) } else { None };
95849587
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
95859588
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
95869589
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
@@ -9630,14 +9633,21 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
96309633
let chan = chan_lock.channel_by_id.get(&channel_id).unwrap();
96319634
chan.get_dust_buffer_feerate(None) as u64
96329635
};
9636+
let max_dust_htlc_exposure_msat = if multiplier_dust_limit {
9637+
nodes[0].fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64
9638+
* config.channel_config.max_dust_htlc_exposure_multiplier_thousandths.unwrap()
9639+
} else {
9640+
config.channel_config.max_dust_htlc_exposure_msat
9641+
};
9642+
96339643
let dust_outbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(opt_anchors) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
9634-
let dust_outbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
9644+
let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
96359645

96369646
let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(opt_anchors) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
9637-
let dust_inbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
9647+
let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
96389648

96399649
let dust_htlc_on_counterparty_tx: u64 = 4;
9640-
let dust_htlc_on_counterparty_tx_msat: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
9650+
let dust_htlc_on_counterparty_tx_msat: u64 = max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
96419651

96429652
if on_holder_tx {
96439653
if dust_outbound_balance {
@@ -9689,7 +9699,7 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
96899699
), true, APIError::ChannelUnavailable { .. }, {});
96909700
}
96919701
} else if exposure_breach_event == ExposureEvent::AtHTLCReception {
9692-
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], if on_holder_tx { dust_inbound_htlc_on_holder_tx_msat } else { dust_htlc_on_counterparty_tx_msat + 1 });
9702+
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], if on_holder_tx { dust_inbound_htlc_on_holder_tx_msat } else { dust_htlc_on_counterparty_tx_msat + 4 });
96939703
nodes[1].node.send_payment_with_route(&route, payment_hash,
96949704
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
96959705
check_added_monitors!(nodes[1], 1);
@@ -9702,18 +9712,24 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
97029712
// Outbound dust balance: 6399 sats
97039713
let dust_inbound_overflow = dust_inbound_htlc_on_holder_tx_msat * (dust_inbound_htlc_on_holder_tx + 1);
97049714
let dust_outbound_overflow = dust_outbound_htlc_on_holder_tx_msat * dust_outbound_htlc_on_holder_tx + dust_inbound_htlc_on_holder_tx_msat;
9705-
nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", if dust_outbound_balance { dust_outbound_overflow } else { dust_inbound_overflow }, config.channel_config.max_dust_htlc_exposure_msat), 1);
9715+
nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", if dust_outbound_balance { dust_outbound_overflow } else { dust_inbound_overflow }, max_dust_htlc_exposure_msat), 1);
97069716
} else {
97079717
// Outbound dust balance: 5200 sats
97089718
nodes[0].logger.assert_log("lightning::ln::channel".to_string(),
97099719
format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
9710-
dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 1,
9711-
config.channel_config.max_dust_htlc_exposure_msat), 1);
9720+
dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 4,
9721+
max_dust_htlc_exposure_msat), 1);
97129722
}
97139723
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
97149724
route.paths[0].hops.last_mut().unwrap().fee_msat = 2_500_000;
9715-
nodes[0].node.send_payment_with_route(&route, payment_hash,
9716-
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
9725+
// For the multiplier dust exposure limit, since it scales with feerate,
9726+
// we need to add a lot of HTLCs that will become dust at the new feerate
9727+
// to cross the threshold.
9728+
for _ in 0..20 {
9729+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(1_000), None);
9730+
nodes[0].node.send_payment_with_route(&route, payment_hash,
9731+
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
9732+
}
97179733
{
97189734
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
97199735
*feerate_lock = *feerate_lock * 10;
@@ -9728,20 +9744,25 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
97289744
added_monitors.clear();
97299745
}
97309746

9747+
fn do_test_max_dust_htlc_exposure_by_threshold_type(multiplier_dust_limit: bool) {
9748+
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
9749+
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
9750+
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
9751+
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
9752+
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
9753+
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
9754+
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
9755+
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
9756+
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
9757+
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
9758+
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
9759+
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
9760+
}
9761+
97319762
#[test]
97329763
fn test_max_dust_htlc_exposure() {
9733-
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true);
9734-
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true);
9735-
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true);
9736-
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false);
9737-
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false);
9738-
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false);
9739-
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true);
9740-
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false);
9741-
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true);
9742-
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false);
9743-
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
9744-
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
9764+
do_test_max_dust_htlc_exposure_by_threshold_type(false);
9765+
do_test_max_dust_htlc_exposure_by_threshold_type(true);
97459766
}
97469767

97479768
#[test]

lightning/src/ln/onion_route_tests.rs

+10
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ fn do_test_onion_failure_stale_channel_update(announced_channel: bool) {
671671
config.channel_handshake_config.announced_channel = announced_channel;
672672
config.channel_handshake_limits.force_announced_channel_preference = false;
673673
config.accept_forwards_to_priv_channels = !announced_channel;
674+
config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = None;
674675
let chanmon_cfgs = create_chanmon_cfgs(3);
675676
let persister;
676677
let chain_monitor;
@@ -1371,11 +1372,20 @@ fn test_phantom_failure_too_low_recv_amt() {
13711372

13721373
#[test]
13731374
fn test_phantom_dust_exposure_failure() {
1375+
do_test_phantom_dust_exposure_failure(false);
1376+
do_test_phantom_dust_exposure_failure(true);
1377+
}
1378+
1379+
fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) {
13741380
// Set the max dust exposure to the dust limit.
13751381
let max_dust_exposure = 546;
13761382
let mut receiver_config = UserConfig::default();
13771383
receiver_config.channel_config.max_dust_htlc_exposure_msat = max_dust_exposure;
13781384
receiver_config.channel_handshake_config.announced_channel = true;
1385+
// Default test fee estimator rate is 253, so to set the max dust exposure to the dust limit,
1386+
// we need to set the multiplier to 2.
1387+
receiver_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths =
1388+
if multiplier_dust_limit { Some(2) } else { None };
13791389

13801390
let chanmon_cfgs = create_chanmon_cfgs(2);
13811391
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);

lightning/src/ln/priv_short_conf_tests.rs

+2
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,12 @@ fn do_test_1_conf_open(connect_style: ConnectStyle) {
141141
alice_config.channel_handshake_config.minimum_depth = 1;
142142
alice_config.channel_handshake_config.announced_channel = true;
143143
alice_config.channel_handshake_limits.force_announced_channel_preference = false;
144+
alice_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = None;
144145
let mut bob_config = UserConfig::default();
145146
bob_config.channel_handshake_config.minimum_depth = 1;
146147
bob_config.channel_handshake_config.announced_channel = true;
147148
bob_config.channel_handshake_limits.force_announced_channel_preference = false;
149+
bob_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = None;
148150
let chanmon_cfgs = create_chanmon_cfgs(2);
149151
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
150152
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(alice_config), Some(bob_config)]);

0 commit comments

Comments
 (0)