Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
outbound_capacity_msat: capacity.saturating_mul(1000),
next_outbound_htlc_limit_msat: capacity.saturating_mul(1000),
next_outbound_htlc_minimum_msat: 0,
next_splice_out_maximum_sat: capacity,
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
config: None,
Expand Down
505 changes: 249 additions & 256 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions lightning/src/ln/channel_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ pub struct ChannelDetails {
/// an upper-bound. This is intended for use when routing, allowing us to ensure we pick a
/// route which is valid.
pub next_outbound_htlc_minimum_msat: u64,
/// The maximum value of the next splice out from our channel balance.
pub next_splice_out_maximum_sat: u64,
/// The available inbound capacity for the remote peer to send HTLCs to us. This does not
/// include any pending HTLCs which are not yet fully resolved (and, thus, whose balance is not
/// available for inclusion in new inbound HTLCs).
Expand Down Expand Up @@ -533,6 +535,7 @@ impl ChannelDetails {
outbound_capacity_msat: 0,
next_outbound_htlc_limit_msat: 0,
next_outbound_htlc_minimum_msat: u64::MAX,
next_splice_out_maximum_sat: 0,
}
});
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =
Expand Down Expand Up @@ -582,6 +585,7 @@ impl ChannelDetails {
outbound_capacity_msat: balance.outbound_capacity_msat,
next_outbound_htlc_limit_msat: balance.next_outbound_htlc_limit_msat,
next_outbound_htlc_minimum_msat: balance.next_outbound_htlc_minimum_msat,
next_splice_out_maximum_sat: balance.next_splice_out_maximum_sat,
user_channel_id: context.get_user_id(),
confirmations_required: channel.minimum_depth(),
confirmations: Some(funding.get_funding_tx_confirmations(best_block_height)),
Expand Down Expand Up @@ -621,6 +625,7 @@ impl_writeable_tlv_based!(ChannelDetails, {
(20, inbound_capacity_msat, required),
(21, next_outbound_htlc_minimum_msat, (default_value, 0)),
(22, confirmations_required, option),
(23, next_splice_out_maximum_sat, (default_value, u64::from(outbound_capacity_msat.0.unwrap()) / 1000)),
(24, force_close_spend_delay, option),
(26, is_outbound, required),
(28, is_channel_ready, required),
Expand Down Expand Up @@ -725,6 +730,7 @@ mod tests {
outbound_capacity_msat: 24_300,
next_outbound_htlc_limit_msat: 20_000,
next_outbound_htlc_minimum_msat: 132,
next_splice_out_maximum_sat: 20,
inbound_capacity_msat: 42,
unspendable_punishment_reserve: Some(8273),
confirmations_required: Some(5),
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3887,7 +3887,7 @@ impl<
override_config: Option<UserConfig>,
trusted_channel_features: Option<TrustedChannelFeatures>,
) -> Result<ChannelId, APIError> {
if channel_value_satoshis < 1000 {
if channel_value_satoshis < crate::ln::channel::MIN_CHANNEL_VALUE_SATOSHIS {
return Err(APIError::APIMisuseError {
err: format!(
"Channel value must be at least 1000 satoshis. It was {channel_value_satoshis}"
Expand Down Expand Up @@ -8121,6 +8121,7 @@ impl<
outbound_capacity_msat: 0,
next_outbound_htlc_limit_msat: 0,
next_outbound_htlc_minimum_msat: u64::MAX,
next_splice_out_maximum_sat: 0,
}
});
let is_in_range = (balances.next_outbound_htlc_minimum_msat
Expand Down
69 changes: 36 additions & 33 deletions lightning/src/ln/funding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl core::fmt::Display for FundingContributionError {
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct PriorContribution {
contribution: FundingContribution,
/// The holder's balance, used for feerate adjustment.
/// The holder's spliceable balance, used for feerate adjustment.
///
/// This value is captured at [`ChannelManager::splice_channel`] time and may become stale
/// if balances change before the contribution is used. Staleness is acceptable here because
Expand All @@ -203,12 +203,12 @@ pub(super) struct PriorContribution {
///
/// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel
/// [`ChannelManager::funding_contributed`]: crate::ln::channelmanager::ChannelManager::funding_contributed
holder_balance: Amount,
spliceable_balance: Amount,
}

impl PriorContribution {
pub(super) fn new(contribution: FundingContribution, holder_balance: Amount) -> Self {
Self { contribution, holder_balance }
pub(super) fn new(contribution: FundingContribution, spliceable_balance: Amount) -> Self {
Self { contribution, spliceable_balance }
}
}

Expand Down Expand Up @@ -632,14 +632,14 @@ impl FundingContribution {
/// `target_feerate`. If dropping change leaves surplus value, that surplus remains in the
/// channel contribution.
///
/// For input-less contributions, `holder_balance` must be provided to cover the outputs and
/// For input-less contributions, `spliceable_balance` must be provided to cover the outputs and
/// fees from the channel balance.
///
/// Returns `None` if the request would require new wallet inputs or cannot accommodate the
/// requested feerate.
fn amend_without_coin_selection(
self, inputs: FundingInputs, outputs: &[TxOut], target_feerate: FeeRate,
max_feerate: FeeRate, holder_balance: Amount,
max_feerate: FeeRate, spliceable_balance: Amount,
) -> Option<Self> {
// NOTE: The contribution returned is not guaranteed to be valid. We defer doing so until
// `compute_feerate_adjustment`.
Expand Down Expand Up @@ -717,7 +717,7 @@ impl FundingContribution {
let new_contribution_at_current_feerate =
adjust_for_inputs_and_outputs(self, inputs, outputs)?;
let mut new_contribution_at_target_feerate = new_contribution_at_current_feerate
.at_feerate(target_feerate, holder_balance, true)
.at_feerate(target_feerate, spliceable_balance, true)
.ok()?;
new_contribution_at_target_feerate.max_feerate = max_feerate;

Expand Down Expand Up @@ -771,7 +771,7 @@ impl FundingContribution {
///
/// Returns `Err` if the contribution cannot accommodate the target feerate.
fn compute_feerate_adjustment(
&self, target_feerate: FeeRate, holder_balance: Amount, is_initiator: bool,
&self, target_feerate: FeeRate, spliceable_balance: Amount, is_initiator: bool,
) -> Result<(Amount, Option<Amount>), FeeRateAdjustmentError> {
if target_feerate < self.feerate {
return Err(FeeRateAdjustmentError::FeeRateTooLow {
Expand Down Expand Up @@ -864,10 +864,12 @@ impl FundingContribution {
let total_cost = target_fee
.checked_add(value_removed)
.ok_or(FeeRateAdjustmentError::FeeBufferOverflow)?;
if total_cost > holder_balance {
if total_cost > spliceable_balance {
return Err(FeeRateAdjustmentError::FeeBufferInsufficient {
source: "channel balance - withdrawal outputs",
available: holder_balance.checked_sub(value_removed).unwrap_or(Amount::ZERO),
available: spliceable_balance
.checked_sub(value_removed)
.unwrap_or(Amount::ZERO),
required: target_fee,
});
}
Expand All @@ -879,10 +881,10 @@ impl FundingContribution {
/// estimate, and feerate. Returns the adjusted contribution, or an error if the feerate
/// can't be accommodated.
fn at_feerate(
mut self, feerate: FeeRate, holder_balance: Amount, is_initiator: bool,
mut self, feerate: FeeRate, spliceable_balance: Amount, is_initiator: bool,
) -> Result<Self, FeeRateAdjustmentError> {
let (new_estimated_fee, new_change) =
self.compute_feerate_adjustment(feerate, holder_balance, is_initiator)?;
self.compute_feerate_adjustment(feerate, spliceable_balance, is_initiator)?;
match new_change {
Some(value) => self.change_output.as_mut().unwrap().value = value,
None => self.change_output = None,
Expand All @@ -899,9 +901,9 @@ impl FundingContribution {
/// This adjusts the change output so the acceptor pays their target fee at the target
/// feerate.
pub(super) fn for_acceptor_at_feerate(
self, feerate: FeeRate, holder_balance: Amount,
self, feerate: FeeRate, spliceable_balance: Amount,
) -> Result<Self, FeeRateAdjustmentError> {
self.at_feerate(feerate, holder_balance, false)
self.at_feerate(feerate, spliceable_balance, false)
}

/// Adjusts the contribution's change output for the minimum RBF feerate.
Expand All @@ -910,9 +912,9 @@ impl FundingContribution {
/// below the minimum RBF feerate, this adjusts the change output so the initiator pays fees
/// at the minimum RBF feerate.
pub(super) fn for_initiator_at_feerate(
self, feerate: FeeRate, holder_balance: Amount,
self, feerate: FeeRate, spliceable_balance: Amount,
) -> Result<Self, FeeRateAdjustmentError> {
self.at_feerate(feerate, holder_balance, true)
self.at_feerate(feerate, spliceable_balance, true)
}

/// Returns the net value at the given target feerate without mutating `self`.
Expand All @@ -921,10 +923,10 @@ impl FundingContribution {
/// can't be accommodated) and computes the adjusted net value (returning `Ok` with the value
/// accounting for the target feerate).
fn net_value_at_feerate(
&self, target_feerate: FeeRate, holder_balance: Amount, is_initiator: bool,
&self, target_feerate: FeeRate, spliceable_balance: Amount, is_initiator: bool,
) -> Result<SignedAmount, FeeRateAdjustmentError> {
let (new_estimated_fee, new_change) =
self.compute_feerate_adjustment(target_feerate, holder_balance, is_initiator)?;
self.compute_feerate_adjustment(target_feerate, spliceable_balance, is_initiator)?;

let prev_fee = self
.estimated_fee
Expand Down Expand Up @@ -952,17 +954,17 @@ impl FundingContribution {
/// Returns the net value at the given target feerate without mutating `self`,
/// assuming acceptor fee responsibility.
pub(super) fn net_value_for_acceptor_at_feerate(
&self, target_feerate: FeeRate, holder_balance: Amount,
&self, target_feerate: FeeRate, spliceable_balance: Amount,
) -> Result<SignedAmount, FeeRateAdjustmentError> {
self.net_value_at_feerate(target_feerate, holder_balance, false)
self.net_value_at_feerate(target_feerate, spliceable_balance, false)
}

/// Returns the net value at the given target feerate without mutating `self`,
/// assuming initiator fee responsibility.
pub(super) fn net_value_for_initiator_at_feerate(
&self, target_feerate: FeeRate, holder_balance: Amount,
&self, target_feerate: FeeRate, spliceable_balance: Amount,
) -> Result<SignedAmount, FeeRateAdjustmentError> {
self.net_value_at_feerate(target_feerate, holder_balance, true)
self.net_value_at_feerate(target_feerate, spliceable_balance, true)
}

/// The net value contributed to a channel by the splice.
Expand Down Expand Up @@ -1059,13 +1061,13 @@ impl<State> FundingBuilderInner<State> {
fn build_from_prior_contribution(
&mut self, contribution: PriorContribution,
) -> Result<FundingContribution, FundingContributionError> {
let PriorContribution { contribution, holder_balance } = contribution;
let PriorContribution { contribution, spliceable_balance } = contribution;

if self.request_matches_prior(&contribution) {
// Same request, but the feerate may have changed. Adjust the prior contribution
// to the new feerate if possible.
return contribution
.for_initiator_at_feerate(self.feerate, holder_balance)
.for_initiator_at_feerate(self.feerate, spliceable_balance)
.map(|mut adjusted| {
adjusted.max_feerate = self.max_feerate;
adjusted
Expand All @@ -1084,7 +1086,7 @@ impl<State> FundingBuilderInner<State> {
&self.outputs,
self.feerate,
self.max_feerate,
holder_balance,
spliceable_balance,
)
.ok_or_else(|| FundingContributionError::MissingCoinSelectionSource);
}
Expand Down Expand Up @@ -2181,8 +2183,8 @@ mod tests {
};

// Balance of 55,000 sats can't cover outputs (50,000) + target_fee at 50k sat/kwu.
let holder_balance = Amount::from_sat(55_000);
let result = contribution.for_acceptor_at_feerate(target_feerate, holder_balance);
let spliceable_balance = Amount::from_sat(55_000);
let result = contribution.for_acceptor_at_feerate(target_feerate, spliceable_balance);
assert!(matches!(result, Err(FeeRateAdjustmentError::FeeBufferInsufficient { .. })));
}

Expand Down Expand Up @@ -2601,8 +2603,8 @@ mod tests {
};

// Balance of 40,000 sats is less than outputs (50,000) + target_fee.
let holder_balance = Amount::from_sat(40_000);
let result = contribution.for_acceptor_at_feerate(target_feerate, holder_balance);
let spliceable_balance = Amount::from_sat(40_000);
let result = contribution.for_acceptor_at_feerate(target_feerate, spliceable_balance);
assert!(matches!(result, Err(FeeRateAdjustmentError::FeeBufferInsufficient { .. })));
}

Expand All @@ -2627,9 +2629,9 @@ mod tests {
};

// Balance of 100,000 sats is more than outputs (50,000) + target_fee.
let holder_balance = Amount::from_sat(100_000);
let spliceable_balance = Amount::from_sat(100_000);
let contribution =
contribution.for_acceptor_at_feerate(target_feerate, holder_balance).unwrap();
contribution.for_acceptor_at_feerate(target_feerate, spliceable_balance).unwrap();
let expected_target_fee =
estimate_transaction_fee(&[], &outputs, None, false, true, target_feerate);
assert_eq!(contribution.estimated_fee, expected_target_fee);
Expand Down Expand Up @@ -2657,8 +2659,9 @@ mod tests {
};

// Balance of 40,000 sats is less than outputs (50,000) + target_fee.
let holder_balance = Amount::from_sat(40_000);
let result = contribution.net_value_for_acceptor_at_feerate(target_feerate, holder_balance);
let spliceable_balance = Amount::from_sat(40_000);
let result =
contribution.net_value_for_acceptor_at_feerate(target_feerate, spliceable_balance);
assert!(matches!(result, Err(FeeRateAdjustmentError::FeeBufferInsufficient { .. })));
}

Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/htlc_reserve_unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2581,7 +2581,7 @@ fn test_0reserve_no_outputs() {
do_test_0reserve_no_outputs_p2a_anchor();
}

fn setup_0reserve_no_outputs_channels<'a, 'b, 'c, 'd>(
pub(crate) fn setup_0reserve_no_outputs_channels<'a, 'b, 'c, 'd>(
nodes: &'a Vec<Node<'b, 'c, 'd>>, channel_value_sat: u64, dust_limit_satoshis: u64,
) -> (ChannelId, Transaction) {
let node_a_id = nodes[0].node.get_our_node_id();
Expand Down
Loading
Loading