From a39357e08a86a71e80451ab56f36a17f36178a55 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 29 Sep 2023 17:54:24 +0000 Subject: [PATCH] Add tx fee information to `Balance::ClaimableOnChannelClose` `Balance::ClaimableOnChannelClose` excludes the commitment transaction fee, which makes it hard to use for current balance calculation. Here we add it, setting the value to zero for inbound channels (i.e. ones for which we don't pay the fee). --- lightning/src/chain/channelmonitor.rs | 32 +++++++++++++++++++++++---- lightning/src/ln/channel.rs | 4 ++-- lightning/src/ln/monitor_tests.rs | 19 +++++++++++----- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index fb2f11458..e12873d2f 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -622,6 +622,13 @@ pub enum Balance { /// 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. + /// + /// 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 channel has been closed, and the given balance is ours but awaiting confirmations until /// we consider it spendable. @@ -912,6 +919,10 @@ pub(crate) struct ChannelMonitorImpl { // of block connection between ChannelMonitors and the ChannelManager. funding_spend_seen: bool, + /// True if the commitment transaction fee is paid by us. + /// Added in 0.0.124. + holder_pays_commitment_tx_fee: Option, + /// Set to `Some` of the confirmed transaction spending the funding input of the channel after /// reaching `ANTI_REORG_DELAY` confirmations. funding_spend_confirmed: Option, @@ -1160,6 +1171,7 @@ impl Writeable for ChannelMonitorImpl { (17, self.initial_counterparty_commitment_info, option), (19, self.channel_id, required), (21, self.balances_empty_height, option), + (23, self.holder_pays_commitment_tx_fee, option), }); Ok(()) @@ -1261,7 +1273,7 @@ impl ChannelMonitor { pub(crate) fn new(secp_ctx: Secp256k1, keys: Signer, shutdown_script: Option, on_counterparty_tx_csv: u16, destination_script: &Script, funding_info: (OutPoint, ScriptBuf), - channel_parameters: &ChannelTransactionParameters, + channel_parameters: &ChannelTransactionParameters, holder_pays_commitment_tx_fee: bool, funding_redeemscript: ScriptBuf, channel_value_satoshis: u64, commitment_transaction_number_obscure_factor: u64, initial_holder_commitment_tx: HolderCommitmentTransaction, @@ -1353,6 +1365,7 @@ impl ChannelMonitor { onchain_tx_handler, + holder_pays_commitment_tx_fee: Some(holder_pays_commitment_tx_fee), lockdown_from_offchain: false, holder_tx_signed: false, funding_spend_seen: false, @@ -2306,8 +2319,11 @@ impl ChannelMonitor { } } else { let mut claimable_inbound_htlc_value_sat = 0; + let mut nondust_htlc_count = 0; for (htlc, _, source) in us.current_holder_commitment_tx.htlc_outputs.iter() { - if htlc.transaction_output_index.is_none() { continue; } + if htlc.transaction_output_index.is_some() { + nondust_htlc_count += 1; + } else { continue; } if htlc.offered { let outbound_payment = match source { None => { @@ -2337,6 +2353,11 @@ impl ChannelMonitor { } res.push(Balance::ClaimableOnChannelClose { amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat + claimable_inbound_htlc_value_sat, + transaction_fee_satoshis: if us.holder_pays_commitment_tx_fee.unwrap_or(true) { + chan_utils::commit_tx_fee_sat( + us.current_holder_commitment_tx.feerate_per_kw, nondust_htlc_count, + us.onchain_tx_handler.channel_type_features()) + } else { 0 }, }); } @@ -4743,6 +4764,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut initial_counterparty_commitment_info = None; let mut balances_empty_height = None; let mut channel_id = None; + let mut holder_pays_commitment_tx_fee = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -4755,6 +4777,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (17, initial_counterparty_commitment_info, option), (19, channel_id, option), (21, balances_empty_height, option), + (23, holder_pays_commitment_tx_fee, option), }); // `HolderForceClosedWithInfo` replaced `HolderForceClosed` in v0.0.122. If we have both @@ -4824,6 +4847,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP lockdown_from_offchain, holder_tx_signed, + holder_pays_commitment_tx_fee, funding_spend_seen: funding_spend_seen.unwrap(), funding_spend_confirmed, confirmed_commitment_tx_counterparty_output, @@ -5063,7 +5087,7 @@ mod tests { let monitor = ChannelMonitor::new(Secp256k1::new(), keys, Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(), (OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()), - &channel_parameters, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), + &channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), best_block, dummy_key, channel_id); let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]); @@ -5311,7 +5335,7 @@ mod tests { let monitor = ChannelMonitor::new(Secp256k1::new(), keys, Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(), (OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()), - &channel_parameters, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), + &channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), best_block, dummy_key, channel_id); let chan_id = monitor.inner.lock().unwrap().channel_id(); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 7a03fe785..daa7b63df 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7861,7 +7861,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, shutdown_script, self.context.get_holder_selected_contest_delay(), &self.context.destination_script, (funding_txo, funding_txo_script), - &self.context.channel_transaction_parameters, + &self.context.channel_transaction_parameters, self.context.is_outbound(), funding_redeemscript.clone(), self.context.channel_value_satoshis, obscure_factor, holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); @@ -8173,7 +8173,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, shutdown_script, self.context.get_holder_selected_contest_delay(), &self.context.destination_script, (funding_txo, funding_txo_script.clone()), - &self.context.channel_transaction_parameters, + &self.context.channel_transaction_parameters, self.context.is_outbound(), funding_redeemscript.clone(), self.context.channel_value_satoshis, obscure_factor, holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 486c0bded..757ca41e1 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -240,10 +240,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 + amount_satoshis: 1_000_000 - 1_000 - commitment_tx_fee - anchor_outputs_value, + transaction_fee_satoshis: commitment_tx_fee, }], nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()); - assert_eq!(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 1_000, }], + assert_eq!(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 1_000, transaction_fee_satoshis: 0 }], nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()); nodes[0].node.close_channel(&chan_id, &nodes[1].node.get_our_node_id()).unwrap(); @@ -441,10 +442,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 }; assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - commitment_tx_fee - anchor_outputs_value, + transaction_fee_satoshis: commitment_tx_fee, }, sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]), sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 1_000, + transaction_fee_satoshis: 0, }, received_htlc_balance.clone(), received_htlc_timeout_balance.clone()]), sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); @@ -491,6 +494,7 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { 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 + transaction_fee_satoshis: commitment_tx_fee, }, sent_htlc_timeout_balance.clone()]; if !prev_commitment_tx { a_expected_balances.push(sent_htlc_balance.clone()); @@ -499,6 +503,7 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); assert_eq!(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 1_000 + 3_000 + 4_000, + transaction_fee_satoshis: 0, }], nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()); @@ -977,15 +982,17 @@ fn test_no_preimage_inbound_htlc_balances() { // Both A and B will have an HTLC that's claimable on timeout and one that's claimable if they // receive the preimage. These will remain the same through the channel closure and until the // HTLC output is spent. - + 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 - chan_feerate * - (chan_utils::commitment_tx_base_weight(&channel_type_features) + 2 * chan_utils::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + amount_satoshis: 1_000_000 - 500_000 - 10_000 - commitment_tx_fee, + transaction_fee_satoshis: commitment_tx_fee, }, a_received_htlc_balance.clone(), a_sent_htlc_balance.clone()]), sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 500_000 - 20_000, + transaction_fee_satoshis: 0, }, b_received_htlc_balance.clone(), b_sent_htlc_balance.clone()]), sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); @@ -1264,6 +1271,7 @@ fn do_test_revoked_counterparty_commitment_balances(anchors: bool, confirm_htlc_ // lists the two on-chain timeout-able HTLCs as claimable balances. assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000, + transaction_fee_satoshis: 0, }, Balance::MaybeTimeoutClaimableHTLC { amount_satoshis: 2_000, claimable_height: missing_htlc_cltv_timeout, @@ -1817,6 +1825,7 @@ fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) { assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 100_000 - 4_000 - 3_000, + transaction_fee_satoshis: 0, }, Balance::MaybeTimeoutClaimableHTLC { amount_satoshis: 4_000, claimable_height: htlc_cltv_timeout, -- 2.39.5