Skip to content

Commit f8785a4

Browse files
author
Antoine Riard
committed
Implement detection of onchain timeout of a HTLC, passing failure backward
Add test_htlc_on_chain_timeout
1 parent b85634d commit f8785a4

File tree

2 files changed

+177
-7
lines changed

2 files changed

+177
-7
lines changed

src/ln/channelmanager.rs

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,13 +2142,17 @@ impl ChainListener for ChannelManager {
21422142
for (arr, vec) in payment_preimage.iter_mut().zip(tx.input[0].witness[3].iter()) {
21432143
*arr = *vec;
21442144
}
2145-
hash_to_remove.push((payment_preimage, htlc_with_hash.clone()));
2145+
hash_to_remove.push((Some(payment_preimage), htlc_with_hash.clone()));
21462146
} else if tx.input.len() > 0 && tx.input[0].witness.len() == 3 && tx.input[0].witness[2].len() == 133 && payment_hash160 == tx.input[0].witness[2][109..129] {
21472147
let mut payment_preimage = [0; 32];
21482148
for (arr, vec) in payment_preimage.iter_mut().zip(tx.input[0].witness[1].iter()) {
21492149
*arr = *vec;
21502150
}
2151-
hash_to_remove.push((payment_preimage, htlc_with_hash.clone()));
2151+
hash_to_remove.push((Some(payment_preimage), htlc_with_hash.clone()));
2152+
} else if tx.input.len() > 0 && tx.input[0].witness.len() == 5 && tx.input[0].witness[4].len() == 133 && payment_hash160 == tx.input[0].witness[4][109..129] {
2153+
hash_to_remove.push((None, htlc_with_hash.clone()));
2154+
} else if tx.input.len() > 0 && tx.input[0].witness.len() == 3 && tx.input[0].witness[2].len() == 138 && payment_hash160 == tx.input[0].witness[2][69..89] {
2155+
hash_to_remove.push((None, htlc_with_hash.clone()));
21522156
}
21532157
}
21542158
}
@@ -2159,11 +2163,20 @@ impl ChainListener for ChannelManager {
21592163

21602164
{
21612165
let mut channel_state = Some(self.channel_state.lock().unwrap());
2162-
for (preimage, hash) in hash_to_remove {
2163-
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2164-
if let Some(mut sources) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&hash) {
2165-
for source in sources.drain(..) {
2166-
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), preimage);
2166+
for (payment_preimage, payment_hash) in hash_to_remove {
2167+
if let Some(preimage) = payment_preimage {
2168+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2169+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2170+
for source in entry.drain(..) {
2171+
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), preimage);
2172+
}
2173+
}
2174+
} else {
2175+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2176+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2177+
for source in entry.drain(..) {
2178+
self.fail_htlc_backwards_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), &payment_hash, HTLCFailReason::Reason { failure_code: 0x1000 | 14, data: Vec::new() });
2179+
}
21672180
}
21682181
}
21692182
}
@@ -4367,6 +4380,117 @@ mod tests {
43674380
}
43684381
}
43694382

