Skip to content

Commit e8efa8c

Browse files
author
Antoine Riard
committed
Implement detection of onchain timeout of a HTLC in ChannelManager block_connected,
passing failure backward Add test_htlc_on_chain_timeout
1 parent b4769df commit e8efa8c

File tree

2 files changed

+168
-4
lines changed

2 files changed

+168
-4
lines changed

src/ln/channelmanager.rs

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2136,14 +2136,21 @@ impl ChainListener for ChannelManager {
21362136

21372137
{
21382138
let mut channel_state = Some(self.channel_state.lock().unwrap());
2139-
for (preimage, hash) in hash_to_remove {
2140-
if let Some(preimage) = preimage {
2139+
for (payment_preimage, payment_hash) in hash_to_remove {
2140+
if let Some(preimage) = payment_preimage {
21412141
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2142-
if let Some(mut sources) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&hash) {
2143-
for source in sources.drain(..) {
2142+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2143+
for source in entry.drain(..) {
21442144
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), preimage);
21452145
}
21462146
}
2147+
} else {
2148+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2149+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2150+
for source in entry.drain(..) {
2151+
self.fail_htlc_backwards_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), &payment_hash, HTLCFailReason::Reason { failure_code: 0x1000 | 14, data: Vec::new() });
2152+
}
2153+
}
21472154
}
21482155
}
21492156
}
@@ -4346,6 +4353,117 @@ mod tests {
43464353
}
43474354
}
43484355

4356+
#[test]
4357+
fn test_htlc_on_chain_timeout() {
4358+
// Test that in case of an unilateral close onchain, we detect the state of output thanks to
4359+
// ChainWatchInterface and timeout the HTLC bacward accordingly. So here we test that ChannelManager is
4360+
// broadcasting the right event to other nodes in payment path.
4361+
// A ------------------> B ----------------------> C (timeout)
4362+
// A's commitment tx C's commitment tx
4363+
// \ \
4364+
// B's HTLC timeout tx B's timeout tx
4365+
4366+
let nodes = create_network(3);
4367+
4368+
// Create some intial channels
4369+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
4370+
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
4371+
4372+
// Rebalance the network a bit by relaying one payment thorugh all the channels...
4373+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
4374+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
4375+
4376+
let (_payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
4377+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42};
4378+
4379+
// Brodacast legit commitment tx from C on B's chain
4380+
let commitment_tx = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone();
4381+
nodes[2].node.fail_htlc_backwards(&payment_hash);
4382+
{
4383+
let mut added_monitors = nodes[2].chan_monitor.added_monitors.lock().unwrap();
4384+
assert_eq!(added_monitors.len(), 1);
4385+
added_monitors.clear();
4386+
}
4387+
let events = nodes[2].node.get_and_clear_pending_events();
4388+
assert_eq!(events.len(), 1);
4389+
match events[0] {
4390+
Event::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, .. } } => {
4391+
assert!(update_add_htlcs.is_empty());
4392+
assert!(!update_fail_htlcs.is_empty());
4393+
assert!(update_fulfill_htlcs.is_empty());
4394+
assert!(update_fail_malformed_htlcs.is_empty());
4395+
assert_eq!(nodes[1].node.get_our_node_id(), *node_id);
4396+
},
4397+
_ => panic!("Unexpected event"),
4398+
};
4399+
nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
4400+
let events = nodes[2].node.get_and_clear_pending_events();
4401+
assert_eq!(events.len(), 1);
4402+
match events[0] {
4403+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4404+
_ => panic!("Unexpected event"),
4405+
}
4406+
let mut funding_tx_map = HashMap::new();
4407+
funding_tx_map.insert(chan_2.3.txid(), chan_2.3.clone());
4408+
commitment_tx[0].verify(&funding_tx_map).unwrap();
4409+
4410+
// Broadcast timeout transaction by B on received output fron C's commitment tx on B's chain
4411+
// Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
4412+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 200);
4413+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
4414+
assert_eq!(node_txn.len(), 8); // ChannelManager : 2 (commitment tx, HTLC-Timeout), ChannelMonitor : 6 (commitment tx, HTLC-Timeout, timeout tx) * 2 (block-rescan)
4415+
assert_eq!(node_txn[2].input[0].previous_output.txid, node_txn[1].txid());
4416+
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 133);
4417+
4418+
let mut commitment_tx_map = HashMap::new();
4419+
commitment_tx_map.insert(commitment_tx[0].txid(), commitment_tx[0].clone());
4420+
node_txn[0].verify(&commitment_tx_map).unwrap();
4421+
4422+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()]}, 1);
4423+
{
4424+
let mut added_monitors = nodes[1].chan_monitor.added_monitors.lock().unwrap();
4425+
assert_eq!(added_monitors.len(), 1);
4426+
added_monitors.clear();
4427+
}
4428+
let events = nodes[1].node.get_and_clear_pending_events();
4429+
assert_eq!(events.len(), 2);
4430+
match events[0] {
4431+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4432+
_ => panic!("Unexpected event"),
4433+
}
4434+
match events[1] {
4435+
Event::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fail_htlcs, ref update_fulfill_htlcs, ref update_fail_malformed_htlcs, .. } } => {
4436+
assert!(update_add_htlcs.is_empty());
4437+
assert!(!update_fail_htlcs.is_empty());
4438+
assert!(update_fulfill_htlcs.is_empty());
4439+
assert!(update_fail_malformed_htlcs.is_empty());
4440+
assert_eq!(nodes[0].node.get_our_node_id(), *node_id);
4441+
},
4442+
_ => panic!("Unexpected event"),
4443+
};
4444+
4445+
// Broadcast legit commitment tx from A on B's chain
4446+
// Broadcast HTLC Timeout tx by B on offered output from A commitment tx on A's chain
4447+
let commitment_tx = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone();
4448+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
4449+
let events = nodes[1].node.get_and_clear_pending_events();
4450+
assert_eq!(events.len(), 1);
4451+
match events[0] {
4452+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4453+
_ => panic!("Unexpected event"),
4454+
}
4455+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
4456+
4457+
// Verify that A's ChannelManager is able to detect that HTLC is timeout by a HTLC Timeout tx and react backward in consequence
4458+
nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: node_txn }, 1);
4459+
let events = nodes[0].node.get_and_clear_pending_events();
4460+
assert_eq!(events.len(), 1);
4461+
match events[0] {
4462+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4463+
_ => panic!("Unexpected event"),
4464+
}
4465+
}
4466+
43494467
#[test]
43504468
fn test_htlc_ignore_latest_remote_commitment() {
43514469
// 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
@@ -962,6 +962,29 @@ impl ChannelMonitor {
962962
}
963963
}
964964

