Skip to content

Commit 8d5c22f

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 43c4ffe commit 8d5c22f

File tree

2 files changed

+163
-2
lines changed

2 files changed

+163
-2
lines changed

src/ln/channelmanager.rs

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,7 +1520,8 @@ impl ChannelManager {
15201520
rejected_by_dest: !payment_retryable,
15211521
});
15221522
} else {
1523-
panic!("should have onion error packet here");
1523+
//TODO (ariard) which failure code to generate from unilateral/revoked channel closing event ? currently NODE|2 (temporary_node_failure)
1524+
//panic!("should have onion error packet here");
15241525
}
15251526
},
15261527
HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id, htlc_id, incoming_packet_shared_secret }) => {
@@ -2657,11 +2658,14 @@ impl ChainListener for ChannelManager {
26572658
}
26582659
{
26592660
let mut channel_state = Some(self.channel_state.lock().unwrap());
2660-
for (_, payment_preimage, htlc_source) in self.monitor.fetch_pending_htlc_updated() {
2661+
for (payment_hash, payment_preimage, htlc_source) in self.monitor.fetch_pending_htlc_updated() {
26612662
if let Some(source) = htlc_source {
26622663
if let Some(preimage) = payment_preimage {
26632664
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
26642665
self.claim_funds_internal(channel_state.take().unwrap(), source, preimage);
2666+
} else {
2667+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2668+
self.fail_htlc_backwards_internal(channel_state.take().unwrap(), source, &payment_hash, HTLCFailReason::Reason { failure_code: 0x2000 | 2, data: Vec::new() });
26652669
}
26662670
}
26672671
}
@@ -6168,6 +6172,117 @@ mod tests {
61686172
}
61696173
}
61706174

6175+
#[test]
6176+
fn test_htlc_on_chain_timeout() {
6177+
// Test that in case of an unilateral close onchain, we detect the state of output thanks to
6178+
// ChainWatchInterface and timeout the HTLC bacward accordingly. So here we test that ChannelManager is
6179+
// broadcasting the right event to other nodes in payment path.
6180+
// A ------------------> B ----------------------> C (timeout)
6181+
// A's commitment tx C's commitment tx
6182+
// \ \
6183+
// B's HTLC timeout tx B's timeout tx
6184+
6185+
let nodes = create_network(3);
6186+
6187+
// Create some intial channels
6188+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
6189+
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
6190+
6191+
// Rebalance the network a bit by relaying one payment thorugh all the channels...
6192+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
6193+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
6194+
6195+
let (_payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
6196+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42};
6197+
6198+
// Brodacast legit commitment tx from C on B's chain
6199+
let commitment_tx = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone();
6200+
nodes[2].node.fail_htlc_backwards(&payment_hash, PaymentFailReason::PreimageUnknown);
6201+
{
6202+
let mut added_monitors = nodes[2].chan_monitor.added_monitors.lock().unwrap();
6203+
assert_eq!(added_monitors.len(), 1);
6204+
added_monitors.clear();
6205+
}
6206+
let events = nodes[2].node.get_and_clear_pending_msg_events();
6207+
assert_eq!(events.len(), 1);
6208+
match events[0] {
6209+
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, .. } } => {
6210+
assert!(update_add_htlcs.is_empty());
6211+
assert!(!update_fail_htlcs.is_empty());
6212+
assert!(update_fulfill_htlcs.is_empty());
6213+
assert!(update_fail_malformed_htlcs.is_empty());
6214+
assert_eq!(nodes[1].node.get_our_node_id(), *node_id);
6215+
},
6216+
_ => panic!("Unexpected event"),
6217+
};
6218+
nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
6219+
let events = nodes[2].node.get_and_clear_pending_msg_events();
6220+
assert_eq!(events.len(), 1);
6221+
match events[0] {
6222+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6223+
_ => panic!("Unexpected event"),
6224+
}
6225+
let mut funding_tx_map = HashMap::new();
6226+
funding_tx_map.insert(chan_2.3.txid(), chan_2.3.clone());
6227+
commitment_tx[0].verify(&funding_tx_map).unwrap();
6228+
6229+
// Broadcast timeout transaction by B on received output fron C's commitment tx on B's chain
6230+
// Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
6231+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 200);
6232+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
6233+
assert_eq!(node_txn.len(), 8); // ChannelManager : 2 (commitment tx, HTLC-Timeout), ChannelMonitor : 6 (commitment tx, HTLC-Timeout, timeout tx) * 2 (block-rescan)
6234+
assert_eq!(node_txn[2].input[0].previous_output.txid, node_txn[1].txid());
6235+
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 133);
6236+
6237+
let mut commitment_tx_map = HashMap::new();
6238+
commitment_tx_map.insert(commitment_tx[0].txid(), commitment_tx[0].clone());
6239+
node_txn[0].verify(&commitment_tx_map).unwrap();
6240+
6241+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()]}, 1);
6242+
{
6243+
let mut added_monitors = nodes[1].chan_monitor.added_monitors.lock().unwrap();
6244+
assert_eq!(added_monitors.len(), 1);
6245+
added_monitors.clear();
6246+
}
6247+
let events = nodes[1].node.get_and_clear_pending_msg_events();
6248+
assert_eq!(events.len(), 2);
6249+
match events[0] {
6250+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6251+
_ => panic!("Unexpected event"),
6252+
}
6253+
match events[1] {
6254+
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, .. } } => {
6255+
assert!(update_add_htlcs.is_empty());
6256+
assert!(!update_fail_htlcs.is_empty());
6257+
assert!(update_fulfill_htlcs.is_empty());
6258+
assert!(update_fail_malformed_htlcs.is_empty());
6259+
assert_eq!(nodes[0].node.get_our_node_id(), *node_id);
6260+
},
6261+
_ => panic!("Unexpected event"),
6262+
};
6263+
6264+
// Broadcast legit commitment tx from A on B's chain
6265+
// Broadcast HTLC Timeout tx by B on offered output from A commitment tx on A's chain
6266+
let commitment_tx = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone();
6267+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
6268+
let events = nodes[1].node.get_and_clear_pending_msg_events();
6269+
assert_eq!(events.len(), 1);
6270+
match events[0] {
6271+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6272+
_ => panic!("Unexpected event"),
6273+
}
6274+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
6275+
6276+
// Verify that A's ChannelManager is able to detect that HTLC is timeout by a HTLC Timeout tx and react backward in consequence
6277+
nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: node_txn }, 1);
6278+
let events = nodes[0].node.get_and_clear_pending_msg_events();
6279+
assert_eq!(events.len(), 1);
6280+
match events[0] {
6281+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6282+
_ => panic!("Unexpected event"),
6283+
}
6284+
}
6285+
61716286
#[test]
61726287
fn test_htlc_ignore_latest_remote_commitment() {
61736288
// 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
@@ -1390,6 +1390,29 @@ impl ChannelMonitor {
13901390
}
13911391
}
13921392

