]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Merge pull request #391 from ariard/2019-11-fix-preimage-collision-onchain
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Fri, 15 Nov 2019 19:20:43 +0000 (19:20 +0000)
committerGitHub <noreply@github.com>
Fri, 15 Nov 2019 19:20:43 +0000 (19:20 +0000)
Avoid claiming remote received HTLCs with side-learned preimage

src/ln/channelmonitor.rs
src/ln/functional_tests.rs

index 5bcd74f76f10108832922ad00d8cb93ea795357a..9deb11073ffc31273d233277070f528c4eca478f 100644 (file)
@@ -1643,46 +1643,48 @@ impl ChannelMonitor {
                                                                return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); // Corrupted per_commitment_data, fuck this user
                                                        }
                                                        if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
-                                                               let input = TxIn {
-                                                                       previous_output: BitcoinOutPoint {
-                                                                               txid: commitment_txid,
-                                                                               vout: transaction_output_index,
-                                                                       },
-                                                                       script_sig: Script::new(),
-                                                                       sequence: idx as u32, // reset to 0xfffffffd in sign_input
-                                                                       witness: Vec::new(),
-                                                               };
-                                                               if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
-                                                                       inputs.push(input);
-                                                                       inputs_desc.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC });
-                                                                       inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
-                                                                       total_value += tx.output[transaction_output_index as usize].value;
-                                                               } else {
-                                                                       let mut single_htlc_tx = Transaction {
-                                                                               version: 2,
-                                                                               lock_time: 0,
-                                                                               input: vec![input],
-                                                                               output: vec!(TxOut {
-                                                                                       script_pubkey: self.destination_script.clone(),
-                                                                                       value: htlc.amount_msat / 1000,
-                                                                               }),
+                                                               if htlc.offered {
+                                                                       let input = TxIn {
+                                                                               previous_output: BitcoinOutPoint {
+                                                                                       txid: commitment_txid,
+                                                                                       vout: transaction_output_index,
+                                                                               },
+                                                                               script_sig: Script::new(),
+                                                                               sequence: idx as u32, // reset to 0xfffffffd in sign_input
+                                                                               witness: Vec::new(),
                                                                        };
-                                                                       let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]);
-                                                                       let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
-                                                                       let mut used_feerate;
-                                                                       if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
-                                                                               let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
-                                                                               let (redeemscript, htlc_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], htlc.amount_msat / 1000, payment_preimage.0.to_vec());
-                                                                               assert!(predicted_weight >= single_htlc_tx.get_weight());
-                                                                               spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
-                                                                                       outpoint: BitcoinOutPoint { txid: single_htlc_tx.txid(), vout: 0 },
-                                                                                       output: single_htlc_tx.output[0].clone(),
-                                                                               });
-                                                                               match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
-                                                                                       hash_map::Entry::Occupied(_) => {},
-                                                                                       hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
+                                                                       if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
+                                                                               inputs.push(input);
+                                                                               inputs_desc.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC });
+                                                                               inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
+                                                                               total_value += tx.output[transaction_output_index as usize].value;
+                                                                       } else {
+                                                                               let mut single_htlc_tx = Transaction {
+                                                                                       version: 2,
+                                                                                       lock_time: 0,
+                                                                                       input: vec![input],
+                                                                                       output: vec!(TxOut {
+                                                                                               script_pubkey: self.destination_script.clone(),
+                                                                                               value: htlc.amount_msat / 1000,
+                                                                                       }),
+                                                                               };
+                                                                               let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]);
+                                                                               let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
+                                                                               let mut used_feerate;
+                                                                               if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
+                                                                                       let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
+                                                                                       let (redeemscript, htlc_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], htlc.amount_msat / 1000, payment_preimage.0.to_vec());
+                                                                                       assert!(predicted_weight >= single_htlc_tx.get_weight());
+                                                                                       spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
+                                                                                               outpoint: BitcoinOutPoint { txid: single_htlc_tx.txid(), vout: 0 },
+                                                                                               output: single_htlc_tx.output[0].clone(),
+                                                                                       });
+                                                                                       match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
+                                                                                               hash_map::Entry::Occupied(_) => {},
+                                                                                               hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
+                                                                                       }
+                                                                                       txn_to_broadcast.push(single_htlc_tx);
                                                                                }
