Skip to content

Commit 06364a7

Browse files
author
Antoine Riard
committed
Detect onchain timeout of a HTLC in ChannelManager block_connected
Pass failure backward Add test_htlc_on_chain_timeout
1 parent d1ff8eb commit 06364a7

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

src/ln/channelmanager.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,9 @@ impl ChainListener for ChannelManager {
25732573
if let Some(preimage) = htlc_data.0 {
25742574
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
25752575
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(htlc_source), preimage);
2576+
} else {
2577+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2578+
self.fail_htlc_backwards_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(htlc_source), &htlc_data.1, HTLCFailReason::Reason { failure_code: 0x1000 | 14, data: Vec::new() });
25762579
}
25772580
}
25782581
}
@@ -6066,6 +6069,117 @@ mod tests {
60666069
}
60676070
}
60686071

6072+
#[test]
6073+
fn test_htlc_on_chain_timeout() {
6074+
// Test that in case of an unilateral close onchain, we detect the state of output thanks to
6075+
// ChainWatchInterface and timeout the HTLC bacward accordingly. So here we test that ChannelManager is
6076+
// broadcasting the right event to other nodes in payment path.
6077+
// A ------------------> B ----------------------> C (timeout)
6078+
// A's commitment tx C's commitment tx
6079+
// \ \
6080+
// B's HTLC timeout tx B's timeout tx
6081+
6082+
let nodes = create_network(3);
6083+
6084+
// Create some intial channels
6085+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
6086+
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
6087+
6088+
// Rebalance the network a bit by relaying one payment thorugh all the channels...
6089+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
6090+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
6091+
6092+
let (_payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
6093+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42};
6094+
6095+
// Brodacast legit commitment tx from C on B's chain
6096+
let commitment_tx = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone();
6097+
nodes[2].node.fail_htlc_backwards(&payment_hash, PaymentFailReason::PreimageUnknown);
6098+
{
6099+
let mut added_monitors = nodes[2].chan_monitor.added_monitors.lock().unwrap();
6100+
assert_eq!(added_monitors.len(), 1);
6101+
added_monitors.clear();
6102+
}
6103+
let events = nodes[2].node.get_and_clear_pending_msg_events();
6104+
assert_eq!(events.len(), 1);
6105+
match events[0] {
6106+
MessageSendEvent::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, .. } } => {
6107+
assert!(update_add_htlcs.is_empty());
6108+
assert!(!update_fail_htlcs.is_empty());
6109+
assert!(update_fulfill_htlcs.is_empty());
6110+
assert!(update_fail_malformed_htlcs.is_empty());
6111+
assert_eq!(nodes[1].node.get_our_node_id(), *node_id);
6112+
},
6113+
_ => panic!("Unexpected event"),
6114+
};
6115+
nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
6116+
let events = nodes[2].node.get_and_clear_pending_msg_events();
6117+
assert_eq!(events.len(), 1);
6118+
match events[0] {
6119+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6120+
_ => panic!("Unexpected event"),
6121+
}
6122+
let mut funding_tx_map = HashMap::new();
6123+
funding_tx_map.insert(chan_2.3.txid(), chan_2.3.clone());
6124+
commitment_tx[0].verify(&funding_tx_map).unwrap();
6125+
6126+
// Broadcast timeout transaction by B on received output fron C's commitment tx on B's chain
6127+
// Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
6128+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 200);
6129+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
6130+
assert_eq!(node_txn.len(), 8); // ChannelManager : 2 (commitment tx, HTLC-Timeout), ChannelMonitor : 6 (commitment tx, HTLC-Timeout, timeout tx) * 2 (block-rescan)
6131+
assert_eq!(node_txn[2].input[0].previous_output.txid, node_txn[1].txid());
6132+
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 133);
6133+
6134+
let mut commitment_tx_map = HashMap::new();
6135+
commitment_tx_map.insert(commitment_tx[0].txid(), commitment_tx[0].clone());
6136+
node_txn[0].verify(&commitment_tx_map).unwrap();
6137+
6138+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()]}, 1);
6139+
{
6140+
let mut added_monitors = nodes[1].chan_monitor.added_monitors.lock().unwrap();
6141+
assert_eq!(added_monitors.len(), 1);
6142+
added_monitors.clear();
6143+
}
6144+
let events = nodes[1].node.get_and_clear_pending_msg_events();
6145+
assert_eq!(events.len(), 2);
6146+
match events[0] {
6147+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6148+
_ => panic!("Unexpected event"),
6149+
}
6150+
match events[1] {
6151+
MessageSendEvent::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fail_htlcs, ref update_fulfill_htlcs, ref update_fail_malformed_htlcs, .. } } => {
6152+
assert!(update_add_htlcs.is_empty());
6153+
assert!(!update_fail_htlcs.is_empty());
6154+
assert!(update_fulfill_htlcs.is_empty());
6155+
assert!(update_fail_malformed_htlcs.is_empty());
6156+
assert_eq!(nodes[0].node.get_our_node_id(), *node_id);
6157+
},
6158+
_ => panic!("Unexpected event"),
6159+
};
6160+
6161+
// Broadcast legit commitment tx from A on B's chain
6162+
// Broadcast HTLC Timeout tx by B on offered output from A commitment tx on A's chain
6163+
let commitment_tx = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone();
6164+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
6165+
let events = nodes[1].node.get_and_clear_pending_msg_events();
6166+
assert_eq!(events.len(), 1);
6167+
match events[0] {
6168+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6169+
_ => panic!("Unexpected event"),
6170+
}
6171+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
6172+
6173+
// Verify that A's ChannelManager is able to detect that HTLC is timeout by a HTLC Timeout tx and react backward in consequence
6174+
nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: node_txn }, 1);
6175+
let events = nodes[0].node.get_and_clear_pending_msg_events();
6176+
assert_eq!(events.len(), 1);
6177+
match events[0] {
6178+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6179+
_ => panic!("Unexpected event"),
6180+
}
6181+
}
6182+
60696183
#[test]
60706184
fn test_htlc_ignore_latest_remote_commitment() {
60716185
// Test that HTLC transactions spending the latest remote commitment transaction are simply

src/ln/channelmonitor.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,29 @@ impl ChannelMonitor {
11391139
}
11401140
}
11411141

