Skip to content

Commit

Permalink
Use multiplier in dust exposure threshold calculation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alecchendev committed Jun 19, 2023
1 parent ab9fe62 commit c24d052
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 24 deletions.
5 changes: 4 additions & 1 deletion lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4966,7 +4966,10 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
}

pub fn get_max_dust_htlc_exposure_msat(&self) -> u64 {
self.config.options.max_dust_htlc_exposure_msat
match (self.config.options.max_dust_htlc_exposure_multiplier_thousandths, self.high_priority_feerate_per_kw) {
(Some(multiplier_thousandths), Some(feerate_per_kw)) => feerate_per_kw as u64 * multiplier_thousandths,
_ => self.config.options.max_dust_htlc_exposure_msat,
}
}

/// Sets the channel's high priority feerate per kw.
Expand Down
5 changes: 5 additions & 0 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2528,7 +2528,12 @@ pub fn test_default_channel_config() -> UserConfig {
default_config.channel_handshake_config.our_htlc_minimum_msat = 1000;
// When most of our tests were written, we didn't have the notion of a `max_dust_htlc_exposure_msat`,
// It now defaults to 5_000_000 msat; to avoid interfering with tests we bump it to 50_000_000 msat.
// This is less relevant now as it's overriden by max_dust_htlc_exposure_multiplier_thousandths
// by default.
default_config.channel_config.max_dust_htlc_exposure_msat = 50_000_000;
// Similarly to the previous threshold, this was added after most of our tests were written, so
// we bump the default value to avoid interference.
default_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = Some(50_000_000 / 253);
default_config
}

Expand Down
67 changes: 44 additions & 23 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use crate::chain;
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
use crate::chain::chaininterface::{LowerBoundedFeeEstimator, FeeEstimator, ConfirmationTarget};
use crate::chain::channelmonitor;
use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
use crate::chain::transaction::OutPoint;
Expand Down Expand Up @@ -9565,7 +9565,7 @@ enum ExposureEvent {
AtUpdateFeeOutbound,
}

fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool) {
fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool, multiplier_dust_limit: bool) {
// Test that we properly reject dust HTLC violating our `max_dust_htlc_exposure_msat`
// policy.
//
Expand All @@ -9581,6 +9581,9 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
let chanmon_cfgs = create_chanmon_cfgs(2);
let mut config = test_default_channel_config();
config.channel_config.max_dust_htlc_exposure_msat = 5_000_000; // default setting value
// 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.
config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = if multiplier_dust_limit { Some(5_000_000 / 253) } else { None };
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);
Expand Down Expand Up @@ -9630,14 +9633,21 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
let chan = chan_lock.channel_by_id.get(&channel_id).unwrap();
chan.get_dust_buffer_feerate(None) as u64
};
let max_dust_htlc_exposure_msat = if multiplier_dust_limit {
nodes[0].fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64
* config.channel_config.max_dust_htlc_exposure_multiplier_thousandths.unwrap()
} else {
config.channel_config.max_dust_htlc_exposure_msat
};

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;
let dust_outbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
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(opt_anchors) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
let dust_inbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;

let dust_htlc_on_counterparty_tx: u64 = 4;
let dust_htlc_on_counterparty_tx_msat: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
let dust_htlc_on_counterparty_tx_msat: u64 = max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;

if on_holder_tx {
if dust_outbound_balance {
Expand Down Expand Up @@ -9689,7 +9699,7 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
), true, APIError::ChannelUnavailable { .. }, {});
}
} else if exposure_breach_event == ExposureEvent::AtHTLCReception {
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 });
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 });
nodes[1].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[1], 1);
Expand All @@ -9702,18 +9712,24 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
// Outbound dust balance: 6399 sats
let dust_inbound_overflow = dust_inbound_htlc_on_holder_tx_msat * (dust_inbound_htlc_on_holder_tx + 1);
let dust_outbound_overflow = dust_outbound_htlc_on_holder_tx_msat * dust_outbound_htlc_on_holder_tx + dust_inbound_htlc_on_holder_tx_msat;
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);
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);
} else {
// Outbound dust balance: 5200 sats
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 counterparty commitment tx",
dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 1,
config.channel_config.max_dust_htlc_exposure_msat), 1);
dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 4,
max_dust_htlc_exposure_msat), 1);
}
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
route.paths[0].hops.last_mut().unwrap().fee_msat = 2_500_000;
nodes[0].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
// 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 {
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();
}
{
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
*feerate_lock = *feerate_lock * 10;
Expand All @@ -9728,20 +9744,25 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
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);
}

#[test]
fn test_max_dust_htlc_exposure() {
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true);
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true);
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true);
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false);
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false);
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false);
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true);
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false);
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true);
do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false);
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
do_test_max_dust_htlc_exposure_by_threshold_type(false);
do_test_max_dust_htlc_exposure_by_threshold_type(true);
}

#[test]
Expand Down
10 changes: 10 additions & 0 deletions lightning/src/ln/onion_route_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ fn do_test_onion_failure_stale_channel_update(announced_channel: bool) {
config.channel_handshake_config.announced_channel = announced_channel;
config.channel_handshake_limits.force_announced_channel_preference = false;
config.accept_forwards_to_priv_channels = !announced_channel;
config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = None;
let chanmon_cfgs = create_chanmon_cfgs(3);
let persister;
let chain_monitor;
Expand Down Expand Up @@ -1371,11 +1372,20 @@ fn test_phantom_failure_too_low_recv_amt() {

#[test]
fn test_phantom_dust_exposure_failure() {
do_test_phantom_dust_exposure_failure(false);
do_test_phantom_dust_exposure_failure(true);
}

fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) {
// Set the max dust exposure to the dust limit.
let max_dust_exposure = 546;
let mut receiver_config = UserConfig::default();
receiver_config.channel_config.max_dust_htlc_exposure_msat = max_dust_exposure;
receiver_config.channel_handshake_config.announced_channel = true;
// Default test fee estimator rate is 253, so to set the max dust exposure to the dust limit,
// we need to set the multiplier to 2.
receiver_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths =
if multiplier_dust_limit { Some(2) } else { None };

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
Expand Down
2 changes: 2 additions & 0 deletions lightning/src/ln/priv_short_conf_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ fn do_test_1_conf_open(connect_style: ConnectStyle) {
alice_config.channel_handshake_config.minimum_depth = 1;
alice_config.channel_handshake_config.announced_channel = true;
alice_config.channel_handshake_limits.force_announced_channel_preference = false;
alice_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = None;
let mut bob_config = UserConfig::default();
bob_config.channel_handshake_config.minimum_depth = 1;
bob_config.channel_handshake_config.announced_channel = true;
bob_config.channel_handshake_limits.force_announced_channel_preference = false;
bob_config.channel_config.max_dust_htlc_exposure_multiplier_thousandths = None;
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(alice_config), Some(bob_config)]);
Expand Down

0 comments on commit c24d052

Please sign in to comment.