Skip to content

Commit 839f60e

Browse files
committed
Test the calculation of excess fees on counterparty transactions
1 parent b618b79 commit 839f60e

File tree

1 file changed

+200
-2
lines changed

1 file changed

+200
-2
lines changed

lightning/src/ln/functional_tests.rs

Lines changed: 200 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::ln::types::ChannelId;
2424
use crate::types::payment::{PaymentPreimage, PaymentSecret, PaymentHash};
2525
use crate::ln::channel::{CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY, Channel};
2626
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
27-
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError};
27+
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError, MIN_CHAN_DUST_LIMIT_SATOSHIS};
2828
use crate::ln::{chan_utils, onion_utils};
2929
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};
3030
use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
@@ -37,7 +37,7 @@ use crate::util::test_utils::{self, TestLogger, WatchtowerPersister};
3737
use crate::util::errors::APIError;
3838
use crate::util::ser::{Writeable, ReadableArgs};
3939
use crate::util::string::UntrustedString;
40-
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
40+
use crate::util::config::{UserConfig, MaxDustHTLCExposure, ChannelConfigUpdate};
4141

4242
use bitcoin::hash_types::BlockHash;
4343
use bitcoin::locktime::absolute::LockTime;
@@ -10604,6 +10604,204 @@ fn test_nondust_htlc_fees_are_dust() {
1060410604
expect_payment_failed_conditions(&nodes[2], payment_hash, false, PaymentFailedConditions::new());
1060510605
}
1060610606

