From 09de3b7647e3647e2a86cb3b53385a9b1fb1d5ba Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 8 Dec 2025 21:27:07 +0000 Subject: [PATCH 1/7] Expose our "offchain balance" in `Balance`s `Balance` seeks to expose our balance accurate based on what we would get, less fees, if we were to force-close right now. This is great, but for an end-user wallet its not really what you want to display as the "balance" of the wallet. Instead, you want to give a balance which matches the sum of HTLCs received over time, which is only possible when balance information is avilable which ignores things like dust, anchors, fees, and reserve values. Here we provide such a balance - a new "offchain balance" in `HolderCommitmentTransactionBalance`. --- lightning/src/chain/channelmonitor.rs | 33 ++++++++-- lightning/src/ln/chan_utils.rs | 67 ++++++++++++++++++++- lightning/src/ln/htlc_reserve_unit_tests.rs | 2 +- lightning/src/ln/monitor_tests.rs | 22 +++++-- lightning/src/ln/update_fee_tests.rs | 4 +- lightning/src/sign/tx_builder.rs | 7 ++- 6 files changed, 118 insertions(+), 17 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 10e5049682e..07fde4fdda6 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1,4 +1,4 @@ -// This file is Copyright its original authors, visible in version control +// This file is Copyright its original authors, visible in version controlXXX // history. // // This file is licensed under the Apache License, Version 2.0 , /// 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, @@ -3117,6 +3129,8 @@ impl ChannelMonitor { .chain(us.pending_funding.iter()) .map(|funding| { let to_self_value_sat = funding.current_holder_commitment_tx.to_broadcaster_value_sat(); + let to_self_offchain_msat = + funding.current_holder_commitment_tx.to_broadcaster_value_offchain_msat(); // 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) { @@ -3128,6 +3142,8 @@ impl ChannelMonitor { }; HolderCommitmentTransactionBalance { amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat, + amount_offchain_satoshis: + to_self_offchain_msat.map(|v| v / 1_000 + claimable_inbound_htlc_value_sat), transaction_fee_satoshis, } }) @@ -4556,16 +4572,23 @@ impl ChannelMonitorImpl { }) } - #[rustfmt::skip] fn build_counterparty_commitment_tx( &self, channel_parameters: &ChannelTransactionParameters, commitment_number: u64, their_per_commitment_point: &PublicKey, to_broadcaster_value: u64, to_countersignatory_value: u64, feerate_per_kw: u32, - nondust_htlcs: Vec + nondust_htlcs: Vec, ) -> CommitmentTransaction { let channel_parameters = &channel_parameters.as_counterparty_broadcastable(); - CommitmentTransaction::new(commitment_number, their_per_commitment_point, - to_broadcaster_value, to_countersignatory_value, feerate_per_kw, nondust_htlcs, channel_parameters, &self.onchain_tx_handler.secp_ctx) + CommitmentTransaction::new_without_broadcaster_value( + commitment_number, + their_per_commitment_point, + to_broadcaster_value, + to_countersignatory_value, + feerate_per_kw, + nondust_htlcs, + channel_parameters, + &self.onchain_tx_handler.secp_ctx, + ) } #[rustfmt::skip] diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 431fdd2859c..aafafbd4bff 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1375,7 +1375,9 @@ impl HolderCommitmentTransaction { for _ in 0..nondust_htlcs.len() { counterparty_htlc_sigs.push(dummy_sig); } - let inner = CommitmentTransaction::new(0, &dummy_key, 0, 0, 0, nondust_htlcs, &channel_parameters.as_counterparty_broadcastable(), &secp_ctx); + let inner = CommitmentTransaction::new_without_broadcaster_value( + 0, &dummy_key, 0, 0, 0, nondust_htlcs, &channel_parameters.as_counterparty_broadcastable(), &secp_ctx + ); HolderCommitmentTransaction { inner, counterparty_sig: dummy_sig, @@ -1606,6 +1608,7 @@ impl<'a> TrustedClosingTransaction<'a> { #[derive(Clone, Debug)] pub struct CommitmentTransaction { commitment_number: u64, + to_broadcaster_value_offchain_msat: Option, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, to_broadcaster_delay: Option, // Added in 0.0.117 @@ -1626,6 +1629,7 @@ impl PartialEq for CommitmentTransaction { #[rustfmt::skip] fn eq(&self, o: &Self) -> bool { let eq = self.commitment_number == o.commitment_number && + self.to_broadcaster_value_offchain_msat == o.to_broadcaster_value_offchain_msat && self.to_broadcaster_value_sat == o.to_broadcaster_value_sat && self.to_countersignatory_value_sat == o.to_countersignatory_value_sat && self.feerate_per_kw == o.feerate_per_kw && @@ -1655,6 +1659,7 @@ impl Writeable for CommitmentTransaction { (12, self.nondust_htlcs, required_vec), (14, legacy_deserialization_prevention_marker, option), (15, self.channel_type_features, required), + (17, self.to_broadcaster_value_offchain_msat, option), }); Ok(()) } @@ -1674,6 +1679,7 @@ impl Readable for CommitmentTransaction { (12, nondust_htlcs, required_vec), (14, _legacy_deserialization_prevention_marker, (option, explicit_type: ())), (15, channel_type_features, option), + (17, to_broadcaster_value_offchain_msat, option), }); let mut additional_features = ChannelTypeFeatures::empty(); @@ -1683,6 +1689,7 @@ impl Readable for CommitmentTransaction { Ok(Self { commitment_number: commitment_number.0.unwrap(), to_broadcaster_value_sat: to_broadcaster_value_sat.0.unwrap(), + to_broadcaster_value_offchain_msat, to_countersignatory_value_sat: to_countersignatory_value_sat.0.unwrap(), to_broadcaster_delay, feerate_per_kw: feerate_per_kw.0.unwrap(), @@ -1700,8 +1707,55 @@ impl CommitmentTransaction { /// All HTLCs MUST be above the dust limit for the channel. /// The broadcaster and countersignatory amounts MUST be either 0 or above dust. If the amount /// is 0, the corresponding output will be omitted from the transaction. + pub fn new( + commitment_number: u64, per_commitment_point: &PublicKey, to_broadcaster_value_sat: u64, + to_broadcaster_value_offchain_msat: u64, to_countersignatory_value_sat: u64, + feerate_per_kw: u32, nondust_htlcs: Vec, + channel_parameters: &DirectedChannelTransactionParameters, + secp_ctx: &Secp256k1, + ) -> CommitmentTransaction { + debug_assert!(to_broadcaster_value_sat * 1000 <= to_broadcaster_value_offchain_msat); + Self::build( + commitment_number, + per_commitment_point, + to_broadcaster_value_sat, + Some(to_broadcaster_value_offchain_msat), + to_countersignatory_value_sat, + feerate_per_kw, + nondust_htlcs, + channel_parameters, + secp_ctx, + ) + } + + pub(crate) fn new_without_broadcaster_value( + commitment_number: u64, per_commitment_point: &PublicKey, to_broadcaster_value_sat: u64, + to_countersignatory_value_sat: u64, feerate_per_kw: u32, + nondust_htlcs: Vec, + channel_parameters: &DirectedChannelTransactionParameters, + secp_ctx: &Secp256k1, + ) -> CommitmentTransaction { + Self::build( + commitment_number, + per_commitment_point, + to_broadcaster_value_sat, + None, + to_countersignatory_value_sat, + feerate_per_kw, + nondust_htlcs, + channel_parameters, + secp_ctx, + ) + } + #[rustfmt::skip] - pub fn new(commitment_number: u64, per_commitment_point: &PublicKey, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, feerate_per_kw: u32, mut nondust_htlcs: Vec, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1) -> CommitmentTransaction { + fn build( + commitment_number: u64, per_commitment_point: &PublicKey, to_broadcaster_value_sat: u64, + to_broadcaster_value_offchain_msat: Option, to_countersignatory_value_sat: u64, + feerate_per_kw: u32, mut nondust_htlcs: Vec, + channel_parameters: &DirectedChannelTransactionParameters, + secp_ctx: &Secp256k1, + ) -> CommitmentTransaction { let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat); let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); let keys = TxCreationKeys::from_channel_static_keys(per_commitment_point, channel_parameters.broadcaster_pubkeys(), channel_parameters.countersignatory_pubkeys(), secp_ctx); @@ -1717,6 +1771,7 @@ impl CommitmentTransaction { CommitmentTransaction { commitment_number, to_broadcaster_value_sat, + to_broadcaster_value_offchain_msat, to_countersignatory_value_sat, to_broadcaster_delay: Some(channel_parameters.contest_delay()), feerate_per_kw, @@ -2034,6 +2089,12 @@ impl CommitmentTransaction { self.keys.per_commitment_point } + /// The value which is owed the broadcaster (excluding HTLCs) before reductions due to dust + /// limits, being rounded down to the nearest sat, reserve value(s). + pub fn to_broadcaster_value_offchain_msat(&self) -> Option { + self.to_broadcaster_value_offchain_msat + } + /// The value to be sent to the broadcaster pub fn to_broadcaster_value_sat(&self) -> u64 { self.to_broadcaster_value_sat.to_sat() @@ -2324,7 +2385,7 @@ mod tests { #[rustfmt::skip] fn build(&self, to_broadcaster_sats: u64, to_countersignatory_sats: u64, nondust_htlcs: Vec) -> CommitmentTransaction { - CommitmentTransaction::new( + CommitmentTransaction::new_without_broadcaster_value( self.commitment_number, &self.per_commitment_point, to_broadcaster_sats, to_countersignatory_sats, self.feerate_per_kw, nondust_htlcs, &self.channel_parameters.as_holder_broadcastable(), &self.secp_ctx ) diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index 86c95721d47..cfa839b9673 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -2346,7 +2346,7 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); let chan_signer = channel.as_funded().unwrap().get_signer(); - let commitment_tx = CommitmentTransaction::new( + let commitment_tx = CommitmentTransaction::new_without_broadcaster_value( commitment_number, &remote_point, node_1_balance_sat, diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 34064ebb484..24c77a77fbf 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -338,6 +338,7 @@ fn do_chanmon_claim_value_coop_close(keyed_anchors: bool, p2a_anchor: bool) { assert_eq!(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 1_000_000 - 1_000 - commitment_tx_fee - anchor_outputs_value, + amount_offchain_satoshis: Some(1_000_000 - 1_000), transaction_fee_satoshis: commitment_tx_fee, }], confirmed_balance_candidate_index: 0, @@ -350,6 +351,7 @@ fn do_chanmon_claim_value_coop_close(keyed_anchors: bool, p2a_anchor: bool) { assert_eq!(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 1_000, + amount_offchain_satoshis: Some(1_000), transaction_fee_satoshis: 0, }], confirmed_balance_candidate_index: 0, @@ -537,10 +539,12 @@ fn do_test_claim_value_force_close(keyed_anchors: bool, p2a_anchor: bool, prev_c let commitment_tx_fee = chan_feerate as u64 * (chan_utils::commitment_tx_base_weight(&channel_type_features) + 2 * chan_utils::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000; let anchor_outputs_value = if keyed_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 */ + let amount_offchain_satoshis = 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - 1 /* msat amount that is burned to fees */; + let amount_satoshis = amount_offchain_satoshis - commitment_tx_fee - anchor_outputs_value; assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis, + amount_offchain_satoshis: Some(amount_offchain_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: if p2a_anchor { 0 } else { 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value }, }], @@ -554,6 +558,7 @@ fn do_test_claim_value_force_close(keyed_anchors: bool, p2a_anchor: bool, prev_c assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 1_000, + amount_offchain_satoshis: Some(1_000), transaction_fee_satoshis: 0, }], confirmed_balance_candidate_index: 0, @@ -600,17 +605,19 @@ fn do_test_claim_value_force_close(keyed_anchors: bool, p2a_anchor: bool, prev_c let commitment_tx_fee = chan_feerate as u64 * (chan_utils::commitment_tx_base_weight(&channel_type_features) + if prev_commitment_tx { 1 } else { 2 } * chan_utils::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000; - let amount_satoshis = 1_000_000 - // Channel funding value in satoshis + let amount_offchain_satoshis = 1_000_000 - // Channel funding value in satoshis 4_000 - // The to-be-failed HTLC value in satoshis 3_000 - // The claimed HTLC value in satoshis 1_000 - // The push_msat value in satoshis 3 - // The dust HTLC value in satoshis - commitment_tx_fee - // The commitment transaction fee with two HTLC outputs - anchor_outputs_value - // The anchor outputs value in satoshis 1; // The rounded up msat part of the one HTLC + let amount_satoshis = amount_offchain_satoshis - + commitment_tx_fee - // The commitment transaction fee with two HTLC outputs + anchor_outputs_value; // The anchor outputs value in satoshis let mut a_expected_balances = vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { - amount_satoshis, // Channel funding value in satoshis + amount_satoshis, + amount_offchain_satoshis: Some(amount_offchain_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: if p2a_anchor { 0 } else { 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value }, }], @@ -629,6 +636,7 @@ fn do_test_claim_value_force_close(keyed_anchors: bool, p2a_anchor: bool, prev_c assert_eq!(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 1_000 + 3_000 + 4_000, + amount_offchain_satoshis: Some(1_000 + 3_000 + 4_000), transaction_fee_satoshis: 0, }], confirmed_balance_candidate_index: 0, @@ -1168,6 +1176,7 @@ fn test_no_preimage_inbound_htlc_balances() { assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 1_000_000 - 500_000 - 10_000 - commitment_tx_fee, + amount_offchain_satoshis: Some(1_000_000 - 500_000 - 10_000), transaction_fee_satoshis: commitment_tx_fee, }], confirmed_balance_candidate_index: 0, @@ -1181,6 +1190,7 @@ fn test_no_preimage_inbound_htlc_balances() { assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 500_000 - 20_000, + amount_offchain_satoshis: Some(500_000 - 20_000), transaction_fee_satoshis: 0, }], confirmed_balance_candidate_index: 0, @@ -1480,6 +1490,7 @@ fn do_test_revoked_counterparty_commitment_balances(keyed_anchors: bool, p2a_anc Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000 - 1 /* rounded up msat parts of HTLCs */, + amount_offchain_satoshis: Some(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, @@ -2026,6 +2037,7 @@ fn do_test_revoked_counterparty_aggregated_claims(keyed_anchors: bool, p2a_ancho assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { balance_candidates: vec![HolderCommitmentTransactionBalance { amount_satoshis: 100_000 - 4_000 - 3_000 - 1 /* rounded up msat parts of HTLCs */, + amount_offchain_satoshis: Some(100_000 - 4_000 - 3_000 - 1) /* rounded up msat parts of HTLCs */, transaction_fee_satoshis: 0, }], confirmed_balance_candidate_index: 0, diff --git a/lightning/src/ln/update_fee_tests.rs b/lightning/src/ln/update_fee_tests.rs index 060496d3bee..ed9cbf72368 100644 --- a/lightning/src/ln/update_fee_tests.rs +++ b/lightning/src/ln/update_fee_tests.rs @@ -489,7 +489,7 @@ pub fn do_test_update_fee_that_funder_cannot_afford(channel_type_features: Chann let local_chan_signer = local_chan.as_funded().unwrap().get_signer(); let nondust_htlcs: Vec = vec![]; - let commitment_tx = CommitmentTransaction::new( + let commitment_tx = CommitmentTransaction::new_without_broadcaster_value( INITIAL_COMMITMENT_NUMBER - 1, &remote_point, push_sats, @@ -590,7 +590,7 @@ pub fn test_update_fee_that_saturates_subs() { get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); let local_chan_signer = local_chan.as_funded().unwrap().get_signer(); let nondust_htlcs: Vec = vec![]; - let commitment_tx = CommitmentTransaction::new( + let commitment_tx = CommitmentTransaction::new_without_broadcaster_value( INITIAL_COMMITMENT_NUMBER, &remote_point, 8500, diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs index 74941ec8a87..4d890993c22 100644 --- a/lightning/src/sign/tx_builder.rs +++ b/lightning/src/sign/tx_builder.rs @@ -417,7 +417,11 @@ impl TxBuilder for SpecTxBuilder { ) }; - let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote }; + let (mut to_broadcaster_value_sat, to_broadcaster_value_offchain_msat) = if local { + (value_to_self, value_to_self_after_htlcs_msat) + } else { + (value_to_remote, value_to_remote_after_htlcs_msat) + }; let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self }; if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis { @@ -451,6 +455,7 @@ impl TxBuilder for SpecTxBuilder { commitment_number, per_commitment_point, to_broadcaster_value_sat, + to_broadcaster_value_offchain_msat, to_countersignatory_value_sat, feerate_per_kw, htlcs_in_tx, From 83a5102736e1fcd008dce4f1f206ac457f76e8a0 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 9 Dec 2025 15:44:46 +0000 Subject: [PATCH 2/7] f rename --- lightning/src/chain/channelmonitor.rs | 2 +- lightning/src/ln/chan_utils.rs | 6 +++--- lightning/src/ln/htlc_reserve_unit_tests.rs | 2 +- lightning/src/ln/update_fee_tests.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 07fde4fdda6..95a881214bd 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4579,7 +4579,7 @@ impl ChannelMonitorImpl { nondust_htlcs: Vec, ) -> CommitmentTransaction { let channel_parameters = &channel_parameters.as_counterparty_broadcastable(); - CommitmentTransaction::new_without_broadcaster_value( + CommitmentTransaction::new_without_broadcaster_offchain_value( commitment_number, their_per_commitment_point, to_broadcaster_value, diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index aafafbd4bff..250164b9d88 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1375,7 +1375,7 @@ impl HolderCommitmentTransaction { for _ in 0..nondust_htlcs.len() { counterparty_htlc_sigs.push(dummy_sig); } - let inner = CommitmentTransaction::new_without_broadcaster_value( + let inner = CommitmentTransaction::new_without_broadcaster_offchain_value( 0, &dummy_key, 0, 0, 0, nondust_htlcs, &channel_parameters.as_counterparty_broadcastable(), &secp_ctx ); HolderCommitmentTransaction { @@ -1728,7 +1728,7 @@ impl CommitmentTransaction { ) } - pub(crate) fn new_without_broadcaster_value( + pub(crate) fn new_without_broadcaster_offchain_value( commitment_number: u64, per_commitment_point: &PublicKey, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, feerate_per_kw: u32, nondust_htlcs: Vec, @@ -2385,7 +2385,7 @@ mod tests { #[rustfmt::skip] fn build(&self, to_broadcaster_sats: u64, to_countersignatory_sats: u64, nondust_htlcs: Vec) -> CommitmentTransaction { - CommitmentTransaction::new_without_broadcaster_value( + CommitmentTransaction::new_without_broadcaster_offchain_value( self.commitment_number, &self.per_commitment_point, to_broadcaster_sats, to_countersignatory_sats, self.feerate_per_kw, nondust_htlcs, &self.channel_parameters.as_holder_broadcastable(), &self.secp_ctx ) diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index cfa839b9673..9a376a51e5a 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -2346,7 +2346,7 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); let chan_signer = channel.as_funded().unwrap().get_signer(); - let commitment_tx = CommitmentTransaction::new_without_broadcaster_value( + let commitment_tx = CommitmentTransaction::new_without_broadcaster_offchain_value( commitment_number, &remote_point, node_1_balance_sat, diff --git a/lightning/src/ln/update_fee_tests.rs b/lightning/src/ln/update_fee_tests.rs index ed9cbf72368..30151aab222 100644 --- a/lightning/src/ln/update_fee_tests.rs +++ b/lightning/src/ln/update_fee_tests.rs @@ -489,7 +489,7 @@ pub fn do_test_update_fee_that_funder_cannot_afford(channel_type_features: Chann let local_chan_signer = local_chan.as_funded().unwrap().get_signer(); let nondust_htlcs: Vec = vec![]; - let commitment_tx = CommitmentTransaction::new_without_broadcaster_value( + let commitment_tx = CommitmentTransaction::new_without_broadcaster_offchain_value( INITIAL_COMMITMENT_NUMBER - 1, &remote_point, push_sats, @@ -590,7 +590,7 @@ pub fn test_update_fee_that_saturates_subs() { get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); let local_chan_signer = local_chan.as_funded().unwrap().get_signer(); let nondust_htlcs: Vec = vec![]; - let commitment_tx = CommitmentTransaction::new_without_broadcaster_value( + let commitment_tx = CommitmentTransaction::new_without_broadcaster_offchain_value( INITIAL_COMMITMENT_NUMBER, &remote_point, 8500, From b006a8246d412a8767e2a800642d867dc3a68929 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 9 Dec 2025 15:44:22 +0000 Subject: [PATCH 3/7] f drop extra crap --- lightning/src/chain/channelmonitor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 95a881214bd..23edaeb991b 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1,4 +1,4 @@ -// This file is Copyright its original authors, visible in version controlXXX +// This file is Copyright its original authors, visible in version control // history. // // This file is licensed under the Apache License, Version 2.0 Date: Mon, 8 Dec 2025 21:43:47 +0000 Subject: [PATCH 4/7] Run `rustfmt` on `CommitmentTransaction`'s builder --- lightning/src/ln/chan_utils.rs | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 250164b9d88..aade4d435a3 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1748,7 +1748,6 @@ impl CommitmentTransaction { ) } - #[rustfmt::skip] fn build( commitment_number: u64, per_commitment_point: &PublicKey, to_broadcaster_value_sat: u64, to_broadcaster_value_offchain_msat: Option, to_countersignatory_value_sat: u64, @@ -1758,15 +1757,32 @@ impl CommitmentTransaction { ) -> CommitmentTransaction { let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat); let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); - let keys = TxCreationKeys::from_channel_static_keys(per_commitment_point, channel_parameters.broadcaster_pubkeys(), channel_parameters.countersignatory_pubkeys(), secp_ctx); + let keys = TxCreationKeys::from_channel_static_keys( + per_commitment_point, + channel_parameters.broadcaster_pubkeys(), + channel_parameters.countersignatory_pubkeys(), + secp_ctx, + ); // Build and sort the outputs of the transaction. // Also sort the HTLC output data in `nondust_htlcs` in the same order, and populate the // transaction output indices therein. - let outputs = Self::build_outputs_and_htlcs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, &mut nondust_htlcs, channel_parameters); + let outputs = Self::build_outputs_and_htlcs( + &keys, + to_broadcaster_value_sat, + to_countersignatory_value_sat, + &mut nondust_htlcs, + channel_parameters, + ); - let (obscured_commitment_transaction_number, txins) = Self::build_inputs(commitment_number, channel_parameters); - let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs, channel_parameters); + let (obscured_commitment_transaction_number, txins) = + Self::build_inputs(commitment_number, channel_parameters); + let transaction = Self::make_transaction( + obscured_commitment_transaction_number, + txins, + outputs, + channel_parameters, + ); let txid = transaction.compute_txid(); CommitmentTransaction { commitment_number, @@ -1778,10 +1794,7 @@ impl CommitmentTransaction { nondust_htlcs, channel_type_features: channel_parameters.channel_type_features().clone(), keys, - built: BuiltCommitmentTransaction { - transaction, - txid - }, + built: BuiltCommitmentTransaction { transaction, txid }, } } From bee791706ce25f0f4b27c2de21a429cd3a255e29 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 8 Dec 2025 21:55:52 +0000 Subject: [PATCH 5/7] Add an "offchain balance" accessor to `Balance` In a previous commit we introduced the concept of the "offchain balance" to `HolderCommitmentTransactionBalance` to better capture the type of balance that end-user walets likely wish to expose. Here we expose an accessor on `Balance` to fetch that concept across difference `Balance` variants easily. Fixes #4241 --- lightning/src/chain/channelmonitor.rs | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 23edaeb991b..97e40964cbb 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1011,6 +1011,39 @@ impl Balance { Balance::MaybePreimageClaimableHTLC { .. } => 0, } } + + /// The "offchain balance", in satoshis. + /// + /// When the channel has yet to close, this returns the balance we are owed, ignoring fees, + /// reserve values, anchors, and dust limits. This more closely corresponds with the sum of our + /// inbound and outbound payments and may be more useful as the balance displayed in an + /// end-user wallet. Still, it is somewhat misleading from an on-chain-funds-available + /// perspective. + /// + /// For pending payments, splice behavior, or behavior after a channel has been closed, this + /// behaves the same as [`Self::claimable_amount_satoshis`]. + #[rustfmt::skip] + pub fn offchain_amount_satoshis(&self) -> u64 { + match self { + Balance::ClaimableOnChannelClose { + balance_candidates, confirmed_balance_candidate_index, .. + } => { + if *confirmed_balance_candidate_index != 0 { + let candidate = &balance_candidates[*confirmed_balance_candidate_index]; + candidate.amount_offchain_satoshis.unwrap_or(candidate.amount_satoshis) + } else { + balance_candidates.last().map(|balance| balance.amount_offchain_satoshis.unwrap_or(balance.amount_satoshis)).unwrap_or(0) + } + }, + Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }| + Balance::ContentiousClaimable { amount_satoshis, .. }| + Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. } + => *amount_satoshis, + Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. } + => if *outbound_payment { 0 } else { *amount_satoshis }, + Balance::MaybePreimageClaimableHTLC { .. } => 0, + } + } } /// An HTLC which has been irrevocably resolved on-chain, and has reached ANTI_REORG_DELAY. From ec72e568c4c52e430e9e1f2343f8068c0c34ee22 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 9 Dec 2025 15:44:38 +0000 Subject: [PATCH 6/7] f is the sum, not close to --- lightning/src/chain/channelmonitor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 97e40964cbb..700f8740268 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1015,10 +1015,10 @@ impl Balance { /// The "offchain balance", in satoshis. /// /// When the channel has yet to close, this returns the balance we are owed, ignoring fees, - /// reserve values, anchors, and dust limits. This more closely corresponds with the sum of our - /// inbound and outbound payments and may be more useful as the balance displayed in an - /// end-user wallet. Still, it is somewhat misleading from an on-chain-funds-available - /// perspective. + /// reserve values, anchors, and dust limits. This is the sum of our inbound and outbound + /// payments, initial channel contribution, and splices and may be more useful as the balance + /// displayed in an end-user wallet. Still, it is somewhat misleading from an + /// on-chain-funds-available perspective. /// /// For pending payments, splice behavior, or behavior after a channel has been closed, this /// behaves the same as [`Self::claimable_amount_satoshis`]. From 4f1abd344aec9fcddb5e6882cd357945cea9ff3f Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 8 Dec 2025 21:56:11 +0000 Subject: [PATCH 7/7] Run `rustfmt` on `Balance` methods --- lightning/src/chain/channelmonitor.rs | 49 +++++++++++++++++---------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 700f8740268..30f7d46bb70 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -990,11 +990,12 @@ impl Balance { /// [`Balance::MaybePreimageClaimableHTLC`]. /// /// On-chain fees required to claim the balance are not included in this amount. - #[rustfmt::skip] pub fn claimable_amount_satoshis(&self) -> u64 { match self { Balance::ClaimableOnChannelClose { - balance_candidates, confirmed_balance_candidate_index, .. + balance_candidates, + confirmed_balance_candidate_index, + .. } => { if *confirmed_balance_candidate_index != 0 { balance_candidates[*confirmed_balance_candidate_index].amount_satoshis @@ -1002,12 +1003,16 @@ impl Balance { balance_candidates.last().map(|balance| balance.amount_satoshis).unwrap_or(0) } }, - Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }| - Balance::ContentiousClaimable { amount_satoshis, .. }| - Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. } - => *amount_satoshis, - Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. } - => if *outbound_payment { 0 } else { *amount_satoshis }, + Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. } + | Balance::ContentiousClaimable { amount_satoshis, .. } + | Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. } => *amount_satoshis, + Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. } => { + if *outbound_payment { + 0 + } else { + *amount_satoshis + } + }, Balance::MaybePreimageClaimableHTLC { .. } => 0, } } @@ -1022,25 +1027,35 @@ impl Balance { /// /// For pending payments, splice behavior, or behavior after a channel has been closed, this /// behaves the same as [`Self::claimable_amount_satoshis`]. - #[rustfmt::skip] pub fn offchain_amount_satoshis(&self) -> u64 { match self { Balance::ClaimableOnChannelClose { - balance_candidates, confirmed_balance_candidate_index, .. + balance_candidates, + confirmed_balance_candidate_index, + .. } => { if *confirmed_balance_candidate_index != 0 { let candidate = &balance_candidates[*confirmed_balance_candidate_index]; candidate.amount_offchain_satoshis.unwrap_or(candidate.amount_satoshis) } else { - balance_candidates.last().map(|balance| balance.amount_offchain_satoshis.unwrap_or(balance.amount_satoshis)).unwrap_or(0) + balance_candidates + .last() + .map(|balance| { + balance.amount_offchain_satoshis.unwrap_or(balance.amount_satoshis) + }) + .unwrap_or(0) + } + }, + Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. } + | Balance::ContentiousClaimable { amount_satoshis, .. } + | Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. } => *amount_satoshis, + Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. } => { + if *outbound_payment { + 0 + } else { + *amount_satoshis } }, - Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }| - Balance::ContentiousClaimable { amount_satoshis, .. }| - Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. } - => *amount_satoshis, - Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. } - => if *outbound_payment { 0 } else { *amount_satoshis }, Balance::MaybePreimageClaimableHTLC { .. } => 0, } }