4383+
#[test]
4384+
fn test_htlc_on_chain_timeout() {
4385+
// Test that in case of an unilateral close onchain, we detect the state of output thanks to
4386+
// ChainWatchInterface and timeout the HTLC bacward accordingly. So here we test that ChannelManager is
4387+
// broadcasting the right event to other nodes in payment path.
4388+
// A ------------------> B ----------------------> C (timeout)
4389+
// A's commitment tx C's commitment tx
4390+
// \ \
4391+
// B's HTLC timeout tx B's timeout tx
4392+
4393+
let nodes = create_network(3);
4394+
4395+
// Create some intial channels
4396+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
4397+
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
4398+
4399+
// Rebalance the network a bit by relaying one payment thorugh all the channels...
4400+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
4401+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
4402+
4403+
let (_payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
4404+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42};
4405+
4406+
// Brodacast legit commitment tx from C on B's chain
4407+
let commitment_tx = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone();
4408+
nodes[2].node.fail_htlc_backwards(&payment_hash);
4409+
{
4410+
let mut added_monitors = nodes[2].chan_monitor.added_monitors.lock().unwrap();
4411+
assert_eq!(added_monitors.len(), 1);
4412+
added_monitors.clear();
4413+
}
4414+
let events = nodes[2].node.get_and_clear_pending_events();
4415+
assert_eq!(events.len(), 1);
4416+
match events[0] {
4417+
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, .. } } => {
4418+
assert!(update_add_htlcs.is_empty());
4419+
assert!(!update_fail_htlcs.is_empty());
4420+
assert!(update_fulfill_htlcs.is_empty());
4421+
assert!(update_fail_malformed_htlcs.is_empty());
4422+
assert_eq!(nodes[1].node.get_our_node_id(), *node_id);
4423+
},
4424+
_ => panic!("Unexpected event"),
4425+
};
4426+
nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
4427+
let events = nodes[2].node.get_and_clear_pending_events();
4428+
assert_eq!(events.len(), 1);
4429+
match events[0] {
4430+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4431+
_ => panic!("Unexpected event"),
4432+
}
4433+
let mut funding_tx_map = HashMap::new();
4434+
funding_tx_map.insert(chan_2.3.txid(), chan_2.3.clone());
4435+
commitment_tx[0].verify(&funding_tx_map).unwrap();
4436+
4437+
// Broadcast timeout transaction by B on received output fron C's commitment tx on B's chain
4438+
// Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
4439+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 200);
4440+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
4441+
assert_eq!(node_txn.len(), 8); // ChannelManager : 2 (commitment tx, HTLC-Timeout), ChannelMonitor : 6 (commitment tx, HTLC-Timeout, timeout tx) * 2 (block-rescan)
4442+
assert_eq!(node_txn[2].input[0].previous_output.txid, node_txn[1].txid());
4443+
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 133);
4444+
4445+
let mut commitment_tx_map = HashMap::new();
4446+
commitment_tx_map.insert(commitment_tx[0].txid(), commitment_tx[0].clone());
4447+
node_txn[0].verify(&commitment_tx_map).unwrap();
4448+
4449+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()]}, 1);
4450+
{
4451+
let mut added_monitors = nodes[1].chan_monitor.added_monitors.lock().unwrap();
4452+
assert_eq!(added_monitors.len(), 1);
4453+
added_monitors.clear();
4454+
}
4455+
let events = nodes[1].node.get_and_clear_pending_events();
4456+
assert_eq!(events.len(), 2);
4457+
match events[0] {
4458+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4459+
_ => panic!("Unexpected event"),
4460+
}
4461+
match events[1] {
4462+
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, .. } } => {
4463+
assert!(update_add_htlcs.is_empty());
4464+
assert!(!update_fail_htlcs.is_empty());
4465+
assert!(update_fulfill_htlcs.is_empty());
4466+
assert!(update_fail_malformed_htlcs.is_empty());
4467+
assert_eq!(nodes[0].node.get_our_node_id(), *node_id);
4468+
},
4469+
_ => panic!("Unexpected event"),
4470+
};
4471+
4472+
// Broadcast legit commitment tx from A on B's chain
4473+
// Broadcast HTLC Timeout tx by B on offered output from A commitment tx on A's chain
4474+
let commitment_tx = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone();
4475+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
4476+
let events = nodes[1].node.get_and_clear_pending_events();
4477+
assert_eq!(events.len(), 1);
4478+
match events[0] {
4479+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4480+
_ => panic!("Unexpected event"),
4481+
}
4482+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
4483+
4484+
// Verify that A's ChannelManager is able to detect that HTLC is timeout by a HTLC Timeout tx and react backward in consequence
4485+
nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: node_txn }, 1);
4486+
let events = nodes[0].node.get_and_clear_pending_events();
4487+
assert_eq!(events.len(), 1);
4488+
match events[0] {
4489+
Event::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
4490+
_ => panic!("Unexpected event"),
4491+
}
4492+
}
4493+
43704494
#[test]
43714495
fn test_htlc_ignore_latest_remote_commitment() {
43724496
// 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
@@ -948,6 +948,29 @@ impl ChannelMonitor {
948948
}
949949
}
950950

951+
macro_rules! sign_input_timeout {
952+
($sighash_parts: expr, $input: expr, $amount: expr) => {
953+
{
954+
let (sig, redeemscript) = match self.key_storage {
955+
KeyStorage::PrivMode { ref htlc_base_key, .. } => {
956+
let htlc = &per_commitment_option.unwrap()[$input.sequence as usize];
957+
let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
958+
let sighash = ignore_error!(Message::from_slice(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]));
959+
let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key));
960+
(self.secp_ctx.sign(&sighash, &htlc_key), redeemscript)
961+
},
962+
KeyStorage::SigsMode { .. } => {
963+
unimplemented!();
964+
}
965+
};
966+
$input.witness.push(sig.serialize_der(&self.secp_ctx).to_vec());
967+
$input.witness[0].push(SigHashType::All as u8);
968+
$input.witness.push(vec![0]);
969+
$input.witness.push(redeemscript.into_bytes());
970+
}
971+
}
972+
}
973+
951974
for (idx, htlc) in per_commitment_data.iter().enumerate() {
952975
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
953976
let input = TxIn {
@@ -978,6 +1001,29 @@ impl ChannelMonitor {
9781001
txn_to_broadcast.push(single_htlc_tx);
9791002
}
9801003
}
1004+
if !htlc.offered {
1005+
let input = TxIn {
1006+
previous_output: BitcoinOutPoint {
1007+
txid: commitment_txid,
1008+
vout: htlc.transaction_output_index,
1009+
},
1010+
script_sig: Script::new(),
1011+
sequence: idx as u32,
1012+
witness: Vec::new(),
1013+
};
1014+
let mut timeout_tx = Transaction {
1015+
version: 2,
1016+
lock_time: htlc.cltv_expiry,
1017+
input: vec![input],
1018+
output: vec!(TxOut {
1019+
script_pubkey: self.destination_script.clone(),
1020+
value: htlc.amount_msat / 1000,
1021+
}),
1022+
};
1023+
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
1024+
sign_input_timeout!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000);
1025+
txn_to_broadcast.push(timeout_tx);
1026+
}
9811027
}
9821028

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