Skip to content

Commit

Permalink
Fix handling of duplicate initial ChannelMonitor writing
Browse files Browse the repository at this point in the history
In e06484b, 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 574c77e, 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.
  • Loading branch information
TheBlueMatt committed Dec 29, 2023
1 parent 5eea305 commit 7bc2a14
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 1 deletion.
7 changes: 6 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
},
Expand Down
40 changes: 40 additions & 0 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 7bc2a14

Please sign in to comment.