diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 4a182c33beb..1f9e42ce832 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -628,7 +628,7 @@ fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) { ); } -#[derive(Copy, Clone)] +#[derive(Clone, Copy, PartialEq)] enum ChanType { Legacy, KeyedAnchors, @@ -2082,6 +2082,7 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { connect_peers(&nodes[0], &nodes[1]); connect_peers(&nodes[1], &nodes[2]); + let set_0reserve = if chan_type == ChanType::Legacy { false } else { true }; // Create 3 channels between A-B and 3 channels between B-C (6 total). // // Use distinct version numbers for each funding transaction so each test @@ -2089,12 +2090,12 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), // channel 3 A has 0-reserve (trusted accept). make_channel(&nodes[0], &nodes[1], 1, false, false, &mut chain_state); - make_channel(&nodes[0], &nodes[1], 2, true, true, &mut chain_state); - make_channel(&nodes[0], &nodes[1], 3, false, true, &mut chain_state); + make_channel(&nodes[0], &nodes[1], 2, set_0reserve, set_0reserve, &mut chain_state); + make_channel(&nodes[0], &nodes[1], 3, false, set_0reserve, &mut chain_state); // B-C: channel 4 B has 0-reserve (via trusted accept), // channel 5 C has 0-reserve (via trusted open). - make_channel(&nodes[1], &nodes[2], 4, false, true, &mut chain_state); - make_channel(&nodes[1], &nodes[2], 5, true, false, &mut chain_state); + make_channel(&nodes[1], &nodes[2], 4, false, set_0reserve, &mut chain_state); + make_channel(&nodes[1], &nodes[2], 5, set_0reserve, false, &mut chain_state); make_channel(&nodes[1], &nodes[2], 6, false, false, &mut chain_state); // Wipe the transactions-broadcasted set to make sure we don't broadcast diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 58dd6ea30c0..063b5303639 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3832,6 +3832,14 @@ impl ChannelContext { "Funding must be smaller than the total bitcoin supply. It was {channel_value_satoshis}" ))); } + if !channel_type.supports_anchors_zero_fee_htlc_tx() + && !channel_type.supports_anchor_zero_fee_commitments() + && holder_selected_channel_reserve_satoshis == 0 + { + return Err(ChannelError::close( + "0-reserve is not allowed on legacy channels".to_owned(), + )); + } if msg_channel_reserve_satoshis > channel_value_satoshis { return Err(ChannelError::close(format!( "Bogus channel_reserve_satoshis ({msg_channel_reserve_satoshis}). Must be no greater than channel_value_satoshis: {channel_value_satoshis}" @@ -4323,6 +4331,14 @@ impl ChannelContext { } let channel_type = get_initial_channel_type(&config, their_features); + if !channel_type.supports_anchors_zero_fee_htlc_tx() + && !channel_type.supports_anchor_zero_fee_commitments() + && holder_selected_channel_reserve_satoshis == 0 + { + return Err(APIError::APIMisuseError { + err: "0-reserve is not allowed on legacy channels".to_owned(), + }); + } debug_assert!(!channel_type.supports_any_optional_bits()); debug_assert!(!channel_type .requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config))); @@ -4869,6 +4885,14 @@ impl ChannelContext { } let channel_type = funding.get_channel_type(); + if !channel_type.supports_anchors_zero_fee_htlc_tx() + && !channel_type.supports_anchor_zero_fee_commitments() + && funding.holder_selected_channel_reserve_satoshis == 0 + { + return Err(ChannelError::close( + "0-reserve is not allowed on legacy channels".to_owned(), + )); + } if common_fields.max_accepted_htlcs > max_htlcs(channel_type) { return Err(ChannelError::close(format!( "max_accepted_htlcs was {}. It must not be larger than {}", @@ -6537,17 +6561,15 @@ impl ChannelContext { /// If we receive an error message when attempting to open a channel, it may only be a rejection /// of the channel type we tried, not of our ability to open any channel at all. We can see if a /// downgrade of channel features would be possible so that we can still open the channel. - #[rustfmt::skip] pub(crate) fn maybe_downgrade_channel_features( &mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator, user_config: &UserConfig, their_features: &InitFeatures, ) -> Result<(), ()> { - if !funding.is_outbound() || - !matches!( + if !funding.is_outbound() + || !matches!( self.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT - ) - { + ) { return Err(()); } if funding.get_channel_type() == &ChannelTypeFeatures::only_static_remote_key() { @@ -6578,11 +6600,17 @@ impl ChannelContext { } let next_channel_type = get_initial_channel_type(user_config, &eligible_features); + if !next_channel_type.supports_anchors_zero_fee_htlc_tx() + && !next_channel_type.supports_anchor_zero_fee_commitments() + && funding.holder_selected_channel_reserve_satoshis == 0 + { + // 0-reserve is not allowed on legacy channels + return Err(()); + } - self.feerate_per_kw = selected_commitment_sat_per_1000_weight( - &fee_estimator, &next_channel_type, - ); - funding.channel_transaction_parameters.channel_type_features = next_channel_type; + self.feerate_per_kw = + selected_commitment_sat_per_1000_weight(&fee_estimator, &next_channel_type); + funding.channel_transaction_parameters.channel_type_features = next_channel_type; Ok(()) } diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index 50ef0721e07..2c048c9906c 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -24,7 +24,8 @@ use crate::ln::channelmanager::{ MAX_UNFUNDED_CHANS_PER_PEER, }; use crate::ln::msgs::{ - AcceptChannel, BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent, + AcceptChannel, BaseMessageHandler, ChannelMessageHandler, ErrorAction, ErrorMessage, + MessageSendEvent, }; use crate::ln::types::ChannelId; use crate::ln::{functional_test_utils::*, msgs}; @@ -48,6 +49,7 @@ use bitcoin::{Amount, Sequence, Transaction, TxIn, TxOut, Witness}; use lightning_macros::xtest; use lightning_types::features::ChannelTypeFeatures; +use types::string::UntrustedString; #[test] fn test_outbound_chans_unlimited() { @@ -2496,3 +2498,189 @@ fn test_fund_pending_channel() { }; check_closed_event(&nodes[0], 1, reason, &[node_b_id], 100_000); } + +#[xtest(feature = "_externalize_tests")] +fn test_holder_selected_0reserve_on_legacy_channel_is_not_allowed() { + { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut channel_config = test_default_channel_config(); + assert!(channel_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx); + let node_chanmgrs = create_node_chanmgrs( + 2, + &node_cfgs, + &[Some(channel_config.clone()), Some(channel_config.clone())], + ); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let _node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let mut legacy_channel_config = test_default_channel_config(); + legacy_channel_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; + legacy_channel_config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = + false; + + // User tries to open a legacy 0-reserve channel with a config override, we fail + assert_eq!( + nodes[0] + .node + .create_channel_to_trusted_peer_0reserve( + node_b_id, + 100_000, + 0, + 42, + None, + Some(legacy_channel_config) + ) + .unwrap_err(), + APIError::APIMisuseError { + err: "0-reserve is not allowed on legacy channels".to_owned() + } + ); + } + { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut channel_config = test_default_channel_config(); + channel_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; + channel_config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; + let node_chanmgrs = create_node_chanmgrs( + 2, + &node_cfgs, + &[Some(channel_config.clone()), Some(channel_config.clone())], + ); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let _node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // User tries to open a legacy 0-reserve channel from the default config, we fail + assert_eq!( + nodes[0] + .node + .create_channel_to_trusted_peer_0reserve(node_b_id, 100_000, 0, 42, None, None) + .unwrap_err(), + APIError::APIMisuseError { + err: "0-reserve is not allowed on legacy channels".to_owned() + } + ); + } + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut channel_config = test_default_channel_config(); + channel_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; + channel_config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; + let node_chanmgrs = create_node_chanmgrs( + 2, + &node_cfgs, + &[Some(channel_config.clone()), Some(channel_config.clone())], + ); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + let mut open_channel_msg_0reserve = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + open_channel_msg_0reserve.channel_reserve_satoshis = 0; + assert_eq!( + open_channel_msg_0reserve.common_fields.channel_type, + Some(ChannelTypeFeatures::only_static_remote_key()) + ); + + // User accepts a legacy channel, and sets 0-reserve for the counterparty, we fail + nodes[1].node.handle_open_channel(node_a_id, &open_channel_msg_0reserve); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + let error = nodes[1] + .node + .accept_inbound_channel_from_trusted_peer( + &temporary_channel_id, + &node_a_id, + 42, + TrustedChannelFeatures::ZeroReserve, + None, + ) + .unwrap_err(); + assert_eq!( + error, + APIError::ChannelUnavailable { + err: "0-reserve is not allowed on legacy channels".to_owned() + } + ); + }, + _ => panic!("Unexpected event"), + } + let err_msg = get_err_msg(&nodes[1], &node_a_id); + assert_eq!( + err_msg, + ErrorMessage { + channel_id: open_channel_msg_0reserve.common_fields.temporary_channel_id, + data: "0-reserve is not allowed on legacy channels".to_string() + } + ); + + // But legacy channels where only the counterparty sets 0-reserve are ok! + // Here node 1 accepts 0-reserve from node 0, and node 1 sets some non-zero reserve... + handle_and_accept_open_channel(&nodes[1], node_a_id, &open_channel_msg_0reserve); + let mut accept_channel_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + // Override the reserve selected by node 1, make sure node 0 accepts too + accept_channel_msg.channel_reserve_satoshis = 0; + + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel_msg); + + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + assert!( + matches!(events[0], Event::FundingGenerationReady { channel_value_satoshis: 100_000, user_channel_id: 42, counterparty_node_id, .. } if counterparty_node_id == node_b_id) + ); +} + +#[xtest(feature = "_externalize_tests")] +fn test_error_if_0reserve_negotiates_down_to_legacy() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let channel_config = test_default_channel_config(); + assert!(channel_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx); + let node_chanmgrs = create_node_chanmgrs( + 2, + &node_cfgs, + &[Some(channel_config.clone()), Some(channel_config.clone())], + ); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0] + .node + .create_channel_to_trusted_peer_0reserve(node_b_id, 100_000, 0, 42, None, None) + .unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + assert_eq!( + open_channel_msg.common_fields.channel_type, + Some(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()) + ); + assert_eq!(open_channel_msg.channel_reserve_satoshis, 0); + + let reason = "Don't like your channel".to_owned(); + nodes[0].node.handle_error( + node_b_id, + &ErrorMessage { + channel_id: open_channel_msg.common_fields.temporary_channel_id, + data: reason.clone(), + }, + ); + + let reason = ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(reason) }; + let expected_closing = ExpectedCloseEvent::from_id_reason( + open_channel_msg.common_fields.temporary_channel_id, + false, + reason, + ); + check_closed_events(&nodes[0], &[expected_closing]); +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 19fd2f96797..55fe83b2bc3 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3617,6 +3617,8 @@ pub enum TrustedChannelFeatures { /// with a revoked commitment transaction *for free*. /// /// Note that there is no guarantee that the counterparty accepts such a channel themselves. + /// + /// The zero-reserve feature is not allowed on legacy / anchorless channels. ZeroReserve, /// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`] ZeroConfZeroReserve, @@ -3873,6 +3875,8 @@ impl< /// transaction *for free*. /// /// Note that there is no guarantee that the counterparty accepts such a channel. + /// + /// The zero-reserve feature is not allowed on legacy / anchorless channels. pub fn create_channel_to_trusted_peer_0reserve( &self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index 68581ef580a..17b0ffee4cf 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -23,10 +23,11 @@ use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::tx_builder::{SpecTxBuilder, TxBuilder}; use crate::sign::ChannelSigner; use crate::types::features::ChannelTypeFeatures; -use crate::types::payment::{PaymentHash, PaymentPreimage}; +use crate::types::payment::PaymentPreimage; use crate::util::config::UserConfig; use crate::util::errors::APIError; +use bolt11_invoice::PaymentHash; use lightning_macros::xtest; use bitcoin::secp256k1::{Secp256k1, SecretKey}; @@ -39,9 +40,7 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { // in normal testing, we test it explicitly here. let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let legacy_cfg = test_legacy_channel_config(); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[Some(legacy_cfg.clone()), Some(legacy_cfg)]); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let node_a_id = nodes[0].node.get_our_node_id(); @@ -52,13 +51,14 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { // Have node0 initiate a channel to node1 with aforementioned parameters let mut push_amt = 100_000_000; let feerate_per_kw = 253; - let channel_type_features = ChannelTypeFeatures::only_static_remote_key(); + let channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); push_amt -= feerate_per_kw as u64 * (commitment_tx_base_weight(&channel_type_features) + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000; push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false) .unwrap() * 1000; + push_amt -= 2 * 330_000; let push = if send_from_initiator { 0 } else { push_amt }; let temp_channel_id = @@ -109,10 +109,8 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { &nodes[0], &[&nodes[1]], 100_000_000 - // Note that for outbound channels we have to consider the commitment tx fee and the - // "fee spike buffer", which is currently a multiple of the total commitment tx fee as - // well as an additional HTLC. - - FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE * commit_tx_fee_msat(feerate_per_kw, 2, &channel_type_features), + - commit_tx_fee_msat(feerate_per_kw, 2, &channel_type_features) + - 2 * 330_000, ); } else { send_payment(&nodes[1], &[&nodes[0]], push_amt); @@ -2358,12 +2356,6 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { fn test_create_channel_to_trusted_peer_0reserve() { let mut config = test_default_channel_config(); - // Legacy channels - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - let channel_type = do_test_create_channel_to_trusted_peer_0reserve(config.clone()); - assert_eq!(channel_type, ChannelTypeFeatures::only_static_remote_key()); - // Anchor channels config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; @@ -2416,14 +2408,8 @@ fn do_test_create_channel_to_trusted_peer_0reserve(mut config: UserConfig) -> Ch } else { 0 }; - let spike_multiple = if channel_type == ChannelTypeFeatures::only_static_remote_key() { - FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 - } else { - 1 - }; - let spiked_feerate = spike_multiple * feerate_per_kw; let reserved_commit_tx_fee_sat = chan_utils::commit_tx_fee_sat( - spiked_feerate, + feerate_per_kw, 2, // We reserve space for two HTLCs, the next outbound non-dust HTLC, and the fee spike buffer HTLC &channel_type, ); @@ -2446,12 +2432,6 @@ fn do_test_create_channel_to_trusted_peer_0reserve(mut config: UserConfig) -> Ch fn test_accept_inbound_channel_from_trusted_peer_0reserve() { let mut config = test_default_channel_config(); - // Legacy channels - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - let channel_type = do_test_accept_inbound_channel_from_trusted_peer_0reserve(config.clone()); - assert_eq!(channel_type, ChannelTypeFeatures::only_static_remote_key()); - // Anchor channels config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; @@ -2539,14 +2519,8 @@ fn do_test_accept_inbound_channel_from_trusted_peer_0reserve( } else { 0 }; - let spike_multiple = if channel_type == ChannelTypeFeatures::only_static_remote_key() { - FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 - } else { - 1 - }; - let spiked_feerate = spike_multiple * feerate_per_kw; let reserved_commit_tx_fee_sat = chan_utils::commit_tx_fee_sat( - spiked_feerate, + feerate_per_kw, 2, // We reserve space for two HTLCs, the next outbound non-dust HTLC, and the fee spike buffer HTLC &channel_type, ); @@ -2565,20 +2539,8 @@ fn do_test_accept_inbound_channel_from_trusted_peer_0reserve( channel_type } -enum LegacyChannelsNoOutputs { - PaymentSucceeds, - FailsReceiverUpdateAddHTLC, - FailsReceiverCanAcceptHTLCA, - FailsReceiverCanAcceptHTLCB, -} - #[xtest(feature = "_externalize_tests")] fn test_0reserve_no_outputs() { - do_test_0reserve_no_outputs_legacy(LegacyChannelsNoOutputs::PaymentSucceeds); - do_test_0reserve_no_outputs_legacy(LegacyChannelsNoOutputs::FailsReceiverCanAcceptHTLCA); - do_test_0reserve_no_outputs_legacy(LegacyChannelsNoOutputs::FailsReceiverCanAcceptHTLCB); - do_test_0reserve_no_outputs_legacy(LegacyChannelsNoOutputs::FailsReceiverUpdateAddHTLC); - do_test_0reserve_no_outputs_keyed_anchors(true); do_test_0reserve_no_outputs_keyed_anchors(false); @@ -2678,189 +2640,6 @@ pub(crate) fn setup_0reserve_no_outputs_channels<'a, 'b, 'c, 'd>( (channel_id, tx) } -fn do_test_0reserve_no_outputs_legacy(no_outputs_case: LegacyChannelsNoOutputs) { - let mut config = test_default_channel_config(); - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage = - 100; - - let channel_type = ChannelTypeFeatures::only_static_remote_key(); - - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let _node_b_id = nodes[1].node.get_our_node_id(); - - let feerate_per_kw = 253; - let spike_multiple = FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; - let dust_limit_satoshis: u64 = 546; - let channel_value_sat = 1000; - - let (channel_id, _funding_tx) = - setup_0reserve_no_outputs_channels(&nodes, channel_value_sat, dust_limit_satoshis); - assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type); - - // Sending the biggest dust HTLC possible trims our balance output! - let (timeout_tx_fee_sat, success_tx_fee_sat) = - second_stage_tx_fees_sat(&channel_type, spike_multiple * feerate_per_kw); - let max_dust_htlc_sat = dust_limit_satoshis + success_tx_fee_sat - 1; - assert!( - channel_value_sat - .saturating_sub(commit_tx_fee_sat(feerate_per_kw, 0, &channel_type)) - .saturating_sub(max_dust_htlc_sat) - < dust_limit_satoshis - ); - - // We can't afford the fee for an additional non-dust HTLC + the fee spike HTLC, so we can only send - // dust HTLCs... - let min_local_nondust_htlc_sat = dust_limit_satoshis + timeout_tx_fee_sat; - assert!( - channel_value_sat - commit_tx_fee_sat(spike_multiple * feerate_per_kw, 2, &channel_type) - < min_local_nondust_htlc_sat - ); - - // We cannot trim our own balance output, otherwise we'd have no outputs on the commitment. We must - // also reserve enough fees to pay for an incoming non-dust HTLC, aka the fee spike buffer HTLC. - let min_value_sat = core::cmp::max( - commit_tx_fee_sat(spike_multiple * feerate_per_kw, 0, &channel_type) + dust_limit_satoshis, - commit_tx_fee_sat(spike_multiple * feerate_per_kw, 1, &channel_type), - ); - // At this point the tighter requirement is "must have an output" - assert!( - commit_tx_fee_sat(spike_multiple * feerate_per_kw, 0, &channel_type) + dust_limit_satoshis - > commit_tx_fee_sat(spike_multiple * feerate_per_kw, 1, &channel_type) - ); - // But say at 9sat/vb with default dust limit, - // the tighter requirement is actually "must have funds for an inbound HTLC" ! - assert!( - commit_tx_fee_sat(9 * 250, 0, &channel_type) + 354 - < commit_tx_fee_sat(9 * 250, 1, &channel_type) - ); - let sender_amount_msat = (channel_value_sat - min_value_sat) * 1000; - let details_0 = &nodes[0].node.list_channels()[0]; - assert_eq!(details_0.next_outbound_htlc_minimum_msat, 1000); - assert_eq!(details_0.next_outbound_htlc_limit_msat, sender_amount_msat); - assert!(details_0.next_outbound_htlc_limit_msat > details_0.next_outbound_htlc_minimum_msat); - - let (sender_amount_msat, receiver_amount_msat) = match no_outputs_case { - LegacyChannelsNoOutputs::PaymentSucceeds => (sender_amount_msat, sender_amount_msat), - LegacyChannelsNoOutputs::FailsReceiverCanAcceptHTLCA => { - // A dust HTLC with 1msat added to it will break counterparty `can_accept_incoming_htlc` - // validation, as this dust HTLC would push the holder's balance output below the - // dust limit at the spike multiple feerate. - (sender_amount_msat, sender_amount_msat + 1) - }, - LegacyChannelsNoOutputs::FailsReceiverCanAcceptHTLCB => { - // In `validate_update_add_htlc`, we check that there is still some output present on - // the commitment given the *current* set of HTLCs, and the *current* feerate. So this - // HTLC will pass at `validate_update_add_htlc`, but will fail in - // `can_accept_incoming_htlc` due to failed fee spike buffer checks. - let receiver_amount_msat = (channel_value_sat - - commit_tx_fee_sat(feerate_per_kw, 0, &channel_type) - - dust_limit_satoshis) - * 1000; - (sender_amount_msat, receiver_amount_msat) - }, - LegacyChannelsNoOutputs::FailsReceiverUpdateAddHTLC => { - // Same value as above, just add 1msat, and this fails at `validate_update_add_htlc` - let receiver_amount_msat = (channel_value_sat - - commit_tx_fee_sat(feerate_per_kw, 0, &channel_type) - - dust_limit_satoshis) - * 1000; - (sender_amount_msat, receiver_amount_msat + 1) - }, - }; - - if let LegacyChannelsNoOutputs::PaymentSucceeds = no_outputs_case { - send_payment(&nodes[0], &[&nodes[1]], sender_amount_msat); - // Node 1 the fundee has 0-reserve too, so whatever they receive, they can send right back! - // Node 0 should *always* have the funds to cover the fee of a single non-dust HTLC from node 1. - assert_eq!( - nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat, - sender_amount_msat - ); - send_payment(&nodes[1], &[&nodes[0]], sender_amount_msat); - } else { - let (route, payment_hash, _, payment_secret) = - get_route_and_payment_hash!(nodes[0], nodes[1], sender_amount_msat); - let secp_ctx = Secp256k1::new(); - let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); - let cur_height = nodes[0].node.best_block.read().unwrap().height + 1; - let onion_keys = - onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv); - let recipient_onion_fields = - RecipientOnionFields::secret_only(payment_secret, sender_amount_msat); - let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::test_build_onion_payloads( - &route.paths[0], - &recipient_onion_fields, - cur_height, - &None, - None, - None, - ) - .unwrap(); - assert_eq!(htlc_msat, sender_amount_msat); - let onion_packet = - onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash) - .unwrap(); - let msg = msgs::UpdateAddHTLC { - channel_id, - htlc_id: 0, - amount_msat: receiver_amount_msat, - payment_hash, - cltv_expiry: htlc_cltv, - onion_routing_packet: onion_packet, - skimmed_fee_msat: None, - blinding_point: None, - hold_htlc: None, - accountable: None, - }; - - nodes[1].node.handle_update_add_htlc(node_a_id, &msg); - - if let LegacyChannelsNoOutputs::FailsReceiverUpdateAddHTLC = no_outputs_case { - nodes[1].logger.assert_log_contains( - "lightning::ln::channelmanager", - "Remote HTLC add would overdraw remaining funds", - 3, - ); - assert_eq!(nodes[1].node.list_channels().len(), 0); - let err_msg = check_closed_broadcast(&nodes[1], 1, true).pop().unwrap(); - assert_eq!(err_msg.data, "Remote HTLC add would overdraw remaining funds"); - let reason = ClosureReason::ProcessingError { - err: "Remote HTLC add would overdraw remaining funds".to_string(), - }; - check_added_monitors(&nodes[1], 1); - check_closed_event(&nodes[1], 1, reason, &[node_a_id], channel_value_sat); - - return; - } - - let htlcs_in_commitment = vec![HTLCOutputInCommitment { - offered: false, - amount_msat: receiver_amount_msat, - cltv_expiry: htlc_cltv, - payment_hash, - transaction_output_index: Some(1), - }]; - - manually_trigger_update_fail_htlc( - &nodes, - channel_id, - channel_value_sat * 1000, - dust_limit_satoshis, - payment_hash, - htlcs_in_commitment, - false, - ); - } -} - fn manually_trigger_update_fail_htlc<'a, 'b, 'c, 'd>( nodes: &'a Vec>, channel_id: ChannelId, value_to_self_msat: u64, dust_limit_satoshis: u64, payment_hash: PaymentHash, diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 6bd5d5224f7..e6ad2d249b9 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -8311,20 +8311,6 @@ fn test_no_disconnect_after_quiescence_on_reconnect() { #[test] fn test_0reserve_splice() { let mut config = test_default_channel_config(); - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - let a = do_test_0reserve_splice_holder_validation(false, false, false, config.clone()); - let _b = do_test_0reserve_splice_holder_validation(true, false, false, config.clone()); - let _c = do_test_0reserve_splice_holder_validation(false, true, false, config.clone()); - let _d = do_test_0reserve_splice_holder_validation(true, true, false, config.clone()); - - let _e = do_test_0reserve_splice_holder_validation(false, false, true, config.clone()); - let _f = do_test_0reserve_splice_holder_validation(true, false, true, config.clone()); - let _g = do_test_0reserve_splice_holder_validation(false, true, true, config.clone()); - let _h = do_test_0reserve_splice_holder_validation(true, true, true, config.clone()); - - assert_eq!(a, ChannelTypeFeatures::only_static_remote_key()); - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; let a = do_test_0reserve_splice_holder_validation(false, false, false, config.clone()); @@ -8354,20 +8340,6 @@ fn test_0reserve_splice() { assert_eq!(a, ChannelTypeFeatures::anchors_zero_fee_commitments()); let mut config = test_default_channel_config(); - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - let a = do_test_0reserve_splice_counterparty_validation(false, false, false, config.clone()); - let _b = do_test_0reserve_splice_counterparty_validation(true, false, false, config.clone()); - let _c = do_test_0reserve_splice_counterparty_validation(false, true, false, config.clone()); - let _d = do_test_0reserve_splice_counterparty_validation(true, true, false, config.clone()); - - let _e = do_test_0reserve_splice_counterparty_validation(false, false, true, config.clone()); - let _f = do_test_0reserve_splice_counterparty_validation(true, false, true, config.clone()); - let _g = do_test_0reserve_splice_counterparty_validation(false, true, true, config.clone()); - let _h = do_test_0reserve_splice_counterparty_validation(true, true, true, config.clone()); - - assert_eq!(a, ChannelTypeFeatures::only_static_remote_key()); - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; let a = do_test_0reserve_splice_counterparty_validation(false, false, false, config.clone()); @@ -8426,11 +8398,6 @@ fn do_test_0reserve_splice_holder_validation( let feerate = if channel_type == ChannelTypeFeatures::anchors_zero_fee_commitments() { 0 } else { 253 }; - let spiked_feerate = if channel_type == ChannelTypeFeatures::only_static_remote_key() { - feerate * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 - } else { - feerate - }; let anchors_sat = if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 @@ -8442,7 +8409,7 @@ fn do_test_0reserve_splice_holder_validation( send_payment(&nodes[0], &[&nodes[1]], channel_value_sat / 2 * 1_000); channel_value_sat / 2 } else if !node_0_is_initiator { - let tx_fee_msat = chan_utils::commit_tx_fee_sat(spiked_feerate, 2, &channel_type) * 1000; + let tx_fee_msat = chan_utils::commit_tx_fee_sat(feerate, 2, &channel_type) * 1000; let node_0_details = &nodes[0].node.list_channels()[0]; let outbound_capacity_msat = node_0_details.outbound_capacity_msat; let available_capacity_msat = node_0_details.next_outbound_htlc_limit_msat; @@ -8479,12 +8446,12 @@ fn do_test_0reserve_splice_holder_validation( // The estimated fees to splice out a single output at 253sat/kw let estimated_fees_sat = 183; let mut splice_out_max_value = if counterparty_has_output && node_0_is_initiator { - let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 1, &channel_type); + let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 1, &channel_type); Amount::from_sat( initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - estimated_fees_sat, ) } else if !counterparty_has_output && node_0_is_initiator { - let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 0, &channel_type); + let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type); Amount::from_sat( initiator_value_to_self_sat - commit_tx_fee_sat @@ -8573,11 +8540,6 @@ fn do_test_0reserve_splice_counterparty_validation( let feerate = if channel_type == ChannelTypeFeatures::anchors_zero_fee_commitments() { 0 } else { 253 }; - let spiked_feerate = if channel_type == ChannelTypeFeatures::only_static_remote_key() { - feerate * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 - } else { - feerate - }; let anchors_sat = if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 @@ -8589,7 +8551,7 @@ fn do_test_0reserve_splice_counterparty_validation( send_payment(&nodes[0], &[&nodes[1]], channel_value_sat / 2 * 1_000); channel_value_sat / 2 } else if !node_0_is_initiator { - let tx_fee_msat = chan_utils::commit_tx_fee_sat(spiked_feerate, 2, &channel_type) * 1000; + let tx_fee_msat = chan_utils::commit_tx_fee_sat(feerate, 2, &channel_type) * 1000; let node_0_details = &nodes[0].node.list_channels()[0]; let outbound_capacity_msat = node_0_details.outbound_capacity_msat; let available_capacity_msat = node_0_details.next_outbound_htlc_limit_msat; @@ -8601,7 +8563,7 @@ fn do_test_0reserve_splice_counterparty_validation( let node_0_to_local_output_msat = channel_value_sat * 1000 - available_capacity_msat - anchors_sat * 1000 - - chan_utils::commit_tx_fee_sat(spiked_feerate, 0, &channel_type) * 1000; + - chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type) * 1000; assert!(node_0_to_local_output_msat / 1000 < dust_limit_satoshis); let commit_tx = &get_local_commitment_txn!(nodes[0], channel_id)[0]; assert_eq!( @@ -8624,10 +8586,10 @@ fn do_test_0reserve_splice_counterparty_validation( }; let mut splice_out_value_incl_fees = if counterparty_has_output && node_0_is_initiator { - let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 1, &channel_type); + let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 1, &channel_type); Amount::from_sat(initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat) } else if !counterparty_has_output && node_0_is_initiator { - let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 0, &channel_type); + let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type); Amount::from_sat( initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - dust_limit_satoshis, ) diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs index 8300d17b7f7..6859d4dcf62 100644 --- a/lightning/src/sign/tx_builder.rs +++ b/lightning/src/sign/tx_builder.rs @@ -285,6 +285,15 @@ fn get_next_commitment_stats( // For zero-reserve channels, we check two things independently: // 1) Given the current set of HTLCs and feerate, does the commitment have at least one output ? + // + // We only assume fee spikes in legacy channels, and we do not allow + // `holder_selected_channel_reserve_satoshis` to be set to zero in such channels. It is + // nonetheless still possible to reach the no-outputs case in a fee spike with solely the + // counterparty selected reserve set to zero, so we still guard against this case here. + // + // We don't guard against no-outputs under fee spikes further below in + // `get_available_balances`; in the worst case, the receiver of the HTLC we just sent fails + // it back. if !has_output( is_outbound_from_holder, holder_balance_before_fee_msat, @@ -349,9 +358,9 @@ fn get_next_commitment_stats( // 3) s < (100h + 100 - 100d - c) / 99 fn get_next_splice_out_maximum_sat( is_outbound_from_holder: bool, channel_value_satoshis: u64, local_balance_before_fee_msat: u64, - remote_balance_before_fee_msat: u64, spiked_feerate: u32, - spiked_feerate_nondust_htlc_count: usize, post_splice_delta_above_reserve_sat: u64, - channel_constraints: &ChannelConstraints, channel_type: &ChannelTypeFeatures, + remote_balance_before_fee_msat: u64, feerate_per_kw: u32, nondust_htlc_count: usize, + post_splice_delta_above_reserve_sat: u64, channel_constraints: &ChannelConstraints, + channel_type: &ChannelTypeFeatures, ) -> u64 { let local_balance_before_fee_sat = local_balance_before_fee_msat / 1000; let mut next_splice_out_maximum_sat = if channel_constraints @@ -423,23 +432,23 @@ fn get_next_splice_out_maximum_sat( // // If the current `next_splice_out_maximum_sat` would produce a local commitment with no // outputs, bump this maximum such that, after the splice, the holder's balance covers at - // least `dust_limit_satoshis` and, if they are the funder, `current_spiked_tx_fee_sat`. - // We don't include an additional non-dust inbound HTLC in the `current_spiked_tx_fee_sat`, + // least `dust_limit_satoshis` and, if they are the funder, `current_tx_fee_sat`. + // We don't include an additional non-dust inbound HTLC in the `current_tx_fee_sat`, // because we don't mind if the holder dips below their dust limit to cover the fee for that // inbound non-dust HTLC. if !has_output( is_outbound_from_holder, local_balance_before_fee_msat.saturating_sub(next_splice_out_maximum_sat * 1000), remote_balance_before_fee_msat, - spiked_feerate, - spiked_feerate_nondust_htlc_count, + feerate_per_kw, + nondust_htlc_count, channel_constraints.holder_dust_limit_satoshis, channel_type, ) { let dust_limit_satoshis = channel_constraints.holder_dust_limit_satoshis; - let current_spiked_tx_fee_sat = commit_tx_fee_sat(spiked_feerate, 0, channel_type); + let current_tx_fee_sat = commit_tx_fee_sat(feerate_per_kw, 0, channel_type); let min_balance_sat = if is_outbound_from_holder { - dust_limit_satoshis.saturating_add(current_spiked_tx_fee_sat) + dust_limit_satoshis.saturating_add(current_tx_fee_sat) } else { dust_limit_satoshis }; @@ -476,13 +485,12 @@ fn get_available_balances( if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 }; // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop - let spiked_feerate = feerate_per_kw.saturating_mul( - if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() { + let spiked_feerate = + feerate_per_kw.saturating_mul(if !channel_type.supports_anchors_zero_fee_htlc_tx() { crate::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 } else { 1 - }, - ); + }); let local_nondust_htlc_count = pending_htlcs .iter() @@ -495,17 +503,6 @@ fn get_available_balances( ) }) .count(); - let local_spiked_nondust_htlc_count = pending_htlcs - .iter() - .filter(|htlc| { - !htlc.is_dust( - true, - spiked_feerate, - channel_constraints.holder_dust_limit_satoshis, - channel_type, - ) - }) - .count(); // Note here we use the htlc count at the current feerate together with the spiked feerate; // this makes sure that the holder can afford any fee bump between 1x to 2x from the current @@ -571,9 +568,9 @@ fn get_available_balances( channel_value_satoshis, local_balance_before_fee_msat, remote_balance_before_fee_msat, - spiked_feerate, - // The number of non-dust HTLCs on the local commitment at the spiked feerate - local_spiked_nondust_htlc_count, + feerate_per_kw, + // The number of non-dust HTLCs on the local commitment at the current feerate + local_nondust_htlc_count, // The post-splice minimum balance of the holder if is_outbound_from_holder { local_min_commit_tx_fee_sat } else { 0 }, &channel_constraints, @@ -704,19 +701,7 @@ fn get_available_balances( } // Now adjust our min and max size HTLC to make sure both the local and the remote commitments still have - // at least one output at the spiked feerate. - - let remote_spiked_nondust_htlc_count = pending_htlcs - .iter() - .filter(|htlc| { - !htlc.is_dust( - false, - spiked_feerate, - channel_constraints.counterparty_dust_limit_satoshis, - channel_type, - ) - }) - .count(); + // at least one output at the current feerate. let (next_outbound_htlc_minimum_msat, available_capacity_msat) = adjust_boundaries_if_max_dust_htlc_produces_no_output( @@ -724,8 +709,8 @@ fn get_available_balances( is_outbound_from_holder, local_balance_before_fee_msat, remote_balance_before_fee_msat, - spiked_feerate, - local_spiked_nondust_htlc_count, + feerate_per_kw, + local_nondust_htlc_count, channel_constraints.holder_dust_limit_satoshis, channel_type, next_outbound_htlc_minimum_msat, @@ -738,8 +723,8 @@ fn get_available_balances( is_outbound_from_holder, local_balance_before_fee_msat, remote_balance_before_fee_msat, - spiked_feerate, - remote_spiked_nondust_htlc_count, + feerate_per_kw, + remote_nondust_htlc_count, channel_constraints.counterparty_dust_limit_satoshis, channel_type, next_outbound_htlc_minimum_msat, @@ -760,14 +745,13 @@ fn get_available_balances( fn adjust_boundaries_if_max_dust_htlc_produces_no_output( local: bool, is_outbound_from_holder: bool, holder_balance_before_fee_msat: u64, - counterparty_balance_before_fee_msat: u64, spiked_feerate: u32, - spiked_feerate_nondust_htlc_count: usize, dust_limit_satoshis: u64, - channel_type: &ChannelTypeFeatures, next_outbound_htlc_minimum_msat: u64, - available_capacity_msat: u64, + counterparty_balance_before_fee_msat: u64, feerate_per_kw: u32, nondust_htlc_count: usize, + dust_limit_satoshis: u64, channel_type: &ChannelTypeFeatures, + next_outbound_htlc_minimum_msat: u64, available_capacity_msat: u64, ) -> (u64, u64) { // First, determine the biggest dust HTLC we could send let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = - second_stage_tx_fees_sat(channel_type, spiked_feerate); + second_stage_tx_fees_sat(channel_type, feerate_per_kw); let min_nondust_htlc_sat = dust_limit_satoshis + if local { htlc_timeout_tx_fee_sat } else { htlc_success_tx_fee_sat }; let max_dust_htlc_msat = (min_nondust_htlc_sat.saturating_mul(1000)).saturating_sub(1); @@ -778,8 +762,8 @@ fn adjust_boundaries_if_max_dust_htlc_produces_no_output( is_outbound_from_holder, holder_balance_before_fee_msat.saturating_sub(max_dust_htlc_msat), counterparty_balance_before_fee_msat, - spiked_feerate, - spiked_feerate_nondust_htlc_count, + feerate_per_kw, + nondust_htlc_count, dust_limit_satoshis, channel_type, ) { @@ -796,16 +780,15 @@ fn adjust_boundaries_if_max_dust_htlc_produces_no_output( // Note that this will be a dust HTLC. } else { // Remember we've got no non-dust HTLCs on the commitment here - let current_spiked_tx_fee_sat = commit_tx_fee_sat(spiked_feerate, 0, channel_type); - let spike_buffer_tx_fee_sat = commit_tx_fee_sat(spiked_feerate, 1, channel_type); + let current_tx_fee_sat = commit_tx_fee_sat(feerate_per_kw, 0, channel_type); + let spike_buffer_tx_fee_sat = commit_tx_fee_sat(feerate_per_kw, 1, channel_type); // In case we are the funder, we must cover the greater of - // 1) The dust_limit_satoshis plus the fee of the existing commitment at the spiked feerate. + // 1) The dust_limit_satoshis plus the fee of the existing commitment at the current feerate. // 2) The fee of the commitment with an additional non-dust HTLC, aka the fee spike buffer HTLC. // In this case we don't mind the holder balance output dropping below the dust limit, as // this additional non-dust HTLC will create the single remaining output on the commitment. let min_balance_msat = if is_outbound_from_holder { - cmp::max(dust_limit_satoshis + current_spiked_tx_fee_sat, spike_buffer_tx_fee_sat) - * 1000 + cmp::max(dust_limit_satoshis + current_tx_fee_sat, spike_buffer_tx_fee_sat) * 1000 // In case we are the fundee, we can send dust HTLCs as long as our own balance output // remains above the dust limit. } else {