diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 4acba6f4085..f49764b657f 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, @@ -2327,19 +2346,33 @@ 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] + /// + /// 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 + &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, + false, + ); } /// Unsafe test-only version of `broadcast_latest_holder_commitment_txn` used by our test framework @@ -3952,12 +3985,26 @@ 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] + /// 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, @@ -3969,6 +4016,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, @@ -4291,7 +4344,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()); @@ -5310,7 +5363,21 @@ 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 { + 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() { @@ -5331,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); @@ -5582,13 +5654,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. @@ -5826,7 +5901,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; @@ -5887,14 +5962,14 @@ 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); } } /// 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); @@ -6560,6 +6635,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), @@ -6580,6 +6657,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. @@ -6693,6 +6772,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, @@ -7029,7 +7112,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]); @@ -7290,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, + best_block, dummy_key, channel_id, false, ); let chan_id = monitor.inner.lock().unwrap().channel_id(); 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(), 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