Account for pending splice in claimable balances
What changed, and why it matters
This commit changes how LDK reports a user's spendable balance when a channel splice is in progress. Previously, the accounting only looked at the original funding transaction. Now it also considers alternative 'candidate' commitment transactions tied to pending splices or RBF fee bumps, and reports the balance that matches whichever splice transaction has actually confirmed on-chain. This is primarily a correctness and user-visibility fix; it does not by itself introduce a remote exploit, but wrong balance reporting during a splice could mislead wallets or downstream software about how much money is available.
Review downstream consumers of get_claimable_balances and claimable_amount_satoshis to ensure they handle the new multi-candidate shape correctly, especially during pending splices. Treat this as a bug-fix/correctness improvement rather than an emergency vulnerability patch, but verify that no wallet logic relies on the old single-value assumption in a way that could now be unsafe.
Security signals we found
Balance-reporting correctness during pending channel splices
Use of confirmed funding scope rather than always the original funding scope when scanning commitment transactions
New public struct HolderCommitmentTransactionBalance exposes per-candidate fee and amount data
Behavior change in claimable_amount_satoshis: may now report latest splice/RBF candidate instead of original channel balance
No explicit security advisory, CVE, or researcher attribution in commit or supplied references
Evidence from the diff
The patch refactors Balance::ClaimableOnChannelClose to carry a Vec
Changed components
lightning/src/chain/channelmonitor.rsChannelMonitor::get_claimable_balancesBalance::ClaimableOnChannelCloseHolderCommitmentTransactionBalanceSplice/RBF funding-scope handlingInspect captured patch +150 / −53
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index ee36c40..d8268af 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -777,6 +777,24 @@ pub enum BalanceSource {
Htlc,
}
+/// The claimable balance of a holder commitment transaction that has yet to be broadcast.
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[cfg_attr(test, derive(PartialOrd, Ord))]
+pub struct HolderCommitmentTransactionBalance {
+ /// The amount available to claim, in satoshis, excluding the on-chain fees which will be
+ /// required to do so.
+ pub amount_satoshis: u64,
+ /// The transaction fee we pay for the closing commitment transaction. This amount is not
+ /// included in the [`HolderCommitmentTransactionBalance::amount_satoshis`] value.
+ /// This amount includes the sum of dust HTLCs on the commitment transaction, any elided anchors,
+ /// as well as the sum of msat amounts rounded down from non-dust HTLCs.
+ ///
+ /// Note that if this channel is inbound (and thus our counterparty pays the commitment
+ /// transaction fee) this value will be zero. For [`ChannelMonitor`]s created prior to LDK
+ /// 0.0.124, the channel is always treated as outbound (and thus this value is never zero).
+ pub transaction_fee_satoshis: u64,
+}
+
/// Details about the balance(s) available for spending once the channel appears on chain.
///
/// See [`ChannelMonitor::get_claimable_balances`] for more details on when these will or will not
@@ -785,21 +803,26 @@ pub enum BalanceSource {
#[cfg_attr(test, derive(PartialOrd, Ord))]
pub enum Balance {
/// The channel is not yet closed (or the commitment or closing transaction has not yet
- /// appeared in a block). The given balance is claimable (less on-chain fees) if the channel is
- /// force-closed now.
+ /// appeared in a block).
ClaimableOnChannelClose {
- /// The amount available to claim, in satoshis, excluding the on-chain fees which will be
- /// required to do so.
- amount_satoshis: u64,
- /// The transaction fee we pay for the closing commitment transaction. This amount is not
- /// included in the [`Balance::ClaimableOnChannelClose::amount_satoshis`] value.
- /// This amount includes the sum of dust HTLCs on the commitment transaction, any elided anchors,
- /// as well as the sum of msat amounts rounded down from non-dust HTLCs.
+ /// A list of balance candidates based on the latest set of valid holder commitment
+ /// transactions that can hit the chain. Typically, a channel only has one valid holder
+ /// commitment transaction that spends the current funding output. As soon as a channel is
+ /// spliced, an alternative holder commitment transaction exists spending the new funding
+ /// output. More alternative holder commitment transactions can exist as the splice remains
+ /// pending and RBF attempts are made.
///
- /// Note that if this channel is inbound (and thus our counterparty pays the commitment
- /// transaction fee) this value will be zero. For [`ChannelMonitor`]s created prior to LDK
- /// 0.0.124, the channel is always treated as outbound (and thus this value is never zero).
- transaction_fee_satoshis: u64,
+ /// The candidates are sorted by the order in which the holder commitment transactions were
+ /// negotiated. When only one candidate exists, the channel does not have a splice pending.
+ /// When multiple candidates exist, the last one reflects the balance of the
+ /// latest splice/RBF attempt, while the first reflects the balance prior to the splice
+ /// occurring.
+ balance_candidates: Vec<HolderCommitmentTransactionBalance>,
+ /// The index within [`Balance::ClaimableOnChannelClose::balance_candidates`] for the
+ /// balance according to the current onchain state of the channel. This can be helpful when
+ /// wanting to determine the claimable amount when the holder commitment transaction for the
+ /// current funding transaction is broadcast and/or confirms.
+ confirmed_balance_candidate_index: usize,
/// The amount of millisatoshis which has been burned to fees from HTLCs which are outbound
/// from us and are related to a payment which was sent by us. This is the sum of the
/// millisatoshis part of all HTLCs which are otherwise represented by
@@ -821,7 +844,7 @@ pub enum Balance {
/// to us and for which we know the preimage. This is the sum of the millisatoshis part of
/// all HTLCs which would be represented by [`Balance::ContentiousClaimable`] on channel
/// close, but whose current value is included in
- /// [`Balance::ClaimableOnChannelClose::amount_satoshis`], as well as any dust HTLCs which
+ /// [`HolderCommitmentTransactionBalance::amount_satoshis`], as well as any dust HTLCs which
/// would otherwise be represented the same.
///
/// This amount (rounded up to a whole satoshi value) will not be included in the counterparty's
@@ -915,6 +938,13 @@ pub enum Balance {
impl Balance {
/// The amount claimable, in satoshis.
///
+ /// When the channel has yet to close, this returns the balance we expect to claim from the
+ /// channel. This may change throughout the lifetime of the channel due to payments, but also
+ /// due to splicing. If there's a pending splice, this will return the balance we expect to have
+ /// assuming the latest negotiated splice confirms. However, if one of the negotiated splice
+ /// transactions has already confirmed but is not yet locked, this reports the corresponding
+ /// balance for said splice transaction instead.
+ ///
/// For outbound payments, this excludes the balance from the possible HTLC timeout.
///
/// For forwarded payments, this includes the balance from the possible HTLC timeout as
@@ -928,7 +958,15 @@ impl Balance {
#[rustfmt::skip]
pub fn claimable_amount_satoshis(&self) -> u64 {
match self {
- Balance::ClaimableOnChannelClose { amount_satoshis, .. }|
+ Balance::ClaimableOnChannelClose {
+ balance_candidates, confirmed_balance_candidate_index, ..
+ } => {
+ if *confirmed_balance_candidate_index != 0 {
+ balance_candidates[*confirmed_balance_candidate_index].amount_satoshis
+ } else {
+ balance_candidates.last().map(|balance| balance.amount_satoshis).unwrap_or(0)
+ }
+ },
Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }|
Balance::ContentiousClaimable { amount_satoshis, .. }|
Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
@@ -2671,7 +2709,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
debug_assert!(htlc_input_idx_opt.is_some());
BitcoinOutPoint::new(*txid, htlc_input_idx_opt.unwrap_or(0))
} else {
- debug_assert!(!self.channel_type_features().supports_anchors_zero_fee_htlc_tx());
+ let funding = get_confirmed_funding_scope!(self);
+ debug_assert!(!funding.channel_type_features().supports_anchors_zero_fee_htlc_tx());
BitcoinOutPoint::new(*txid, 0)
}
} else {
@@ -2833,8 +2872,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}
if let Some(txid) = confirmed_txid {
+ let funding_spent = get_confirmed_funding_scope!(us);
let mut found_commitment_tx = false;
- if let Some(counterparty_tx_htlcs) = us.funding.counterparty_claimable_outpoints.get(&txid) {
+ if let Some(counterparty_tx_htlcs) = funding_spent.counterparty_claimable_outpoints.get(&txid) {
// First look for the to_remote output back to us.
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
@@ -2855,7 +2895,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
// confirmation with the same height or have never met our dust amount.
}
}
- if Some(txid) == us.funding.current_counterparty_commitment_txid || Some(txid) == us.funding.prev_counterparty_commitment_txid {
+ if Some(txid) == funding_spent.current_counterparty_commitment_txid || Some(txid) == funding_spent.prev_counterparty_commitment_txid {
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
} else {
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> {
}
}
found_commitment_tx = true;
- } else if txid == us.funding.current_holder_commitment_tx.trust().txid() {
+ } else if txid == funding_spent.current_holder_commitment_tx.trust().txid() {
walk_htlcs!(true, false, holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES));
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
res.push(Balance::ClaimableAwaitingConfirmations {
- amount_satoshis: us.funding.current_holder_commitment_tx.to_broadcaster_value_sat(),
+ amount_satoshis: funding_spent.current_holder_commitment_tx.to_broadcaster_value_sat(),
confirmation_height: conf_thresh,
source: BalanceSource::HolderForceClosed,
});
}
found_commitment_tx = true;
- } else if let Some(prev_holder_commitment_tx) = &us.funding.prev_holder_commitment_tx {
+ } else if let Some(prev_holder_commitment_tx) = &funding_spent.prev_holder_commitment_tx {
if txid == prev_holder_commitment_tx.trust().txid() {
walk_htlcs!(true, false, holder_commitment_htlcs!(us, PREV_WITH_SOURCES).unwrap());
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
@@ -2927,7 +2967,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
// neither us nor our counterparty misbehaved. At worst we've under-estimated
// the amount we can claim as we'll punish a misbehaving counterparty.
res.push(Balance::ClaimableAwaitingConfirmations {
- amount_satoshis: us.funding.current_holder_commitment_tx.to_broadcaster_value_sat(),
+ amount_satoshis: funding_spent.current_holder_commitment_tx.to_broadcaster_value_sat(),
confirmation_height: conf_thresh,
source: BalanceSource::CoopClose,
});
@@ -2939,6 +2979,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
let mut outbound_forwarded_htlc_rounded_msat = 0;
let mut inbound_claiming_htlc_rounded_msat = 0;
let mut inbound_htlc_rounded_msat = 0;
+ // We share the same set of HTLCs across all scopes, so we don't need to check the other
+ // scopes as it'd be redundant.
for (htlc, source) in holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES) {
let rounded_value_msat = if htlc.transaction_output_index.is_none() {
htlc.amount_msat
@@ -2980,16 +3022,40 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}
}
}
- let to_self_value_sat = us.funding.current_holder_commitment_tx.to_broadcaster_value_sat();
+ let balance_candidates = core::iter::once(&us.funding)
+ .chain(us.pending_funding.iter())
+ .map(|funding| {
+ let to_self_value_sat = funding.current_holder_commitment_tx.to_broadcaster_value_sat();
+ // In addition to `commit_tx_fee_sat`, this can also include dust HTLCs, any
+ // elided anchors, and the total msat amount rounded down from non-dust HTLCs.
+ let transaction_fee_satoshis = if us.holder_pays_commitment_tx_fee.unwrap_or(true) {
+ let transaction = &funding.current_holder_commitment_tx.trust().built_transaction().transaction;
+ let output_value_sat: u64 = transaction.output.iter().map(|txout| txout.value.to_sat()).sum();
+ funding.channel_parameters.channel_value_satoshis - output_value_sat
+ } else {
+ 0
+ };
+ HolderCommitmentTransactionBalance {
+ amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat,
+ transaction_fee_satoshis,
+ }
+ })
+ .collect();
+ let confirmed_balance_candidate_index = core::iter::once(&us.funding)
+ .chain(us.pending_funding.iter())
+ .enumerate()
+ .find(|(_, funding)| {
+ us.alternative_funding_confirmed
+ .map(|(funding_txid_confirmed, _)| funding.funding_txid() == funding_txid_confirmed)
+ // If `alternative_funding_confirmed` is not set, we can assume the current
+ // funding is confirmed.
+ .unwrap_or(true)
+ })
+ .map(|(idx, _)| idx)
+ .expect("We must have one FundingScope that is confirmed");
res.push(Balance::ClaimableOnChannelClose {
- amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat,
- // In addition to `commit_tx_fee_sat`, this can also include dust HTLCs, any elided anchors,
- // and the total msat amount rounded down from non-dust HTLCs
- transaction_fee_satoshis: if us.holder_pays_commitment_tx_fee.unwrap_or(true) {
- let transaction = &us.funding.current_holder_commitment_tx.trust().built_transaction().transaction;
- let output_value_sat: u64 = transaction.output.iter().map(|txout| txout.value.to_sat()).sum();
- us.funding.channel_parameters.channel_value_satoshis - output_value_sat
- } else { 0 },
+ balance_candidates,
+ confirmed_balance_candidate_index,
outbound_payment_htlc_rounded_msat,
outbound_forwarded_htlc_rounded_msat,
inbound_claiming_htlc_rounded_msat,
diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs
index 8e08e5c..64ec63f 100644
--- a/lightning/src/ln/monitor_tests.rs
+++ b/lightning/src/ln/monitor_tests.rs
@@ -13,7 +13,7 @@
use crate::sign::{ecdsa::EcdsaChannelSigner, OutputSpender, SignerProvider, SpendableOutputDescriptor};
use crate::chain::Watch;
-use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS,LATENCY_GRACE_PERIOD_BLOCKS, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, Balance, BalanceSource, ChannelMonitorUpdateStep};
+use crate::chain::channelmonitor::{Balance, BalanceSource, ChannelMonitorUpdateStep, HolderCommitmentTransactionBalance, ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::chain::transaction::OutPoint;
use crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
use crate::events::bump_transaction::{BumpTransactionEvent};
@@ -334,8 +334,11 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) {
let commitment_tx_fee = chan_feerate * chan_utils::commitment_tx_base_weight(&channel_type_features) / 1000;
let anchor_outputs_value = if anchors { channel::ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 };
assert_eq!(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 1_000_000 - 1_000 - commitment_tx_fee - anchor_outputs_value,
- transaction_fee_satoshis: commitment_tx_fee,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 1_000_000 - 1_000 - commitment_tx_fee - anchor_outputs_value,
+ transaction_fee_satoshis: commitment_tx_fee,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 0,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
@@ -343,7 +346,11 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) {
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances());
assert_eq!(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 1_000, transaction_fee_satoshis: 0,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 1_000,
+ transaction_fee_satoshis: 0,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 0,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
@@ -530,9 +537,12 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
let anchor_outputs_value = if anchors { 2 * channel::ANCHOR_OUTPUT_VALUE_SATOSHI } else { 0 };
let amount_satoshis = 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - commitment_tx_fee - anchor_outputs_value - 1; /* msat amount that is burned to fees */
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis,
- // In addition to `commitment_tx_fee`, this also includes the dust HTLC, and the total msat amount rounded down from non-dust HTLCs
- transaction_fee_satoshis: 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis,
+ // In addition to `commitment_tx_fee`, this also includes the dust HTLC, and the total msat amount rounded down from non-dust HTLCs
+ transaction_fee_satoshis: 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 3300,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
@@ -540,8 +550,11 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
}, sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 1_000,
- transaction_fee_satoshis: 0,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 1_000,
+ transaction_fee_satoshis: 0,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 0,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
@@ -594,9 +607,12 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
anchor_outputs_value - // The anchor outputs value in satoshis
1; // The rounded up msat part of the one HTLC
let mut a_expected_balances = vec![Balance::ClaimableOnChannelClose {
- amount_satoshis, // Channel funding value in satoshis
- // In addition to `commitment_tx_fee`, this also includes the dust HTLC, and the total msat amount rounded down from non-dust HTLCs
- transaction_fee_satoshis: 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis, // Channel funding value in satoshis
+ // In addition to `commitment_tx_fee`, this also includes the dust HTLC, and the total msat amount rounded down from non-dust HTLCs
+ transaction_fee_satoshis: 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 3000 + if prev_commitment_tx {
200 /* 1 to-be-failed HTLC */ } else { 300 /* 2 HTLCs */ },
outbound_forwarded_htlc_rounded_msat: 0,
@@ -609,8 +625,11 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
assert_eq!(sorted_vec(a_expected_balances),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances()));
assert_eq!(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 1_000 + 3_000 + 4_000,
- transaction_fee_satoshis: 0,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 1_000 + 3_000 + 4_000,
+ transaction_fee_satoshis: 0,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 0,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 3000 + if prev_commitment_tx {
@@ -1126,8 +1145,11 @@ fn test_no_preimage_inbound_htlc_balances() {
let commitment_tx_fee = chan_feerate *
(chan_utils::commitment_tx_base_weight(&channel_type_features) + 2 * chan_utils::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000;
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 1_000_000 - 500_000 - 10_000 - commitment_tx_fee,
- transaction_fee_satoshis: commitment_tx_fee,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 1_000_000 - 500_000 - 10_000 - commitment_tx_fee,
+ transaction_fee_satoshis: commitment_tx_fee,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 0,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
@@ -1136,8 +1158,11 @@ fn test_no_preimage_inbound_htlc_balances() {
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 500_000 - 20_000,
- transaction_fee_satoshis: 0,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 500_000 - 20_000,
+ transaction_fee_satoshis: 0,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 0,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
@@ -1430,8 +1455,11 @@ fn do_test_revoked_counterparty_commitment_balances(anchors: bool, confirm_htlc_
assert_eq!(
sorted_vec(vec![
Balance::ClaimableOnChannelClose {
- amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000 - 1 /* rounded up msat parts of HTLCs */,
- transaction_fee_satoshis: 0,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000 - 1 /* rounded up msat parts of HTLCs */,
+ transaction_fee_satoshis: 0,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 3200,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 100,
@@ -1967,8 +1995,11 @@ fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) {
let _a_htlc_msgs = get_htlc_update_msgs!(&nodes[0], nodes[1].node.get_our_node_id());
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- amount_satoshis: 100_000 - 4_000 - 3_000 - 1 /* rounded up msat parts of HTLCs */,
- transaction_fee_satoshis: 0,
+ balance_candidates: vec![HolderCommitmentTransactionBalance {
+ amount_satoshis: 100_000 - 4_000 - 3_000 - 1 /* rounded up msat parts of HTLCs */,
+ transaction_fee_satoshis: 0,
+ }],
+ confirmed_balance_candidate_index: 0,
outbound_payment_htlc_rounded_msat: 100,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
Why this scored 32/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.