-                                                                               txn_to_broadcast.push(single_htlc_tx);
                                                                        }
                                                                }
                                                        }
index 044c1673011dd66d81112fe7c673a9aaf19287a0..0d25b3cdd52441849e49e6122dafe34980a9fb81 100644 (file)
@@ -1245,6 +1245,73 @@ fn duplicate_htlc_test() {
        claim_payment(&nodes[1], &vec!(&nodes[3])[..], payment_preimage);
 }
 
+#[test]
+fn test_duplicate_htlc_different_direction_onchain() {
+       // Test that ChannelMonitor doesn't generate 2 preimage txn
+       // when we have 2 HTLCs with same preimage that go across a node
+       // in opposite directions.
+       let nodes = create_network(2, &[None, None]);
+
+       let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1, LocalFeatures::new(), LocalFeatures::new());
+
+       // balancing
+       send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
+
+       let (payment_preimage, payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 900_000);
+
+       let route = nodes[1].router.get_route(&nodes[0].node.get_our_node_id(), None, &Vec::new(), 800_000, TEST_FINAL_CLTV).unwrap();
+       send_along_route_with_hash(&nodes[1], route, &vec!(&nodes[0])[..], 800_000, payment_hash);
+
+       // Provide preimage to node 0 by claiming payment
+       nodes[0].node.claim_funds(payment_preimage);
+       check_added_monitors!(nodes[0], 1);
+
+       // Broadcast node 1 commitment txn
+       let remote_txn = nodes[1].node.channel_state.lock().unwrap().by_id.get(&chan_1.2).unwrap().last_local_commitment_txn.clone();
+
+       assert_eq!(remote_txn[0].output.len(), 4); // 1 local, 1 remote, 1 htlc inbound, 1 htlc outbound
+       let mut has_both_htlcs = 0; // check htlcs match ones committed
+       for outp in remote_txn[0].output.iter() {
+               if outp.value == 800_000 / 1000 {
+                       has_both_htlcs += 1;
+               } else if outp.value == 900_000 / 1000 {
+                       has_both_htlcs += 1;
+               }
+       }
+       assert_eq!(has_both_htlcs, 2);
+
+       let header = BlockHeader { version: 0x2000_0000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
+
+       nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![remote_txn[0].clone()] }, 1);
+
+       // Check we only broadcast 1 timeout tx
+       let claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
+       let htlc_pair = if claim_txn[0].output[0].value == 800_000 / 1000 { (claim_txn[0].clone(), claim_txn[1].clone()) } else { (claim_txn[1].clone(), claim_txn[0].clone()) };
+       assert_eq!(claim_txn.len(), 6);
+       assert_eq!(htlc_pair.0.input.len(), 1);
+       assert_eq!(htlc_pair.0.input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT); // HTLC 1 <--> 0, preimage tx
+       check_spends!(htlc_pair.0, remote_txn[0].clone());
+       assert_eq!(htlc_pair.1.input.len(), 1);
+       assert_eq!(htlc_pair.1.input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT); // HTLC 0 <--> 1, timeout tx
+       check_spends!(htlc_pair.1, remote_txn[0].clone());
+
+       let events = nodes[0].node.get_and_clear_pending_msg_events();
+       assert_eq!(events.len(), 2);
+       for e in events {
+               match e {
+                       MessageSendEvent::BroadcastChannelUpdate { .. } => {},
+                       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_eq!(update_fulfill_htlcs.len(), 1);
+                               assert!(update_fail_malformed_htlcs.is_empty());
+                               assert_eq!(nodes[1].node.get_our_node_id(), *node_id);
+                       },
+                       _ => panic!("Unexpected event"),
+               }
+       }
+}
+
 fn do_channel_reserve_test(test_recv: bool) {
        use ln::msgs::HandleError;