]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Detect onchain timeout of a HTLC in ChannelManager block_connected
authorAntoine Riard <ariard@student.42.fr>
Fri, 5 Oct 2018 14:35:10 +0000 (14:35 +0000)
committerAntoine Riard <ariard@student.42.fr>
Fri, 30 Nov 2018 16:14:18 +0000 (11:14 -0500)
Pass failure backward

Add test_htlc_on_chain_timeout

src/ln/channelmanager.rs
src/ln/channelmonitor.rs

index c73a1051aaabb5284efb1bbed60ad69793edded6..2f4d2b22771d6408e87267edf0fb0df84032aa21 100644 (file)
@@ -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
index ca8f20bbae75929f4e84afc0ee0a417117d3cc00..ff58fdd143adffc1314c5953ceef9885dc6e7d24 100644 (file)
@@ -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