diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 50c395df658..bb721c74d1f 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2554,6 +2554,9 @@ pub(super) struct FundingScope { value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs /// minimum channel reserve for self to maintain - set by them. + #[cfg(any(test, feature = "_externalize_tests"))] + pub(super) counterparty_selected_channel_reserve_satoshis: Option, + #[cfg(not(any(test, feature = "_externalize_tests")))] counterparty_selected_channel_reserve_satoshis: Option, #[cfg(any(test, feature = "_externalize_tests"))] diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index 86d98b78826..290ae18f6fc 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -1053,10 +1053,10 @@ pub fn test_chan_reserve_dust_inbound_htlcs_outbound_chan() { * 1000; create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt); - let (htlc_success_tx_fee_sat, _) = + let (_htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat(&channel_type_features, feerate_per_kw); let dust_amt = crate::ln::channel::MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 - + htlc_success_tx_fee_sat * 1000 + + htlc_timeout_tx_fee_sat * 1000 - 1; // In the previous code, routing this dust payment would cause nodes[0] to perceive a channel // reserve violation even though it's a dust HTLC and therefore shouldn't count towards the @@ -3528,3 +3528,268 @@ fn test_fail_cannot_afford_dust_htlcs_at_spike_multiple_if_nondust_at_base_feera true, ); } + +#[xtest(feature = "_externalize_tests")] +fn test_available_balances_both_commitments_dust_on_funder_commitment() { + let mut config = test_default_channel_config(); + + 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::anchors_zero_htlc_fee_and_dependencies(); + + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + const FEERATE: u32 = 253; + const TOTAL_ANCHORS_MSAT: u64 = 2 * 330_000; + const NODE_0_DUST_LIMIT_MSAT: u64 = 10_000 * 1000; + const NODE_1_DUST_LIMIT_MSAT: u64 = 354 * 1000; + const CHANNEL_VALUE_MSAT: u64 = 50_000 * 1000; + const NODE_0_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000; + const NODE_1_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000; + const NODE_0_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 1_000 * 1_000; + const NODE_1_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 10_000 * 1_000; + + let channel_id = create_announced_chan_between_nodes_with_value( + &nodes, + 0, + 1, + CHANNEL_VALUE_MSAT / 1000, + NODE_1_VALUE_TO_SELF_MSAT, + ) + .2; + assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type); + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().holder_dust_limit_satoshis = NODE_0_DUST_LIMIT_MSAT / 1000; + chan.funding_mut().counterparty_selected_channel_reserve_satoshis = + Some(NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000); + assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_1_DUST_LIMIT_MSAT / 1000); + assert_eq!( + chan.funding().holder_selected_channel_reserve_satoshis, + NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000 + ); + } + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().counterparty_dust_limit_satoshis = NODE_0_DUST_LIMIT_MSAT / 1000; + chan.funding_mut().holder_selected_channel_reserve_satoshis = + NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000; + assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_1_DUST_LIMIT_MSAT / 1000); + assert_eq!( + chan.funding().counterparty_selected_channel_reserve_satoshis, + Some(NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000) + ); + } + + // This HTLC is only present on node 1's commitment + const SNEAKY_HTLC_MSAT: u64 = 5_000_000; + + route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT); + + let node_1_details = &nodes[1].node.list_channels()[0]; + let expected_outbound_capacity_msat = + NODE_1_VALUE_TO_SELF_MSAT - SNEAKY_HTLC_MSAT - NODE_0_SELECTED_CHANNEL_RESERVE_MSAT; + assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat); + let expected_available_capacity_msat = expected_outbound_capacity_msat; + assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat); + let expected_splice_out_max = + NODE_1_VALUE_TO_SELF_MSAT / 1000 - SNEAKY_HTLC_MSAT / 1000 - NODE_1_DUST_LIMIT_MSAT / 1000; + assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max); + + let node_0_details = &nodes[0].node.list_channels()[0]; + let expected_outbound_capacity_msat = + NODE_0_VALUE_TO_SELF_MSAT - NODE_1_SELECTED_CHANNEL_RESERVE_MSAT - TOTAL_ANCHORS_MSAT; + assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat); + let expected_available_capacity_msat = + expected_outbound_capacity_msat - commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000; + assert_eq!(node_0_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat); + let expected_splice_out_max = NODE_0_VALUE_TO_SELF_MSAT / 1000 + - TOTAL_ANCHORS_MSAT / 1000 + - commit_tx_fee_sat(FEERATE, 2, &channel_type) + - NODE_0_DUST_LIMIT_MSAT / 1000; + assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_splice_out_max); + + let node_0_payment_msat = expected_available_capacity_msat; + send_payment(&nodes[0], &[&nodes[1]], node_0_payment_msat); + + route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT); + route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT); + + let node_0_details = &nodes[0].node.list_channels()[0]; + let expected_outbound_capacity_msat = NODE_0_VALUE_TO_SELF_MSAT + - node_0_payment_msat + - NODE_1_SELECTED_CHANNEL_RESERVE_MSAT + - TOTAL_ANCHORS_MSAT; + assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat); + assert_eq!( + node_0_details.outbound_capacity_msat, + commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000 + ); + assert_eq!(node_0_details.next_outbound_htlc_limit_msat, 0); + assert_eq!(node_0_details.next_splice_out_maximum_sat, 0); + + let node_1_details = &nodes[1].node.list_channels()[0]; + let expected_outbound_capacity_msat = NODE_1_VALUE_TO_SELF_MSAT + node_0_payment_msat + - 3 * SNEAKY_HTLC_MSAT + - NODE_0_SELECTED_CHANNEL_RESERVE_MSAT; + assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat); + let (_htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(&channel_type, FEERATE); + let expected_available_capacity_msat = + (NODE_1_DUST_LIMIT_MSAT / 1000 + htlc_timeout_tx_fee_sat) * 1000 - 1; + assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat); + let expected_splice_out_max = NODE_1_VALUE_TO_SELF_MSAT / 1000 + node_0_payment_msat / 1000 + - 3 * SNEAKY_HTLC_MSAT / 1000 + - NODE_1_DUST_LIMIT_MSAT / 1000; + assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max); +} + +#[xtest(feature = "_externalize_tests")] +fn test_available_balances_both_commitments_dust_on_fundee_commitment() { + let mut config = test_default_channel_config(); + + 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::anchors_zero_htlc_fee_and_dependencies(); + + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + const FEERATE: u32 = 253; + const TOTAL_ANCHORS_MSAT: u64 = 2 * 330_000; + const NODE_0_DUST_LIMIT_MSAT: u64 = 354 * 1000; + const NODE_1_DUST_LIMIT_MSAT: u64 = 10_000 * 1000; + const CHANNEL_VALUE_MSAT: u64 = 50_000 * 1000; + const NODE_0_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000; + const NODE_1_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000; + const NODE_0_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 10_000 * 1_000; + const NODE_1_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 1_000 * 1_000; + + let channel_id = create_announced_chan_between_nodes_with_value( + &nodes, + 0, + 1, + CHANNEL_VALUE_MSAT / 1000, + NODE_1_VALUE_TO_SELF_MSAT, + ) + .2; + assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type); + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().counterparty_dust_limit_satoshis = NODE_1_DUST_LIMIT_MSAT / 1000; + chan.funding_mut().holder_selected_channel_reserve_satoshis = + NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000; + assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_0_DUST_LIMIT_MSAT / 1000); + assert_eq!( + chan.funding().counterparty_selected_channel_reserve_satoshis, + Some(NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000) + ); + } + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().holder_dust_limit_satoshis = NODE_1_DUST_LIMIT_MSAT / 1000; + chan.funding_mut().counterparty_selected_channel_reserve_satoshis = + Some(NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000); + assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_0_DUST_LIMIT_MSAT / 1000); + assert_eq!( + chan.funding().holder_selected_channel_reserve_satoshis, + NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000 + ); + } + + // This HTLC is only present on node 0's commitment + const SNEAKY_HTLC_MSAT: u64 = 5_000_000; + + route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT); + + let node_1_details = &nodes[1].node.list_channels()[0]; + let expected_outbound_capacity_msat = + NODE_1_VALUE_TO_SELF_MSAT - SNEAKY_HTLC_MSAT - NODE_0_SELECTED_CHANNEL_RESERVE_MSAT; + assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat); + let expected_available_capacity_msat = expected_outbound_capacity_msat; + assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat); + let expected_splice_out_max = + NODE_1_VALUE_TO_SELF_MSAT / 1000 - SNEAKY_HTLC_MSAT / 1000 - NODE_1_DUST_LIMIT_MSAT / 1000; + assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max); + + let node_0_details = &nodes[0].node.list_channels()[0]; + + let expected_outbound_capacity_msat = + NODE_0_VALUE_TO_SELF_MSAT - NODE_1_SELECTED_CHANNEL_RESERVE_MSAT - TOTAL_ANCHORS_MSAT; + assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat); + + let expected_splice_out_max = NODE_0_VALUE_TO_SELF_MSAT / 1000 + - TOTAL_ANCHORS_MSAT / 1000 + - commit_tx_fee_sat(FEERATE, 2, &channel_type) + - NODE_0_DUST_LIMIT_MSAT / 1000; + assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_splice_out_max); + + let expected_available_capacity_msat = + expected_outbound_capacity_msat - commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000; + assert_eq!(node_0_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat); + + let node_0_payment_msat = expected_available_capacity_msat; + send_payment(&nodes[0], &[&nodes[1]], node_0_payment_msat); + + route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT); + route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT); + + let node_0_details = &nodes[0].node.list_channels()[0]; + let expected_outbound_capacity_msat = NODE_0_VALUE_TO_SELF_MSAT + - node_0_payment_msat + - NODE_1_SELECTED_CHANNEL_RESERVE_MSAT + - TOTAL_ANCHORS_MSAT; + assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat); + assert_eq!( + node_0_details.outbound_capacity_msat, + commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000 + ); + assert_eq!(node_0_details.next_outbound_htlc_limit_msat, 0); + + let local_balance_before_fee_sat = + NODE_0_VALUE_TO_SELF_MSAT / 1000 - node_0_payment_msat / 1000 - TOTAL_ANCHORS_MSAT / 1000; + let post_splice_delta_above_reserve = commit_tx_fee_sat(FEERATE, 4, &channel_type); + let divident_sat = local_balance_before_fee_sat * 100 + 100 + - (post_splice_delta_above_reserve * 100) + - CHANNEL_VALUE_MSAT / 1000; + let expected_splice_out_max = (divident_sat - 1) / 99; + assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_splice_out_max); + + let node_1_details = &nodes[1].node.list_channels()[0]; + let expected_outbound_capacity_msat = NODE_1_VALUE_TO_SELF_MSAT + node_0_payment_msat + - 3 * SNEAKY_HTLC_MSAT + - NODE_0_SELECTED_CHANNEL_RESERVE_MSAT; + assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat); + let (htlc_success_tx_fee_sat, _htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(&channel_type, FEERATE); + let expected_available_capacity_msat = + (NODE_0_DUST_LIMIT_MSAT / 1000 + htlc_success_tx_fee_sat) * 1000 - 1; + assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat); + let expected_splice_out_max = NODE_1_VALUE_TO_SELF_MSAT / 1000 + node_0_payment_msat / 1000 + - 3 * SNEAKY_HTLC_MSAT / 1000 + - NODE_1_DUST_LIMIT_MSAT / 1000; + assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max); +} diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 35c72509d0b..5e02c42b19b 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -9304,3 +9304,175 @@ fn do_test_splice_out_initiator_reserve_breach_zero_fee_commitments( acceptor.logger.assert_log("lightning::ln::channelmanager", cannot_splice_out, 1); } } + +#[test] +fn test_splice_out_maximum_on_both_commitments_dust_on_fundee_commitment() { + use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut config = test_default_channel_config(); + config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage = + 100; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + const CHANNEL_VALUE_SAT: u64 = 100_000; + const FEERATE: u32 = 253; + const TOTAL_ANCHORS_SAT: u64 = 2 * 330; + const NODE_0_DUST_LIMIT_SAT: u64 = 354; + const NODE_1_DUST_LIMIT_SAT: u64 = 10_000; + + let (channel_id, _transaction) = + setup_0reserve_no_outputs_channels(&nodes, CHANNEL_VALUE_SAT, NODE_0_DUST_LIMIT_SAT); + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().counterparty_dust_limit_satoshis = NODE_1_DUST_LIMIT_SAT; + assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_0_DUST_LIMIT_SAT); + assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0); + assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0)); + } + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().holder_dust_limit_satoshis = NODE_1_DUST_LIMIT_SAT; + assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_0_DUST_LIMIT_SAT); + assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0); + assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0)); + } + + let details = &nodes[0].node.list_channels()[0]; + let channel_type = details.channel_type.clone().unwrap(); + assert_eq!(channel_type, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()); + + // This HTLC is only present on node 0's commitment + const SNEAKY_HTLC_SAT: u64 = 5_000; + + let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], SNEAKY_HTLC_SAT * 1000); + + let node_0_details = &nodes[0].node.list_channels()[0]; + let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 0, &channel_type); + let expected_next_splice_out_maximum_sat = CHANNEL_VALUE_SAT + - SNEAKY_HTLC_SAT + - TOTAL_ANCHORS_SAT + - reserved_fee_sat + - NODE_1_DUST_LIMIT_SAT; + assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat); + let node_1_details = &nodes[1].node.list_channels()[0]; + assert_eq!(node_1_details.next_splice_out_maximum_sat, 0); + + fail_payment(&nodes[0], &[&nodes[1]], payment_hash); + + let details = &nodes[0].node.list_channels()[0]; + let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 2, &channel_type); + let expected_available_capacity_sat = CHANNEL_VALUE_SAT - TOTAL_ANCHORS_SAT - reserved_fee_sat; + assert_eq!(details.next_outbound_htlc_limit_msat, expected_available_capacity_sat * 1000); + let node_0_payment_sat = expected_available_capacity_sat; + send_payment(&nodes[0], &[&nodes[1]], node_0_payment_sat * 1000); + + // Make sure the local output is now gone from node 1's commitment + assert!(TOTAL_ANCHORS_SAT + reserved_fee_sat < NODE_1_DUST_LIMIT_SAT); + + let details = &nodes[1].node.list_channels()[0]; + let expected_next_splice_out_maximum_sat = node_0_payment_sat - NODE_1_DUST_LIMIT_SAT; + assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat); + + let details = &nodes[0].node.list_channels()[0]; + let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 1, &channel_type); + let expected_next_splice_out_maximum_sat = + CHANNEL_VALUE_SAT - node_0_payment_sat - TOTAL_ANCHORS_SAT - reserved_fee_sat; + assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat); +} + +#[test] +fn test_splice_out_maximum_on_both_commitments_dust_on_funder_commitment() { + use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut config = test_default_channel_config(); + config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage = + 100; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + const CHANNEL_VALUE_SAT: u64 = 100_000; + const FEERATE: u32 = 253; + const TOTAL_ANCHORS_SAT: u64 = 2 * 330; + const NODE_0_DUST_LIMIT_SAT: u64 = 10_000; + const NODE_1_DUST_LIMIT_SAT: u64 = 354; + + let (channel_id, _transaction) = + setup_0reserve_no_outputs_channels(&nodes, CHANNEL_VALUE_SAT, NODE_1_DUST_LIMIT_SAT); + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().holder_dust_limit_satoshis = NODE_0_DUST_LIMIT_SAT; + assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_1_DUST_LIMIT_SAT); + assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0); + assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0)); + } + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id); + chan.context_mut().counterparty_dust_limit_satoshis = NODE_0_DUST_LIMIT_SAT; + assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_1_DUST_LIMIT_SAT); + assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0); + assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0)); + } + + let details = &nodes[0].node.list_channels()[0]; + let channel_type = details.channel_type.clone().unwrap(); + assert_eq!(channel_type, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()); + + // This HTLC is only present on node 1's commitment + const SNEAKY_HTLC_SAT: u64 = 5_000; + + let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], SNEAKY_HTLC_SAT * 1000); + + let node_0_details = &nodes[0].node.list_channels()[0]; + let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 0, &channel_type); + let expected_next_splice_out_maximum_sat = CHANNEL_VALUE_SAT + - SNEAKY_HTLC_SAT + - TOTAL_ANCHORS_SAT + - reserved_fee_sat + - NODE_0_DUST_LIMIT_SAT; + assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat); + let node_1_details = &nodes[1].node.list_channels()[0]; + assert_eq!(node_1_details.next_splice_out_maximum_sat, 0); + + fail_payment(&nodes[0], &[&nodes[1]], payment_hash); + + let details = &nodes[0].node.list_channels()[0]; + let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 2, &channel_type); + let expected_available_capacity_sat = CHANNEL_VALUE_SAT - TOTAL_ANCHORS_SAT - reserved_fee_sat; + assert_eq!(details.next_outbound_htlc_limit_msat, expected_available_capacity_sat * 1000); + let node_0_payment_sat = expected_available_capacity_sat; + send_payment(&nodes[0], &[&nodes[1]], node_0_payment_sat * 1000); + + // Make sure the local output is now gone from node 0's commitment + assert!(TOTAL_ANCHORS_SAT + reserved_fee_sat < NODE_0_DUST_LIMIT_SAT); + + let details = &nodes[1].node.list_channels()[0]; + let expected_next_splice_out_maximum_sat = node_0_payment_sat - NODE_0_DUST_LIMIT_SAT; + assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat); + + let details = &nodes[0].node.list_channels()[0]; + let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 1, &channel_type); + let expected_next_splice_out_maximum_sat = + CHANNEL_VALUE_SAT - node_0_payment_sat - TOTAL_ANCHORS_SAT - reserved_fee_sat; + assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat); +} diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs index 6859d4dcf62..8f699fc85aa 100644 --- a/lightning/src/sign/tx_builder.rs +++ b/lightning/src/sign/tx_builder.rs @@ -358,10 +358,18 @@ 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, feerate_per_kw: u32, nondust_htlc_count: usize, - post_splice_delta_above_reserve_sat: u64, channel_constraints: &ChannelConstraints, - channel_type: &ChannelTypeFeatures, + remote_balance_before_fee_msat: u64, local_nondust_htlc_count: usize, + remote_nondust_htlc_count: usize, feerate_per_kw: u32, spiked_feerate: u32, + channel_constraints: &ChannelConstraints, channel_type: &ChannelTypeFeatures, ) -> u64 { + let post_splice_delta_above_reserve_sat = if is_outbound_from_holder { + let nondust_htlc_count = cmp::max(local_nondust_htlc_count, remote_nondust_htlc_count); + let commit_tx_fee_sat = + commit_tx_fee_sat(spiked_feerate, nondust_htlc_count + 1, channel_type); + commit_tx_fee_sat + } else { + 0 + }; let local_balance_before_fee_sat = local_balance_before_fee_msat / 1000; let mut next_splice_out_maximum_sat = if channel_constraints .counterparty_selected_channel_reserve_satoshis @@ -424,37 +432,47 @@ fn get_next_splice_out_maximum_sat( } max_splice_out_sat } else { - // In a zero-reserve channel, the holder is free to withdraw up to its `post_splice_delta_above_reserve_sat` + // In a zero-reserve channel, the holder is free to withdraw up to its `post_splice_delta_above_reserve_sat`. local_balance_before_fee_sat.saturating_sub(post_splice_delta_above_reserve_sat) }; - // We only bother to check the local commitment here, the counterparty will check its own commitment. - // // 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_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, - feerate_per_kw, - nondust_htlc_count, + // + // We use the regular feerate instead of the spiked feerate here as zero-reserve is not + // allowed on legacy channels. + let current_tx_fee_sat = commit_tx_fee_sat(feerate_per_kw, 0, channel_type); + let mut trim_splice_out_max_if_no_outputs = |nondust_htlc_count, dust_limit_satoshis| { + 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, + feerate_per_kw, + nondust_htlc_count, + dust_limit_satoshis, + channel_type, + ) { + let min_balance_sat = if is_outbound_from_holder { + dust_limit_satoshis.saturating_add(current_tx_fee_sat) + } else { + dust_limit_satoshis + }; + next_splice_out_maximum_sat = + (local_balance_before_fee_msat / 1000).saturating_sub(min_balance_sat); + } + }; + trim_splice_out_max_if_no_outputs( + local_nondust_htlc_count, channel_constraints.holder_dust_limit_satoshis, - channel_type, - ) { - let dust_limit_satoshis = channel_constraints.holder_dust_limit_satoshis; - 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_tx_fee_sat) - } else { - dust_limit_satoshis - }; - next_splice_out_maximum_sat = - (local_balance_before_fee_msat / 1000).saturating_sub(min_balance_sat); - } + ); + trim_splice_out_max_if_no_outputs( + remote_nondust_htlc_count, + channel_constraints.counterparty_dust_limit_satoshis, + ); if channel_value_satoshis < next_splice_out_maximum_sat + MIN_CHANNEL_VALUE_SATOSHIS { next_splice_out_maximum_sat = @@ -464,127 +482,20 @@ fn get_next_splice_out_maximum_sat( next_splice_out_maximum_sat } -fn get_available_balances( - is_outbound_from_holder: bool, channel_value_satoshis: u64, value_to_holder_msat: u64, - pending_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32, - dust_exposure_limiting_feerate: Option, max_dust_htlc_exposure_msat: u64, - channel_constraints: ChannelConstraints, channel_type: &ChannelTypeFeatures, -) -> crate::ln::channel::AvailableBalances { - // When sizing the next HTLC add, we take the remote's view of the set of pending HTLCs in - // `ChannelContext::get_next_commitment_htlcs`, set this view to `pending_htlcs` here, and use this set of - // pending HTLCs to calculate stats on our own commitment below. - // - // This means we do *not* include `LocalRemoved` HTLCs. `LocalRemoved` and `LocalAnnounced` HTLCs are applied - // atomically to our own commitment upon the counterparty's next ack. - // - // `RemoteRemoved` HTLCs *are* included. While we don't expect these HTLCs to be present in our next - // commitment, we have not ack'ed these removals yet, so we expect the counterparty to count them when - // validating our own HTLC add. These HTLCs would also revert to `Committed` upon a disconnection. - - let fee_spike_buffer_htlc = - 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 !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() - .filter(|htlc| { - !htlc.is_dust( - true, - feerate_per_kw, - 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 - // feerate. - let local_max_commit_tx_fee_sat = commit_tx_fee_sat( - spiked_feerate, - local_nondust_htlc_count + fee_spike_buffer_htlc + 1, - channel_type, - ); - let local_min_commit_tx_fee_sat = commit_tx_fee_sat( - spiked_feerate, - local_nondust_htlc_count + fee_spike_buffer_htlc, - channel_type, - ); - let (local_dust_exposure_msat, _) = get_dust_exposure_stats( - true, - pending_htlcs, - feerate_per_kw, - dust_exposure_limiting_feerate, - channel_constraints.holder_dust_limit_satoshis, - channel_type, - ); - let remote_nondust_htlc_count = pending_htlcs - .iter() - .filter(|htlc| { - !htlc.is_dust( - false, - feerate_per_kw, - channel_constraints.counterparty_dust_limit_satoshis, - channel_type, - ) - }) - .count(); - let remote_commit_tx_fee_sat = - commit_tx_fee_sat(feerate_per_kw, remote_nondust_htlc_count + 1, channel_type); - let (remote_dust_exposure_msat, extra_htlc_remote_dust_exposure_msat) = get_dust_exposure_stats( - false, - pending_htlcs, - feerate_per_kw, - dust_exposure_limiting_feerate, - channel_constraints.counterparty_dust_limit_satoshis, - channel_type, - ); - - let outbound_htlcs_value_msat: u64 = - pending_htlcs.iter().filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat)).sum(); - let inbound_htlcs_value_msat: u64 = - pending_htlcs.iter().filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)).sum(); - let total_anchors_sat = total_anchors_sat(channel_type); - let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = - saturating_sub_from_funder( - is_outbound_from_holder, - value_to_holder_msat.saturating_sub(outbound_htlcs_value_msat), - (channel_value_satoshis * 1000) - .checked_sub(value_to_holder_msat) - .unwrap() - .saturating_sub(inbound_htlcs_value_msat), - total_anchors_sat.saturating_mul(1000), - ); - - let next_splice_out_maximum_sat = get_next_splice_out_maximum_sat( - is_outbound_from_holder, - channel_value_satoshis, - local_balance_before_fee_msat, - remote_balance_before_fee_msat, - 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, - channel_type, - ); - - let outbound_capacity_msat = local_balance_before_fee_msat - .saturating_sub(channel_constraints.counterparty_selected_channel_reserve_satoshis * 1000); - - let mut available_capacity_msat = outbound_capacity_msat; - let (real_htlc_success_tx_fee_sat, real_htlc_timeout_tx_fee_sat) = - second_stage_tx_fees_sat(channel_type, feerate_per_kw); +fn adjust_capacity_for_holder_reserved_fee( + outbound_capacity_msat: u64, local_nondust_htlc_count: usize, remote_nondust_htlc_count: usize, + feerate_per_kw: u32, spiked_feerate: u32, channel_constraints: &ChannelConstraints, + channel_type: &ChannelTypeFeatures, +) -> u64 { + let read_available_capacity = |nondust_htlc_count, htlc_dust_limit_sat| { + // 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 + // feerate. + let max_commit_tx_fee_sat = + commit_tx_fee_sat(spiked_feerate, nondust_htlc_count + 2, channel_type); + let min_commit_tx_fee_sat = + commit_tx_fee_sat(spiked_feerate, nondust_htlc_count + 1, channel_type); - if is_outbound_from_holder { // We should mind channel commit tx fee when computing how much of the available capacity // can be used in the next htlc. Mirrors the logic in send_htlc. // @@ -593,47 +504,92 @@ fn get_available_balances( // dependency. // This complicates the computation around dust-values, up to the one-htlc-value. - let real_dust_limit_timeout_sat = - real_htlc_timeout_tx_fee_sat + channel_constraints.holder_dust_limit_satoshis; - let max_reserved_commit_tx_fee_msat = local_max_commit_tx_fee_sat * 1000; - let min_reserved_commit_tx_fee_msat = local_min_commit_tx_fee_sat * 1000; - // We will first subtract the fee as if we were above-dust. Then, if the resulting // value ends up being below dust, we have this fee available again. In that case, // match the value to right-below-dust. let capacity_minus_max_commitment_fee_msat = - available_capacity_msat.saturating_sub(max_reserved_commit_tx_fee_msat); - if capacity_minus_max_commitment_fee_msat < real_dust_limit_timeout_sat * 1000 { + outbound_capacity_msat.saturating_sub(max_commit_tx_fee_sat * 1000); + if capacity_minus_max_commitment_fee_msat < htlc_dust_limit_sat * 1000 { let capacity_minus_min_commitment_fee_msat = - available_capacity_msat.saturating_sub(min_reserved_commit_tx_fee_msat); - available_capacity_msat = cmp::min( - real_dust_limit_timeout_sat * 1000 - 1, - capacity_minus_min_commitment_fee_msat, - ); + outbound_capacity_msat.saturating_sub(min_commit_tx_fee_sat * 1000); + cmp::min(htlc_dust_limit_sat * 1000 - 1, capacity_minus_min_commitment_fee_msat) } else { - available_capacity_msat = capacity_minus_max_commitment_fee_msat; + capacity_minus_max_commitment_fee_msat } - } else { + }; + + let (real_htlc_success_tx_fee_sat, real_htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(channel_type, feerate_per_kw); + let available_capacity_on_local_commitment = read_available_capacity( + local_nondust_htlc_count, + channel_constraints.holder_dust_limit_satoshis + real_htlc_timeout_tx_fee_sat, + ); + let available_capacity_on_remote_commitment = read_available_capacity( + remote_nondust_htlc_count, + channel_constraints.counterparty_dust_limit_satoshis + real_htlc_success_tx_fee_sat, + ); + cmp::min(available_capacity_on_local_commitment, available_capacity_on_remote_commitment) +} + +fn adjust_capacity_for_counterparty_reserved_fee( + outbound_capacity_msat: u64, remote_balance_before_fee_msat: u64, + local_nondust_htlc_count: usize, remote_nondust_htlc_count: usize, feerate_per_kw: u32, + channel_constraints: &ChannelConstraints, channel_type: &ChannelTypeFeatures, +) -> u64 { + let read_available_capacity = |nondust_htlc_count, htlc_dust_limit_sat| { + let commit_tx_fee_sat = + commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count + 1, channel_type); // If the channel is inbound (i.e. counterparty pays the fee), we need to make sure // sending a new HTLC won't reduce their balance below our reserve threshold. - let real_dust_limit_success_sat = - real_htlc_success_tx_fee_sat + channel_constraints.counterparty_dust_limit_satoshis; - let max_reserved_commit_tx_fee_msat = remote_commit_tx_fee_sat * 1000; - - let holder_selected_chan_reserve_msat = - channel_constraints.holder_selected_channel_reserve_satoshis * 1000; if remote_balance_before_fee_msat - < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_msat + < commit_tx_fee_sat * 1000 + + channel_constraints.holder_selected_channel_reserve_satoshis * 1000 { // If another HTLC's fee would reduce the remote's balance below the reserve limit // we've selected for them, we can only send dust HTLCs. - available_capacity_msat = - cmp::min(available_capacity_msat, real_dust_limit_success_sat * 1000 - 1); + cmp::min(outbound_capacity_msat, htlc_dust_limit_sat * 1000 - 1) + } else { + outbound_capacity_msat } - } + }; + let (real_htlc_success_tx_fee_sat, real_htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(channel_type, feerate_per_kw); + let available_capacity_on_local_commitment = read_available_capacity( + local_nondust_htlc_count, + channel_constraints.holder_dust_limit_satoshis + real_htlc_timeout_tx_fee_sat, + ); + let available_capacity_on_remote_commitment = read_available_capacity( + remote_nondust_htlc_count, + channel_constraints.counterparty_dust_limit_satoshis + real_htlc_success_tx_fee_sat, + ); + cmp::min(available_capacity_on_local_commitment, available_capacity_on_remote_commitment) +} +fn adjust_min_max_htlc_for_dust_exposure( + pending_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, max_dust_htlc_exposure_msat: u64, + channel_constraints: &ChannelConstraints, channel_type: &ChannelTypeFeatures, + mut available_capacity_msat: u64, +) -> (u64, u64, u64) { let mut next_outbound_htlc_minimum_msat = channel_constraints.counterparty_htlc_minimum_msat; + let (local_dust_exposure_msat, _) = get_dust_exposure_stats( + true, + pending_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + channel_constraints.holder_dust_limit_satoshis, + channel_type, + ); + let (remote_dust_exposure_msat, extra_htlc_remote_dust_exposure_msat) = get_dust_exposure_stats( + false, + pending_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + channel_constraints.counterparty_dust_limit_satoshis, + channel_type, + ); + // If we get close to our maximum dust exposure, we end up in a situation where we can send // between zero and the remaining dust exposure limit remaining OR above the dust limit. // Because we cannot express this as a simple min/max, we prefer to tell the user they can @@ -689,9 +645,130 @@ fn get_available_balances( } } + let dust_exposure_msat = cmp::max(local_dust_exposure_msat, remote_dust_exposure_msat); + + (next_outbound_htlc_minimum_msat, available_capacity_msat, dust_exposure_msat) +} + +fn get_available_balances( + is_outbound_from_holder: bool, channel_value_satoshis: u64, value_to_holder_msat: u64, + pending_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, max_dust_htlc_exposure_msat: u64, + channel_constraints: ChannelConstraints, channel_type: &ChannelTypeFeatures, +) -> crate::ln::channel::AvailableBalances { + // When sizing the next HTLC add, we take the remote's view of the set of pending HTLCs in + // `ChannelContext::get_next_commitment_htlcs`, set this view to `pending_htlcs` here, and use this set of + // pending HTLCs to calculate stats on our own commitment below. + // + // This means we do *not* include `LocalRemoved` HTLCs. `LocalRemoved` and `LocalAnnounced` HTLCs are applied + // atomically to our own commitment upon the counterparty's next ack. + // + // `RemoteRemoved` HTLCs *are* included. While we don't expect these HTLCs to be present in our next + // commitment, we have not ack'ed these removals yet, so we expect the counterparty to count them when + // validating our own HTLC add. These HTLCs would also revert to `Committed` upon a disconnection. + + // 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 !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() + .filter(|htlc| { + !htlc.is_dust( + true, + feerate_per_kw, + channel_constraints.holder_dust_limit_satoshis, + channel_type, + ) + }) + .count(); + + let remote_nondust_htlc_count = pending_htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust( + false, + feerate_per_kw, + channel_constraints.counterparty_dust_limit_satoshis, + channel_type, + ) + }) + .count(); + + let outbound_htlcs_value_msat: u64 = + pending_htlcs.iter().filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat)).sum(); + let inbound_htlcs_value_msat: u64 = + pending_htlcs.iter().filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)).sum(); + let total_anchors_sat = total_anchors_sat(channel_type); + let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = + saturating_sub_from_funder( + is_outbound_from_holder, + value_to_holder_msat.saturating_sub(outbound_htlcs_value_msat), + (channel_value_satoshis * 1000) + .checked_sub(value_to_holder_msat) + .unwrap() + .saturating_sub(inbound_htlcs_value_msat), + total_anchors_sat.saturating_mul(1000), + ); + + let next_splice_out_maximum_sat = get_next_splice_out_maximum_sat( + is_outbound_from_holder, + channel_value_satoshis, + local_balance_before_fee_msat, + remote_balance_before_fee_msat, + local_nondust_htlc_count, + remote_nondust_htlc_count, + feerate_per_kw, + spiked_feerate, + &channel_constraints, + channel_type, + ); + + let outbound_capacity_msat = local_balance_before_fee_msat + .saturating_sub(channel_constraints.counterparty_selected_channel_reserve_satoshis * 1000); + + let available_capacity_msat = if is_outbound_from_holder { + adjust_capacity_for_holder_reserved_fee( + outbound_capacity_msat, + local_nondust_htlc_count, + remote_nondust_htlc_count, + feerate_per_kw, + spiked_feerate, + &channel_constraints, + channel_type, + ) + } else { + adjust_capacity_for_counterparty_reserved_fee( + outbound_capacity_msat, + remote_balance_before_fee_msat, + local_nondust_htlc_count, + remote_nondust_htlc_count, + feerate_per_kw, + &channel_constraints, + channel_type, + ) + }; + + let (next_outbound_htlc_minimum_msat, mut available_capacity_msat, dust_exposure_msat) = + adjust_min_max_htlc_for_dust_exposure( + pending_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + max_dust_htlc_exposure_msat, + &channel_constraints, + channel_type, + available_capacity_msat, + ); + available_capacity_msat = cmp::min( available_capacity_msat, - channel_constraints.counterparty_max_htlc_value_in_flight_msat - outbound_htlcs_value_msat, + channel_constraints + .counterparty_max_htlc_value_in_flight_msat + .saturating_sub(outbound_htlcs_value_msat), ); if pending_htlcs.iter().filter(|htlc| htlc.outbound).count() + 1 @@ -702,7 +779,38 @@ 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 current feerate. + let (next_outbound_htlc_minimum_msat, available_capacity_msat) = + adjust_min_max_htlc_if_max_dust_htlc_produces_no_output( + is_outbound_from_holder, + local_balance_before_fee_msat, + remote_balance_before_fee_msat, + local_nondust_htlc_count, + remote_nondust_htlc_count, + feerate_per_kw, + &channel_constraints, + channel_type, + next_outbound_htlc_minimum_msat, + available_capacity_msat, + ); + + crate::ln::channel::AvailableBalances { + inbound_capacity_msat: remote_balance_before_fee_msat + .saturating_sub(channel_constraints.holder_selected_channel_reserve_satoshis * 1000), + outbound_capacity_msat, + next_outbound_htlc_limit_msat: available_capacity_msat, + next_outbound_htlc_minimum_msat, + dust_exposure_msat, + next_splice_out_maximum_sat, + } +} +fn adjust_min_max_htlc_if_max_dust_htlc_produces_no_output( + is_outbound_from_holder: bool, local_balance_before_fee_msat: u64, + remote_balance_before_fee_msat: u64, local_nondust_htlc_count: usize, + remote_nondust_htlc_count: usize, feerate_per_kw: u32, + channel_constraints: &ChannelConstraints, channel_type: &ChannelTypeFeatures, + next_outbound_htlc_minimum_msat: u64, available_capacity_msat: u64, +) -> (u64, u64) { let (next_outbound_htlc_minimum_msat, available_capacity_msat) = adjust_boundaries_if_max_dust_htlc_produces_no_output( true, @@ -730,17 +838,7 @@ fn get_available_balances( next_outbound_htlc_minimum_msat, available_capacity_msat, ); - let dust_exposure_msat = cmp::max(local_dust_exposure_msat, remote_dust_exposure_msat); - - crate::ln::channel::AvailableBalances { - inbound_capacity_msat: remote_balance_before_fee_msat - .saturating_sub(channel_constraints.holder_selected_channel_reserve_satoshis * 1000), - outbound_capacity_msat, - next_outbound_htlc_limit_msat: available_capacity_msat, - next_outbound_htlc_minimum_msat, - dust_exposure_msat, - next_splice_out_maximum_sat, - } + (next_outbound_htlc_minimum_msat, available_capacity_msat) } fn adjust_boundaries_if_max_dust_htlc_produces_no_output(