@@ -122,6 +122,8 @@ pub struct AvailableBalances {
122122 pub next_outbound_htlc_limit_msat: u64,
123123 /// The minimum value we can assign to the next outbound HTLC
124124 pub next_outbound_htlc_minimum_msat: u64,
125+ /// The maximum value of the next splice-out
126+ pub next_splice_out_maximum_sat: u64,
125127}
126128
127129#[derive(Debug, Clone, Copy, PartialEq)]
@@ -6767,7 +6769,7 @@ pub(crate) fn get_legacy_default_holder_selected_channel_reserve_satoshis(
67676769///
67686770/// This is used both for outbound and inbound channels and has lower bound
67696771/// of `dust_limit_satoshis`.
6770- fn get_v2_channel_reserve_satoshis(
6772+ pub(crate) fn get_v2_channel_reserve_satoshis(
67716773 channel_value_satoshis: u64, dust_limit_satoshis: u64, is_0reserve: bool,
67726774) -> u64 {
67736775 if is_0reserve {
@@ -12374,17 +12376,16 @@ where
1237412376 .as_ref()
1237512377 .and_then(|pending_splice| pending_splice.contributions.last())
1237612378 {
12377- let holder_balance = self
12378- .get_holder_counterparty_balances_floor_incl_fee(&self.funding)
12379- .map(|(h, _)| h)
12379+ let spliceable_balance = self
12380+ .get_next_splice_out_maximum(&self.funding)
1238012381 .map_err(|e| APIError::ChannelUnavailable {
1238112382 err: format!(
1238212383 "Channel {} cannot be spliced at this time: {}",
1238312384 self.context.channel_id(),
1238412385 e
1238512386 ),
1238612387 })?;
12387- Some(PriorContribution::new(prior.clone(), holder_balance ))
12388+ Some(PriorContribution::new(prior.clone(), spliceable_balance ))
1238812389 } else {
1238912390 None
1239012391 }
@@ -12480,16 +12481,13 @@ where
1248012481 return contribution;
1248112482 }
1248212483
12483- let holder_balance = match self
12484- .get_holder_counterparty_balances_floor_incl_fee(&self.funding)
12485- .map(|(holder, _)| holder)
12486- {
12484+ let spliceable_balance = match self.get_next_splice_out_maximum(&self.funding) {
1248712485 Ok(balance) => balance,
1248812486 Err(_) => return contribution,
1248912487 };
1249012488
1249112489 if let Err(e) =
12492- contribution.net_value_for_initiator_at_feerate(min_rbf_feerate, holder_balance )
12490+ contribution.net_value_for_initiator_at_feerate(min_rbf_feerate, spliceable_balance )
1249312491 {
1249412492 log_info!(
1249512493 logger,
@@ -12510,7 +12508,7 @@ where
1251012508 min_rbf_feerate,
1251112509 );
1251212510 contribution
12513- .for_initiator_at_feerate(min_rbf_feerate, holder_balance )
12511+ .for_initiator_at_feerate(min_rbf_feerate, spliceable_balance )
1251412512 .expect("feerate compatibility already checked")
1251512513 }
1251612514
@@ -12881,9 +12879,8 @@ where
1288112879 fn resolve_queued_contribution<L: Logger>(
1288212880 &self, feerate: FeeRate, logger: &L,
1288312881 ) -> Result<(Option<SignedAmount>, Option<Amount>), ChannelError> {
12884- let holder_balance = self
12885- .get_holder_counterparty_balances_floor_incl_fee(&self.funding)
12886- .map(|(holder, _)| holder)
12882+ let spliceable_balance = self
12883+ .get_next_splice_out_maximum(&self.funding)
1288712884 .map_err(|e| {
1288812885 log_info!(
1288912886 logger,
@@ -12895,9 +12892,9 @@ where
1289512892 })
1289612893 .ok();
1289712894
12898- let net_value = match holder_balance .and_then(|_| self.queued_funding_contribution()) {
12895+ let net_value = match spliceable_balance .and_then(|_| self.queued_funding_contribution()) {
1289912896 Some(c) => {
12900- match c.net_value_for_acceptor_at_feerate(feerate, holder_balance .unwrap()) {
12897+ match c.net_value_for_acceptor_at_feerate(feerate, spliceable_balance .unwrap()) {
1290112898 Ok(net_value) => Some(net_value),
1290212899 Err(FeeRateAdjustmentError::FeeRateTooHigh { .. }) => {
1290312900 return Err(ChannelError::Abort(AbortReason::FeeRateTooHigh));
@@ -12917,7 +12914,7 @@ where
1291712914 None => None,
1291812915 };
1291912916
12920- Ok((net_value, holder_balance ))
12917+ Ok((net_value, spliceable_balance ))
1292112918 }
1292212919
1292312920 pub(crate) fn splice_init<ES: EntropySource, L: Logger>(
@@ -13324,6 +13321,9 @@ where
1332413321 /// of the channel due to the v2 reserve, and the zero-reserve-at-least-one-output
1332513322 /// requirements. Note you cannot simply subtract out the reserve, as splicing funds out
1332613323 /// of the channel changes the reserve the holder must keep in the channel.
13324+ ///
13325+ /// See [`FundedChannel::get_next_splice_out_maximum`] for the maximum value of the next
13326+ /// splice out of the holder's balance.
1332713327 fn get_holder_counterparty_balances_floor_incl_fee(
1332813328 &self, funding: &FundingScope,
1332913329 ) -> Result<(Amount, Amount), String> {
@@ -13394,6 +13394,55 @@ where
1339413394 Ok((holder_balance_floor, counterparty_balance_floor))
1339513395 }
1339613396
13397+ /// Determines the maximum value that the holder can splice out of the channel, accounting
13398+ /// for the updated reserves after said splice. This maximum also makes sure the local
13399+ /// commitment retains at least one output after the splice, which is particularly relevant
13400+ /// for zero-reserve channels.
13401+ fn get_next_splice_out_maximum(&self, funding: &FundingScope) -> Result<Amount, String> {
13402+ let include_counterparty_unknown_htlcs = true;
13403+ // We are not interested in dust exposure
13404+ let dust_exposure_limiting_feerate = None;
13405+
13406+ // When reading the available balances, we take the remote's view of the pending
13407+ // HTLCs, see `tx_builder` for further details
13408+ let (remote_stats, _remote_htlcs) = self
13409+ .context
13410+ .get_next_remote_commitment_stats(
13411+ funding,
13412+ None, // htlc_candidate
13413+ include_counterparty_unknown_htlcs,
13414+ 0,
13415+ self.context.feerate_per_kw,
13416+ dust_exposure_limiting_feerate,
13417+ )
13418+ .map_err(|()| "Balance exhausted on remote commitment")?;
13419+
13420+ let next_splice_out_maximum_sat =
13421+ remote_stats.available_balances.next_splice_out_maximum_sat;
13422+
13423+ #[cfg(debug_assertions)]
13424+ {
13425+ // After this max splice out, validation passes, accounting for the updated reserves
13426+ self.validate_splice_contributions(
13427+ SignedAmount::from_sat(-(next_splice_out_maximum_sat as i64)),
13428+ SignedAmount::ZERO,
13429+ funding.counterparty_funding_pubkey().clone(),
13430+ funding.get_holder_pubkeys().clone(),
13431+ )
13432+ .unwrap();
13433+ // Splice-out an additional satoshi, and validation fails!
13434+ self.validate_splice_contributions(
13435+ SignedAmount::from_sat(-((next_splice_out_maximum_sat + 1) as i64)),
13436+ SignedAmount::ZERO,
13437+ funding.counterparty_funding_pubkey().clone(),
13438+ funding.get_holder_pubkeys().clone(),
13439+ )
13440+ .unwrap_err();
13441+ }
13442+
13443+ Ok(Amount::from_sat(next_splice_out_maximum_sat))
13444+ }
13445+
1339713446 pub fn splice_locked<NS: NodeSigner, L: Logger>(
1339813447 &mut self, msg: &msgs::SpliceLocked, node_signer: &NS, chain_hash: ChainHash,
1339913448 user_config: &UserConfig, block_height: u32, logger: &L,
@@ -13619,6 +13668,9 @@ where
1361913668 next_outbound_htlc_minimum_msat: acc
1362013669 .next_outbound_htlc_minimum_msat
1362113670 .max(e.next_outbound_htlc_minimum_msat),
13671+ next_splice_out_maximum_sat: acc
13672+ .next_splice_out_maximum_sat
13673+ .min(e.next_splice_out_maximum_sat),
1362213674 })
1362313675 })
1362413676 }
0 commit comments