Skip to content

Commit a2c725c

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 74c258f commit a2c725c

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
@@ -2599,14 +2599,21 @@ impl ChainListener for ChannelManager {
25992599
}
26002600
{
26012601
let mut channel_state = Some(self.channel_state.lock().unwrap());
2602-
for (preimage, hash) in hash_to_remove {
2603-
if let Some(preimage) = preimage {
2602+
for (payment_preimage, payment_hash) in hash_to_remove {
2603+
if let Some(preimage) = payment_preimage {
26042604
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2605-
if let Some(mut sources) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&hash) {
2606-
for source in sources.drain(..) {
2605+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2606+
for source in entry.drain(..) {
26072607
self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), preimage);
26082608
}
26092609
}
2610+
} else {
2611+
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());}
2612+
if let Some(mut entry) = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash) {
2613+
for source in entry.drain(..) {
2614+
self.fail_htlc_backwards_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(source), &payment_hash, HTLCFailReason::Reason { failure_code: 0x1000 | 14, data: Vec::new() });
2615+
}
2616+
}
26102617
}
26112618
}
26122619
}
@@ -6099,6 +6106,117 @@ mod tests {
60996106
}
61006107
}
61016108

6109+
#[test]
6110+
fn test_htlc_on_chain_timeout() {
6111+
// Test that in case of an unilateral close onchain, we detect the state of output thanks to
6112+
// ChainWatchInterface and timeout the HTLC bacward accordingly. So here we test that ChannelManager is
6113+
// broadcasting the right event to other nodes in payment path.
6114+
// A ------------------> B ----------------------> C (timeout)
6115+
// A's commitment tx C's commitment tx
6116+
// \ \
6117+
// B's HTLC timeout tx B's timeout tx
6118+
6119+
let nodes = create_network(3);
6120+
6121+
// Create some intial channels
6122+
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
6123+
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
6124+
6125+
// Rebalance the network a bit by relaying one payment thorugh all the channels...
6126+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
6127+
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
6128+
6129+
let (_payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
6130+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42};
6131+
6132+
// Brodacast legit commitment tx from C on B's chain
6133+
let commitment_tx = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone();
6134+
nodes[2].node.fail_htlc_backwards(&payment_hash, PaymentFailReason::PreimageUnknown);
6135+
{
6136+
let mut added_monitors = nodes[2].chan_monitor.added_monitors.lock().unwrap();
6137+
assert_eq!(added_monitors.len(), 1);
6138+
added_monitors.clear();
6139+
}
6140+
let events = nodes[2].node.get_and_clear_pending_msg_events();
6141+
assert_eq!(events.len(), 1);
6142+
match events[0] {
6143+
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, .. } } => {
6144+
assert!(update_add_htlcs.is_empty());
6145+
assert!(!update_fail_htlcs.is_empty());
6146+
assert!(update_fulfill_htlcs.is_empty());
6147+
assert!(update_fail_malformed_htlcs.is_empty());
6148+
assert_eq!(nodes[1].node.get_our_node_id(), *node_id);
6149+
},
6150+
_ => panic!("Unexpected event"),
6151+
};
6152+
nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
6153+
let events = nodes[2].node.get_and_clear_pending_msg_events();
6154+
assert_eq!(events.len(), 1);
6155+
match events[0] {
6156+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6157+
_ => panic!("Unexpected event"),
6158+
}
6159+
let mut funding_tx_map = HashMap::new();
6160+
funding_tx_map.insert(chan_2.3.txid(), chan_2.3.clone());
6161+
commitment_tx[0].verify(&funding_tx_map).unwrap();
6162+
6163+
// Broadcast timeout transaction by B on received output fron C's commitment tx on B's chain
6164+
// Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
6165+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 200);
6166+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
6167+
assert_eq!(node_txn.len(), 8); // ChannelManager : 2 (commitment tx, HTLC-Timeout), ChannelMonitor : 6 (commitment tx, HTLC-Timeout, timeout tx) * 2 (block-rescan)
6168+
assert_eq!(node_txn[2].input[0].previous_output.txid, node_txn[1].txid());
6169+
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 133);
6170+
6171+
let mut commitment_tx_map = HashMap::new();
6172+
commitment_tx_map.insert(commitment_tx[0].txid(), commitment_tx[0].clone());
6173+
node_txn[0].verify(&commitment_tx_map).unwrap();
6174+
6175+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()]}, 1);
6176+
{
6177+
let mut added_monitors = nodes[1].chan_monitor.added_monitors.lock().unwrap();
6178+
assert_eq!(added_monitors.len(), 1);
6179+
added_monitors.clear();
6180+
}
6181+
let events = nodes[1].node.get_and_clear_pending_msg_events();
6182+
assert_eq!(events.len(), 2);
6183+
match events[0] {
6184+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6185+
_ => panic!("Unexpected event"),
6186+
}
6187+
match events[1] {
6188+
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, .. } } => {
6189+
assert!(update_add_htlcs.is_empty());
6190+
assert!(!update_fail_htlcs.is_empty());
6191+
assert!(update_fulfill_htlcs.is_empty());
6192+
assert!(update_fail_malformed_htlcs.is_empty());
6193+
assert_eq!(nodes[0].node.get_our_node_id(), *node_id);
6194+
},
6195+
_ => panic!("Unexpected event"),
6196+
};
6197+
6198+
// Broadcast legit commitment tx from A on B's chain
6199+
// Broadcast HTLC Timeout tx by B on offered output from A commitment tx on A's chain
6200+
let commitment_tx = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone();
6201+
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1);
6202+
let events = nodes[1].node.get_and_clear_pending_msg_events();
6203+
assert_eq!(events.len(), 1);
6204+
match events[0] {
6205+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6206+
_ => panic!("Unexpected event"),
6207+
}
6208+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
6209+
6210+
// Verify that A's ChannelManager is able to detect that HTLC is timeout by a HTLC Timeout tx and react backward in consequence
6211+
nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: node_txn }, 1);
6212+
let events = nodes[0].node.get_and_clear_pending_msg_events();
6213+
assert_eq!(events.len(), 1);
6214+
match events[0] {
6215+
MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {},
6216+
_ => panic!("Unexpected event"),
6217+
}
6218+
}
6219+
61026220
#[test]
61036221
fn test_htlc_ignore_latest_remote_commitment() {
61046222
// 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
@@ -1085,6 +1085,29 @@ impl ChannelMonitor {
10851085
}
10861086
}
10871087

