+
+#[test]
+fn batch_funding_failure() {
+ // Provides test coverage of batch funding failure, which previously deadlocked
+ 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_a = exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0);
+ let temp_chan_id_b = exchange_open_accept_chan(&nodes[0], &nodes[2], 1_000_000, 0);
+
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 2);
+ // Build a transaction which only has the output for one of the two channels we're trying to
+ // confirm. Previously this led to a deadlock in channel closure handling.
+ let mut tx = Transaction { version: 2, lock_time: LockTime::ZERO, input: Vec::new(), output: Vec::new() };
+ let mut chans = Vec::new();
+ for (idx, ev) in events.iter().enumerate() {
+ if let Event::FundingGenerationReady { temporary_channel_id, counterparty_node_id, output_script, .. } = ev {
+ if idx == 0 {
+ tx.output.push(TxOut { value: 1_000_000, script_pubkey: output_script.clone() });
+ }
+ chans.push((temporary_channel_id, counterparty_node_id));
+ } else { panic!(); }
+ }
+
+ let err = "Error in transaction funding: Misuse error: No output matched the script_pubkey and value in the FundingGenerationReady event".to_string();
+ let temp_err = "No output matched the script_pubkey and value in the FundingGenerationReady event".to_string();
+ let post_funding_chan_id_a = ChannelId::v1_from_funding_txid(tx.txid().as_ref(), 0);
+ let close = [
+ ExpectedCloseEvent::from_id_reason(post_funding_chan_id_a, true, ClosureReason::ProcessingError { err: err.clone() }),
+ ExpectedCloseEvent::from_id_reason(temp_chan_id_b, false, ClosureReason::ProcessingError { err: temp_err }),
+ ];
+
+ nodes[0].node.batch_funding_transaction_generated(&chans, tx).unwrap_err();
+
+ let msgs = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msgs.len(), 3);
+ // We currently spuriously send `FundingCreated` for the first channel and then immediately
+ // fail both channels, which isn't ideal but should be fine.
+ assert!(msgs.iter().any(|msg| {
+ if let MessageSendEvent::HandleError { action: msgs::ErrorAction::SendErrorMessage {
+ msg: msgs::ErrorMessage { channel_id, .. }, ..
+ }, .. } = msg {
+ *channel_id == temp_chan_id_b
+ } else { false }
+ }));
+ let funding_created_pos = msgs.iter().position(|msg| {
+ if let MessageSendEvent::SendFundingCreated { msg: msgs::FundingCreated { temporary_channel_id, .. }, .. } = msg {
+ assert_eq!(*temporary_channel_id, temp_chan_id_a);
+ true
+ } else { false }
+ }).unwrap();
+ let funded_channel_close_pos = msgs.iter().position(|msg| {
+ if let MessageSendEvent::HandleError { action: msgs::ErrorAction::SendErrorMessage {
+ msg: msgs::ErrorMessage { channel_id, .. }, ..
+ }, .. } = msg {
+ *channel_id == post_funding_chan_id_a
+ } else { false }
+ }).unwrap();
+
+ // The error message uses the funded channel_id so must come after the funding_created
+ assert!(funded_channel_close_pos > funding_created_pos);
+
+ check_closed_events(&nodes[0], &close);
+ assert_eq!(nodes[0].node.list_channels().len(), 0);
+}