Skip to content

Commit 7bc2a14

Browse files
committed
Fix handling of duplicate initial ChannelMonitor writing
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.
1 parent 5eea305 commit 7bc2a14

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

lightning/src/ln/channelmanager.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -6374,7 +6374,7 @@ where
63746374
let res =
63756375
chan.funding_signed(&msg, best_block, &self.signer_provider, &&logger);
63766376
match res {
6377-
Ok((chan, monitor)) => {
6377+
Ok((mut chan, monitor)) => {
63786378
if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) {
63796379
// We really should be able to insert here without doing a second
63806380
// lookup, but sadly rust stdlib doesn't currently allow keeping
@@ -6386,6 +6386,11 @@ where
63866386
Ok(())
63876387
} else {
63886388
let e = ChannelError::Close("Channel funding outpoint was a duplicate".to_owned());
6389+
// We weren't able to watch the channel to begin with, so no
6390+
// updates should be made on it. Previously, full_stack_target
6391+
// found an (unreachable) panic when the monitor update contained
6392+
// within `shutdown_finish` was applied.
6393+
chan.unset_funding_info(msg.channel_id);
63896394
return Err(convert_chan_phase_err!(self, e, &mut ChannelPhase::Funded(chan), &msg.channel_id).1);
63906395
}
63916396
},

lightning/src/ln/functional_tests.rs

+40
Original file line numberDiff line numberDiff line change
@@ -9032,6 +9032,46 @@ fn test_peer_funding_sidechannel() {
90329032
get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id());
90339033
}
90349034

9035+
#[test]
9036+
fn test_duplicate_conflicting_funding_from_second_peer() {
9037+
// Test that if a user tries to fund a channel with a funding outpoint they'd previously used
9038+
// we don't try to remove the previous ChannelMonitor. This is largely a test to ensure we
9039+
// don't regress in the fuzzer, as such funding getting passed our outpoint-matches checks
9040+
// implies the user (and our counterparty) has reused cryptographic keys across channels, which
9041+
// we require the user not do.
9042+
let chanmon_cfgs = create_chanmon_cfgs(4);
9043+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
9044+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
9045+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
9046+
9047+
let temp_chan_id = exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0);
9048+
9049+
let (_, tx, funding_output) =
9050+
create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
9051+
9052+
// Now that we have a funding outpoint, create a dummy `ChannelMonitor` and insert it into
9053+
// nodes[0]'s ChainMonitor so that the initial `ChannelMonitor` write fails.
9054+
let dummy_chan_id = create_chan_between_nodes(&nodes[2], &nodes[3]).3;
9055+
let dummy_monitor = get_monitor!(nodes[2], dummy_chan_id).clone();
9056+
nodes[0].chain_monitor.chain_monitor.watch_channel(funding_output, dummy_monitor).unwrap();
9057+
9058+
nodes[0].node.funding_transaction_generated(&temp_chan_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
9059+
9060+
let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
9061+
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
9062+
let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
9063+
check_added_monitors!(nodes[1], 1);
9064+
expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
9065+
9066+
nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg);
9067+
// At this point, the channel should be closed, after having generated one monitor write (the
9068+
// watch_channel call which failed), but zero monitor updates.
9069+
check_added_monitors!(nodes[0], 1);
9070+
get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id());
9071+
let err_reason = ClosureReason::ProcessingError { err: "Channel funding outpoint was a duplicate".to_owned() };
9072+
check_closed_events(&nodes[0], &[ExpectedCloseEvent::from_id_reason(funding_signed_msg.channel_id, true, err_reason)]);
9073+
}
9074+
90359075
#[test]
90369076
fn test_duplicate_funding_err_in_funding() {
90379077
// Test that if we have a live channel with one peer, then another peer comes along and tries

0 commit comments

Comments
 (0)