1393+
macro_rules! sign_input_timeout {
1394+
($sighash_parts: expr, $input: expr, $amount: expr) => {
1395+
{
1396+
let (sig, redeemscript) = match self.key_storage {
1397+
Storage::Local { ref htlc_base_key, .. } => {
1398+
let htlc = &per_commitment_option.unwrap()[$input.sequence as usize].0;
1399+
let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
1400+
let sighash = ignore_error!(Message::from_slice(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]));
1401+
let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key));
1402+
(self.secp_ctx.sign(&sighash, &htlc_key), redeemscript)
1403+
},
1404+
Storage::Watchtower { .. } => {
1405+
unimplemented!();
1406+
}
1407+
};
1408+
$input.witness.push(sig.serialize_der(&self.secp_ctx).to_vec());
1409+
$input.witness[0].push(SigHashType::All as u8);
1410+
$input.witness.push(vec![0]);
1411+
$input.witness.push(redeemscript.into_bytes());
1412+
}
1413+
}
1414+
}
1415+
13931416
for (idx, &(ref htlc, _)) in per_commitment_data.iter().enumerate() {
13941417
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
13951418
let input = TxIn {
@@ -1424,6 +1447,29 @@ impl ChannelMonitor {
14241447
txn_to_broadcast.push(single_htlc_tx);
14251448
}
14261449
}
1450+
if !htlc.offered {
1451+
let input = TxIn {
1452+
previous_output: BitcoinOutPoint {
1453+
txid: commitment_txid,
1454+
vout: htlc.transaction_output_index,
1455+
},
1456+
script_sig: Script::new(),
1457+
sequence: idx as u32,
1458+
witness: Vec::new(),
1459+
};
1460+
let mut timeout_tx = Transaction {
1461+
version: 2,
1462+
lock_time: htlc.cltv_expiry,
1463+
input: vec![input],
1464+
output: vec!(TxOut {
1465+
script_pubkey: self.destination_script.clone(),
1466+
value: htlc.amount_msat / 1000,
1467+
}),
1468+
};
1469+
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
1470+
sign_input_timeout!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000);
1471+
txn_to_broadcast.push(timeout_tx);
1472+
}
14271473
}
14281474

14291475
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)