1142+
macro_rules! sign_input_timeout {
1143+
($sighash_parts: expr, $input: expr, $amount: expr) => {
1144+
{
1145+
let (sig, redeemscript) = match self.key_storage {
1146+
KeyStorage::PrivMode { ref htlc_base_key, .. } => {
1147+
let htlc = &per_commitment_option.unwrap()[$input.sequence as usize];
1148+
let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
1149+
let sighash = ignore_error!(Message::from_slice(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]));
1150+
let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key));
1151+
(self.secp_ctx.sign(&sighash, &htlc_key), redeemscript)
1152+
},
1153+
KeyStorage::SigsMode { .. } => {
1154+
unimplemented!();
1155+
}
1156+
};
1157+
$input.witness.push(sig.serialize_der(&self.secp_ctx).to_vec());
1158+
$input.witness[0].push(SigHashType::All as u8);
1159+
$input.witness.push(vec![0]);
1160+
$input.witness.push(redeemscript.into_bytes());
1161+
}
1162+
}
1163+
}
1164+
11421165
for (idx, htlc) in per_commitment_data.iter().enumerate() {
11431166
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
11441167
let input = TxIn {
@@ -1173,6 +1196,29 @@ impl ChannelMonitor {
11731196
txn_to_broadcast.push(single_htlc_tx);
11741197
}
11751198
}
1199+
if !htlc.offered {
1200+
let input = TxIn {
1201+
previous_output: BitcoinOutPoint {
1202+
txid: commitment_txid,
1203+
vout: htlc.transaction_output_index,
1204+
},
1205+
script_sig: Script::new(),
1206+
sequence: idx as u32,
1207+
witness: Vec::new(),
1208+
};
1209+
let mut timeout_tx = Transaction {
1210+
version: 2,
1211+
lock_time: htlc.cltv_expiry,
1212+
input: vec![input],
1213+
output: vec!(TxOut {
1214+
script_pubkey: self.destination_script.clone(),
1215+
value: htlc.amount_msat / 1000,
1216+
}),
1217+
};
1218+
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
1219+
sign_input_timeout!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000);
1220+
txn_to_broadcast.push(timeout_tx);
1221+
}
11761222
}
11771223

11781224
if inputs.is_empty() { return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); } // Nothing to be done...probably a false positive/local tx

0 commit comments

Comments
 (0)