Skip to content

Commit f918c0e

Browse files
committed
Do not panic if a peer learns our funding info before we fund
We'd previously assumed that LDK would receive `funding_transaction_generated` prior to our peer learning the txid and panicked if the peer tried to open a redundant channel to us with the same funding outpoint. While this assumption is generally safe, some users may have out-of-band protocols where they notify their LSP about a funding outpoint first, or this may be violated in the future with collaborative transaction construction protocols, i.e. the upcoming dual-funding protocol.
1 parent 0f06f82 commit f918c0e

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3738,7 +3738,7 @@ where
37383738
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
37393739
let peer_state = &mut *peer_state_lock;
37403740
let funding_txo;
3741-
let (chan, msg_opt) = match peer_state.channel_by_id.remove(temporary_channel_id) {
3741+
let (mut chan, msg_opt) = match peer_state.channel_by_id.remove(temporary_channel_id) {
37423742
Some(ChannelPhase::UnfundedOutboundV1(mut chan)) => {
37433743
funding_txo = find_funding_output(&chan, &funding_transaction)?;
37443744

@@ -3788,8 +3788,20 @@ where
37883788
},
37893789
hash_map::Entry::Vacant(e) => {
37903790
let mut outpoint_to_peer = self.outpoint_to_peer.lock().unwrap();
3791-
if outpoint_to_peer.insert(funding_txo, chan.context.get_counterparty_node_id()).is_some() {
3792-
panic!("outpoint_to_peer map already contained funding outpoint, which shouldn't be possible");
3791+
match outpoint_to_peer.entry(funding_txo) {
3792+
hash_map::Entry::Vacant(e) => { e.insert(chan.context.get_counterparty_node_id()); },
3793+
hash_map::Entry::Occupied(o) => {
3794+
let err = format!(
3795+
"An existing channel using outpoint {} is open with peer {}",
3796+
funding_txo, o.get()
3797+
);
3798+
mem::drop(outpoint_to_peer);
3799+
mem::drop(peer_state_lock);
3800+
mem::drop(per_peer_state);
3801+
let reason = ClosureReason::ProcessingError { err: err.clone() };
3802+
self.finish_close_channel(chan.context.force_shutdown(true, reason));
3803+
return Err(APIError::ChannelUnavailable { err });
3804+
}
37933805
}
37943806
e.insert(ChannelPhase::UnfundedOutboundV1(chan));
37953807
}

lightning/src/ln/functional_test_utils.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1218,7 +1218,7 @@ pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(initiator: &'a Node<'b, 'c, 'd>, r
12181218
(tx, as_channel_ready.channel_id)
12191219
}
12201220

1221-
pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64) -> Transaction {
1221+
pub fn exchange_open_accept_chan<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64) -> ChannelId {
12221222
let create_chan_id = node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None, None).unwrap();
12231223
let open_channel_msg = get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id());
12241224
assert_eq!(open_channel_msg.temporary_channel_id, create_chan_id);
@@ -1238,6 +1238,11 @@ pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, '
12381238
node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), &accept_channel_msg);
12391239
assert_ne!(node_b.node.list_channels().iter().find(|channel| channel.channel_id == create_chan_id).unwrap().user_channel_id, 0);
12401240

1241+
create_chan_id
1242+
}
1243+
1244+
pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64) -> Transaction {
1245+
let create_chan_id = exchange_open_accept_chan(node_a, node_b, channel_value, push_msat);
12411246
sign_funding_transaction(node_a, node_b, channel_value, create_chan_id)
12421247
}
12431248

lightning/src/ln/functional_tests.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8977,6 +8977,61 @@ fn test_duplicate_temporary_channel_id_from_different_peers() {
89778977
}
89788978
}
89798979

8980+
#[test]
8981+
fn test_peer_funding_sidechannel() {
8982+
// Test that if a peer somehow learns which txid we'll use for our channel funding before we
8983+
// receive `funding_transaction_generated` the peer cannot cause us to crash. We'd previously
8984+
// assumed that LDK would receive `funding_transaction_generated` prior to our peer learning
8985+
// the txid and panicked if the peer tried to open a redundant channel to us with the same
8986+
// funding outpoint.
8987+
//
8988+
// While this assumption is generally safe, some users may have out-of-band protocols where
8989+
// they notify their LSP about a funding outpoint first, or this may be violated in the future
8990+
// with collaborative transaction construction protocols, i.e. dual-funding.
8991+
let chanmon_cfgs = create_chanmon_cfgs(3);
8992+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
8993+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
8994+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
8995+
8996+
let temp_chan_id_ab = exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0);
8997+
let temp_chan_id_ca = exchange_open_accept_chan(&nodes[2], &nodes[0], 1_000_000, 0);
8998+
8999+
let (_, tx, funding_output) =
9000+
create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
9001+
9002+
let cs_funding_events = nodes[2].node.get_and_clear_pending_events();
9003+
assert_eq!(cs_funding_events.len(), 1);
9004+
match cs_funding_events[0] {
9005+
Event::FundingGenerationReady { .. } => {}
9006+
_ => panic!("Unexpected event {:?}", cs_funding_events),
9007+
}
9008+
9009+
nodes[2].node.funding_transaction_generated_unchecked(&temp_chan_id_ca, &nodes[0].node.get_our_node_id(), tx.clone(), funding_output.index).unwrap();
9010+
let funding_created_msg = get_event_msg!(nodes[2], MessageSendEvent::SendFundingCreated, nodes[0].node.get_our_node_id());
9011+
nodes[0].node.handle_funding_created(&nodes[2].node.get_our_node_id(), &funding_created_msg);
9012+
get_event_msg!(nodes[0], MessageSendEvent::SendFundingSigned, nodes[2].node.get_our_node_id());
9013+
expect_channel_pending_event(&nodes[0], &nodes[2].node.get_our_node_id());
9014+
check_added_monitors!(nodes[0], 1);
9015+
9016+
let res = nodes[0].node.funding_transaction_generated(&temp_chan_id_ab, &nodes[1].node.get_our_node_id(), tx.clone());
9017+
let err_msg = format!("{:?}", res.unwrap_err());
9018+
assert!(err_msg.contains("An existing channel using outpoint "));
9019+
assert!(err_msg.contains(" is open with peer"));
9020+
// Even though the last funding_transaction_generated errored, it still generated a
9021+
// SendFundingCreated. However, when the peer responds with a funding_signed it will send the
9022+
// appropriate error message.
9023+
let as_funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
9024+
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &as_funding_created);
9025+
check_added_monitors!(nodes[1], 1);
9026+
expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
9027+
let reason = ClosureReason::ProcessingError { err: format!("An existing channel using outpoint {} is open with peer {}", funding_output, nodes[2].node.get_our_node_id()), };
9028+
check_closed_events(&nodes[0], &[ExpectedCloseEvent::from_id_reason(funding_output.to_channel_id(), true, reason)]);
9029+
9030+
let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
9031+
nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed);
9032+
get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id());
9033+
}
9034+
89809035
#[test]
89819036
fn test_duplicate_funding_err_in_funding() {
89829037
// Test that if we have a live channel with one peer, then another peer comes along and tries

0 commit comments

Comments
 (0)