mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-15 15:39:09 +01:00
Merge pull request #3572 from tankyleo/25-01-htlc-dust-exposure
Tweak htlc dust exposure due to excess fees
This commit is contained in:
commit
81f8b6786c
3 changed files with 322 additions and 74 deletions
|
@ -196,15 +196,16 @@ pub(crate) fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, channel_t
|
||||||
/ 1000
|
/ 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn per_outbound_htlc_counterparty_commit_tx_fee_msat(feerate_per_kw: u32, channel_type_features: &ChannelTypeFeatures) -> u64 {
|
pub(crate) fn commit_and_htlc_tx_fees_sat(feerate_per_kw: u32, num_accepted_htlcs: usize, num_offered_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
|
||||||
// Note that we need to divide before multiplying to round properly,
|
let num_htlcs = num_accepted_htlcs + num_offered_htlcs;
|
||||||
// since the lowest denomination of bitcoin on-chain is the satoshi.
|
let commit_tx_fees_sat = commit_tx_fee_sat(feerate_per_kw, num_htlcs, channel_type_features);
|
||||||
let commitment_tx_fee = COMMITMENT_TX_WEIGHT_PER_HTLC * feerate_per_kw as u64 / 1000 * 1000;
|
let htlc_tx_fees_sat = if !channel_type_features.supports_anchors_zero_fee_htlc_tx() {
|
||||||
if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
|
num_accepted_htlcs as u64 * htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
|
||||||
commitment_tx_fee + htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
|
+ num_offered_htlcs as u64 * htlc_timeout_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
|
||||||
} else {
|
} else {
|
||||||
commitment_tx_fee
|
0
|
||||||
}
|
};
|
||||||
|
commit_tx_fees_sat + htlc_tx_fees_sat
|
||||||
}
|
}
|
||||||
|
|
||||||
// Various functions for key derivation and transaction creation for use within channels. Primarily
|
// Various functions for key derivation and transaction creation for use within channels. Primarily
|
||||||
|
|
|
@ -46,7 +46,7 @@ use crate::ln::chan_utils::{
|
||||||
HolderCommitmentTransaction, ChannelTransactionParameters,
|
HolderCommitmentTransaction, ChannelTransactionParameters,
|
||||||
CounterpartyChannelTransactionParameters, MAX_HTLCS,
|
CounterpartyChannelTransactionParameters, MAX_HTLCS,
|
||||||
get_commitment_transaction_number_obscure_factor,
|
get_commitment_transaction_number_obscure_factor,
|
||||||
ClosingTransaction, commit_tx_fee_sat, per_outbound_htlc_counterparty_commit_tx_fee_msat,
|
ClosingTransaction, commit_tx_fee_sat,
|
||||||
};
|
};
|
||||||
use crate::ln::chan_utils;
|
use crate::ln::chan_utils;
|
||||||
use crate::ln::onion_utils::HTLCFailReason;
|
use crate::ln::onion_utils::HTLCFailReason;
|
||||||
|
@ -866,6 +866,10 @@ struct HTLCStats {
|
||||||
pending_inbound_htlcs_value_msat: u64,
|
pending_inbound_htlcs_value_msat: u64,
|
||||||
pending_outbound_htlcs_value_msat: u64,
|
pending_outbound_htlcs_value_msat: u64,
|
||||||
on_counterparty_tx_dust_exposure_msat: u64,
|
on_counterparty_tx_dust_exposure_msat: u64,
|
||||||
|
// If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate,
|
||||||
|
// this will be set to the dust exposure that would result from us adding an additional nondust outbound
|
||||||
|
// htlc on the counterparty's commitment transaction.
|
||||||
|
extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat: Option<u64>,
|
||||||
on_holder_tx_dust_exposure_msat: u64,
|
on_holder_tx_dust_exposure_msat: u64,
|
||||||
outbound_holding_cell_msat: u64,
|
outbound_holding_cell_msat: u64,
|
||||||
on_holder_tx_outbound_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
|
on_holder_tx_outbound_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
|
||||||
|
@ -3794,20 +3798,13 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
|
||||||
.or(self.pending_update_fee.map(|(fee, _)| fee))
|
.or(self.pending_update_fee.map(|(fee, _)| fee))
|
||||||
.unwrap_or(self.feerate_per_kw)
|
.unwrap_or(self.feerate_per_kw)
|
||||||
.checked_sub(dust_exposure_limiting_feerate);
|
.checked_sub(dust_exposure_limiting_feerate);
|
||||||
if let Some(excess_feerate) = excess_feerate_opt {
|
let extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat = excess_feerate_opt.map(|excess_feerate| {
|
||||||
let on_counterparty_tx_nondust_htlcs =
|
let extra_htlc_dust_exposure = on_counterparty_tx_dust_exposure_msat
|
||||||
on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs;
|
+ chan_utils::commit_and_htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1, on_counterparty_tx_offered_nondust_htlcs, &self.channel_type) * 1000;
|
||||||
on_counterparty_tx_dust_exposure_msat +=
|
on_counterparty_tx_dust_exposure_msat
|
||||||
commit_tx_fee_sat(excess_feerate, on_counterparty_tx_nondust_htlcs, &self.channel_type) * 1000;
|
+= chan_utils::commit_and_htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs, on_counterparty_tx_offered_nondust_htlcs, &self.channel_type) * 1000;
|
||||||
if !self.channel_type.supports_anchors_zero_fee_htlc_tx() {
|
extra_htlc_dust_exposure
|
||||||
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 {
|
HTLCStats {
|
||||||
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
|
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
|
||||||
|
@ -3815,6 +3812,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
|
||||||
pending_inbound_htlcs_value_msat,
|
pending_inbound_htlcs_value_msat,
|
||||||
pending_outbound_htlcs_value_msat,
|
pending_outbound_htlcs_value_msat,
|
||||||
on_counterparty_tx_dust_exposure_msat,
|
on_counterparty_tx_dust_exposure_msat,
|
||||||
|
extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat,
|
||||||
on_holder_tx_dust_exposure_msat,
|
on_holder_tx_dust_exposure_msat,
|
||||||
outbound_holding_cell_msat,
|
outbound_holding_cell_msat,
|
||||||
on_holder_tx_outbound_holding_cell_htlcs_count,
|
on_holder_tx_outbound_holding_cell_htlcs_count,
|
||||||
|
@ -4019,13 +4017,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
|
||||||
context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
|
context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
|
||||||
};
|
};
|
||||||
|
|
||||||
let excess_feerate_opt = self.feerate_per_kw.checked_sub(dust_exposure_limiting_feerate);
|
if let Some(extra_htlc_dust_exposure) = htlc_stats.extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat {
|
||||||
if let Some(excess_feerate) = excess_feerate_opt {
|
if extra_htlc_dust_exposure > max_dust_htlc_exposure_msat {
|
||||||
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
|
// If adding an extra HTLC would put us over the dust limit in total fees, we cannot
|
||||||
// send any non-dust HTLCs.
|
// send any non-dust HTLCs.
|
||||||
available_capacity_msat = cmp::min(available_capacity_msat, htlc_success_dust_limit * 1000);
|
available_capacity_msat = cmp::min(available_capacity_msat, htlc_success_dust_limit * 1000);
|
||||||
|
@ -7486,6 +7479,8 @@ impl<SP: Deref> FundedChannel<SP> where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When this function is called, the HTLC is already irrevocably committed to the channel;
|
||||||
|
/// this function determines whether to fail the HTLC, or forward / claim it.
|
||||||
pub fn can_accept_incoming_htlc<F: Deref, L: Deref>(
|
pub fn can_accept_incoming_htlc<F: Deref, L: Deref>(
|
||||||
&self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
|
&self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
|
||||||
) -> Result<(), (&'static str, u16)>
|
) -> Result<(), (&'static str, u16)>
|
||||||
|
@ -7500,33 +7495,19 @@ impl<SP: Deref> FundedChannel<SP> where
|
||||||
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&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 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 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() {
|
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat;
|
||||||
(0, 0)
|
if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
|
||||||
|
// Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction
|
||||||
|
log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
|
||||||
|
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
|
||||||
|
return Err(("Exceeded our total dust exposure limit on counterparty commitment tx", 0x1000|7))
|
||||||
|
}
|
||||||
|
let htlc_success_dust_limit = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
|
||||||
|
0
|
||||||
} else {
|
} else {
|
||||||
let dust_buffer_feerate = self.context.get_dust_buffer_feerate(None) as u64;
|
let dust_buffer_feerate = self.context.get_dust_buffer_feerate(None) as u64;
|
||||||
(dust_buffer_feerate * htlc_timeout_tx_weight(self.context.get_channel_type()) / 1000,
|
dust_buffer_feerate * htlc_success_tx_weight(self.context.get_channel_type()) / 1000
|
||||||
dust_buffer_feerate * htlc_success_tx_weight(self.context.get_channel_type()) / 1000)
|
|
||||||
};
|
};
|
||||||
let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.context.counterparty_dust_limit_satoshis;
|
|
||||||
if msg.amount_msat / 1000 < exposure_dust_limit_timeout_sats {
|
|
||||||
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat;
|
|
||||||
if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
|
|
||||||
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
|
|
||||||
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;
|
let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis;
|
||||||
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
|
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
|
||||||
let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat;
|
let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat;
|
||||||
|
|
|
@ -24,7 +24,7 @@ use crate::ln::types::ChannelId;
|
||||||
use crate::types::payment::{PaymentPreimage, PaymentSecret, PaymentHash};
|
use crate::types::payment::{PaymentPreimage, PaymentSecret, PaymentHash};
|
||||||
use crate::ln::channel::{get_holder_selected_channel_reserve_satoshis, Channel, InboundV1Channel, OutboundV1Channel, COINBASE_MATURITY, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT};
|
use crate::ln::channel::{get_holder_selected_channel_reserve_satoshis, Channel, InboundV1Channel, OutboundV1Channel, COINBASE_MATURITY, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT};
|
||||||
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
|
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
|
||||||
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError};
|
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError, MIN_CHAN_DUST_LIMIT_SATOSHIS};
|
||||||
use crate::ln::{chan_utils, onion_utils};
|
use crate::ln::{chan_utils, onion_utils};
|
||||||
use crate::ln::chan_utils::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
|
use crate::ln::chan_utils::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
|
||||||
use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
|
use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
|
||||||
|
@ -10378,13 +10378,13 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
|
||||||
} else { 0 };
|
} else { 0 };
|
||||||
let initial_feerate = if apply_excess_fee { 253 * 2 } else { 253 };
|
let initial_feerate = if apply_excess_fee { 253 * 2 } else { 253 };
|
||||||
let expected_dust_buffer_feerate = initial_feerate + 2530;
|
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());
|
let mut commitment_tx_cost_msat = commit_tx_fee_msat(initial_feerate - 253, nondust_htlc_count_in_limit, &ChannelTypeFeatures::empty());
|
||||||
commitment_tx_cost +=
|
commitment_tx_cost_msat +=
|
||||||
if on_holder_tx {
|
if on_holder_tx {
|
||||||
htlc_success_tx_weight(&ChannelTypeFeatures::empty())
|
htlc_success_tx_weight(&ChannelTypeFeatures::empty())
|
||||||
} else {
|
} else {
|
||||||
htlc_timeout_tx_weight(&ChannelTypeFeatures::empty())
|
htlc_timeout_tx_weight(&ChannelTypeFeatures::empty())
|
||||||
} * (initial_feerate as u64 - 253) / 1000 * nondust_htlc_count_in_limit;
|
} * (initial_feerate as u64 - 253) * nondust_htlc_count_in_limit;
|
||||||
{
|
{
|
||||||
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
|
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
|
||||||
*feerate_lock = initial_feerate;
|
*feerate_lock = initial_feerate;
|
||||||
|
@ -10393,8 +10393,8 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
|
||||||
// Default test fee estimator rate is 253 sat/kw, so we set the multiplier to 5_000_000 / 253
|
// 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
|
// to get roughly the same initial value as the default setting when this test was
|
||||||
// originally written.
|
// originally written.
|
||||||
MaxDustHTLCExposure::FeeRateMultiplier((5_000_000 + commitment_tx_cost) / 253)
|
MaxDustHTLCExposure::FeeRateMultiplier((5_000_000 + commitment_tx_cost_msat) / 253)
|
||||||
} else { MaxDustHTLCExposure::FixedLimitMsat(5_000_000 + commitment_tx_cost) };
|
} else { MaxDustHTLCExposure::FixedLimitMsat(5_000_000 + commitment_tx_cost_msat) };
|
||||||
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
|
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
|
||||||
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
|
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
|
||||||
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
|
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
|
||||||
|
@ -10538,8 +10538,8 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
|
||||||
} else {
|
} else {
|
||||||
// Outbound dust balance: 5200 sats
|
// Outbound dust balance: 5200 sats
|
||||||
nodes[0].logger.assert_log("lightning::ln::channel",
|
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",
|
format!("Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
|
||||||
dust_htlc_on_counterparty_tx_msat * dust_htlc_on_counterparty_tx + commitment_tx_cost + 4,
|
dust_htlc_on_counterparty_tx_msat * dust_htlc_on_counterparty_tx + commitment_tx_cost_msat + 4,
|
||||||
max_dust_htlc_exposure_msat), 1);
|
max_dust_htlc_exposure_msat), 1);
|
||||||
}
|
}
|
||||||
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
|
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
|
||||||
|
@ -10596,9 +10596,20 @@ fn test_max_dust_htlc_exposure() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nondust_htlc_fees_are_dust() {
|
fn test_nondust_htlc_excess_fees_are_dust() {
|
||||||
// Test that the transaction fees paid in nondust HTLCs count towards our dust limit
|
// Test that the excess transaction fees paid in nondust HTLCs count towards our dust limit
|
||||||
|
const DEFAULT_FEERATE: u32 = 253;
|
||||||
|
const HIGH_FEERATE: u32 = 275;
|
||||||
|
const EXCESS_FEERATE: u32 = HIGH_FEERATE - DEFAULT_FEERATE;
|
||||||
let chanmon_cfgs = create_chanmon_cfgs(3);
|
let chanmon_cfgs = create_chanmon_cfgs(3);
|
||||||
|
{
|
||||||
|
// Set the feerate of the channel funder above the `dust_exposure_limiting_feerate` of
|
||||||
|
// the fundee. This delta means that the fundee will add the mining fees of the commitment and
|
||||||
|
// htlc transactions in excess of its `dust_exposure_limiting_feerate` to its total dust htlc
|
||||||
|
// exposure.
|
||||||
|
let mut feerate_lock = chanmon_cfgs[1].fee_estimator.sat_per_kw.lock().unwrap();
|
||||||
|
*feerate_lock = HIGH_FEERATE;
|
||||||
|
}
|
||||||
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
||||||
|
|
||||||
let mut config = test_default_channel_config();
|
let mut config = test_default_channel_config();
|
||||||
|
@ -10606,18 +10617,16 @@ fn test_nondust_htlc_fees_are_dust() {
|
||||||
config.channel_config.max_dust_htlc_exposure =
|
config.channel_config.max_dust_htlc_exposure =
|
||||||
MaxDustHTLCExposure::FeeRateMultiplier(10_000);
|
MaxDustHTLCExposure::FeeRateMultiplier(10_000);
|
||||||
// Make sure the HTLC limits don't get in the way
|
// Make sure the HTLC limits don't get in the way
|
||||||
config.channel_handshake_limits.min_max_accepted_htlcs = 400;
|
config.channel_handshake_limits.min_max_accepted_htlcs = chan_utils::MAX_HTLCS;
|
||||||
config.channel_handshake_config.our_max_accepted_htlcs = 400;
|
config.channel_handshake_config.our_max_accepted_htlcs = chan_utils::MAX_HTLCS;
|
||||||
config.channel_handshake_config.our_htlc_minimum_msat = 1;
|
config.channel_handshake_config.our_htlc_minimum_msat = 1;
|
||||||
|
config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
|
||||||
|
|
||||||
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config), Some(config), Some(config)]);
|
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config), Some(config), Some(config)]);
|
||||||
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
|
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
// Create a channel from 1 -> 0 but immediately push all of the funds towards 0
|
// Leave enough on the funder side to let it pay the mining fees for a commit tx with tons of htlcs
|
||||||
let chan_id_1 = create_announced_chan_between_nodes(&nodes, 1, 0).2;
|
let chan_id_1 = create_announced_chan_between_nodes_with_value(&nodes, 1, 0, 1_000_000, 750_000_000).2;
|
||||||
while nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat > 0 {
|
|
||||||
send_payment(&nodes[1], &[&nodes[0]], nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First get the channel one HTLC_VALUE HTLC away from the dust limit by sending dust HTLCs
|
// First get the channel one HTLC_VALUE HTLC away from the dust limit by sending dust HTLCs
|
||||||
// repeatedly until we run out of space.
|
// repeatedly until we run out of space.
|
||||||
|
@ -10637,16 +10646,24 @@ fn test_nondust_htlc_fees_are_dust() {
|
||||||
assert_ne!(nodes[0].node.list_channels()[0].next_outbound_htlc_minimum_msat, 0,
|
assert_ne!(nodes[0].node.list_channels()[0].next_outbound_htlc_minimum_msat, 0,
|
||||||
"Make sure we are able to send once we clear one HTLC");
|
"Make sure we are able to send once we clear one HTLC");
|
||||||
|
|
||||||
|
// Skip the router complaint when node 0 will attempt to pay node 1
|
||||||
|
let (route_0_1, payment_hash_0_1, _, payment_secret_0_1) = get_route_and_payment_hash!(nodes[0], nodes[1], dust_limit * 2);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
|
||||||
// At this point we have somewhere between dust_limit and dust_limit * 2 left in our dust
|
// At this point we have somewhere between dust_limit and dust_limit * 2 left in our dust
|
||||||
// exposure limit, and we want to max that out using non-dust HTLCs.
|
// exposure limit, and we want to max that out using non-dust HTLCs.
|
||||||
let commitment_tx_per_htlc_cost =
|
let commitment_tx_per_htlc_cost =
|
||||||
htlc_success_tx_weight(&ChannelTypeFeatures::empty()) * 253;
|
htlc_success_tx_weight(&ChannelTypeFeatures::empty()) * EXCESS_FEERATE as u64;
|
||||||
let max_htlcs_remaining = dust_limit * 2 / commitment_tx_per_htlc_cost;
|
let max_htlcs_remaining = dust_limit * 2 / commitment_tx_per_htlc_cost;
|
||||||
assert!(max_htlcs_remaining < 30,
|
assert!(max_htlcs_remaining < chan_utils::MAX_HTLCS.into(),
|
||||||
"We should be able to fill our dust limit without too many HTLCs");
|
"We should be able to fill our dust limit without too many HTLCs");
|
||||||
for i in 0..max_htlcs_remaining + 1 {
|
for i in 0..max_htlcs_remaining + 1 {
|
||||||
assert_ne!(i, max_htlcs_remaining);
|
assert_ne!(i, max_htlcs_remaining);
|
||||||
if nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat < dust_limit {
|
if nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat <= dust_limit {
|
||||||
// We found our limit, and it was less than max_htlcs_remaining!
|
// We found our limit, and it was less than max_htlcs_remaining!
|
||||||
// At this point we can only send dust HTLCs as any non-dust HTLCs will overuse our
|
// At this point we can only send dust HTLCs as any non-dust HTLCs will overuse our
|
||||||
// remaining dust exposure.
|
// remaining dust exposure.
|
||||||
|
@ -10655,6 +10672,57 @@ fn test_nondust_htlc_fees_are_dust() {
|
||||||
route_payment(&nodes[0], &[&nodes[1]], dust_limit * 2);
|
route_payment(&nodes[0], &[&nodes[1]], dust_limit * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
|
||||||
|
// Send an additional non-dust htlc from 1 to 0, and check the complaint
|
||||||
|
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], dust_limit * 2);
|
||||||
|
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);
|
||||||
|
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
|
||||||
|
assert_eq!(events.len(), 1);
|
||||||
|
let payment_event = SendEvent::from_event(events.remove(0));
|
||||||
|
nodes[0].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
|
||||||
|
commitment_signed_dance!(nodes[0], nodes[1], payment_event.commitment_msg, false);
|
||||||
|
expect_pending_htlcs_forwardable!(nodes[0]);
|
||||||
|
expect_htlc_handling_failed_destinations!(nodes[0].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]);
|
||||||
|
nodes[0].logger.assert_log("lightning::ln::channel",
|
||||||
|
format!("Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
|
||||||
|
2535000, 2530000), 1);
|
||||||
|
check_added_monitors!(nodes[0], 1);
|
||||||
|
|
||||||
|
// Clear the failed htlc
|
||||||
|
let updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
|
||||||
|
assert!(updates.update_add_htlcs.is_empty());
|
||||||
|
assert!(updates.update_fulfill_htlcs.is_empty());
|
||||||
|
assert_eq!(updates.update_fail_htlcs.len(), 1);
|
||||||
|
assert!(updates.update_fail_malformed_htlcs.is_empty());
|
||||||
|
assert!(updates.update_fee.is_none());
|
||||||
|
nodes[1].node.handle_update_fail_htlc(nodes[0].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
|
||||||
|
commitment_signed_dance!(nodes[1], nodes[0], updates.commitment_signed, false);
|
||||||
|
expect_payment_failed!(nodes[1], payment_hash, false);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
|
||||||
|
// Send an additional non-dust htlc from 0 to 1 using the pre-calculated route above, and check the immediate complaint
|
||||||
|
unwrap_send_err!(nodes[0], nodes[0].node.send_payment_with_route(route_0_1, payment_hash_0_1,
|
||||||
|
RecipientOnionFields::secret_only(payment_secret_0_1), PaymentId(payment_hash_0_1.0)
|
||||||
|
), true, APIError::ChannelUnavailable { .. }, {});
|
||||||
|
nodes[0].logger.assert_log("lightning::ln::outbound_payment",
|
||||||
|
format!("Failed to send along path due to error: Channel unavailable: Cannot send more than our next-HTLC maximum - {} msat", 2325000), 1);
|
||||||
|
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
|
||||||
// At this point non-dust HTLCs are no longer accepted from node 0 -> 1, we also check that
|
// At this point non-dust HTLCs are no longer accepted from node 0 -> 1, we also check that
|
||||||
// such HTLCs can't be routed over the same channel either.
|
// such HTLCs can't be routed over the same channel either.
|
||||||
create_announced_chan_between_nodes(&nodes, 2, 0);
|
create_announced_chan_between_nodes(&nodes, 2, 0);
|
||||||
|
@ -10682,6 +10750,204 @@ fn test_nondust_htlc_fees_are_dust() {
|
||||||
expect_payment_failed_conditions(&nodes[2], payment_hash, false, PaymentFailedConditions::new());
|
expect_payment_failed_conditions(&nodes[2], payment_hash, false, PaymentFailedConditions::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_test_nondust_htlc_fees_dust_exposure_delta(features: ChannelTypeFeatures) {
|
||||||
|
// Tests the increase in htlc dust exposure due to the excess mining fees of a single non-dust
|
||||||
|
// HTLC on the counterparty commitment transaction, for both incoming and outgoing htlcs.
|
||||||
|
//
|
||||||
|
// Brings the dust exposure up to the base dust exposure using dust htlcs.
|
||||||
|
// Sets the max dust exposure to 1msat below the expected dust exposure given an additional non-dust htlc.
|
||||||
|
// Checks a failed payment for a non-dust htlc.
|
||||||
|
// Sets the max dust exposure equal to the expected dust exposure given an additional non-dust htlc.
|
||||||
|
// Checks a successful payment for a non-dust htlc.
|
||||||
|
//
|
||||||
|
// Runs this sequence for both directions.
|
||||||
|
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(2);
|
||||||
|
|
||||||
|
const DEFAULT_FEERATE: u64 = 253;
|
||||||
|
const HIGH_FEERATE: u64 = 275;
|
||||||
|
const EXCESS_FEERATE: u64 = HIGH_FEERATE - DEFAULT_FEERATE;
|
||||||
|
|
||||||
|
const DUST_HTLC_COUNT: usize = 4;
|
||||||
|
// Set dust htlcs to a satoshi value plus a non-zero msat amount to assert that
|
||||||
|
// the dust accounting rounds transaction fees to the lower satoshi, but does not round dust htlc values.
|
||||||
|
const DUST_HTLC_MSAT: u64 = 125_123;
|
||||||
|
const BASE_DUST_EXPOSURE_MSAT: u64 = DUST_HTLC_COUNT as u64 * DUST_HTLC_MSAT;
|
||||||
|
|
||||||
|
const NON_DUST_HTLC_MSAT: u64 = 4_000_000;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Set the feerate of the channel funder above the `dust_exposure_limiting_feerate` of
|
||||||
|
// the fundee. This delta means that the fundee will add the mining fees of the commitment and
|
||||||
|
// htlc transactions in excess of its `dust_exposure_limiting_feerate` to its total dust htlc
|
||||||
|
// exposure.
|
||||||
|
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
|
||||||
|
*feerate_lock = HIGH_FEERATE as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `expected_dust_exposure_msat` to match the calculation in `FundedChannel::can_accept_incoming_htlc`
|
||||||
|
// only_static_remote_key: 500_492 + 22 * (724 + 172) / 1000 * 1000 + 22 * 663 / 1000 * 1000 = 533_492
|
||||||
|
// anchors_zero_htlc_fee: 500_492 + 22 * (1_124 + 172) / 1000 * 1000 = 528_492
|
||||||
|
let mut expected_dust_exposure_msat = BASE_DUST_EXPOSURE_MSAT + EXCESS_FEERATE * (commitment_tx_base_weight(&features) + COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000;
|
||||||
|
if features == ChannelTypeFeatures::only_static_remote_key() {
|
||||||
|
expected_dust_exposure_msat += EXCESS_FEERATE * htlc_timeout_tx_weight(&features) / 1000 * 1000;
|
||||||
|
assert_eq!(expected_dust_exposure_msat, 533_492);
|
||||||
|
} else {
|
||||||
|
assert_eq!(expected_dust_exposure_msat, 528_492);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut default_config = test_default_channel_config();
|
||||||
|
if features == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
|
||||||
|
default_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
|
||||||
|
// in addition to the one above, this setting is also needed to create an anchor channel
|
||||||
|
default_config.manually_accept_inbound_channels = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set node 1's max dust htlc exposure to 1msat below `expected_dust_exposure_msat`
|
||||||
|
let mut fixed_limit_config = default_config.clone();
|
||||||
|
fixed_limit_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat - 1);
|
||||||
|
|
||||||
|
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(default_config), Some(fixed_limit_config)]);
|
||||||
|
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
let chan_id = create_chan_between_nodes_with_value(&nodes[0], &nodes[1], 100_000, 50_000_000).3;
|
||||||
|
|
||||||
|
let node_1_dust_buffer_feerate = {
|
||||||
|
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
|
||||||
|
let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
|
||||||
|
let chan = chan_lock.channel_by_id.get(&chan_id).unwrap();
|
||||||
|
chan.context().get_dust_buffer_feerate(None) as u64
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip the router complaint when node 1 will attempt to pay node 0
|
||||||
|
let (route_1_0, payment_hash_1_0, _, payment_secret_1_0) = get_route_and_payment_hash!(nodes[1], nodes[0], NON_DUST_HTLC_MSAT);
|
||||||
|
|
||||||
|
// Bring node 1's dust htlc exposure up to `BASE_DUST_EXPOSURE_MSAT`
|
||||||
|
for _ in 0..DUST_HTLC_COUNT {
|
||||||
|
route_payment(&nodes[0], &[&nodes[1]], DUST_HTLC_MSAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
|
||||||
|
// Send an additional non-dust htlc from 0 to 1, and check the complaint
|
||||||
|
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], NON_DUST_HTLC_MSAT);
|
||||||
|
nodes[0].node.send_payment_with_route(route, payment_hash,
|
||||||
|
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
|
||||||
|
check_added_monitors!(nodes[0], 1);
|
||||||
|
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||||
|
assert_eq!(events.len(), 1);
|
||||||
|
let payment_event = SendEvent::from_event(events.remove(0));
|
||||||
|
nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
|
||||||
|
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
|
||||||
|
expect_pending_htlcs_forwardable!(nodes[1]);
|
||||||
|
expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]);
|
||||||
|
nodes[1].logger.assert_log("lightning::ln::channel",
|
||||||
|
format!("Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
|
||||||
|
expected_dust_exposure_msat, expected_dust_exposure_msat - 1), 1);
|
||||||
|
check_added_monitors!(nodes[1], 1);
|
||||||
|
|
||||||
|
// Clear the failed htlc
|
||||||
|
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
|
||||||
|
assert!(updates.update_add_htlcs.is_empty());
|
||||||
|
assert!(updates.update_fulfill_htlcs.is_empty());
|
||||||
|
assert_eq!(updates.update_fail_htlcs.len(), 1);
|
||||||
|
assert!(updates.update_fail_malformed_htlcs.is_empty());
|
||||||
|
assert!(updates.update_fee.is_none());
|
||||||
|
nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
|
||||||
|
commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);
|
||||||
|
expect_payment_failed!(nodes[0], payment_hash, false);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
|
||||||
|
// Set node 1's max dust htlc exposure equal to the `expected_dust_exposure_msat`
|
||||||
|
nodes[1].node.update_partial_channel_config(&nodes[0].node.get_our_node_id(), &[chan_id], &ChannelConfigUpdate {
|
||||||
|
max_dust_htlc_exposure_msat: Some(MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat)),
|
||||||
|
..ChannelConfigUpdate::default()
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
// Check a successful payment
|
||||||
|
send_payment(&nodes[0], &[&nodes[1]], NON_DUST_HTLC_MSAT);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
|
||||||
|
// The `expected_dust_exposure_msat` for the outbound htlc changes in the non-anchor case, as the htlc success and timeout transactions have different weights
|
||||||
|
// only_static_remote_key: 500_492 + 22 * (724 + 172) / 1000 * 1000 + 22 * 703 / 1000 * 1000 = 534_492
|
||||||
|
if features == ChannelTypeFeatures::only_static_remote_key() {
|
||||||
|
expected_dust_exposure_msat = BASE_DUST_EXPOSURE_MSAT + EXCESS_FEERATE * (commitment_tx_base_weight(&features) + COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000 + EXCESS_FEERATE * htlc_success_tx_weight(&features) / 1000 * 1000;
|
||||||
|
assert_eq!(expected_dust_exposure_msat, 534_492);
|
||||||
|
} else {
|
||||||
|
assert_eq!(expected_dust_exposure_msat, 528_492);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set node 1's max dust htlc exposure to 1msat below `expected_dust_exposure_msat`
|
||||||
|
nodes[1].node.update_partial_channel_config(&nodes[0].node.get_our_node_id(), &[chan_id], &ChannelConfigUpdate {
|
||||||
|
max_dust_htlc_exposure_msat: Some(MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat - 1)),
|
||||||
|
..ChannelConfigUpdate::default()
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
// Send an additional non-dust htlc from 1 to 0 using the pre-calculated route above, and check the immediate complaint
|
||||||
|
unwrap_send_err!(nodes[1], nodes[1].node.send_payment_with_route(route_1_0, payment_hash_1_0,
|
||||||
|
RecipientOnionFields::secret_only(payment_secret_1_0), PaymentId(payment_hash_1_0.0)
|
||||||
|
), true, APIError::ChannelUnavailable { .. }, {});
|
||||||
|
let dust_limit = if features == ChannelTypeFeatures::only_static_remote_key() {
|
||||||
|
MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 + htlc_success_tx_weight(&features) * node_1_dust_buffer_feerate / 1000 * 1000
|
||||||
|
} else {
|
||||||
|
MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000
|
||||||
|
};
|
||||||
|
nodes[1].logger.assert_log("lightning::ln::outbound_payment",
|
||||||
|
format!("Failed to send along path due to error: Channel unavailable: Cannot send more than our next-HTLC maximum - {} msat", dust_limit), 1);
|
||||||
|
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
|
||||||
|
// Set node 1's max dust htlc exposure equal to `expected_dust_exposure_msat`
|
||||||
|
nodes[1].node.update_partial_channel_config(&nodes[0].node.get_our_node_id(), &[chan_id], &ChannelConfigUpdate {
|
||||||
|
max_dust_htlc_exposure_msat: Some(MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat)),
|
||||||
|
..ChannelConfigUpdate::default()
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
// Check a successful payment
|
||||||
|
send_payment(&nodes[1], &[&nodes[0]], NON_DUST_HTLC_MSAT);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels().len(), 1);
|
||||||
|
assert_eq!(nodes[1].node.list_channels().len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
|
||||||
|
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nondust_htlc_fees_dust_exposure_delta() {
|
||||||
|
do_test_nondust_htlc_fees_dust_exposure_delta(ChannelTypeFeatures::only_static_remote_key());
|
||||||
|
do_test_nondust_htlc_fees_dust_exposure_delta(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_non_final_funding_tx() {
|
fn test_non_final_funding_tx() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue