Add more logs in OnchainTxHandler
[rust-lightning] / lightning / src / ln / onchaintx.rs
index 7bacc91a441a6113fe263d3caa45ecab3ec0485f..3f21d4c1f64b4a4a73b708c9e924863960384e50 100644 (file)
@@ -14,7 +14,7 @@ use secp256k1::Secp256k1;
 use secp256k1;
 
 use ln::msgs::DecodeError;
-use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial};
+use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest};
 use ln::chan_utils::HTLCType;
 use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
 use chain::keysinterface::SpendableOutputDescriptor;
@@ -316,6 +316,11 @@ impl OnchainTxHandler {
                tx_weight
        }
 
+       /// In LN, output claimed are time-sensitive, which means we have to spend them before reaching some timelock expiration. At in-channel
+       /// output detection, we generate a first version of a claim tx and associate to it a height timer. A height timer is an absolute block
+       /// height than once reached we should generate a new bumped "version" of the claim tx to be sure than we safely claim outputs before
+       /// than our counterparty can do it too. If timelock expires soon, height timer is going to be scale down in consequence to increase
+       /// frequency of the bump and so increase our bets of success.
        fn get_height_timer(current_height: u32, timelock_expiration: u32) -> u32 {
                if timelock_expiration <= current_height + 3 {
                        return current_height + 1
@@ -390,13 +395,15 @@ impl OnchainTxHandler {
                        }
                }
 
+               // Compute new height timer to decide when we need to regenerate a new bumped version of the claim tx (if we
+               // didn't receive confirmation of it before, or not enough reorg-safe depth on top of it).
                let new_timer = Self::get_height_timer(height, cached_claim_datas.soonest_timelock);
                let mut inputs_witnesses_weight = 0;
                let mut amt = 0;
                for per_outp_material in cached_claim_datas.per_input_material.values() {
                        match per_outp_material {
-                               &InputMaterial::Revoked { ref script, ref is_htlc, ref amount, .. } => {
-                                       inputs_witnesses_weight += Self::get_witnesses_weight(if !is_htlc { &[InputDescriptors::RevokedOutput] } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::OfferedHTLC) { &[InputDescriptors::RevokedOfferedHTLC] } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::AcceptedHTLC) { &[InputDescriptors::RevokedReceivedHTLC] } else { unreachable!() });
+                               &InputMaterial::Revoked { ref witness_script, ref is_htlc, ref amount, .. } => {
+                                       inputs_witnesses_weight += Self::get_witnesses_weight(if !is_htlc { &[InputDescriptors::RevokedOutput] } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::OfferedHTLC) { &[InputDescriptors::RevokedOfferedHTLC] } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::AcceptedHTLC) { &[InputDescriptors::RevokedReceivedHTLC] } else { unreachable!() });
                                        amt += *amount;
                                },
                                &InputMaterial::RemoteHTLC { ref preimage, ref amount, .. } => {
@@ -429,9 +436,9 @@ impl OnchainTxHandler {
 
                for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() {
                        match per_outp_material {
-                               &InputMaterial::Revoked { ref script, ref pubkey, ref key, ref is_htlc, ref amount } => {
+                               &InputMaterial::Revoked { ref witness_script, ref pubkey, ref key, ref is_htlc, ref amount } => {
                                        let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
-                                       let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &script, *amount)[..]);
+                                       let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]);
                                        let sig = self.secp_ctx.sign(&sighash, &key);
                                        bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
                                        bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
@@ -440,13 +447,13 @@ impl OnchainTxHandler {
                                        } else {
                                                bumped_tx.input[i].witness.push(vec!(1));
                                        }
-                                       bumped_tx.input[i].witness.push(script.clone().into_bytes());
-                                       log_trace!(self, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if !is_htlc { "to_local" } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::OfferedHTLC) { "offered" } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::AcceptedHTLC) { "received" } else { "" }, outp.vout, outp.txid, new_feerate);
+                                       bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
+                                       log_trace!(self, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if !is_htlc { "to_local" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::OfferedHTLC) { "offered" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::AcceptedHTLC) { "received" } else { "" }, outp.vout, outp.txid, new_feerate);
                                },
-                               &InputMaterial::RemoteHTLC { ref script, ref key, ref preimage, ref amount, ref locktime } => {
+                               &InputMaterial::RemoteHTLC { ref witness_script, ref key, ref preimage, ref amount, ref locktime } => {
                                        if !preimage.is_some() { bumped_tx.lock_time = *locktime }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation
                                        let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
-                                       let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &script, *amount)[..]);
+                                       let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]);
                                        let sig = self.secp_ctx.sign(&sighash, &key);
                                        bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
                                        bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
@@ -455,7 +462,7 @@ impl OnchainTxHandler {
                                        } else {
                                                bumped_tx.input[i].witness.push(vec![0]);
                                        }
