Skip to content

Commit 11e77e6

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 78ca873 commit 11e77e6

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
@@ -2136,13 +2136,17 @@ impl ChainListener for ChannelManager {
21362136
for (arr, vec) in payment_preimage.iter_mut().zip(tx.input[0].witness[3].iter()) {
21372137
*arr = *vec;
21382138
}
2139-
hash_to_remove.push((payment_preimage, htlc_with_hash.clone()));
2139+
hash_to_remove.push((Some(payment_preimage), htlc_with_hash.clone()));
21402140
} 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] {
21412141
let mut payment_preimage = [0; 32];
21422142
for (arr, vec) in payment_preimage.iter_mut().zip(tx.input[0].witness[1].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()));
2146+
} 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] {
2147+
hash_to_remove.push((None, htlc_with_hash.clone()));
2148+
} 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] {
2149+
hash_to_remove.push((None, htlc_with_hash.clone()));
21462150
}
21472151
}
21482152
}
@@ -2153,11 +2157,20 @@ impl ChainListener for ChannelManager {
21532157

21542158
{
21552159
let mut channel_state = Some(self.channel_state.lock().unwrap());
2156-
for (preimage, hash) in hash_to_remove {
2157-
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2158-
if let Some(mut sources) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&hash) {
2159-
for source in sources.drain(..) {
2160-
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), preimage);
2160+
for (payment_preimage, payment_hash) in hash_to_remove {
2161+
if let Some(preimage) = payment_preimage {
2162+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2163+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2164+
for source in entry.drain(..) {
2165+
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), preimage);
2166+
}
2167+
}
2168+
} else {
2169+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2170+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2171+
for source in entry.drain(..) {
2172+
self.fail_htlc_backwards_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), &payment_hash, HTLCFailReason::Reason { failure_code: 0x1000 | 14, data: Vec::new() });
2173+
}
21612174
}
21622175
}
21632176
}
@@ -4361,6 +4374,117 @@ mod tests {
43614374
}
43624375
}
43634376

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