From: Matt Corallo Date: Mon, 23 Nov 2020 18:34:31 +0000 (-0500) Subject: Don't create chan-closed mon update for outbound never-signed chans X-Git-Tag: v0.0.12~3^2 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=refs%2Fheads%2F2020-11-dup-chan-id-crash;p=rust-lightning Don't create chan-closed mon update for outbound never-signed chans Like the previous commit for channel-closed monitor updates for inbound channels during processing of a funding_created message, this resolves a more general issue for closing outbound channels which have sent a funding_created but not yet received a funding_signed. This issue was also detected by full_stack_target. To make similar issues easier to detect in testing and fuzzing, an additional assertion is added to panic on updates to a channel monitor before registering it. --- diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 240084af5..13a0a8831 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -194,6 +194,13 @@ where C::Target: chain::Filter, match monitors.get_mut(&funding_txo) { None => { log_error!(self.logger, "Failed to update channel monitor: no such monitor registered"); + + // We should never ever trigger this from within ChannelManager. Technically a + // user could use this object with some proxying in between which makes this + // possible, but in tests and fuzzing, this should be a panic. + #[cfg(any(test, feature = "fuzztarget"))] + panic!("ChannelManager generated a channel update for a channel that was not yet registered!"); + #[cfg(not(any(test, feature = "fuzztarget")))] Err(ChannelMonitorUpdateErr::PermanentFailure) }, Some(orig_monitor) => { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index c34fc6a38..abf2834c2 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -4016,11 +4016,23 @@ impl Channel { _ => {} } } + let funding_txo = if let Some(funding_txo) = self.funding_txo { + // If we haven't yet exchanged funding signatures (ie channel_state < FundingSent), + // returning a channel monitor update here would imply a channel monitor update before + // we even registered the channel monitor to begin with, which is invalid. + // Thus, if we aren't actually at a point where we could conceivably broadcast the + // funding transaction, don't return a funding txo (which prevents providing the + // monitor update to the user, even if we return one). + // See test_duplicate_chan_id and test_pre_lockin_no_chan_closed_update for more. + if self.channel_state & (ChannelState::FundingSent as u32 | ChannelState::ChannelFunded as u32 | ChannelState::ShutdownComplete as u32) != 0 { + Some(funding_txo.clone()) + } else { None } + } else { None }; self.channel_state = ChannelState::ShutdownComplete as u32; self.update_time_counter += 1; self.latest_monitor_update_id += 1; - (self.funding_txo.clone(), ChannelMonitorUpdate { + (funding_txo, ChannelMonitorUpdate { update_id: self.latest_monitor_update_id, updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast }], }, dropped_outbound_htlcs) diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 3916a07de..85dae7698 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -8457,6 +8457,43 @@ fn test_concurrent_monitor_claim() { } } +#[test] +fn test_pre_lockin_no_chan_closed_update() { + // Test that if a peer closes a channel in response to a funding_created message we don't + // generate a channel update (as the channel cannot appear on chain without a funding_signed + // message). + // + // Doing so would imply a channel monitor update before the initial channel monitor + // registration, violating our API guarantees. + // + // Previously, full_stack_target managed to hit this case by opening then closing a channel, + // then opening a second channel with the same funding output as the first (which is not + // rejected because the first channel does not exist in the ChannelManager) and closing it + // before receiving funding_signed. + 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); + + // Create an initial channel + nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap(); + let mut open_chan_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &open_chan_msg); + let accept_chan_msg = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); + nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &accept_chan_msg); + + // Move the first channel through the funding flow... + let (temporary_channel_id, _tx, funding_output) = create_funding_transaction(&nodes[0], 100000, 42); + + nodes[0].node.funding_transaction_generated(&temporary_channel_id, funding_output); + check_added_monitors!(nodes[0], 0); + + let funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); + let channel_id = ::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }.to_channel_id(); + nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id, data: "Hi".to_owned() }); + assert!(nodes[0].chain_monitor.added_monitors.lock().unwrap().is_empty()); +} + #[test] fn test_htlc_no_detection() { // This test is a mutation to underscore the detection logic bug we had