From 6287ed3aad4cd9f9f9ed9c9a4929d87b9a22e5d8 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 12:30:29 -0300 Subject: [PATCH 1/7] Prefactor: drop #[rustfmt::skip] on broadcast_latest_holder_commitment_txn --- lightning/src/chain/channelmonitor.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 4acba6f4085..37e337c34b7 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -2327,19 +2327,21 @@ impl ChannelMonitor { /// close channel with their commitment transaction after a substantial amount of time. Best /// may be to contact the other node operator out-of-band to coordinate other options available /// to you. - #[rustfmt::skip] pub fn broadcast_latest_holder_commitment_txn( - &self, broadcaster: &B, fee_estimator: &F, logger: &L - ) - where + &self, broadcaster: &B, fee_estimator: &F, logger: &L, + ) where B::Target: BroadcasterInterface, F::Target: FeeEstimator, - L::Target: Logger + L::Target: Logger, { let mut inner = self.inner.lock().unwrap(); let fee_estimator = LowerBoundedFeeEstimator::new(&**fee_estimator); let logger = WithChannelMonitor::from_impl(logger, &*inner, None); - inner.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &fee_estimator, &logger); + inner.queue_latest_holder_commitment_txn_for_broadcast( + broadcaster, + &fee_estimator, + &logger, + ); } /// Unsafe test-only version of `broadcast_latest_holder_commitment_txn` used by our test framework From b9158c5a49bbcfa5bdfd726e61590b3fd82b76f1 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 11:45:21 -0300 Subject: [PATCH 2/7] Add manual-funding broadcast tracking to ChannelMonitor Adds `is_manual_broadcast` and `funding_seen_onchain` flags to track whether the channel uses manual funding broadcasts and whether we've seen the funding tx confirm. This enables deferring holder commitment broadcasts until after the funding tx is actually broadcast. For example, in LSPS2 with client_trusts_lsp=true, the LSP may defer broadcasting the funding tx until the client claims an HTLC, so we need to avoid broadcasting commitments that reference outputs that don't exist yet. --- lightning/src/chain/channelmonitor.rs | 27 +++++++++++++++++++++++++++ lightning/src/ln/channel.rs | 1 + 2 files changed, 28 insertions(+) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 37e337c34b7..06f521252e6 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1202,6 +1202,19 @@ pub(crate) struct ChannelMonitorImpl { funding: FundingScope, pending_funding: Vec, + /// True if this channel was configured for manual funding broadcasts. Monitors written by + /// versions prior to LDK 0.2 load with `false` until a new update persists it. + is_manual_broadcast: bool, + /// True once we've observed either funding transaction on-chain. Older monitors prior to LDK 0.2 + /// assume this is `true` when absent during upgrade so holder broadcasts aren't gated unexpectedly. + /// In manual-broadcast channels we also use this to trigger deferred holder + /// broadcasts once the funding transaction finally appears on-chain. + /// + /// Note: This tracks whether the funding transaction was ever broadcast, not whether it is + /// currently confirmed. It is never reset, even if the funding transaction is unconfirmed due + /// to a reorg. + funding_seen_onchain: bool, + latest_update_id: u64, commitment_transaction_number_obscure_factor: u64, @@ -1740,6 +1753,8 @@ pub(crate) fn write_chanmon_internal( (32, channel_monitor.pending_funding, optional_vec), (33, channel_monitor.htlcs_resolved_to_user, required), (34, channel_monitor.alternative_funding_confirmed, option), + (35, channel_monitor.is_manual_broadcast, required), + (37, channel_monitor.funding_seen_onchain, required), }); Ok(()) @@ -1868,6 +1883,7 @@ impl ChannelMonitor { commitment_transaction_number_obscure_factor: u64, initial_holder_commitment_tx: HolderCommitmentTransaction, best_block: BestBlock, counterparty_node_id: PublicKey, channel_id: ChannelId, + is_manual_broadcast: bool, ) -> ChannelMonitor { assert!(commitment_transaction_number_obscure_factor <= (1 << 48)); @@ -1914,6 +1930,9 @@ impl ChannelMonitor { }, pending_funding: vec![], + is_manual_broadcast, + funding_seen_onchain: false, + latest_update_id: 0, commitment_transaction_number_obscure_factor, @@ -6562,6 +6581,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut channel_parameters = None; let mut pending_funding = None; let mut alternative_funding_confirmed = None; + let mut is_manual_broadcast = RequiredWrapper(None); + let mut funding_seen_onchain = RequiredWrapper(None); read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -6582,6 +6603,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (32, pending_funding, optional_vec), (33, htlcs_resolved_to_user, option), (34, alternative_funding_confirmed, option), + (35, is_manual_broadcast, (default_value, false)), + (37, funding_seen_onchain, (default_value, true)), }); // Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so // we can use it to determine if this monitor was last written by LDK 0.1 or later. @@ -6695,6 +6718,10 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP prev_holder_commitment_tx, }, pending_funding: pending_funding.unwrap_or(vec![]), + is_manual_broadcast: is_manual_broadcast.0.unwrap(), + // Older monitors prior to LDK 0.2 assume this is `true` when absent + // during upgrade so holder broadcasts aren't gated unexpectedly. + funding_seen_onchain: funding_seen_onchain.0.unwrap(), latest_update_id, commitment_transaction_number_obscure_factor, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 5bfe585002d..c243fc6f9b3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3196,6 +3196,7 @@ where funding.get_holder_selected_contest_delay(), &context.destination_script, &funding.channel_transaction_parameters, funding.is_outbound(), obscure_factor, holder_commitment_tx, best_block, context.counterparty_node_id, context.channel_id(), + context.is_manual_broadcast, ); channel_monitor.provide_initial_counterparty_commitment_tx( counterparty_initial_commitment_tx.clone(), From 04a2776505d19b8e954ec3d53edba18984272453 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 11:46:54 -0300 Subject: [PATCH 3/7] Set funding_seen_onchain=true in filter_block Marks funding_seen_onchain when we see the funding tx confirm. --- lightning/src/chain/channelmonitor.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 06f521252e6..92f7e9ece40 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -5332,6 +5332,19 @@ impl ChannelMonitorImpl { L::Target: Logger, { let txn_matched = self.filter_block(txdata); + + if !self.funding_seen_onchain { + for &(_, tx) in txdata.iter() { + let txid = tx.compute_txid(); + if txid == self.funding.funding_txid() || + self.pending_funding.iter().any(|f| f.funding_txid() == txid) + { + self.funding_seen_onchain = true; + break; + } + } + } + for tx in &txn_matched { let mut output_val = Amount::ZERO; for out in tx.output.iter() { @@ -5915,7 +5928,7 @@ impl ChannelMonitorImpl { /// Filters a block's `txdata` for transactions spending watched outputs or for any child /// transactions thereof. #[rustfmt::skip] - fn filter_block<'a>(&self, txdata: &TransactionData<'a>) -> Vec<&'a Transaction> { + fn filter_block<'a>(&mut self, txdata: &TransactionData<'a>) -> Vec<&'a Transaction> { let mut matched_txn = new_hash_set(); txdata.iter().filter(|&&(_, tx)| { let mut matches = self.spends_watched_output(tx); From 6c5ef049b8d0ec174d7368d48b7b429efffb4a61 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 11:52:01 -0300 Subject: [PATCH 4/7] Gate holder broadcast queueing on funding confirmation Don't queue holder commitment broadcasts until funding is confirmed, unless explicitly overridden via broadcast_latest_holder_commitment_txn. Attempting to broadcast commitments before funding confirms would fail mempool validation since the funding output doesn't exist yet. --- lightning/src/chain/channelmonitor.rs | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 92f7e9ece40..175db8ae895 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -2346,6 +2346,16 @@ impl ChannelMonitor { /// close channel with their commitment transaction after a substantial amount of time. Best /// may be to contact the other node operator out-of-band to coordinate other options available /// to you. + /// + /// Note: For channels using manual funding broadcast (see + /// [`crate::ln::channelmanager::ChannelManager::funding_transaction_generated_manual_broadcast`]), + /// automatic broadcasts are suppressed until the funding transaction has been observed on-chain. + /// Calling this method overrides that suppression and queues the latest holder commitment + /// transaction for broadcast even if the funding has not yet been seen on-chain. This may result + /// in unconfirmable transactions being broadcast or [`Event::BumpTransaction`] notifications for + /// transactions that cannot be confirmed until the funding transaction is visible. + /// + /// [`Event::BumpTransaction`]: crate::events::Event::BumpTransaction pub fn broadcast_latest_holder_commitment_txn( &self, broadcaster: &B, fee_estimator: &F, logger: &L, ) where @@ -2356,10 +2366,12 @@ impl ChannelMonitor { let mut inner = self.inner.lock().unwrap(); let fee_estimator = LowerBoundedFeeEstimator::new(&**fee_estimator); let logger = WithChannelMonitor::from_impl(logger, &*inner, None); + inner.queue_latest_holder_commitment_txn_for_broadcast( broadcaster, &fee_estimator, &logger, + false, ); } @@ -3977,8 +3989,16 @@ impl ChannelMonitorImpl { } #[rustfmt::skip] + /// Note: For channels where the funding transaction is being manually managed (see + /// [`crate::ln::channelmanager::ChannelManager::funding_transaction_generated_manual_broadcast`]), + /// this method returns without queuing any transactions until the funding transaction has been + /// observed on-chain, unless `require_funding_seen` is `false`. This prevents attempting to + /// broadcast unconfirmable holder commitment transactions before the funding is visible. + /// See also [`ChannelMonitor::broadcast_latest_holder_commitment_txn`]. + /// + /// [`ChannelMonitor::broadcast_latest_holder_commitment_txn`]: crate::chain::channelmonitor::ChannelMonitor::broadcast_latest_holder_commitment_txn pub(crate) fn queue_latest_holder_commitment_txn_for_broadcast( - &mut self, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithChannelMonitor + &mut self, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithChannelMonitor, require_funding_seen: bool, ) where B::Target: BroadcasterInterface, @@ -3990,6 +4010,12 @@ impl ChannelMonitorImpl { message: "ChannelMonitor-initiated commitment transaction broadcast".to_owned(), }; let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs(Some(reason)); + // In manual-broadcast mode, if `require_funding_seen` is true and we have not yet observed + // the funding transaction on-chain, do not queue any transactions. + if require_funding_seen && self.is_manual_broadcast && !self.funding_seen_onchain { + log_info!(logger, "Not broadcasting holder commitment for manual-broadcast channel before funding appears on-chain"); + return; + } 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, @@ -4312,7 +4338,7 @@ impl ChannelMonitorImpl { log_trace!(logger, "Avoiding commitment broadcast, already detected confirmed spend onchain"); continue; } - self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &bounded_fee_estimator, logger); + self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &bounded_fee_estimator, logger, true); } else if !self.holder_tx_signed { log_error!(logger, "WARNING: You have a potentially-unsafe holder commitment transaction available to broadcast"); log_error!(logger, " in channel monitor for channel {}!", &self.channel_id()); @@ -5860,7 +5886,7 @@ impl ChannelMonitorImpl { // Only attempt to broadcast the new commitment after the `block_disconnected` call above so that // it doesn't get removed from the set of pending claims. if should_broadcast_commitment { - self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, &bounded_fee_estimator, logger); + self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, &bounded_fee_estimator, logger, true); } self.best_block = fork_point; @@ -5921,7 +5947,7 @@ impl ChannelMonitorImpl { // Only attempt to broadcast the new commitment after the `transaction_unconfirmed` call above so // that it doesn't get removed from the set of pending claims. if should_broadcast_commitment { - self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, fee_estimator, logger); + self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, fee_estimator, logger, true); } } @@ -7071,7 +7097,7 @@ mod tests { let monitor = ChannelMonitor::new( Secp256k1::new(), keys, Some(shutdown_script.into_inner()), 0, &ScriptBuf::new(), &channel_parameters, true, 0, HolderCommitmentTransaction::dummy(0, funding_outpoint, Vec::new()), - best_block, dummy_key, channel_id, + best_block, dummy_key, channel_id, false, ); let nondust_htlcs = preimages_slice_to_htlcs!(preimages[0..10]); @@ -7332,7 +7358,7 @@ mod tests { let monitor = ChannelMonitor::new( Secp256k1::new(), keys, Some(shutdown_script.into_inner()), 0, &ScriptBuf::new(), &channel_parameters, true, 0, HolderCommitmentTransaction::dummy(0, funding_outpoint, Vec::new()), - best_block, dummy_key, channel_id, + best_block, dummy_key, channel_id, false ); let chan_id = monitor.inner.lock().unwrap().channel_id(); From 4131680db4c9ff934e83940be1158f8b1cc0f8cf Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 11:53:33 -0300 Subject: [PATCH 5/7] Defer claimable tracking until funding tx confirms For manually-broadcast funding, we can't track claimable outputs until the funding tx is actually onchain. Otherwise we'd try to claim outputs that don't exist yet. --- lightning/src/chain/channelmonitor.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 175db8ae895..bbf2e5f4506 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3985,7 +3985,13 @@ impl ChannelMonitorImpl { } claimable_outpoints.append(&mut new_outpoints); } - (claimable_outpoints, watch_outputs) + // In manual-broadcast mode, if we have not yet observed the funding transaction on-chain, + // return empty vectors. + if self.is_manual_broadcast && !self.funding_seen_onchain { + return (Vec::new(), Vec::new()); + } else { + (claimable_outpoints, watch_outputs) + } } #[rustfmt::skip] @@ -5642,13 +5648,16 @@ impl ChannelMonitorImpl { log_trace!(logger, "Processing {} matched transactions for block at height {}.", txn_matched.len(), conf_height); debug_assert!(self.best_block.height >= conf_height); - let should_broadcast = self.should_broadcast_holder_commitment_txn(logger); - if let Some(payment_hash) = should_broadcast { - let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; - let (mut new_outpoints, mut new_outputs) = - self.generate_claimable_outpoints_and_watch_outputs(Some(reason)); - claimable_outpoints.append(&mut new_outpoints); - watch_outputs.append(&mut new_outputs); + // Only generate claims if we haven't already done so (e.g., in transactions_confirmed). + if claimable_outpoints.is_empty() { + let should_broadcast = self.should_broadcast_holder_commitment_txn(logger); + if let Some(payment_hash) = should_broadcast { + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; + let (mut new_outpoints, mut new_outputs) = + self.generate_claimable_outpoints_and_watch_outputs(Some(reason)); + claimable_outpoints.append(&mut new_outpoints); + watch_outputs.append(&mut new_outputs); + } } // Find which on-chain events have reached their confirmation threshold. From be1955a2bdcc0c7409980849dc147a2c3d6a8d69 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 11:54:09 -0300 Subject: [PATCH 6/7] Queue holder commit once funding tx confirms Sets should_broadcast_commitment=true when funding confirms. Since we skip the initial broadcast when funding_seen_onchain is false, we need to queue it once funding actually hits the chain. --- lightning/src/chain/channelmonitor.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index bbf2e5f4506..6fcc12519ae 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -5363,6 +5363,7 @@ impl ChannelMonitorImpl { F::Target: FeeEstimator, L::Target: Logger, { + let funding_seen_before = self.funding_seen_onchain; let txn_matched = self.filter_block(txdata); if !self.funding_seen_onchain { @@ -5397,6 +5398,11 @@ impl ChannelMonitorImpl { let mut watch_outputs = Vec::new(); let mut claimable_outpoints = Vec::new(); + + if self.is_manual_broadcast && !funding_seen_before && self.funding_seen_onchain && self.holder_tx_signed + { + should_broadcast_commitment = true; + } 'tx_iter: for tx in &txn_matched { let txid = tx.compute_txid(); log_trace!(logger, "Transaction {} confirmed in block {}", txid , block_hash); From ea95a15e79fbc9387c602dbab4ad41b9c7bf4939 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 26 Sep 2025 11:54:50 -0300 Subject: [PATCH 7/7] Test manual broadcast tracking and holder commit flow Tests that holder commitment broadcasts are properly deferred until funding confirms, and that the full manual-funding flow works correctly. --- lightning/src/chain/channelmonitor.rs | 2 +- lightning/src/ln/functional_test_utils.rs | 60 +++++++ lightning/src/ln/functional_tests.rs | 197 ++++++++++++++++++++++ 3 files changed, 258 insertions(+), 1 deletion(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 6fcc12519ae..f49764b657f 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -7373,7 +7373,7 @@ mod tests { let monitor = ChannelMonitor::new( Secp256k1::new(), keys, Some(shutdown_script.into_inner()), 0, &ScriptBuf::new(), &channel_parameters, true, 0, HolderCommitmentTransaction::dummy(0, funding_outpoint, Vec::new()), - best_block, dummy_key, channel_id, false + best_block, dummy_key, channel_id, false, ); let chan_id = monitor.inner.lock().unwrap().channel_id(); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index e58a7582e28..17ac959be64 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1804,6 +1804,66 @@ pub fn create_chan_between_nodes_with_value_a<'a, 'b, 'c: 'd, 'd>( (msgs, chan_id, tx) } +pub fn create_channel_manual_funding<'a, 'b, 'c: 'd, 'd>( + nodes: &'a Vec>, initiator: usize, counterparty: usize, channel_value: u64, + push_msat: u64, +) -> (ChannelId, Transaction, OutPoint) { + let node_a = &nodes[initiator]; + let node_b = &nodes[counterparty]; + let node_a_id = node_a.node.get_our_node_id(); + let node_b_id = node_b.node.get_our_node_id(); + + let temp_channel_id = exchange_open_accept_chan(node_a, node_b, channel_value, push_msat); + + let (funding_temp_id, funding_tx, funding_outpoint) = + create_funding_transaction(node_a, &node_b_id, channel_value, 42); + assert_eq!(temp_channel_id, funding_temp_id); + + node_a + .node + .funding_transaction_generated_manual_broadcast( + funding_temp_id, + node_b_id, + funding_tx.clone(), + ) + .unwrap(); + check_added_monitors!(node_a, 0); + + let funding_created = get_event_msg!(node_a, MessageSendEvent::SendFundingCreated, node_b_id); + node_b.node.handle_funding_created(node_a_id, &funding_created); + check_added_monitors!(node_b, 1); + let channel_id_b = expect_channel_pending_event(node_b, &node_a_id); + + let funding_signed = get_event_msg!(node_b, MessageSendEvent::SendFundingSigned, node_a_id); + node_a.node.handle_funding_signed(node_b_id, &funding_signed); + check_added_monitors!(node_a, 1); + + let events = node_a.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + let funding_txid = funding_tx.compute_txid(); + let mut channel_id = None; + for event in events { + match event { + Event::FundingTxBroadcastSafe { funding_txo, counterparty_node_id, .. } => { + assert_eq!(counterparty_node_id, node_b_id); + assert_eq!(funding_txo.txid, funding_txid); + assert_eq!(funding_txo.vout, u32::from(funding_outpoint.index)); + }, + Event::ChannelPending { channel_id: pending_id, counterparty_node_id, .. } => { + assert_eq!(counterparty_node_id, node_b_id); + channel_id = Some(pending_id); + }, + _ => panic!("Unexpected event"), + } + } + let channel_id = channel_id.expect("channel pending event missing"); + assert_eq!(channel_id, channel_id_b); + + assert!(node_a.tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty()); + + (channel_id, funding_tx, funding_outpoint) +} + pub fn create_chan_between_nodes_with_value_b<'a, 'b, 'c>( node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, as_funding_msgs: &(msgs::ChannelReady, msgs::AnnouncementSignatures), diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index d79b3074cc7..a1863cf32e6 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -20,6 +20,7 @@ use crate::chain::channelmonitor::{ }; use crate::chain::transaction::OutPoint; use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch}; +use crate::events::bump_transaction::BumpTransactionEvent; use crate::events::{ ClosureReason, Event, HTLCHandlingFailureType, PathFailure, PaymentFailureReason, PaymentPurpose, @@ -9628,6 +9629,202 @@ pub fn test_remove_expired_inbound_unfunded_channels() { check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 100000); } +#[test] +fn test_manual_broadcast_skips_commitment_until_funding_seen() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + let (channel_id, funding_tx, funding_outpoint) = + create_channel_manual_funding(&nodes, 0, 1, 100_000, 10_000); + + nodes[0] + .node + .force_close_broadcasting_latest_txn(&channel_id, &node_b_id, "manual close".to_owned()) + .unwrap(); + check_added_monitors!(&nodes[0], 1); + check_added_monitors!(&nodes[1], 0); + + assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty()); + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match &events[0] { + Event::ChannelClosed { + reason: ClosureReason::HolderForceClosed { broadcasted_latest_txn, message }, + counterparty_node_id: Some(id), + .. + } => { + assert_eq!(*broadcasted_latest_txn, Some(true)); + assert_eq!(message, "manual close"); + assert_eq!(id, &node_b_id); + }, + _ => panic!("Unexpected event"), + } + nodes[1].node.get_and_clear_pending_events(); + + let monitor_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert!(monitor_events.is_empty()); + + confirm_transaction(&nodes[0], &funding_tx); + confirm_transaction(&nodes[1], &funding_tx); + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); + + { + let monitor = get_monitor!(&nodes[0], channel_id); + // manual override + monitor.broadcast_latest_holder_commitment_txn( + &nodes[0].tx_broadcaster, + &nodes[0].fee_estimator, + &nodes[0].logger, + ); + } + let funding_txid = funding_tx.compute_txid(); + let broadcasts = nodes[0].tx_broadcaster.txn_broadcast(); + assert!(!broadcasts.is_empty()); + let commitment_tx = broadcasts + .iter() + .find(|tx| { + tx.input.iter().any(|input| { + input.previous_output.txid == funding_txid + && input.previous_output.vout == u32::from(funding_outpoint.index) + }) + }) + .expect("commitment transaction not broadcast"); + check_spends!(commitment_tx, funding_tx); + assert_eq!(commitment_tx.input.len(), 1); + let commitment_input = &commitment_tx.input[0]; + assert_eq!(commitment_input.previous_output.txid, funding_txid); + assert_eq!(commitment_input.previous_output.vout, u32::from(funding_outpoint.index)); + + let monitor_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert!(monitor_events.iter().all(|event| !matches!(event, Event::BumpTransaction(_)))); + assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); + assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); +} + +#[test] +fn test_manual_broadcast_detects_funding_and_broadcasts_on_timeout() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + let (channel_id, funding_tx, funding_outpoint) = + create_channel_manual_funding(&nodes, 0, 1, 100_000, 10_000); + + let funding_msgs = + create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &funding_tx); + let confirmed_channel_id = funding_msgs.1; + assert_eq!(confirmed_channel_id, channel_id); + let _announcements = + create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &funding_msgs.0); + + let usable_channels = nodes[0].node.list_usable_channels(); + assert_eq!(usable_channels.len(), 1); + assert_eq!(usable_channels[0].channel_id, channel_id); + + let (_payment_preimage, _payment_hash, _payment_secret, _payment_id) = + route_payment(&nodes[0], &[&nodes[1]], 10_000_000); + nodes[1].node.get_and_clear_pending_events(); + + connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); + connect_blocks(&nodes[1], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); + + let events = nodes[0].node.get_and_clear_pending_events(); + assert!(events.iter().any(|event| matches!( + event, + Event::ChannelClosed { + reason: ClosureReason::HTLCsTimedOut { .. }, + counterparty_node_id: Some(id), + .. + } if id == &node_b_id + ))); + nodes[1].node.get_and_clear_pending_events(); + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); + check_added_monitors!(&nodes[0], 1); + check_added_monitors!(&nodes[1], 0); + + let funding_txid = funding_tx.compute_txid(); + let broadcasts = nodes[0].tx_broadcaster.txn_broadcast(); + assert!(!broadcasts.is_empty()); + let commitment_tx = broadcasts + .iter() + .find(|tx| { + tx.input.iter().any(|input| { + input.previous_output.txid == funding_txid + && input.previous_output.vout == u32::from(funding_outpoint.index) + }) + }) + .expect("commitment transaction not broadcast"); + check_spends!(commitment_tx, funding_tx); + assert_eq!(commitment_tx.input.len(), 1); + + for event in nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events() { + if let Event::BumpTransaction(bump_event) = event { + if let BumpTransactionEvent::ChannelClose { + channel_id: event_channel_id, + counterparty_node_id, + .. + } = &bump_event + { + assert_eq!(*event_channel_id, channel_id); + assert_eq!(*counterparty_node_id, node_b_id); + } + nodes[0].bump_tx_handler.handle_event(&bump_event); + } + } +} + +#[test] +fn test_manual_broadcast_no_bump_events_before_funding_seen() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + let (channel_id, _, _) = create_channel_manual_funding(&nodes, 0, 1, 100_000, 10_000); + + nodes[0] + .node + .force_close_broadcasting_latest_txn(&channel_id, &node_b_id, "manual close".to_owned()) + .unwrap(); + check_added_monitors!(&nodes[0], 1); + check_added_monitors!(&nodes[1], 0); + + assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty()); + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match &events[0] { + Event::ChannelClosed { + reason: ClosureReason::HolderForceClosed { broadcasted_latest_txn, message }, + counterparty_node_id: Some(id), + .. + } => { + assert_eq!(*broadcasted_latest_txn, Some(true)); + assert_eq!(message, "manual close"); + assert_eq!(id, &node_b_id); + }, + _ => panic!("Unexpected event"), + } + nodes[1].node.get_and_clear_pending_events(); + + let monitor_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert!(monitor_events.iter().all(|event| !matches!(event, Event::BumpTransaction(_)))); +} + fn do_test_multi_post_event_actions(do_reload: bool) { // Tests handling multiple post-Event actions at once. // There is specific code in ChannelManager to handle channels where multiple post-Event