Skip to content

Commit 5710e1f

Browse files
committed
Send bogus ChannelReestablish for unknown channels
Unfortunately, lnd doesn't force close on errors (https://github.com/lightningnetwork/lnd/blob/abb1e3463f3a83bbb843d5c399869dbe930ad94f/htlcswitch/link.go#L2119). One of the few ways to get an lnd counterparty to force close is by replicating what they do when restoring static channel backups (SCBs). They send an invalid `ChannelReestablish` with `0` commitment numbers and an invalid `your_last_per_commitment_secret`. Since we received a `ChannelReestablish` for a channel that doesn't exist, we can assume it's likely the channel closed from our point of view, but it remains open on the counterparty's side. By sending this bogus `ChannelReestablish` message now as a response to theirs, we trigger them to force close broadcasting their latest state. If the closing transaction from our point of view remains unconfirmed, it'll enter a race with the counterparty's to-be-broadcast latest commitment transaction.
1 parent 989304e commit 5710e1f

File tree

4 files changed

+111
-19
lines changed

4 files changed

+111
-19
lines changed

lightning/src/ln/channelmanager.rs

+96-2
Original file line numberDiff line numberDiff line change
@@ -6785,7 +6785,10 @@ where
67856785
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
67866786
.ok_or_else(|| {
67876787
debug_assert!(false);
6788-
MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id)
6788+
MsgHandleErrInternal::send_err_msg_no_close(
6789+
format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id),
6790+
msg.channel_id
6791+
)
67896792
})?;
67906793
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
67916794
let peer_state = &mut *peer_state_lock;
@@ -6829,7 +6832,41 @@ where
68296832
"Got a channel_reestablish message for an unfunded channel!".into())), chan_phase_entry);
68306833
}
68316834
},
6832-
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
6835+
hash_map::Entry::Vacant(_) => {
6836+
log_debug!(self.logger, "Sending bogus ChannelReestablish for unknown channel {} to force channel closure",
6837+
log_bytes!(msg.channel_id.0));
6838+
// Unfortunately, lnd doesn't force close on errors
6839+
// (https://github.com/lightningnetwork/lnd/blob/abb1e3463f3a83bbb843d5c399869dbe930ad94f/htlcswitch/link.go#L2119).
6840+
// One of the few ways to get an lnd counterparty to force close is by
6841+
// replicating what they do when restoring static channel backups (SCBs). They
6842+
// send an invalid `ChannelReestablish` with `0` commitment numbers and an
6843+
// invalid `your_last_per_commitment_secret`.
6844+
//
6845+
// Since we received a `ChannelReestablish` for a channel that doesn't exist, we
6846+
// can assume it's likely the channel closed from our point of view, but it
6847+
// remains open on the counterparty's side. By sending this bogus
6848+
// `ChannelReestablish` message now as a response to theirs, we trigger them to
6849+
// force close broadcasting their latest state. If the closing transaction from
6850+
// our point of view remains unconfirmed, it'll enter a race with the
6851+
// counterparty's to-be-broadcast latest commitment transaction.
6852+
peer_state.pending_msg_events.push(MessageSendEvent::SendChannelReestablish {
6853+
node_id: *counterparty_node_id,
6854+
msg: msgs::ChannelReestablish {
6855+
channel_id: msg.channel_id,
6856+
next_local_commitment_number: 0,
6857+
next_remote_commitment_number: 0,
6858+
your_last_per_commitment_secret: [1u8; 32],
6859+
my_current_per_commitment_point: PublicKey::from_secret_key(
6860+
&self.secp_ctx, &SecretKey::from_slice(&[1u8; 32]).unwrap()
6861+
),
6862+
next_funding_txid: None,
6863+
},
6864+
});
6865+
return Err(MsgHandleErrInternal::send_err_msg_no_close(
6866+
format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}",
6867+
counterparty_node_id), msg.channel_id)
6868+
)
6869+
}
68336870
}
68346871
};
68356872

