Skip to content

Commit a33a7bd

Browse files
committed
Account for pending splice in claimable balances
There are two states in which a pending splice balance should be considered: 1. The channel has closed with a confirmed holder commitment transaction from a splice that did not become locked. The balance from this transaction is reported. 2. The channel is open and has multiple holder commitment transaction candidates that are valid based on the funding transaction that confirms. We want to report the pending splice balance to users while it has yet to become locked, such that they are able to see their funds have moved from their onchain wallet to the channel. We default to reporting the latest splice/RBF balance via `Balance::claimable_amount_satoshis` to mimic how an onchain wallet would behave when reporting unconfirmed balance, otherwise we report the balance for the confirmed splice transaction.
1 parent b40dca0 commit a33a7bd

File tree

2 files changed

+146
-53
lines changed

2 files changed

+146
-53
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,24 @@ pub enum BalanceSource {
777777
Htlc,
778778
}
779779

780+
/// The claimable balance of a holder commitment transaction that has yet to be broadcast.
781+
#[derive(Clone, Debug, PartialEq, Eq)]
782+
#[cfg_attr(test, derive(PartialOrd, Ord))]
783+
pub struct HolderCommitmentTransactionBalance {
784+
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
785+
/// required to do so.
786+
pub amount_satoshis: u64,
787+
/// The transaction fee we pay for the closing commitment transaction. This amount is not
788+
/// included in the [`HolderCommitmentTransactionBalance::amount_satoshis`] value.
789+
/// This amount includes the sum of dust HTLCs on the commitment transaction, any elided anchors,
790+
/// as well as the sum of msat amounts rounded down from non-dust HTLCs.
791+
///
792+
/// Note that if this channel is inbound (and thus our counterparty pays the commitment
793+
/// transaction fee) this value will be zero. For [`ChannelMonitor`]s created prior to LDK
794+
/// 0.0.124, the channel is always treated as outbound (and thus this value is never zero).
795+
pub transaction_fee_satoshis: u64,
796+
}
797+
780798
/// Details about the balance(s) available for spending once the channel appears on chain.
781799
///
782800
/// See [`ChannelMonitor::get_claimable_balances`] for more details on when these will or will not
@@ -785,21 +803,26 @@ pub enum BalanceSource {
785803
#[cfg_attr(test, derive(PartialOrd, Ord))]
786804
pub enum Balance {
787805
/// The channel is not yet closed (or the commitment or closing transaction has not yet
788-
/// appeared in a block). The given balance is claimable (less on-chain fees) if the channel is
789-
/// force-closed now.
806+
/// appeared in a block).
790807
ClaimableOnChannelClose {
791-
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
792-
/// required to do so.
793-
amount_satoshis: u64,
794-
/// The transaction fee we pay for the closing commitment transaction. This amount is not
795-
/// included in the [`Balance::ClaimableOnChannelClose::amount_satoshis`] value.
796-
/// This amount includes the sum of dust HTLCs on the commitment transaction, any elided anchors,
797-
/// as well as the sum of msat amounts rounded down from non-dust HTLCs.
808+
/// A list of balance candidates based on the latest set of valid holder commitment
809+
/// transactions that can hit the chain. Typically, a channel only has one valid holder
810+
/// commitment transaction that spends the current funding output. As soon as a channel is
811+
/// spliced, an alternative holder commitment transaction exists spending the new funding
812+
/// output. More alternative holder commitment transactions can exist as the splice remains
813+
/// pending and RBF attempts are made.
798814
///
799-
/// Note that if this channel is inbound (and thus our counterparty pays the commitment
800-
/// transaction fee) this value will be zero. For [`ChannelMonitor`]s created prior to LDK
801-
/// 0.0.124, the channel is always treated as outbound (and thus this value is never zero).
802-
transaction_fee_satoshis: u64,
815+
/// The candidates are sorted by the order in which the holder commitment transactions were
816+
/// negotiated. When only one candidate exists, the channel does not have a splice pending.
817+
/// When multiple candidates exist, the last one reflects the balance of the
818+
/// latest splice/RBF attempt, while the first reflects the balance prior to the splice
819+
/// occurring.
820+
balance_candidates: Vec<HolderCommitmentTransactionBalance>,
821+
/// The index within [`Balance::ClaimableOnChannelClose::balance_candidates`] for the
822+
/// balance according to the current onchain state of the channel. This can be helpful when
823+
/// wanting to determine the claimable amount when the holder commitment transaction for the
824+
/// current funding transaction is broadcast and/or confirms.
825+
confirmed_balance_candidate_index: usize,
803826
/// The amount of millisatoshis which has been burned to fees from HTLCs which are outbound
804827
/// from us and are related to a payment which was sent by us. This is the sum of the
805828
/// millisatoshis part of all HTLCs which are otherwise represented by
@@ -821,7 +844,7 @@ pub enum Balance {
821844
/// to us and for which we know the preimage. This is the sum of the millisatoshis part of
822845
/// all HTLCs which would be represented by [`Balance::ContentiousClaimable`] on channel
823846
/// close, but whose current value is included in
824-
/// [`Balance::ClaimableOnChannelClose::amount_satoshis`], as well as any dust HTLCs which
847+
/// [`HolderCommitmentTransactionBalance::amount_satoshis`], as well as any dust HTLCs which
825848
/// would otherwise be represented the same.
826849
///
827850
/// This amount (rounded up to a whole satoshi value) will not be included in the counterparty's
@@ -928,7 +951,18 @@ impl Balance {
928951
#[rustfmt::skip]
929952
pub fn claimable_amount_satoshis(&self) -> u64 {
930953
match self {
931-
Balance::ClaimableOnChannelClose { amount_satoshis, .. }|
954+
Balance::ClaimableOnChannelClose {
955+
balance_candidates, confirmed_balance_candidate_index, ..
956+
} => {
957+
// If we have multiple candidates due to a splice, and one of the splice
958+
// transactions has confirmed, report the corresponding balance. Otherwise, the
959+
// splice is unconfirmed, so report the balance for the latest negotiated attempt.
960+
if *confirmed_balance_candidate_index != 0 {
961+
balance_candidates[*confirmed_balance_candidate_index].amount_satoshis
962+
} else {
963+
balance_candidates.last().map(|balance| balance.amount_satoshis).unwrap_or(0)
964+
}
965+
},
932966
Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }|
933967
Balance::ContentiousClaimable { amount_satoshis, .. }|
934968
Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
@@ -2671,7 +2705,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
26712705
debug_assert!(htlc_input_idx_opt.is_some());
26722706
BitcoinOutPoint::new(*txid, htlc_input_idx_opt.unwrap_or(0))
26732707
} else {
2674-
debug_assert!(!self.channel_type_features().supports_anchors_zero_fee_htlc_tx());
2708+
let funding = get_confirmed_funding_scope!(self);
2709+
debug_assert!(!funding.channel_type_features().supports_anchors_zero_fee_htlc_tx());
26752710
BitcoinOutPoint::new(*txid, 0)
26762711
}
26772712
} else {
@@ -2833,8 +2868,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
28332868
}
28342869

28352870
if let Some(txid) = confirmed_txid {
2871+
let funding_spent = get_confirmed_funding_scope!(us);
28362872
let mut found_commitment_tx = false;
2837-
if let Some(counterparty_tx_htlcs) = us.funding.counterparty_claimable_outpoints.get(&txid) {
2873+
if let Some(counterparty_tx_htlcs) = funding_spent.counterparty_claimable_outpoints.get(&txid) {
28382874
// First look for the to_remote output back to us.
28392875
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
28402876
if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
@@ -2855,7 +2891,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
28552891
// confirmation with the same height or have never met our dust amount.
28562892
}
28572893
}
2858-
if Some(txid) == us.funding.current_counterparty_commitment_txid || Some(txid) == us.funding.prev_counterparty_commitment_txid {
2894+
if Some(txid) == funding_spent.current_counterparty_commitment_txid || Some(txid) == funding_spent.prev_counterparty_commitment_txid {
28592895
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
28602896
} else {
28612897
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
@@ -2898,17 +2934,17 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
28982934
}
28992935
}
29002936
found_commitment_tx = true;
2901-
} else if txid == us.funding.current_holder_commitment_tx.trust().txid() {
2937+
} else if txid == funding_spent.current_holder_commitment_tx.trust().txid() {
29022938
walk_htlcs!(true, false, holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES));
29032939
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
29042940
res.push(Balance::ClaimableAwaitingConfirmations {
2905-
amount_satoshis: us.funding.current_holder_commitment_tx.to_broadcaster_value_sat(),
2941+
amount_satoshis: funding_spent.current_holder_commitment_tx.to_broadcaster_value_sat(),
29062942
confirmation_height: conf_thresh,
29072943
source: BalanceSource::HolderForceClosed,
29082944
});
29092945
}
29102946
found_commitment_tx = true;
2911-
} else if let Some(prev_holder_commitment_tx) = &us.funding.prev_holder_commitment_tx {
2947+
} else if let Some(prev_holder_commitment_tx) = &funding_spent.prev_holder_commitment_tx {
29122948
if txid == prev_holder_commitment_tx.trust().txid() {
29132949
walk_htlcs!(true, false, holder_commitment_htlcs!(us, PREV_WITH_SOURCES).unwrap());
29142950
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
@@ -2927,7 +2963,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29272963
// neither us nor our counterparty misbehaved. At worst we've under-estimated
29282964
// the amount we can claim as we'll punish a misbehaving counterparty.
29292965
res.push(Balance::ClaimableAwaitingConfirmations {
2930-
amount_satoshis: us.funding.current_holder_commitment_tx.to_broadcaster_value_sat(),
2966+
amount_satoshis: funding_spent.current_holder_commitment_tx.to_broadcaster_value_sat(),
29312967
confirmation_height: conf_thresh,
29322968
source: BalanceSource::CoopClose,
29332969
});
@@ -2939,6 +2975,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29392975
let mut outbound_forwarded_htlc_rounded_msat = 0;
29402976
let mut inbound_claiming_htlc_rounded_msat = 0;
29412977
let mut inbound_htlc_rounded_msat = 0;
2978+
// We share the same set of HTLCs across all scopes, so we don't need to check the other
2979+
// scopes as it'd be redundant.
29422980
for (htlc, source) in holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES) {
29432981
let rounded_value_msat = if htlc.transaction_output_index.is_none() {
29442982
htlc.amount_msat
@@ -2980,16 +3018,40 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29803018
}
29813019
}
29823020
}
2983-
let to_self_value_sat = us.funding.current_holder_commitment_tx.to_broadcaster_value_sat();
3021+
let balance_candidates = core::iter::once(&us.funding)
3022+
.chain(us.pending_funding.iter())
3023+
.map(|funding| {
3024+
let to_self_value_sat = funding.current_holder_commitment_tx.to_broadcaster_value_sat();
3025+
// In addition to `commit_tx_fee_sat`, this can also include dust HTLCs, any
3026+
// elided anchors, and the total msat amount rounded down from non-dust HTLCs.
3027+
let transaction_fee_satoshis = if us.holder_pays_commitment_tx_fee.unwrap_or(true) {
3028+
let transaction = &funding.current_holder_commitment_tx.trust().built_transaction().transaction;
3029+
let output_value_sat: u64 = transaction.output.iter().map(|txout| txout.value.to_sat()).sum();
3030+
funding.channel_parameters.channel_value_satoshis - output_value_sat
3031+
} else {
3032+
0
3033+
};
3034+
HolderCommitmentTransactionBalance {
3035+
amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat,
3036+
transaction_fee_satoshis,
3037+
}
3038+
})
3039+
.collect();
3040+
let confirmed_balance_candidate_index = core::iter::once(&us.funding)
3041+
.chain(us.pending_funding.iter())
3042+
.enumerate()
3043+
.find(|(_, funding)| {
3044+
us.alternative_funding_confirmed
3045+
.map(|(funding_txid_confirmed, _)| funding.funding_txid() == funding_txid_confirmed)
3046+
// If `alternative_funding_confirmed` is not set, we can assume the current
3047+
// funding is confirmed.
3048+
.unwrap_or(true)
3049+
})
3050+
.map(|(idx, _)| idx)
3051+
.expect("FundingScope for confirmed alternative funding must exist");
29843052
res.push(Balance::ClaimableOnChannelClose {
2985-
amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat,
2986-
// In addition to `commit_tx_fee_sat`, this can also include dust HTLCs, any elided anchors,
2987-
// and the total msat amount rounded down from non-dust HTLCs
2988-
transaction_fee_satoshis: if us.holder_pays_commitment_tx_fee.unwrap_or(true) {
2989-
let transaction = &us.funding.current_holder_commitment_tx.trust().built_transaction().transaction;
2990-
let output_value_sat: u64 = transaction.output.iter().map(|txout| txout.value.to_sat()).sum();
2991-
us.funding.channel_parameters.channel_value_satoshis - output_value_sat
2992-
} else { 0 },
3053+
balance_candidates,
3054+
confirmed_balance_candidate_index,
29933055
outbound_payment_htlc_rounded_msat,
29943056
outbound_forwarded_htlc_rounded_msat,
29953057
inbound_claiming_htlc_rounded_msat,

0 commit comments

Comments
 (0)