From: Antoine Riard Date: Fri, 5 Oct 2018 14:35:10 +0000 (+0000) Subject: Detect onchain timeout of a HTLC in ChannelManager block_connected X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=8d5c22f905171ed59b35b993d87129275e2e3fc6;p=rust-lightning Detect onchain timeout of a HTLC in ChannelManager block_connected Pass failure backward Add test_htlc_on_chain_timeout --- diff --git a/src/ln/channelmanager.rs b/src/ln/channelmanager.rs index c73a1051a..2f4d2b227 100644 --- a/src/ln/channelmanager.rs +++ b/src/ln/channelmanager.rs @@ -1520,7 +1520,8 @@ impl ChannelManager { rejected_by_dest: !payment_retryable, }); } else { - panic!("should have onion error packet here"); + //TODO (ariard) which failure code to generate from unilateral/revoked channel closing event ? currently NODE|2 (temporary_node_failure) + //panic!("should have onion error packet here"); } }, HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id, htlc_id, incoming_packet_shared_secret }) => { @@ -2657,11 +2658,14 @@ impl ChainListener for ChannelManager { } { let mut channel_state = Some(self.channel_state.lock().unwrap()); - for (_, payment_preimage, htlc_source) in self.monitor.fetch_pending_htlc_updated() { + for (payment_hash, payment_preimage, htlc_source) in self.monitor.fetch_pending_htlc_updated() { if let Some(source) = htlc_source { if let Some(preimage) = payment_preimage { if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());} self.claim_funds_internal(channel_state.take().unwrap(), source, preimage); + } else { + if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap());} + self.fail_htlc_backwards_internal(channel_state.take().unwrap(), source, &payment_hash, HTLCFailReason::Reason { failure_code: 0x2000 | 2, data: Vec::new() }); } } } @@ -6168,6 +6172,117 @@ mod tests { } } + #[test] + fn test_htlc_on_chain_timeout() { + // Test that in case of an unilateral close onchain, we detect the state of output thanks to + // ChainWatchInterface and timeout the HTLC bacward accordingly. So here we test that ChannelManager is + // broadcasting the right event to other nodes in payment path. + // A ------------------> B ----------------------> C (timeout) + // A's commitment tx C's commitment tx + // \ \ + // B's HTLC timeout tx B's timeout tx + + let nodes = create_network(3); + + // Create some intial channels + let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1); + let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2); + + // Rebalance the network a bit by relaying one payment thorugh all the channels... + send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000); + send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000); + + let (_payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000); + let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42}; + + // Brodacast legit commitment tx from C on B's chain + let commitment_tx = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone(); + nodes[2].node.fail_htlc_backwards(&payment_hash, PaymentFailReason::PreimageUnknown); + { + let mut added_monitors = nodes[2].chan_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + added_monitors.clear(); + } + let events = nodes[2].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + 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, .. } } => { + assert!(update_add_htlcs.is_empty()); + assert!(!update_fail_htlcs.is_empty()); + assert!(update_fulfill_htlcs.is_empty()); + assert!(update_fail_malformed_htlcs.is_empty()); + assert_eq!(nodes[1].node.get_our_node_id(), *node_id); + }, + _ => panic!("Unexpected event"), + }; + nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1); + let events = nodes[2].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {}, + _ => panic!("Unexpected event"), + } + let mut funding_tx_map = HashMap::new(); + funding_tx_map.insert(chan_2.3.txid(), chan_2.3.clone()); + commitment_tx[0].verify(&funding_tx_map).unwrap(); + + // Broadcast timeout transaction by B on received output fron C's commitment tx on B's chain + // Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence + nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 200); + let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); + assert_eq!(node_txn.len(), 8); // ChannelManager : 2 (commitment tx, HTLC-Timeout), ChannelMonitor : 6 (commitment tx, HTLC-Timeout, timeout tx) * 2 (block-rescan) + assert_eq!(node_txn[2].input[0].previous_output.txid, node_txn[1].txid()); + assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 133); + + let mut commitment_tx_map = HashMap::new(); + commitment_tx_map.insert(commitment_tx[0].txid(), commitment_tx[0].clone()); + node_txn[0].verify(&commitment_tx_map).unwrap(); + + nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()]}, 1); + { + let mut added_monitors = nodes[1].chan_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + added_monitors.clear(); + } + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 2); + match events[0] { + MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {}, + _ => panic!("Unexpected event"), + } + match events[1] { + 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, .. } } => { + assert!(update_add_htlcs.is_empty()); + assert!(!update_fail_htlcs.is_empty()); + assert!(update_fulfill_htlcs.is_empty()); + assert!(update_fail_malformed_htlcs.is_empty()); + assert_eq!(nodes[0].node.get_our_node_id(), *node_id); + }, + _ => panic!("Unexpected event"), + }; + + // Broadcast legit commitment tx from A on B's chain + // Broadcast HTLC Timeout tx by B on offered output from A commitment tx on A's chain + let commitment_tx = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone(); + nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1); + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {}, + _ => panic!("Unexpected event"), + } + let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); + + // Verify that A's ChannelManager is able to detect that HTLC is timeout by a HTLC Timeout tx and react backward in consequence + nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: node_txn }, 1); + let events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + MessageSendEvent::BroadcastChannelUpdate { msg: msgs::ChannelUpdate { .. } } => {}, + _ => panic!("Unexpected event"), + } + } + #[test] fn test_htlc_ignore_latest_remote_commitment() { // Test that HTLC transactions spending the latest remote commitment transaction are simply diff --git a/src/ln/channelmonitor.rs b/src/ln/channelmonitor.rs index ca8f20bba..ff58fdd14 100644 --- a/src/ln/channelmonitor.rs +++ b/src/ln/channelmonitor.rs @@ -1390,6 +1390,29 @@ impl ChannelMonitor { } } + macro_rules! sign_input_timeout { + ($sighash_parts: expr, $input: expr, $amount: expr) => { + { + let (sig, redeemscript) = match self.key_storage { + Storage::Local { ref htlc_base_key, .. } => { + let htlc = &per_commitment_option.unwrap()[$input.sequence as usize].0; + let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey); + let sighash = ignore_error!(Message::from_slice(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..])); + let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key)); + (self.secp_ctx.sign(&sighash, &htlc_key), redeemscript) + }, + Storage::Watchtower { .. } => { + unimplemented!(); + } + }; + $input.witness.push(sig.serialize_der(&self.secp_ctx).to_vec()); + $input.witness[0].push(SigHashType::All as u8); + $input.witness.push(vec![0]); + $input.witness.push(redeemscript.into_bytes()); + } + } + } + for (idx, &(ref htlc, _)) in per_commitment_data.iter().enumerate() { if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) { let input = TxIn { @@ -1424,6 +1447,29 @@ impl ChannelMonitor { txn_to_broadcast.push(single_htlc_tx); } } + if !htlc.offered { + let input = TxIn { + previous_output: BitcoinOutPoint { + txid: commitment_txid, + vout: htlc.transaction_output_index, + }, + script_sig: Script::new(), + sequence: idx as u32, + witness: Vec::new(), + }; + let mut timeout_tx = Transaction { + version: 2, + lock_time: htlc.cltv_expiry, + input: vec![input], + output: vec!(TxOut { + script_pubkey: self.destination_script.clone(), + value: htlc.amount_msat / 1000, + }), + }; + let sighash_parts = bip143::SighashComponents::new(&timeout_tx); + sign_input_timeout!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000); + txn_to_broadcast.push(timeout_tx); + } } if inputs.is_empty() { return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); } // Nothing to be done...probably a false positive/local tx