10607+
fn do_test_nondust_htlc_fees_dust_exposure_delta(features: ChannelTypeFeatures) {
10608+
// Tests the increase in htlc dust exposure due to the excess mining fees of a single non-dust
10609+
// HTLC on the counterparty commitment transaction, for both incoming and outgoing htlcs.
10610+
//
10611+
// Brings the dust exposure up to the base dust exposure using dust htlcs.
10612+
// Sets the max dust exposure to 1msat below the expected dust exposure given an additional non-dust htlc.
10613+
// Checks a failed payment for a non-dust htlc.
10614+
// Sets the max dust exposure equal to the expected dust exposure given an additional non-dust htlc.
10615+
// Checks a successful payment for a non-dust htlc.
10616+
//
10617+
// Runs this sequence for both directions.
10618+
10619+
let chanmon_cfgs = create_chanmon_cfgs(2);
10620+
10621+
const DEFAULT_FEERATE: u64 = 253;
10622+
const HIGH_FEERATE: u64 = 275;
10623+
const EXCESS_FEERATE: u64 = HIGH_FEERATE - DEFAULT_FEERATE;
10624+
10625+
const DUST_HTLC_COUNT: usize = 4;
10626+
const DUST_HTLC_MSAT: u64 = 125_000;
10627+
const BASE_DUST_EXPOSURE_MSAT: u64 = DUST_HTLC_COUNT as u64 * DUST_HTLC_MSAT;
10628+
10629+
const NON_DUST_HTLC_MSAT: u64 = 4_000_000;
10630+
10631+
{
10632+
// Set the feerate of the channel funder above the `dust_exposure_limiting_feerate` of
10633+
// the fundee. This delta means that the fundee will add the mining fees of the commitment and
10634+
// htlc transactions in excess of its `dust_exposure_limiting_feerate` to its total dust htlc
10635+
// exposure.
10636+
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
10637+
*feerate_lock = HIGH_FEERATE as u32;
10638+
}
10639+
10640+
// Set `expected_dust_exposure_msat` to match the calculation in `FundedChannel::can_accept_incoming_htlc`
10641+
// only_static_remote_key: 500_000 + 22 * (724 + 172) / 1000 * 1000 + 22 * 663 = 533_586
10642+
// anchors_zero_htlc_fee: 500_000 + 22 * (1_124 + 172) / 1000 * 1000 = 528_000
10643+
let mut expected_dust_exposure_msat = BASE_DUST_EXPOSURE_MSAT + EXCESS_FEERATE * (commitment_tx_base_weight(&features) + COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000;
10644+
if features == ChannelTypeFeatures::only_static_remote_key() {
10645+
expected_dust_exposure_msat += EXCESS_FEERATE * htlc_timeout_tx_weight(&features);
10646+
assert_eq!(expected_dust_exposure_msat, 533_586);
10647+
} else {
10648+
assert_eq!(expected_dust_exposure_msat, 528_000);
10649+
}
10650+
10651+
let mut default_config = test_default_channel_config();
10652+
if features == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
10653+
default_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
10654+
// in addition to the one above, this setting is also needed to create an anchor channel
10655+
default_config.manually_accept_inbound_channels = true;
10656+
}
10657+
10658+
// Set node 1's max dust htlc exposure to 1msat below `expected_dust_exposure_msat`
10659+
let mut fixed_limit_config = default_config.clone();
10660+
fixed_limit_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat - 1);
10661+
10662+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
10663+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(default_config), Some(fixed_limit_config)]);
10664+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
10665+
10666+
let chan_id = create_chan_between_nodes_with_value(&nodes[0], &nodes[1], 100_000, 50_000_000).3;
10667+
10668+
let node_1_dust_buffer_feerate = {
10669+
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
10670+
let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
10671+
let chan = chan_lock.channel_by_id.get(&chan_id).unwrap();
10672+
chan.context().get_dust_buffer_feerate(None) as u64
10673+
};
10674+
10675+
// Skip the router complaint when node 1 will attempt to pay node 0
10676+
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);
10677+
10678+
// Bring node 1's dust htlc exposure up to `BASE_DUST_EXPOSURE_MSAT`
10679+
for _ in 0..DUST_HTLC_COUNT {
10680+
route_payment(&nodes[0], &[&nodes[1]], DUST_HTLC_MSAT);
10681+
}
10682+
10683+
assert_eq!(nodes[0].node.list_channels().len(), 1);
10684+
assert_eq!(nodes[1].node.list_channels().len(), 1);
10685+
10686+
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
10687+
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
10688+
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
10689+
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
10690+
10691+
// Send an additional non-dust htlc from 0 to 1, and check the complaint
10692+
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], NON_DUST_HTLC_MSAT);
10693+
nodes[0].node.send_payment_with_route(route, payment_hash,
10694+
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
10695+
check_added_monitors!(nodes[0], 1);
10696+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
10697+
assert_eq!(events.len(), 1);
10698+
let payment_event = SendEvent::from_event(events.remove(0));
10699+
nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
10700+
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
10701+
expect_pending_htlcs_forwardable!(nodes[1]);
10702+
expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]);
10703+
nodes[1].logger.assert_log("lightning::ln::channel",
10704+
format!("Cannot accept value that would put our exposure to tx fee dust at {} over the limit {} on counterparty commitment tx",
10705+
expected_dust_exposure_msat, expected_dust_exposure_msat - 1), 1);
10706+
check_added_monitors!(nodes[1], 1);
10707+
10708+
// Clear the failed htlc
10709+
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
10710+
assert!(updates.update_add_htlcs.is_empty());
10711+
assert!(updates.update_fulfill_htlcs.is_empty());
10712+
assert_eq!(updates.update_fail_htlcs.len(), 1);
10713+
assert!(updates.update_fail_malformed_htlcs.is_empty());
10714+
assert!(updates.update_fee.is_none());
10715+
nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
10716+
commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);
10717+
expect_payment_failed!(nodes[0], payment_hash, false);
10718+
10719+
assert_eq!(nodes[0].node.list_channels().len(), 1);
10720+
assert_eq!(nodes[1].node.list_channels().len(), 1);
10721+
10722+
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
10723+
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
10724+
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
10725+
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
10726+
10727+
// Set node 1's max dust htlc exposure equal to the `expected_dust_exposure_msat`
10728+
nodes[1].node.update_partial_channel_config(&nodes[0].node.get_our_node_id(), &[chan_id], &ChannelConfigUpdate {
10729+
max_dust_htlc_exposure_msat: Some(MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat)),
10730+
..ChannelConfigUpdate::default()
10731+
}).unwrap();
10732+
10733+
// Check a successful payment
10734+
send_payment(&nodes[0], &[&nodes[1]], NON_DUST_HTLC_MSAT);
10735+
10736+
assert_eq!(nodes[0].node.list_channels().len(), 1);
10737+
assert_eq!(nodes[1].node.list_channels().len(), 1);
10738+
10739+
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
10740+
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
10741+
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
10742+
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
10743+
10744+
// Set `expected_dust_exposure_msat` to match the calculation in `ChannelContext::get_available_balances`
10745+
// only_static_remote_key: 500_000 + 22 * 724 / 1000 * 1000 + 22 * 172 + 22 * 703 = 534_250
10746+
// anchors_zero_htlc_fee: 500_000 + 22 * 1124 / 1000 * 1000 + 22 * 172 = 527_784
10747+
let mut expected_dust_exposure_msat = BASE_DUST_EXPOSURE_MSAT + EXCESS_FEERATE * commitment_tx_base_weight(&features) / 1000 * 1000 + EXCESS_FEERATE * COMMITMENT_TX_WEIGHT_PER_HTLC;
10748+
if features == ChannelTypeFeatures::only_static_remote_key() {
10749+
expected_dust_exposure_msat += EXCESS_FEERATE * htlc_success_tx_weight(&features);
10750+
assert_eq!(expected_dust_exposure_msat, 534_250);
10751+
} else {
10752+
assert_eq!(expected_dust_exposure_msat, 527_784);
10753+
}
10754+
10755+
// Set node 1's max dust htlc exposure to 1msat below `expected_dust_exposure_msat`
10756+
nodes[1].node.update_partial_channel_config(&nodes[0].node.get_our_node_id(), &[chan_id], &ChannelConfigUpdate {
10757+
max_dust_htlc_exposure_msat: Some(MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat - 1)),
10758+
..ChannelConfigUpdate::default()
10759+
}).unwrap();
10760+
10761+
// Send an additional non-dust htlc from 1 to 0 using the pre-calculated route above, and check the immediate complaint
10762+
unwrap_send_err!(nodes[1], nodes[1].node.send_payment_with_route(route_1_0, payment_hash_1_0,
10763+
RecipientOnionFields::secret_only(payment_secret_1_0), PaymentId(payment_hash_1_0.0)
10764+
), true, APIError::ChannelUnavailable { .. }, {});
10765+
let dust_limit = if features == ChannelTypeFeatures::only_static_remote_key() {
10766+
MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 + htlc_success_tx_weight(&features) * node_1_dust_buffer_feerate / 1000 * 1000
10767+
} else {
10768+
MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000
10769+
};
10770+
nodes[1].logger.assert_log("lightning::ln::outbound_payment",
10771+
format!("Failed to send along path due to error: Channel unavailable: Cannot send more than our next-HTLC maximum - {} msat", dust_limit), 1);
10772+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
10773+
10774+
assert_eq!(nodes[0].node.list_channels().len(), 1);
10775+
assert_eq!(nodes[1].node.list_channels().len(), 1);
10776+
10777+
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
10778+
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
10779+
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
10780+
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
10781+
10782+
// Set node 1's max dust htlc exposure equal to `expected_dust_exposure_msat`
10783+
nodes[1].node.update_partial_channel_config(&nodes[0].node.get_our_node_id(), &[chan_id], &ChannelConfigUpdate {
10784+
max_dust_htlc_exposure_msat: Some(MaxDustHTLCExposure::FixedLimitMsat(expected_dust_exposure_msat)),
10785+
..ChannelConfigUpdate::default()
10786+
}).unwrap();
10787+
10788+
// Check a successful payment
10789+
send_payment(&nodes[1], &[&nodes[0]], NON_DUST_HTLC_MSAT);
10790+
10791+
assert_eq!(nodes[0].node.list_channels().len(), 1);
10792+
assert_eq!(nodes[1].node.list_channels().len(), 1);
10793+
10794+
assert_eq!(nodes[0].node.list_channels()[0].pending_inbound_htlcs.len(), 0);
10795+
assert_eq!(nodes[1].node.list_channels()[0].pending_outbound_htlcs.len(), 0);
10796+
assert_eq!(nodes[0].node.list_channels()[0].pending_outbound_htlcs.len(), DUST_HTLC_COUNT);
10797+
assert_eq!(nodes[1].node.list_channels()[0].pending_inbound_htlcs.len(), DUST_HTLC_COUNT);
10798+
}
10799+
10800+
#[test]
10801+
fn test_nondust_htlc_fees_dust_exposure_delta() {
10802+
do_test_nondust_htlc_fees_dust_exposure_delta(ChannelTypeFeatures::only_static_remote_key());
10803+
do_test_nondust_htlc_fees_dust_exposure_delta(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
10804+
}
1060710805

1060810806
#[test]
1060910807
fn test_non_final_funding_tx() {

0 commit comments

Comments
 (0)