1088+
macro_rules! sign_input_timeout {
1089+
($sighash_parts: expr, $input: expr, $amount: expr) => {
1090+
{
1091+
let (sig, redeemscript) = match self.key_storage {
1092+
KeyStorage::PrivMode { ref htlc_base_key, .. } => {
1093+
let htlc = &per_commitment_option.unwrap()[$input.sequence as usize];
1094+
let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
1095+
let sighash = ignore_error!(Message::from_slice(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]));
1096+
let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key));
1097+
(self.secp_ctx.sign(&sighash, &htlc_key), redeemscript)
1098+
},
1099+
KeyStorage::SigsMode { .. } => {
1100+
unimplemented!();
1101+
}
1102+
};
1103+
$input.witness.push(sig.serialize_der(&self.secp_ctx).to_vec());
1104+
$input.witness[0].push(SigHashType::All as u8);
1105+
$input.witness.push(vec![0]);
1106+
$input.witness.push(redeemscript.into_bytes());
1107+
}
1108+
}
1109+
}
1110+
10881111
for (idx, htlc) in per_commitment_data.iter().enumerate() {
10891112
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
10901113
let input = TxIn {
@@ -1119,6 +1142,29 @@ impl ChannelMonitor {
11191142
txn_to_broadcast.push(single_htlc_tx);
11201143
}
11211144
}
1145+
if !htlc.offered {
1146+
let input = TxIn {
1147+
previous_output: BitcoinOutPoint {
1148+
txid: commitment_txid,
1149+
vout: htlc.transaction_output_index,
1150+
},
1151+
script_sig: Script::new(),
1152+
sequence: idx as u32,
1153+
witness: Vec::new(),
1154+
};
1155+
let mut timeout_tx = Transaction {
1156+
version: 2,
1157+
lock_time: htlc.cltv_expiry,
1158+
input: vec![input],
1159+
output: vec!(TxOut {
1160+
script_pubkey: self.destination_script.clone(),
1161+
value: htlc.amount_msat / 1000,
1162+
}),
1163+
};
1164+
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
1165+
sign_input_timeout!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000);
1166+
txn_to_broadcast.push(timeout_tx);
1167+
}
11221168
}
11231169

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