Skip to content

Commit 77668a8

Browse files
committed
Add AvailableBalances::next_splice_out_maximum_sat
We previously determined this value by subtracting the htlcs, the anchors, and the commitment transaction fee. This ignored the reserve, as well as the at-least-one-output requirement in zero-reserve channels. This new field now accounts for both of these constraints. It can be seen as the total spliceable balance from the channel.
1 parent c475a51 commit 77668a8

5 files changed

Lines changed: 227 additions & 51 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

lightning/src/ln/channel_state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ impl ChannelDetails {
533533
outbound_capacity_msat: 0,
534534
next_outbound_htlc_limit_msat: 0,
535535
next_outbound_htlc_minimum_msat: u64::MAX,
536+
next_splice_out_maximum_sat: 0,
536537
}
537538
});
538539
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =

lightning/src/ln/channelmanager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8133,6 +8133,7 @@ impl<
81338133
outbound_capacity_msat: 0,
81348134
next_outbound_htlc_limit_msat: 0,
81358135
next_outbound_htlc_minimum_msat: u64::MAX,
8136+
next_splice_out_maximum_sat: 0,
81368137
}
81378138
});
81388139
let is_in_range = (balances.next_outbound_htlc_minimum_msat

0 commit comments

Comments
 (0)