diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 5a19d19febc..861653a5e6c 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -876,15 +876,20 @@ struct HTLCStats { on_holder_tx_outbound_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included } -/// An enum gathering stats on commitment transaction, either local or remote. -struct CommitmentStats<'a> { +/// A struct gathering data on a commitment, either local or remote. +struct CommitmentData<'a> { + stats: CommitmentStats, + htlcs_included: Vec<(HTLCOutputInCommitment, Option<&'a HTLCSource>)>, // the list of HTLCs (dust HTLCs *included*) which were not ignored when building the transaction + outbound_htlc_preimages: Vec, // preimages for successful offered HTLCs since last commitment + inbound_htlc_preimages: Vec, // preimages for successful received HTLCs since last commitment +} + +/// A struct gathering stats on commitment transaction, either local or remote. +struct CommitmentStats { tx: CommitmentTransaction, // the transaction info total_fee_sat: u64, // the total fee included in the transaction - htlcs_included: Vec<(HTLCOutputInCommitment, Option<&'a HTLCSource>)>, // the list of HTLCs (dust HTLCs *included*) which were not ignored when building the transaction local_balance_msat: u64, // local balance before fees *not* considering dust limits remote_balance_msat: u64, // remote balance before fees *not* considering dust limits - outbound_htlc_preimages: Vec, // preimages for successful offered HTLCs since last commitment - inbound_htlc_preimages: Vec, // preimages for successful received HTLCs since last commitment } /// Used when calculating whether we or the remote can afford an additional HTLC. @@ -2034,7 +2039,10 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide ) -> Result where L::Target: Logger { let funding_script = self.funding().get_funding_redeemscript(); - let initial_commitment_tx = self.context().build_commitment_transaction(self.funding(), holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), true, false, logger).tx; + let commitment_data = self.context().build_commitment_transaction(self.funding(), + holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), + true, false, logger); + let initial_commitment_tx = commitment_data.stats.tx; let trusted_tx = initial_commitment_tx.trust(); let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.funding().get_value_satoshis()); @@ -2071,7 +2079,10 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide } }; let context = self.context(); - let counterparty_initial_commitment_tx = context.build_commitment_transaction(self.funding(), context.cur_counterparty_commitment_transaction_number, &context.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx; + let commitment_data = context.build_commitment_transaction(self.funding(), + context.cur_counterparty_commitment_transaction_number, + &context.counterparty_cur_commitment_point.unwrap(), false, false, logger); + let counterparty_initial_commitment_tx = commitment_data.stats.tx; let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); @@ -3455,9 +3466,11 @@ impl ChannelContext where SP::Target: SignerProvider { { let funding_script = funding.get_funding_redeemscript(); - let commitment_stats = self.build_commitment_transaction(funding, holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), true, false, logger); + let commitment_data = self.build_commitment_transaction(funding, + holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), + true, false, logger); let commitment_txid = { - let trusted_tx = commitment_stats.tx.trust(); + let trusted_tx = commitment_data.stats.tx.trust(); let bitcoin_tx = trusted_tx.built_transaction(); let sighash = bitcoin_tx.get_sighash_all(&funding_script, funding.get_value_satoshis()); @@ -3470,7 +3483,7 @@ impl ChannelContext where SP::Target: SignerProvider { } bitcoin_tx.txid }; - let mut htlcs_cloned: Vec<_> = commitment_stats.htlcs_included.iter().map(|htlc| (htlc.0.clone(), htlc.1.map(|h| h.clone()))).collect(); + let mut htlcs_cloned: Vec<_> = commitment_data.htlcs_included.iter().map(|htlc| (htlc.0.clone(), htlc.1.map(|h| h.clone()))).collect(); // If our counterparty updated the channel fee in this commitment transaction, check that // they can actually afford the new fee now. @@ -3480,7 +3493,7 @@ impl ChannelContext where SP::Target: SignerProvider { if update_fee { debug_assert!(!funding.is_outbound()); let counterparty_reserve_we_require_msat = funding.holder_selected_channel_reserve_satoshis * 1000; - if commitment_stats.remote_balance_msat < commitment_stats.total_fee_sat * 1000 + counterparty_reserve_we_require_msat { + if commitment_data.stats.remote_balance_msat < commitment_data.stats.total_fee_sat * 1000 + counterparty_reserve_we_require_msat { return Err(ChannelError::close("Funding remote cannot afford proposed new fee".to_owned())); } } @@ -3496,14 +3509,14 @@ impl ChannelContext where SP::Target: SignerProvider { && info.next_holder_htlc_id == self.next_holder_htlc_id && info.next_counterparty_htlc_id == self.next_counterparty_htlc_id && info.feerate == self.feerate_per_kw { - assert_eq!(commitment_stats.total_fee_sat, info.fee / 1000); + assert_eq!(commitment_data.stats.total_fee_sat, info.fee / 1000); } } } } - if msg.htlc_signatures.len() != commitment_stats.tx.htlcs().len() { - return Err(ChannelError::close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_stats.tx.htlcs().len()))); + if msg.htlc_signatures.len() != commitment_data.stats.tx.htlcs().len() { + return Err(ChannelError::close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_data.stats.tx.htlcs().len()))); } // Up to LDK 0.0.115, HTLC information was required to be duplicated in the @@ -3523,10 +3536,10 @@ impl ChannelContext where SP::Target: SignerProvider { let mut nondust_htlc_sources = Vec::with_capacity(htlcs_cloned.len()); let mut htlcs_and_sigs = Vec::with_capacity(htlcs_cloned.len()); - let holder_keys = commitment_stats.tx.trust().keys(); + let holder_keys = commitment_data.stats.tx.trust().keys(); for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() { if let Some(_) = htlc.transaction_output_index { - let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.tx.feerate_per_kw(), + let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_data.stats.tx.feerate_per_kw(), funding.get_counterparty_selected_contest_delay().unwrap(), &htlc, &self.channel_type, &holder_keys.broadcaster_delayed_payment_key, &holder_keys.revocation_key); @@ -3554,14 +3567,14 @@ impl ChannelContext where SP::Target: SignerProvider { } let holder_commitment_tx = HolderCommitmentTransaction::new( - commitment_stats.tx, + commitment_data.stats.tx, msg.signature, msg.htlc_signatures.clone(), &funding.get_holder_pubkeys().funding_pubkey, funding.counterparty_funding_pubkey() ); - self.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_stats.outbound_htlc_preimages) + self.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_data.outbound_htlc_preimages) .map_err(|_| ChannelError::close("Failed to validate our commitment".to_owned()))?; Ok(LatestHolderCommitmentTXInfo { @@ -3585,7 +3598,8 @@ impl ChannelContext where SP::Target: SignerProvider { /// generated by the peer which proposed adding the HTLCs, and thus we need to understand both /// which peer generated this transaction and "to whom" this transaction flows. #[inline] - fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) -> CommitmentStats + fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) + -> CommitmentData where L::Target: Logger { let num_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len(); @@ -3808,12 +3822,16 @@ impl ChannelContext where SP::Target: SignerProvider { } }); - CommitmentStats { + let stats = CommitmentStats { tx, total_fee_sat, - htlcs_included: htlcs_in_tx, local_balance_msat: value_to_self_msat, remote_balance_msat: value_to_remote_msat, + }; + + CommitmentData { + stats, + htlcs_included: htlcs_in_tx, inbound_htlc_preimages, outbound_htlc_preimages, } @@ -4602,8 +4620,10 @@ impl ChannelContext where SP::Target: SignerProvider { SP::Target: SignerProvider, L::Target: Logger { - let counterparty_initial_commitment_tx = self.build_commitment_transaction( - funding, self.cur_counterparty_commitment_transaction_number, &self.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx; + let commitment_data = self.build_commitment_transaction(funding, + self.cur_counterparty_commitment_transaction_number, + &self.counterparty_cur_commitment_point.unwrap(), false, false, logger); + let counterparty_initial_commitment_tx = commitment_data.stats.tx; match self.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ref ecdsa) => { @@ -6270,9 +6290,11 @@ impl FundedChannel where // Before proposing a feerate update, check that we can actually afford the new fee. let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw), dust_exposure_limiting_feerate); - let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &self.holder_commitment_point.current_point(), true, true, logger); - let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.tx.htlcs().len() + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000; - let holder_balance_msat = commitment_stats.local_balance_msat - htlc_stats.outbound_holding_cell_msat; + let commitment_data = self.context.build_commitment_transaction(&self.funding, + self.holder_commitment_point.transaction_number(), + &self.holder_commitment_point.current_point(), true, true, logger); + let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_data.stats.tx.htlcs().len() + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000; + let holder_balance_msat = commitment_data.stats.local_balance_msat - htlc_stats.outbound_holding_cell_msat; if holder_balance_msat < buffer_fee_msat + self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { //TODO: auto-close after a number of failures? log_debug!(logger, "Cannot afford to send new feerate at {}", feerate_per_kw); @@ -6583,7 +6605,10 @@ impl FundedChannel where self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); } let funding_signed = if self.context.signer_pending_funding && !self.funding.is_outbound() { - let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number + 1, &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx; + let commitment_data = self.context.build_commitment_transaction(&self.funding, + self.context.cur_counterparty_commitment_transaction_number + 1, + &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger); + let counterparty_initial_commitment_tx = commitment_data.stats.tx; self.context.get_funding_signed_msg(&self.funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx) } else { None }; // Provide a `channel_ready` message if we need to, but only if we're _not_ still pending @@ -8523,8 +8548,10 @@ impl FundedChannel where -> (Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>, CommitmentTransaction) where L::Target: Logger { - let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger); - let counterparty_commitment_tx = commitment_stats.tx; + let commitment_data = self.context.build_commitment_transaction(&self.funding, + self.context.cur_counterparty_commitment_transaction_number, + &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger); + let counterparty_commitment_tx = commitment_data.stats.tx; #[cfg(any(test, fuzzing))] { @@ -8544,7 +8571,7 @@ impl FundedChannel where } } - (commitment_stats.htlcs_included, counterparty_commitment_tx) + (commitment_data.htlcs_included, counterparty_commitment_tx) } /// Only fails in case of signer rejection. Used for channel_reestablish commitment_signed @@ -8554,7 +8581,10 @@ impl FundedChannel where #[cfg(any(test, fuzzing))] self.build_commitment_no_state_update(logger); - let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger); + let commitment_data = self.context.build_commitment_transaction(&self.funding, + self.context.cur_counterparty_commitment_transaction_number, + &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger); + let commitment_stats = commitment_data.stats; let counterparty_commitment_txid = commitment_stats.tx.trust().txid(); match &self.context.holder_signer { @@ -8565,8 +8595,8 @@ impl FundedChannel where let res = ecdsa.sign_counterparty_commitment( &self.funding.channel_transaction_parameters, &commitment_stats.tx, - commitment_stats.inbound_htlc_preimages, - commitment_stats.outbound_htlc_preimages, + commitment_data.inbound_htlc_preimages, + commitment_data.outbound_htlc_preimages, &self.context.secp_ctx, ).map_err(|_| ChannelError::Ignore("Failed to get signatures for new commitment_signed".to_owned()))?; signature = res.0; @@ -9035,7 +9065,10 @@ impl OutboundV1Channel where SP::Target: SignerProvider { /// Only allowed after [`FundingScope::channel_transaction_parameters`] is set. fn get_funding_created_msg(&mut self, logger: &L) -> Option where L::Target: Logger { - let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx; + let commitment_data = self.context.build_commitment_transaction(&self.funding, + self.context.cur_counterparty_commitment_transaction_number, + &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger); + let counterparty_initial_commitment_tx = commitment_data.stats.tx; let signature = match &self.context.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ecdsa) => { @@ -11648,8 +11681,9 @@ mod tests { ( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $opt_anchors: expr, { $( { $htlc_idx: expr, $counterparty_htlc_sig_hex: expr, $htlc_sig_hex: expr, $htlc_tx_hex: expr } ), * } ) => { { - let commitment_stats = chan.context.build_commitment_transaction(&chan.funding, 0xffffffffffff - 42, &per_commitment_point, true, false, &logger); - let commitment_tx = commitment_stats.tx; + let commitment_data = chan.context.build_commitment_transaction(&chan.funding, + 0xffffffffffff - 42, &per_commitment_point, true, false, &logger); + let commitment_tx = commitment_data.stats.tx; let trusted_tx = commitment_tx.trust(); let unsigned_tx = trusted_tx.built_transaction(); let redeemscript = chan.funding.get_funding_redeemscript();