From: Matt Corallo Date: Mon, 25 Dec 2023 06:55:08 +0000 (+0000) Subject: Fix handling of duplicate initial `ChannelMonitor` writing X-Git-Tag: v0.0.120~13^2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=7bc2a148b2525913c9c6e70fe15449fe4835ff2e;p=rust-lightning Fix handling of duplicate initial `ChannelMonitor` writing In e06484b0f44155e647ff29810d2f187967e45813, we added specific handling for outbound-channel initial monitor updates failing - in such a case we have a counterparty who tried to open a second channel with the same funding info we just gave them, causing us to force-close our outbound channel as it shows up as duplicate-funding. Its largely harmless as it leads to a spurious force-closure of a channel with a peer doing something absurd, however it causes the `full_stack_target` fuzzer to fail. Sadly, in 574c77e7bc95fd8dea5a8058b6b35996cc99db8d, as we were dropping handling of `PermanentFailure` handling for updates, we accidentally dropped handling for initial updates as well. Here we fix the issue (again) and add a test. --- diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7fe996075..81b9d766d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6374,7 +6374,7 @@ where let res = chan.funding_signed(&msg, best_block, &self.signer_provider, &&logger); match res { - Ok((chan, monitor)) => { + Ok((mut chan, monitor)) => { if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { // We really should be able to insert here without doing a second // lookup, but sadly rust stdlib doesn't currently allow keeping @@ -6386,6 +6386,11 @@ where Ok(()) } else { let e = ChannelError::Close("Channel funding outpoint was a duplicate".to_owned()); + // We weren't able to watch the channel to begin with, so no + // updates should be made on it. Previously, full_stack_target + // found an (unreachable) panic when the monitor update contained + // within `shutdown_finish` was applied. + chan.unset_funding_info(msg.channel_id); return Err(convert_chan_phase_err!(self, e, &mut ChannelPhase::Funded(chan), &msg.channel_id).1); } }, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 9777ad75d..78207c57b 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -9032,6 +9032,46 @@ fn test_peer_funding_sidechannel() { get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id()); } +#[test] +fn test_duplicate_conflicting_funding_from_second_peer() { + // Test that if a user tries to fund a channel with a funding outpoint they'd previously used + // we don't try to remove the previous ChannelMonitor. This is largely a test to ensure we + // don't regress in the fuzzer, as such funding getting passed our outpoint-matches checks + // implies the user (and our counterparty) has reused cryptographic keys across channels, which + // we require the user not do. + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + let temp_chan_id = exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0); + + let (_, tx, funding_output) = + create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42); + + // Now that we have a funding outpoint, create a dummy `ChannelMonitor` and insert it into + // nodes[0]'s ChainMonitor so that the initial `ChannelMonitor` write fails. + let dummy_chan_id = create_chan_between_nodes(&nodes[2], &nodes[3]).3; + let dummy_monitor = get_monitor!(nodes[2], dummy_chan_id).clone(); + nodes[0].chain_monitor.chain_monitor.watch_channel(funding_output, dummy_monitor).unwrap(); + + nodes[0].node.funding_transaction_generated(&temp_chan_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap(); + + let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg); + let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id()); + check_added_monitors!(nodes[1], 1); + expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg); + // At this point, the channel should be closed, after having generated one monitor write (the + // watch_channel call which failed), but zero monitor updates. + check_added_monitors!(nodes[0], 1); + get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id()); + let err_reason = ClosureReason::ProcessingError { err: "Channel funding outpoint was a duplicate".to_owned() }; + check_closed_events(&nodes[0], &[ExpectedCloseEvent::from_id_reason(funding_signed_msg.channel_id, true, err_reason)]); +} + #[test] fn test_duplicate_funding_err_in_funding() { // Test that if we have a live channel with one peer, then another peer comes along and tries