From 5b32677a4169136baac81e7c77f04f0b8cf08278 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 16 Jul 2025 08:25:31 -0700 Subject: [PATCH 1/2] Detect channel alternative funding transaction confirmation Whether it's a splice, or a dual-funded RBF, we need to know which funding transaction out of all of the negotiated ones is currently confirmed in case we need to broadcast the holder commitment. --- lightning/src/chain/channelmonitor.rs | 101 +++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index ddb1e31f645..30f17e79fec 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -565,6 +565,13 @@ enum OnchainEvent { /// output (and generate a SpendableOutput event). on_to_local_output_csv: Option, }, + /// An alternative funding transaction (due to a splice/RBF) has confirmed but can no longer be + /// locked not as the monitor is no longer allowing updates. Note that we wait to promote the + /// corresponding `FundingScope` until we see a + /// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`], but this event is only applicable + /// once [`ChannelMonitor::no_further_updates_allowed`] returns true. We promote the + /// `FundingScope` once the funding transaction is irrevocably confirmed. + AlternativeFundingConfirmation {}, } impl Writeable for OnchainEventEntry { @@ -609,6 +616,7 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent, (1, MaturingOutput) => { (0, descriptor, required), }, + (2, AlternativeFundingConfirmation) => {}, (3, FundingSpendConfirmation) => { (0, on_local_output_csv, option), (1, commitment_tx_to_counterparty_output, option), @@ -618,7 +626,6 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent, (2, preimage, option), (4, on_to_local_output_csv, option), }, - ); #[derive(Clone, Debug, PartialEq, Eq)] @@ -1280,6 +1287,10 @@ pub(crate) struct ChannelMonitorImpl { // commitment transactions, their ordering with respect to each other must remain the same. current_holder_htlc_data: CommitmentHTLCData, prev_holder_htlc_data: Option, + + // Tracks the txid and confirmation height of a renegotiated funding transaction upon + // confirmation. Used to determine which commitment we should broadcast when necessary. + alternative_funding_confirmed: Option<(Txid, u32)>, } // Macro helper to access holder commitment HTLC data (including both non-dust and dust) while @@ -1555,6 +1566,7 @@ impl Writeable for ChannelMonitorImpl { (29, self.initial_counterparty_commitment_tx, option), (31, self.funding.channel_parameters, required), (32, self.pending_funding, optional_vec), + (34, self.alternative_funding_confirmed, option), }); Ok(()) @@ -1780,6 +1792,8 @@ impl ChannelMonitor { // There are never any HTLCs in the initial commitment transaction current_holder_htlc_data: CommitmentHTLCData::new(), prev_holder_htlc_data: None, + + alternative_funding_confirmed: None, }) } @@ -3815,6 +3829,9 @@ impl ChannelMonitorImpl { for funding in self.pending_funding.drain(..) { self.outputs_to_watch.remove(&funding.funding_txid()); } + if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.take() { + debug_assert_eq!(alternative_funding_txid, new_funding_txid); + } Ok(()) } @@ -4873,6 +4890,57 @@ impl ChannelMonitorImpl { } } + // A splice/dual-funded RBF transaction has confirmed. We can't promote the + // `FundingScope` scope until we see the + // [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] for it, but we track the txid + // so we know which holder commitment transaction we may need to broadcast. + if let Some(alternative_funding) = self.pending_funding.iter() + .find(|funding| funding.funding_txid() == txid) + { + debug_assert!(self.funding_spend_confirmed.is_none()); + debug_assert!( + !self.onchain_events_awaiting_threshold_conf.iter() + .any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. })) + ); + + let (desc, msg) = if alternative_funding.channel_parameters.splice_parent_funding_txid.is_some() { + debug_assert!(tx.input.iter().any(|input| { + let funding_outpoint = self.funding.funding_outpoint().into_bitcoin_outpoint(); + input.previous_output == funding_outpoint + })); + ("Splice", "splice_locked") + } else { + ("RBF", "channel_ready") + }; + let action = if self.no_further_updates_allowed() { + if self.holder_tx_signed { + ", broadcasting post-splice holder commitment transaction".to_string() + } else { + "".to_string() + } + } else { + format!(", waiting for `{msg}` exchange") + }; + log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id()); + + self.alternative_funding_confirmed = Some((txid, height)); + + if self.no_further_updates_allowed() { + // We can no longer rely on + // [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] to promote the + // scope, do so when the funding is no longer under reorg risk. + self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { + txid, + transaction: Some((*tx).clone()), + height, + block_hash: Some(block_hash), + event: OnchainEvent::AlternativeFundingConfirmation {}, + }); + } + + continue 'tx_iter; + } + if tx.input.len() == 1 { // Assuming our keys were not leaked (in which case we're screwed no matter what), // commitment transactions and HTLC transactions will all only ever have one input @@ -5004,7 +5072,7 @@ impl ChannelMonitorImpl { let unmatured_htlcs: Vec<_> = self.onchain_events_awaiting_threshold_conf .iter() .filter_map(|entry| match &entry.event { - OnchainEvent::HTLCUpdate { source, .. } => Some(source), + OnchainEvent::HTLCUpdate { source, .. } => Some(source.clone()), _ => None, }) .collect(); @@ -5019,7 +5087,7 @@ impl ChannelMonitorImpl { #[cfg(debug_assertions)] { debug_assert!( - !unmatured_htlcs.contains(&&source), + !unmatured_htlcs.contains(&source), "An unmature HTLC transaction conflicts with a maturing one; failed to \ call either transaction_unconfirmed for the conflicting transaction \ or block_disconnected for a block containing it."); @@ -5066,6 +5134,15 @@ impl ChannelMonitorImpl { self.funding_spend_confirmed = Some(entry.txid); self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output; }, + OnchainEvent::AlternativeFundingConfirmation {} => { + // An alternative funding transaction has irrevocably confirmed and we're no + // longer allowing monitor updates, so promote the `FundingScope` now. + debug_assert!(self.no_further_updates_allowed()); + debug_assert_ne!(self.funding.funding_txid(), entry.txid); + if let Err(_) = self.promote_funding(entry.txid) { + log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid); + } + }, } } @@ -5178,6 +5255,13 @@ impl ChannelMonitorImpl { //- maturing spendable output has transaction paying us has been disconnected self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height < height); + // TODO: Replace with `take_if` once our MSRV is >= 1.80. + if let Some((_, conf_height)) = self.alternative_funding_confirmed.as_ref() { + if *conf_height == height { + self.alternative_funding_confirmed.take(); + } + } + let bounded_fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.block_disconnected( @@ -5217,6 +5301,13 @@ impl ChannelMonitorImpl { debug_assert!(!self.onchain_events_awaiting_threshold_conf.iter().any(|ref entry| entry.txid == *txid)); + // TODO: Replace with `take_if` once our MSRV is >= 1.80. + if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.as_ref() { + if alternative_funding_txid == txid { + self.alternative_funding_confirmed.take(); + } + } + let conf_target = self.closure_conf_target(); self.onchain_tx_handler.transaction_unconfirmed( txid, broadcaster, conf_target, &self.destination_script, fee_estimator, logger @@ -5870,6 +5961,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut first_negotiated_funding_txo = RequiredWrapper(None); let mut channel_parameters = None; let mut pending_funding = None; + let mut alternative_funding_confirmed = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -5888,6 +5980,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (29, initial_counterparty_commitment_tx, option), (31, channel_parameters, (option: ReadableArgs, None)), (32, pending_funding, optional_vec), + (34, alternative_funding_confirmed, option), }); if let Some(payment_preimages_with_info) = payment_preimages_with_info { if payment_preimages_with_info.len() != payment_preimages.len() { @@ -6057,6 +6150,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP current_holder_htlc_data, prev_holder_htlc_data, + + alternative_funding_confirmed, }))) } } From 9051703cfec9954ebbe2272362f0aad5948a1fdb Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 16 Jul 2025 14:25:03 -0700 Subject: [PATCH 2/2] Broadcast holder commitment for currently confirmed funding A `FundingScope` can only be promoted once a `ChannelMonitorUpdateStep::RenegotiatedFundingLocked` is applied, or if the monitor is no longer accepting updates, once the renegotiated funding transaction is no longer under reorg risk. Because of this, our current `FundingScope` may not reflect the latest confirmed state in the chain. Before making a holder commitment broadcast, we must check which `FundingScope` is currently confirmed to ensure that it can propogate throughout the network. --- lightning/src/chain/channelmonitor.rs | 183 +++++++++++++++++++------- lightning/src/chain/onchaintx.rs | 19 ++- 2 files changed, 148 insertions(+), 54 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 30f17e79fec..4af9d08a4e4 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1106,6 +1106,10 @@ impl FundingScope { fn is_splice(&self) -> bool { self.channel_parameters.splice_parent_funding_txid.is_some() } + + fn channel_type_features(&self) -> &ChannelTypeFeatures { + &self.channel_parameters.channel_type_features + } } impl_writeable_tlv_based!(FundingScope, { @@ -3610,7 +3614,7 @@ impl ChannelMonitorImpl { // Assume that the broadcasted commitment transaction confirmed in the current best // block. Even if not, its a reasonable metric for the bump criteria on the HTLC // transactions. - let (claim_reqs, _) = self.get_broadcasted_holder_claims(holder_commitment_tx, self.best_block.height); + let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.funding, holder_commitment_tx, self.best_block.height); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.update_claims_view_from_requests( claim_reqs, self.best_block.height, self.best_block.height, broadcaster, @@ -3621,25 +3625,37 @@ impl ChannelMonitorImpl { } #[rustfmt::skip] - fn generate_claimable_outpoints_and_watch_outputs(&mut self, reason: ClosureReason) -> (Vec, Vec) { - let holder_commitment_tx = &self.funding.current_holder_commitment_tx; + fn generate_claimable_outpoints_and_watch_outputs( + &mut self, generate_monitor_event_with_reason: Option, + ) -> (Vec, Vec) { + let funding = self.alternative_funding_confirmed + .map(|(alternative_funding_txid, _)| { + self.pending_funding + .iter() + .find(|funding| funding.funding_txid() == alternative_funding_txid) + .expect("FundingScope for confirmed alternative funding must exist") + }) + .unwrap_or(&self.funding); + let holder_commitment_tx = &funding.current_holder_commitment_tx; let funding_outp = HolderFundingOutput::build( holder_commitment_tx.clone(), - self.funding.channel_parameters.clone(), + funding.channel_parameters.clone(), ); - let funding_outpoint = self.get_funding_txo(); + let funding_outpoint = funding.funding_outpoint(); let commitment_package = PackageTemplate::build_package( funding_outpoint.txid.clone(), funding_outpoint.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height, ); let mut claimable_outpoints = vec![commitment_package]; - let event = MonitorEvent::HolderForceClosedWithInfo { - reason, - outpoint: funding_outpoint, - channel_id: self.channel_id, - }; - self.pending_monitor_events.push(event); + if let Some(reason) = generate_monitor_event_with_reason { + let event = MonitorEvent::HolderForceClosedWithInfo { + reason, + outpoint: funding_outpoint, + channel_id: self.channel_id, + }; + self.pending_monitor_events.push(event); + } // Although we aren't signing the transaction directly here, the transaction will be signed // in the claim that is queued to OnchainTxHandler. We set holder_tx_signed here to reject @@ -3649,12 +3665,12 @@ impl ChannelMonitorImpl { // We can't broadcast our HTLC transactions while the commitment transaction is // unconfirmed. We'll delay doing so until we detect the confirmed commitment in // `transactions_confirmed`. - if !self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { + if !funding.channel_type_features().supports_anchors_zero_fee_htlc_tx() { // Because we're broadcasting a commitment transaction, we should construct the package // assuming it gets confirmed in the next block. Sadly, we have code which considers // "not yet confirmed" things as discardable, so we cannot do that here. let (mut new_outpoints, _) = self.get_broadcasted_holder_claims( - holder_commitment_tx, self.best_block.height, + &funding, holder_commitment_tx, self.best_block.height, ); let new_outputs = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); if !new_outputs.is_empty() { @@ -3678,7 +3694,7 @@ impl ChannelMonitorImpl { broadcasted_latest_txn: Some(true), message: "ChannelMonitor-initiated commitment transaction broadcast".to_owned(), }; - let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs(reason); + let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs(Some(reason)); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.update_claims_view_from_requests( claimable_outpoints, self.best_block.height, self.best_block.height, broadcaster, @@ -4524,7 +4540,7 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn get_broadcasted_holder_htlc_descriptors( - &self, holder_tx: &HolderCommitmentTransaction, + &self, funding: &FundingScope, holder_tx: &HolderCommitmentTransaction, ) -> Vec { let tx = holder_tx.trust(); let mut htlcs = Vec::with_capacity(holder_tx.nondust_htlcs().len()); @@ -4542,11 +4558,10 @@ impl ChannelMonitorImpl { }; htlcs.push(HTLCDescriptor { - // TODO(splicing): Consider alternative funding scopes. channel_derivation_parameters: ChannelDerivationParameters { - value_satoshis: self.funding.channel_parameters.channel_value_satoshis, + value_satoshis: funding.channel_parameters.channel_value_satoshis, keys_id: self.channel_keys_id, - transaction_parameters: self.funding.channel_parameters.clone(), + transaction_parameters: funding.channel_parameters.clone(), }, commitment_txid: tx.txid(), per_commitment_number: tx.commitment_number(), @@ -4566,7 +4581,7 @@ impl ChannelMonitorImpl { // script so we can detect whether a holder transaction has been seen on-chain. #[rustfmt::skip] fn get_broadcasted_holder_claims( - &self, holder_tx: &HolderCommitmentTransaction, conf_height: u32, + &self, funding: &FundingScope, holder_tx: &HolderCommitmentTransaction, conf_height: u32, ) -> (Vec, Option<(ScriptBuf, PublicKey, RevocationKey)>) { let tx = holder_tx.trust(); let keys = tx.keys(); @@ -4577,7 +4592,7 @@ impl ChannelMonitorImpl { redeem_script.to_p2wsh(), holder_tx.per_commitment_point(), keys.revocation_key.clone(), )); - let claim_requests = self.get_broadcasted_holder_htlc_descriptors(holder_tx).into_iter() + let claim_requests = self.get_broadcasted_holder_htlc_descriptors(funding, holder_tx).into_iter() .map(|htlc_descriptor| { let counterparty_spendable_height = if htlc_descriptor.htlc.offered { conf_height @@ -4644,7 +4659,8 @@ impl ChannelMonitorImpl { is_holder_tx = true; log_info!(logger, "Got broadcast of latest holder commitment tx {}, searching for available HTLCs to claim", commitment_txid); let holder_commitment_tx = &self.funding.current_holder_commitment_tx; - let res = self.get_broadcasted_holder_claims(holder_commitment_tx, height); + let res = + self.get_broadcasted_holder_claims(&self.funding, holder_commitment_tx, height); let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); append_onchain_update!(res, to_watch); fail_unbroadcast_htlcs!( @@ -4661,7 +4677,8 @@ impl ChannelMonitorImpl { if holder_commitment_tx.trust().txid() == commitment_txid { is_holder_tx = true; log_info!(logger, "Got broadcast of previous holder commitment tx {}, searching for available HTLCs to claim", commitment_txid); - let res = self.get_broadcasted_holder_claims(holder_commitment_tx, height); + let res = + self.get_broadcasted_holder_claims(&self.funding, holder_commitment_tx, height); let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); append_onchain_update!(res, to_watch); fail_unbroadcast_htlcs!( @@ -4697,45 +4714,63 @@ impl ChannelMonitorImpl { } // If we have generated claims for counterparty_commitment_txid earlier, we can rely on always // having claim related htlcs for counterparty_commitment_txid in counterparty_claimable_outpoints. - for (htlc, _) in self.funding.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) { - log_trace!(logger, "Canceling claims for previously confirmed counterparty commitment {}", - counterparty_commitment_txid); - let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 }; - if let Some(vout) = htlc.transaction_output_index { - outpoint.vout = vout; - self.onchain_tx_handler.abandon_claim(&outpoint); + for funding in core::iter::once(&self.funding).chain(self.pending_funding.iter()) { + let mut found_claim = false; + for (htlc, _) in funding.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) { + let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 }; + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + if self.onchain_tx_handler.abandon_claim(&outpoint) { + found_claim = true; + } + } + } + if found_claim { + log_trace!(logger, "Canceled claims for previously confirmed counterparty commitment with txid {counterparty_commitment_txid}"); } } } // Cancel any pending claims for any holder commitments in case they had previously // confirmed or been signed (in which case we will start attempting to claim without // waiting for confirmation). - if self.funding.current_holder_commitment_tx.trust().txid() != *confirmed_commitment_txid { - let txid = self.funding.current_holder_commitment_tx.trust().txid(); - log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}", txid); - let mut outpoint = BitcoinOutPoint { txid, vout: 0 }; - for htlc in self.funding.current_holder_commitment_tx.nondust_htlcs() { - if let Some(vout) = htlc.transaction_output_index { - outpoint.vout = vout; - self.onchain_tx_handler.abandon_claim(&outpoint); - } else { - debug_assert!(false, "Expected transaction output index for non-dust HTLC"); - } - } - } - if let Some(prev_holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { - let txid = prev_holder_commitment_tx.trust().txid(); - if txid != *confirmed_commitment_txid { - log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}", txid); + for funding in core::iter::once(&self.funding).chain(self.pending_funding.iter()) { + if funding.current_holder_commitment_tx.trust().txid() != *confirmed_commitment_txid { + let mut found_claim = false; + let txid = funding.current_holder_commitment_tx.trust().txid(); let mut outpoint = BitcoinOutPoint { txid, vout: 0 }; - for htlc in prev_holder_commitment_tx.nondust_htlcs() { + for htlc in funding.current_holder_commitment_tx.nondust_htlcs() { if let Some(vout) = htlc.transaction_output_index { outpoint.vout = vout; - self.onchain_tx_handler.abandon_claim(&outpoint); + if self.onchain_tx_handler.abandon_claim(&outpoint) { + found_claim = true; + } } else { debug_assert!(false, "Expected transaction output index for non-dust HTLC"); } } + if found_claim { + log_trace!(logger, "Canceled claims for previously broadcast holder commitment with txid {txid}"); + } + } + if let Some(prev_holder_commitment_tx) = &funding.prev_holder_commitment_tx { + let txid = prev_holder_commitment_tx.trust().txid(); + if txid != *confirmed_commitment_txid { + let mut found_claim = false; + let mut outpoint = BitcoinOutPoint { txid, vout: 0 }; + for htlc in prev_holder_commitment_tx.nondust_htlcs() { + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + if self.onchain_tx_handler.abandon_claim(&outpoint) { + found_claim = true; + } + } else { + debug_assert!(false, "Expected transaction output index for non-dust HTLC"); + } + } + if found_claim { + log_trace!(logger, "Canceled claims for previously broadcast holder commitment with txid {txid}"); + } + } } } } @@ -4762,7 +4797,7 @@ impl ChannelMonitorImpl { return holder_transactions; } - self.get_broadcasted_holder_htlc_descriptors(&self.funding.current_holder_commitment_tx) + self.get_broadcasted_holder_htlc_descriptors(&self.funding, &self.funding.current_holder_commitment_tx) .into_iter() .for_each(|htlc_descriptor| { let txid = self.funding.current_holder_commitment_tx.trust().txid(); @@ -4858,6 +4893,7 @@ impl ChannelMonitorImpl { let mut watch_outputs = Vec::new(); let mut claimable_outpoints = Vec::new(); + let mut should_broadcast_commitment = false; 'tx_iter: for tx in &txn_matched { let txid = tx.compute_txid(); log_trace!(logger, "Transaction {} confirmed in block {}", txid , block_hash); @@ -4938,6 +4974,26 @@ impl ChannelMonitorImpl { }); } + if self.holder_tx_signed { + // Cancel any previous claims that are no longer valid as they stemmed from a + // different funding transaction. + let alternative_holder_commitment_txid = + alternative_funding.current_holder_commitment_tx.trust().txid(); + self.cancel_prev_commitment_claims(&logger, &alternative_holder_commitment_txid); + + // Queue claims for the alternative holder commitment since it is the only one + // that can currently confirm so far (until we see a reorg of its funding + // transaction). + // + // It's possible we process a holder/counterparty commitment within this same + // block that would invalidate the one we're intending to broadcast. If we were + // to broadcast our holder commitment now, we wouldn't be able to cancel it via + // our usual `cancel_prev_commitment_claims` path once we see a confirmed + // commitment since the claim would still be pending in `claimable_outpoints` + // (i.e., it wouldn't have been registered with the `OnchainTxHandler` yet). + should_broadcast_commitment = true; + } + continue 'tx_iter; } @@ -4975,6 +5031,10 @@ impl ChannelMonitorImpl { claimable_outpoints.append(&mut new_outpoints); } + + // We've just seen a commitment confirm, which conflicts with the holder + // commitment we intend to broadcast + should_broadcast_commitment = false; } self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { txid, @@ -5023,6 +5083,13 @@ impl ChannelMonitorImpl { self.best_block = BestBlock::new(block_hash, height); } + if should_broadcast_commitment { + let (mut claimables, mut outputs) = + self.generate_claimable_outpoints_and_watch_outputs(None); + claimable_outpoints.append(&mut claimables); + watch_outputs.append(&mut outputs); + } + self.block_confirmed(height, block_hash, txn_matched, watch_outputs, claimable_outpoints, &broadcaster, &fee_estimator, logger) } @@ -5056,7 +5123,7 @@ impl ChannelMonitorImpl { let should_broadcast = self.should_broadcast_holder_commitment_txn(logger); if should_broadcast { - let (mut new_outpoints, mut new_outputs) = self.generate_claimable_outpoints_and_watch_outputs(ClosureReason::HTLCsTimedOut); + let (mut new_outpoints, mut new_outputs) = self.generate_claimable_outpoints_and_watch_outputs(Some(ClosureReason::HTLCsTimedOut)); claimable_outpoints.append(&mut new_outpoints); watch_outputs.append(&mut new_outputs); } @@ -5259,6 +5326,14 @@ impl ChannelMonitorImpl { if let Some((_, conf_height)) = self.alternative_funding_confirmed.as_ref() { if *conf_height == height { self.alternative_funding_confirmed.take(); + if self.holder_tx_signed { + // Cancel any previous claims that are no longer valid as they stemmed from a + // different funding transaction. We'll wait until we see a funding transaction + // confirm again before attempting to broadcast the new valid holder commitment. + let new_holder_commitment_txid = + self.funding.current_holder_commitment_tx.trust().txid(); + self.cancel_prev_commitment_claims(&logger, &new_holder_commitment_txid); + } } } @@ -5305,6 +5380,14 @@ impl ChannelMonitorImpl { if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.as_ref() { if alternative_funding_txid == txid { self.alternative_funding_confirmed.take(); + if self.holder_tx_signed { + // Cancel any previous claims that are no longer valid as they stemmed from a + // different funding transaction. We'll wait until we see a funding transaction + // confirm again before attempting to broadcast the new valid holder commitment. + let new_holder_commitment_txid = + self.funding.current_holder_commitment_tx.trust().txid(); + self.cancel_prev_commitment_claims(&logger, &new_holder_commitment_txid); + } } } diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index f7c2bac3a39..95df05f4fd3 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -723,7 +723,8 @@ impl OnchainTxHandler { } #[rustfmt::skip] - pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) { + pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) -> bool { + let mut found_claim = false; let claim_id = self.claimable_outpoints.get(outpoint).map(|(claim_id, _)| *claim_id) .or_else(|| { self.pending_claim_requests.iter() @@ -733,13 +734,23 @@ impl OnchainTxHandler { if let Some(claim_id) = claim_id { if let Some(claim) = self.pending_claim_requests.remove(&claim_id) { for outpoint in claim.outpoints() { - self.claimable_outpoints.remove(outpoint); + if self.claimable_outpoints.remove(outpoint).is_some() { + found_claim = true; + } } } } else { - self.locktimed_packages.values_mut().for_each(|claims| - claims.retain(|claim| !claim.outpoints().contains(&outpoint))); + self.locktimed_packages.values_mut().for_each(|claims| { + claims.retain(|claim| { + let includes_outpoint = claim.outpoints().contains(&outpoint); + if includes_outpoint { + found_claim = true; + } + !includes_outpoint + }) + }); } + found_claim } /// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link