-                                       bumped_tx.input[i].witness.push(script.clone().into_bytes());
+                                       bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
                                        log_trace!(self, "Going to broadcast Claim Transaction {} claiming remote {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate);
                                },
                                &InputMaterial::LocalHTLC { .. } => {
@@ -471,31 +478,30 @@ impl OnchainTxHandler {
                Some((new_timer, new_feerate, bumped_tx))
        }
 
-       pub(super) fn block_connected<B: Deref, F: Deref>(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec<Vec<(u32, bool, BitcoinOutPoint, InputMaterial)>>, height: u32, broadcaster: B, fee_estimator: F) -> Vec<SpendableOutputDescriptor>
+       pub(super) fn block_connected<B: Deref, F: Deref>(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec<ClaimRequest>, height: u32, broadcaster: B, fee_estimator: F) -> Vec<SpendableOutputDescriptor>
                where B::Target: BroadcasterInterface,
                      F::Target: FeeEstimator
        {
+               log_trace!(self, "Block at height {} connected with {} claim requests", height, claimable_outpoints.len());
                let mut new_claims = Vec::new();
                let mut aggregated_claim = HashMap::new();
                let mut aggregated_soonest = ::std::u32::MAX;
                let mut spendable_outputs = Vec::new();
 
-               // Try to aggregate outputs if they're 1) belong to same parent tx, 2) their
-               // timelock expiration isn't imminent (<= CLTV_SHARED_CLAIM_BUFFER).
-               for siblings_outpoints in claimable_outpoints {
-                       for outp in siblings_outpoints {
-                               // Don't claim a outpoint twice that would be bad for privacy and may uselessly lock a CPFP input for a while
-                               if let Some(_) = self.claimable_outpoints.get(&outp.2) { log_trace!(self, "Bouncing off outpoint {}:{}, already registered its claiming request", outp.2.txid, outp.2.vout); } else {
-                                       log_trace!(self, "Test if outpoint can be aggregated with expiration {} against {}", outp.0, height + CLTV_SHARED_CLAIM_BUFFER);
-                                       if outp.0 <= height + CLTV_SHARED_CLAIM_BUFFER || !outp.1 { // Don't aggregate if outpoint absolute timelock is soon or marked as non-aggregable
-                                               let mut single_input = HashMap::new();
-                                               single_input.insert(outp.2, outp.3);
-                                               new_claims.push((outp.0, single_input));
-                                       } else {
-                                               aggregated_claim.insert(outp.2, outp.3);
-                                               if outp.0 < aggregated_soonest {
-                                                       aggregated_soonest = outp.0;
-                                               }
+               // Try to aggregate outputs if their timelock expiration isn't imminent (absolute_timelock
+               // <= CLTV_SHARED_CLAIM_BUFFER) and they don't require an immediate nLockTime (aggregable).
+               for req in claimable_outpoints {
+                       // Don't claim a outpoint twice that would be bad for privacy and may uselessly lock a CPFP input for a while
+                       if let Some(_) = self.claimable_outpoints.get(&req.outpoint) { log_trace!(self, "Bouncing off outpoint {}:{}, already registered its claiming request", req.outpoint.txid, req.outpoint.vout); } else {
+                               log_trace!(self, "Test if outpoint can be aggregated with expiration {} against {}", req.absolute_timelock, height + CLTV_SHARED_CLAIM_BUFFER);
+                               if req.absolute_timelock <= height + CLTV_SHARED_CLAIM_BUFFER || !req.aggregable { // Don't aggregate if outpoint absolute timelock is soon or marked as non-aggregable
+                                       let mut single_input = HashMap::new();
+                                       single_input.insert(req.outpoint, req.witness_data);
+                                       new_claims.push((req.absolute_timelock, single_input));
+                               } else {
+                                       aggregated_claim.insert(req.outpoint, req.witness_data);
+                                       if req.absolute_timelock < aggregated_soonest {
+                                               aggregated_soonest = req.absolute_timelock;
                                        }
                                }
                        }
@@ -568,9 +574,11 @@ impl OnchainTxHandler {
                                                if set_equality {
                                                        clean_claim_request_after_safety_delay!();
                                                } else { // If false, generate new claim request with update outpoint set
+                                                       let mut at_least_one_drop = false;
                                                        for input in tx.input.iter() {
                                                                if let Some(input_material) = claim_material.per_input_material.remove(&input.previous_output) {
                                                                        claimed_outputs_material.push((input.previous_output, input_material));
+                                                                       at_least_one_drop = true;
                                                                }
                                                                // If there are no outpoints left to claim in this request, drop it entirely after ANTI_REORG_DELAY.
                                                                if claim_material.per_input_material.is_empty() {
@@ -578,7 +586,9 @@ impl OnchainTxHandler {
                                                                }
                                                        }
                                                        //TODO: recompute soonest_timelock to avoid wasting a bit on fees
-                                                       bump_candidates.insert(first_claim_txid_height.0.clone());
+                                                       if at_least_one_drop {
+                                                               bump_candidates.insert(first_claim_txid_height.0.clone());
+                                                       }
                                                }
                                                break; //No need to iterate further, either tx is our or their
                                        } else {
@@ -629,6 +639,7 @@ impl OnchainTxHandler {
                }
 
                // Build, bump and rebroadcast tx accordingly
+               log_trace!(self, "Bumping {} candidates", bump_candidates.len());
                for first_claim_txid in bump_candidates.iter() {
                        if let Some((new_timer, new_feerate)) = {
                                if let Some(claim_material) = self.pending_claim_requests.get(first_claim_txid) {