Skip to content

Commit 5adf873

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 5adf873

File tree

2 files changed

+150
-53
lines changed

2 files changed

+150
-53
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 97 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
@@ -915,6 +938,13 @@ pub enum Balance {
915938
impl Balance {
916939
/// The amount claimable, in satoshis.
917940
///
941+
/// When the channel has yet to close, this returns the balance we expect to claim from the
942+
/// channel. This may change throughout the lifetime of the channel due to payments, but also
943+
/// due to splicing. If there's a pending splice, this will return the balance we expect to have
944+
/// assuming the latest negotiated splice confirms. However, if one of the negotiated splice
945+
/// transactions has already confirmed but is not yet locked, this reports the corresponding
946+
/// balance for said splice transaction instead.
947+
///
918948
/// For outbound payments, this excludes the balance from the possible HTLC timeout.
919949
///
920950
/// For forwarded payments, this includes the balance from the possible HTLC timeout as
@@ -928,7 +958,15 @@ impl Balance {
928958
#[rustfmt::skip]
929959
pub fn claimable_amount_satoshis(&self) -> u64 {
930960
match self {
931-
Balance::ClaimableOnChannelClose { amount_satoshis, .. }|
961+
Balance::ClaimableOnChannelClose {
962+
balance_candidates, confirmed_balance_candidate_index, ..
963+
} => {
964+
if *confirmed_balance_candidate_index != 0 {
965+
balance_candidates[*confirmed_balance_candidate_index].amount_satoshis
966+
} else {
967+
balance_candidates.last().map(|balance| balance.amount_satoshis).unwrap_or(0)
968+
}
969+
},
932970
Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }|
933971
Balance::ContentiousClaimable { amount_satoshis, .. }|
934972
Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
@@ -2671,7 +2709,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
26712709
debug_assert!(htlc_input_idx_opt.is_some());
26722710
BitcoinOutPoint::new(*txid, htlc_input_idx_opt.unwrap_or(0))
26732711
} else {
2674-
debug_assert!(!self.channel_type_features().supports_anchors_zero_fee_htlc_tx());
2712+
let funding = get_confirmed_funding_scope!(self);
2713+
debug_assert!(!funding.channel_type_features().supports_anchors_zero_fee_htlc_tx());
26752714
BitcoinOutPoint::new(*txid, 0)
26762715
}
26772716
} else {
@@ -2833,8 +2872,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
28332872
}
28342873

28352874
if let Some(txid) = confirmed_txid {
2875+
let funding_spent = get_confirmed_funding_scope!(us);
28362876
let mut found_commitment_tx = false;
2837-
if let Some(counterparty_tx_htlcs) = us.funding.counterparty_claimable_outpoints.get(&txid) {
2877+
if let Some(counterparty_tx_htlcs) = funding_spent.counterparty_claimable_outpoints.get(&txid) {
28382878
// First look for the to_remote output back to us.
28392879
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
28402880
if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
@@ -2855,7 +2895,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
28552895
// confirmation with the same height or have never met our dust amount.
28562896
}
28572897
}
2858-
if Some(txid) == us.funding.current_counterparty_commitment_txid || Some(txid) == us.funding.prev_counterparty_commitment_txid {
2898+
if Some(txid) == funding_spent.current_counterparty_commitment_txid || Some(txid) == funding_spent.prev_counterparty_commitment_txid {
28592899
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
28602900
} else {
28612901
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
@@ -2898,17 +2938,17 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
28982938
}
28992939
}
29002940
found_commitment_tx = true;
2901-
} else if txid == us.funding.current_holder_commitment_tx.trust().txid() {
2941+
} else if txid == funding_spent.current_holder_commitment_tx.trust().txid() {
29022942
walk_htlcs!(true, false, holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES));
29032943
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
29042944
res.push(Balance::ClaimableAwaitingConfirmations {
2905-
amount_satoshis: us.funding.current_holder_commitment_tx.to_broadcaster_value_sat(),
2945+
amount_satoshis: funding_spent.current_holder_commitment_tx.to_broadcaster_value_sat(),
29062946
confirmation_height: conf_thresh,
29072947
source: BalanceSource::HolderForceClosed,
29082948
});
29092949
}
29102950
found_commitment_tx = true;
2911-
} else if let Some(prev_holder_commitment_tx) = &us.funding.prev_holder_commitment_tx {
2951+
} else if let Some(prev_holder_commitment_tx) = &funding_spent.prev_holder_commitment_tx {
29122952
if txid == prev_holder_commitment_tx.trust().txid() {
29132953
walk_htlcs!(true, false, holder_commitment_htlcs!(us, PREV_WITH_SOURCES).unwrap());
29142954
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
@@ -2927,7 +2967,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29272967
// neither us nor our counterparty misbehaved. At worst we've under-estimated
29282968
// the amount we can claim as we'll punish a misbehaving counterparty.
29292969
res.push(Balance::ClaimableAwaitingConfirmations {
2930-
amount_satoshis: us.funding.current_holder_commitment_tx.to_broadcaster_value_sat(),
2970+
amount_satoshis: funding_spent.current_holder_commitment_tx.to_broadcaster_value_sat(),
29312971
confirmation_height: conf_thresh,
29322972
source: BalanceSource::CoopClose,
29332973
});
@@ -2939,6 +2979,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29392979
let mut outbound_forwarded_htlc_rounded_msat = 0;
29402980
let mut inbound_claiming_htlc_rounded_msat = 0;
29412981
let mut inbound_htlc_rounded_msat = 0;
2982+
// We share the same set of HTLCs across all scopes, so we don't need to check the other
2983+
// scopes as it'd be redundant.
29422984
for (htlc, source) in holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES) {
29432985
let rounded_value_msat = if htlc.transaction_output_index.is_none() {
29442986
htlc.amount_msat
@@ -2980,16 +3022,40 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29803022
}
29813023
}
29823024
}
2983-
let to_self_value_sat = us.funding.current_holder_commitment_tx.to_broadcaster_value_sat();
3025+
let balance_candidates = core::iter::once(&us.funding)
3026+
.chain(us.pending_funding.iter())
3027+
.map(|funding| {
3028+
let to_self_value_sat = funding.current_holder_commitment_tx.to_broadcaster_value_sat();
3029+
// In addition to `commit_tx_fee_sat`, this can also include dust HTLCs, any
3030+
// elided anchors, and the total msat amount rounded down from non-dust HTLCs.
3031+
let transaction_fee_satoshis = if us.holder_pays_commitment_tx_fee.unwrap_or(true) {
3032+
let transaction = &funding.current_holder_commitment_tx.trust().built_transaction().transaction;
3033+
let output_value_sat: u64 = transaction.output.iter().map(|txout| txout.value.to_sat()).sum();
3034+
funding.channel_parameters.channel_value_satoshis - output_value_sat
3035+
} else {
3036+
0
3037+
};
3038+
HolderCommitmentTransactionBalance {
3039+
amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat,
3040+
transaction_fee_satoshis,
3041+
}
3042+
})
3043+
.collect();
3044+
let confirmed_balance_candidate_index = core::iter::once(&us.funding)
3045+
.chain(us.pending_funding.iter())
3046+
.enumerate()
3047+
.find(|(_, funding)| {
3048+
us.alternative_funding_confirmed
3049+
.map(|(funding_txid_confirmed, _)| funding.funding_txid() == funding_txid_confirmed)
3050+
// If `alternative_funding_confirmed` is not set, we can assume the current
3051+
// funding is confirmed.
3052+
.unwrap_or(true)
3053+
})
3054+
.map(|(idx, _)| idx)
3055+
.expect("We must have one FundingScope that is confirmed");
29843056
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 },
3057+
balance_candidates,
3058+
confirmed_balance_candidate_index,
29933059
outbound_payment_htlc_rounded_msat,
29943060
outbound_forwarded_htlc_rounded_msat,
29953061
inbound_claiming_htlc_rounded_msat,

0 commit comments

Comments
 (0)