Skip to content

Commit 8f02430

Browse files
authored
Merge pull request #2507 from TheBlueMatt/2023-08-lnd-6039
Work around LND bug 6039
2 parents 2b898a3 + b149279 commit 8f02430

File tree

5 files changed

+109
-8
lines changed

5 files changed

+109
-8
lines changed

lightning/src/events/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,7 @@ impl MaybeReadable for Event {
15511551
/// broadcast to most peers).
15521552
/// These events are handled by PeerManager::process_events if you are using a PeerManager.
15531553
#[derive(Clone, Debug)]
1554+
#[cfg_attr(test, derive(PartialEq))]
15541555
pub enum MessageSendEvent {
15551556
/// Used to indicate that we've accepted a channel open and should send the accept_channel
15561557
/// message provided to the given peer.

lightning/src/ln/channel.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -3835,6 +3835,17 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
38353835
}
38363836
}
38373837

3838+
/// Gets the `Shutdown` message we should send our peer on reconnect, if any.
3839+
pub fn get_outbound_shutdown(&self) -> Option<msgs::Shutdown> {
3840+
if self.context.channel_state & (ChannelState::LocalShutdownSent as u32) != 0 {
3841+
assert!(self.context.shutdown_scriptpubkey.is_some());
3842+
Some(msgs::Shutdown {
3843+
channel_id: self.context.channel_id,
3844+
scriptpubkey: self.get_closing_scriptpubkey(),
3845+
})
3846+
} else { None }
3847+
}
3848+
38383849
/// May panic if some calls other than message-handling calls (which will all Err immediately)
38393850
/// have been called between remove_uncommitted_htlcs_and_mark_paused and this call.
38403851
///
@@ -3901,13 +3912,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
39013912
self.context.channel_state &= !(ChannelState::PeerDisconnected as u32);
39023913
self.context.sent_message_awaiting_response = None;
39033914

3904-
let shutdown_msg = if self.context.channel_state & (ChannelState::LocalShutdownSent as u32) != 0 {
3905-
assert!(self.context.shutdown_scriptpubkey.is_some());
3906-
Some(msgs::Shutdown {
3907-
channel_id: self.context.channel_id,
3908-
scriptpubkey: self.get_closing_scriptpubkey(),
3909-
})
3910-
} else { None };
3915+
let shutdown_msg = self.get_outbound_shutdown();
39113916

39123917
let announcement_sigs = self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block.height(), logger);
39133918

lightning/src/ln/channelmanager.rs

+40
Original file line numberDiff line numberDiff line change
@@ -7546,6 +7546,46 @@ where
75467546
fn handle_error(&self, counterparty_node_id: &PublicKey, msg: &msgs::ErrorMessage) {
75477547
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
75487548

7549+
match &msg.data as &str {
7550+
"cannot co-op close channel w/ active htlcs"|
7551+
"link failed to shutdown" =>
7552+
{
7553+
// LND hasn't properly handled shutdown messages ever, and force-closes any time we
7554+
// send one while HTLCs are still present. The issue is tracked at
7555+
// https://github.com/lightningnetwork/lnd/issues/6039 and has had multiple patches
7556+
// to fix it but none so far have managed to land upstream. The issue appears to be
7557+
// very low priority for the LND team despite being marked "P1".
7558+
// We're not going to bother handling this in a sensible way, instead simply
7559+
// repeating the Shutdown message on repeat until morale improves.
7560+
if msg.channel_id != [0; 32] {
7561+
let per_peer_state = self.per_peer_state.read().unwrap();
7562+
let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
7563+
if peer_state_mutex_opt.is_none() { return; }
7564+
let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap();
7565+
if let Some(chan) = peer_state.channel_by_id.get(&msg.channel_id) {
7566+
if let Some(msg) = chan.get_outbound_shutdown() {
7567+
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
7568+
node_id: *counterparty_node_id,
7569+
msg,
7570+
});
7571+
}
7572+
peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError {
7573+
node_id: *counterparty_node_id,
7574+
action: msgs::ErrorAction::SendWarningMessage {
7575+
msg: msgs::WarningMessage {
7576+
channel_id: msg.channel_id,
7577+
data: "You appear to be exhibiting LND bug 6039, we'll keep sending you shutdown messages until you handle them correctly".to_owned()
7578+
},
7579+
log_level: Level::Trace,
7580+
}
7581+
});
7582+
}
7583+
}
7584+
return;
7585+
}
7586+
_ => {}
7587+
}
7588+
75497589
if msg.channel_id == [0; 32] {
75507590
let channel_ids: Vec<[u8; 32]> = {
75517591
let per_peer_state = self.per_peer_state.read().unwrap();

lightning/src/ln/msgs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ enum EncodingType {
11441144
}
11451145

11461146
/// Used to put an error message in a [`LightningError`].
1147-
#[derive(Clone, Debug)]
1147+
#[derive(Clone, Debug, PartialEq)]
11481148
pub enum ErrorAction {
11491149
/// The peer took some action which made us think they were useless. Disconnect them.
11501150
DisconnectPeer {

lightning/src/ln/shutdown_tests.rs

+55
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,61 @@ fn expect_channel_shutdown_state_with_htlc() {
198198
assert!(nodes[0].node.list_channels().is_empty());
199199
}
200200

201+
#[test]
202+
fn test_lnd_bug_6039() {
203+
// LND sends a nonsense error message any time it gets a shutdown if there are still HTLCs
204+
// pending. We currently swallow that error to work around LND's bug #6039. This test emulates
205+
// the LND nonsense and ensures we at least kinda handle it.
206+
let chanmon_cfgs = create_chanmon_cfgs(2);
207+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
208+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
209+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
210+
let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
211+
212+
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
213+
214+
nodes[0].node.close_channel(&chan.2, &nodes[1].node.get_our_node_id()).unwrap();
215+
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
216+
nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
217+
218+
// Generate an lnd-like error message and check that we respond by simply screaming louder to
219+
// see if LND will accept our protocol compliance.
220+
let err_msg = msgs::ErrorMessage { channel_id: chan.2, data: "link failed to shutdown".to_string() };
221+
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &err_msg);
222+
let node_a_responses = nodes[0].node.get_and_clear_pending_msg_events();
223+
assert_eq!(node_a_responses[0], MessageSendEvent::SendShutdown {
224+
node_id: nodes[1].node.get_our_node_id(),
225+
msg: node_0_shutdown,
226+
});
227+
if let MessageSendEvent::HandleError { action: msgs::ErrorAction::SendWarningMessage { .. }, .. }
228+
= node_a_responses[1] {} else { panic!(); }
229+
230+
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
231+
232+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
233+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
234+
235+
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
236+
237+
// Assume that LND will eventually respond to our Shutdown if we clear all the remaining HTLCs
238+
nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
239+
240+
// ClosingSignNegotion process
241+
let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
242+
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
243+
let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
244+
nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
245+
let (_, node_0_2nd_closing_signed) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
246+
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
247+
let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
248+
assert!(node_1_none.is_none());
249+
check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
250+
check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
251+
252+
// Shutdown basically removes the channelDetails, testing of shutdowncomplete state unnecessary
253+
assert!(nodes[0].node.list_channels().is_empty());
254+
}
255+
201256
#[test]
202257
fn expect_channel_shutdown_state_with_force_closure() {
203258
// Test sending a shutdown prior to channel_ready after funding generation

0 commit comments

Comments
 (0)