@@ -11219,6 +11256,63 @@ mod tests {
1121911256
let payment_preimage = PaymentPreimage([42; 32]);
1122011257
assert_eq!(format!("{}", &payment_preimage), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
1122111258
}
11259+
11260+
#[test]
11261+
fn test_trigger_lnd_force_close() {
11262+
let chanmon_cfg = create_chanmon_cfgs(2);
11263+
let node_cfg = create_node_cfgs(2, &chanmon_cfg);
11264+
let user_config = test_default_channel_config();
11265+
let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[Some(user_config), Some(user_config)]);
11266+
let nodes = create_network(2, &node_cfg, &node_chanmgr);
11267+
11268+
// Open a channel, immediately disconnect each other, and broadcast Alice's latest state.
11269+
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
11270+
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
11271+
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
11272+
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
11273+
check_closed_broadcast(&nodes[0], 1, true);
11274+
check_added_monitors(&nodes[0], 1);
11275+
check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
11276+
{
11277+
let txn = nodes[0].tx_broadcaster.txn_broadcast();
11278+
assert_eq!(txn.len(), 1);
11279+
check_spends!(txn[0], funding_tx);
11280+
}
11281+
11282+
// Since they're disconnected, Bob won't receive Alice's `Error` message. Reconnect them
11283+
// such that Bob sends a `ChannelReestablish` to Alice since the channel is still open from
11284+
// their side.
11285+
nodes[1].node.peer_connected(&nodes[0].node.get_our_node_id(), &msgs::Init {
11286+
features: nodes[0].node.init_features(), networks: None, remote_network_address: None
11287+
}, false).unwrap();
11288+
let channel_reestablish = get_event_msg!(
11289+
nodes[1], MessageSendEvent::SendChannelReestablish, nodes[0].node.get_our_node_id()
11290+
);
11291+
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &channel_reestablish);
11292+
11293+
// Alice should respond with an error since the channel isn't known, but a bogus
11294+
// `ChannelReestablish` should be sent first, such that we actually trigger Bob to force
11295+
// close even if it was an lnd node.
11296+
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
11297+
assert_eq!(msg_events.len(), 2);
11298+
if let MessageSendEvent::SendChannelReestablish { node_id, msg } = &msg_events[0] {
11299+
assert_eq!(*node_id, nodes[1].node.get_our_node_id());
11300+
assert_eq!(msg.next_local_commitment_number, 0);
11301+
assert_eq!(msg.next_remote_commitment_number, 0);
11302+
nodes[1].node.handle_channel_reestablish(&nodes[0].node.get_our_node_id(), &msg);
11303+
} else { panic!() };
11304+
check_closed_broadcast(&nodes[1], 1, true);
11305+
check_added_monitors(&nodes[1], 1);
11306+
let expected_close_reason = ClosureReason::ProcessingError {
11307+
err: "Peer sent a garbage channel_reestablish (usually an lnd node with lost state asking us to force-close for them)".to_string()
11308+
};
11309+
check_closed_event!(nodes[1], 1, expected_close_reason, [nodes[0].node.get_our_node_id()], 100000);
11310+
{
11311+
let txn = nodes[1].tx_broadcaster.txn_broadcast();
11312+
assert_eq!(txn.len(), 1);
11313+
check_spends!(txn[0], funding_tx);
11314+
}
11315+
}
1122211316
}
1122311317

1122411318
#[cfg(ldk_bench)]

lightning/src/ln/payment_tests.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -706,8 +706,8 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) {
706706
let bs_reestablish = get_chan_reestablish_msgs!(nodes[1], nodes[0]).pop().unwrap();
707707
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
708708
let as_err = nodes[0].node.get_and_clear_pending_msg_events();
709-
assert_eq!(as_err.len(), 1);
710-
match as_err[0] {
709+
assert_eq!(as_err.len(), 2);
710+
match as_err[1] {
711711
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
712712
assert_eq!(node_id, nodes[1].node.get_our_node_id());
713713
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), msg);
@@ -881,9 +881,9 @@ fn do_test_completed_payment_not_retryable_on_reload(use_dust: bool) {
881881
let bs_reestablish = get_chan_reestablish_msgs!(nodes[1], nodes[0]).pop().unwrap();
882882
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
883883
let as_err = nodes[0].node.get_and_clear_pending_msg_events();
884-
assert_eq!(as_err.len(), 1);
884+
assert_eq!(as_err.len(), 2);
885885
let bs_commitment_tx;
886-
match as_err[0] {
886+
match as_err[1] {
887887
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
888888
assert_eq!(node_id, nodes[1].node.get_our_node_id());
889889
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), msg);

lightning/src/ln/reload_tests.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -589,18 +589,16 @@ fn do_test_data_loss_protect(reconnect_panicing: bool) {
589589

590590
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &retry_reestablish[0]);
591591
let mut err_msgs_0 = Vec::with_capacity(1);
592-
for msg in nodes[0].node.get_and_clear_pending_msg_events() {
593-
if let MessageSendEvent::HandleError { ref action, .. } = msg {
594-
match action {
595-
&ErrorAction::SendErrorMessage { ref msg } => {
596-
assert_eq!(msg.data, format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()));
597-
err_msgs_0.push(msg.clone());
598-
},
599-
_ => panic!("Unexpected event!"),
600-
}
601-
} else {
602-
panic!("Unexpected event!");
592+
if let MessageSendEvent::HandleError { ref action, .. } = nodes[0].node.get_and_clear_pending_msg_events()[1] {
593+
match action {
594+
&ErrorAction::SendErrorMessage { ref msg } => {
595+
assert_eq!(msg.data, format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()));
596+
err_msgs_0.push(msg.clone());
597+
},
598+
_ => panic!("Unexpected event!"),
603599
}
600+
} else {
601+
panic!("Unexpected event!");
604602
}
605603
assert_eq!(err_msgs_0.len(), 1);
606604
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), &err_msgs_0[0]);

lightning/src/ln/shutdown_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -623,8 +623,8 @@ fn do_test_shutdown_rebroadcast(recv_count: u8) {
623623

624624
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &node_1_2nd_reestablish);
625625
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
626-
assert_eq!(msg_events.len(), 1);
627-
if let MessageSendEvent::HandleError { ref action, .. } = msg_events[0] {
626+
assert_eq!(msg_events.len(), 2);
627+
if let MessageSendEvent::HandleError { ref action, .. } = msg_events[1] {
628628
match action {
629629
&ErrorAction::SendErrorMessage { ref msg } => {
630630
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), &msg);

0 commit comments

Comments
 (0)