965+
macro_rules! sign_input_timeout {
966+
($sighash_parts: expr, $input: expr, $amount: expr) => {
967+
{
968+
let (sig, redeemscript) = match self.key_storage {
969+
KeyStorage::PrivMode { ref htlc_base_key, .. } => {
970+
let htlc = &per_commitment_option.unwrap()[$input.sequence as usize];
971+
let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
972+
let sighash = ignore_error!(Message::from_slice(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]));
973+
let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key));
974+
(self.secp_ctx.sign(&sighash, &htlc_key), redeemscript)
975+
},
976+
KeyStorage::SigsMode { .. } => {
977+
unimplemented!();
978+
}
979+
};
980+
$input.witness.push(sig.serialize_der(&self.secp_ctx).to_vec());
981+
$input.witness[0].push(SigHashType::All as u8);
982+
$input.witness.push(vec![0]);
983+
$input.witness.push(redeemscript.into_bytes());
984+
}
985+
}
986+
}
987+
965988
for (idx, htlc) in per_commitment_data.iter().enumerate() {
966989
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
967990
let input = TxIn {
@@ -992,6 +1015,29 @@ impl ChannelMonitor {
9921015
txn_to_broadcast.push(single_htlc_tx);
9931016
}
9941017
}
1018+
if !htlc.offered {
1019+
let input = TxIn {
1020+
previous_output: BitcoinOutPoint {
1021+
txid: commitment_txid,
1022+
vout: htlc.transaction_output_index,
1023+
},
1024+
script_sig: Script::new(),
1025+
sequence: idx as u32,
1026+
witness: Vec::new(),
1027+
};
1028+
let mut timeout_tx = Transaction {
1029+
version: 2,
1030+
lock_time: htlc.cltv_expiry,
1031+
input: vec![input],
1032+
output: vec!(TxOut {
1033+
script_pubkey: self.destination_script.clone(),
1034+
value: htlc.amount_msat / 1000,
1035+
}),
1036+
};
1037+
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
1038+
sign_input_timeout!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000);
1039+
txn_to_broadcast.push(timeout_tx);
1040+
}
9951041
}
9961042

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

0 commit comments

Comments
 (0)