Generate ClaimEvent for HolderHTLCOutput inputs from anchor channels
authorWilmer Paulino <wilmer.paulino@gmail.com>
Wed, 30 Nov 2022 22:03:26 +0000 (14:03 -0800)
committerWilmer Paulino <wilmer.paulino@gmail.com>
Wed, 7 Dec 2022 00:48:20 +0000 (16:48 -0800)
lightning/src/chain/channelmonitor.rs
lightning/src/chain/onchaintx.rs
lightning/src/chain/package.rs

index 684824ee359686d97e8547a5cf625ac71d968546..93e1bb24487e2d3202cb07f35a4a27739bb79dce 100644 (file)
@@ -2425,6 +2425,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
                                                pending_htlcs,
                                        }));
                                },
+                               _ => {},
                        }
                }
                ret
index 8dcfea347040abf5a065217378ea2fcfbabc48c7..18465433d62650d3af1efc461263cc9252ad1dfc 100644 (file)
@@ -25,7 +25,7 @@ use crate::chain::keysinterface::BaseSign;
 use crate::ln::msgs::DecodeError;
 use crate::ln::PaymentPreimage;
 #[cfg(anchors)]
-use crate::ln::chan_utils;
+use crate::ln::chan_utils::{self, HTLCOutputInCommitment};
 use crate::ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction};
 #[cfg(anchors)]
 use crate::chain::chaininterface::ConfirmationTarget;
@@ -174,6 +174,16 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
        }
 }
 
+#[cfg(anchors)]
+/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
+pub(crate) struct ExternalHTLCClaim {
+       pub(crate) commitment_txid: Txid,
+       pub(crate) per_commitment_number: u64,
+       pub(crate) htlc: HTLCOutputInCommitment,
+       pub(crate) preimage: Option<PaymentPreimage>,
+       pub(crate) counterparty_sig: Signature,
+}
+
 // Represents the different types of claims for which events are yielded externally to satisfy said
 // claims.
 #[cfg(anchors)]
@@ -185,6 +195,12 @@ pub(crate) enum ClaimEvent {
                commitment_tx: Transaction,
                anchor_output_idx: u32,
        },
+       /// Event yielded to signal that the commitment transaction has confirmed and its HTLCs must be
+       /// resolved by broadcasting a transaction with sufficient fee to claim them.
+       BumpHTLC {
+               target_feerate_sat_per_1000_weight: u32,
+               htlcs: Vec<ExternalHTLCClaim>,
+       },
 }
 
 /// Represents the different ways an output can be claimed (i.e., spent to an address under our
@@ -488,15 +504,36 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
                // didn't receive confirmation of it before, or not enough reorg-safe depth on top of it).
                let new_timer = Some(cached_request.get_height_timer(cur_height));
                if cached_request.is_malleable() {
+                       #[cfg(anchors)]
+                       { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
+                               if cached_request.requires_external_funding() {
+                                       let target_feerate_sat_per_1000_weight = cached_request
+                                               .compute_package_feerate(fee_estimator, ConfirmationTarget::HighPriority);
+                                       if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
+                                               return Some((
+                                                       new_timer,
+                                                       target_feerate_sat_per_1000_weight as u64,
+                                                       OnchainClaim::Event(ClaimEvent::BumpHTLC {
+                                                               target_feerate_sat_per_1000_weight,
+                                                               htlcs,
+                                                       }),
+                                               ));
+                                       } else {
+                                               return None;
+                                       }
+                               }
+                       }
+
                        let predicted_weight = cached_request.package_weight(&self.destination_script);
-                       if let Some((output_value, new_feerate)) =
-                                       cached_request.compute_package_output(predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger) {
+                       if let Some((output_value, new_feerate)) = cached_request.compute_package_output(
+                               predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger,
+                       ) {
                                assert!(new_feerate != 0);
 
                                let transaction = cached_request.finalize_malleable_package(self, output_value, self.destination_script.clone(), logger).unwrap();
                                log_trace!(logger, "...with timer {} and feerate {}", new_timer.unwrap(), new_feerate);
                                assert!(predicted_weight >= transaction.weight());
-                               return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)))
+                               return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)));
                        }
                } else {
                        // Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
@@ -552,7 +589,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
                                        debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding");
                                        None
                                },
-                       });
+                       })
                }
                None
        }
@@ -640,10 +677,20 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
                                        #[cfg(anchors)]
                                        OnchainClaim::Event(claim_event) => {
                                                log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints());
-                                               let txid = match claim_event {
-                                                       ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(),
+                                               let package_id = match claim_event {
+                                                       ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid().into_inner(),
+                                                       ClaimEvent::BumpHTLC { ref htlcs, .. } => {
+                                                               // Use the same construction as a lightning channel id to generate
+                                                               // the package id for this request based on the first HTLC. It
+                                                               // doesn't matter what we use as long as it's unique per request.
+                                                               let mut package_id = [0; 32];
+                                                               package_id[..].copy_from_slice(&htlcs[0].commitment_txid[..]);
+                                                               let htlc_output_index = htlcs[0].htlc.transaction_output_index.unwrap();
+                                                               package_id[30] ^= ((htlc_output_index >> 8) & 0xff) as u8;
+                                                               package_id[31] ^= ((htlc_output_index >> 0) & 0xff) as u8;
+                                                               package_id
+                                                       },
                                                };
-                                               let package_id = txid.into_inner();
                                                self.pending_claim_events.insert(package_id, claim_event);
                                                package_id
                                        },
@@ -686,14 +733,31 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
                                                // outpoints to know if transaction is the original claim or a bumped one issued
                                                // by us.
                                                let mut set_equality = true;
-                                               if request.outpoints().len() != tx.input.len() {
-                                                       set_equality = false;
+                                               if !request.requires_external_funding() || !request.is_malleable() {
+                                                       // If the claim does not require external funds to be allocated through
+                                                       // additional inputs we can simply check the inputs in order as they
+                                                       // cannot change under us.
+                                                       if request.outpoints().len() != tx.input.len() {
+                                                               set_equality = false;
+                                                       } else {
+                                                               for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
+                                                                       if **claim_inp != tx_inp.previous_output {
+                                                                               set_equality = false;
+                                                                       }
+                                                               }
+                                                       }
                                                } else {
-                                                       for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
-                                                               if **claim_inp != tx_inp.previous_output {
-                                                                       set_equality = false;
+                                                       // Otherwise, we'll do a linear search for each input (we don't expect
+                                                       // large input sets to exist) to ensure the request's input set is fully
+                                                       // spent to be resilient against the external claim reordering inputs.
+                                                       let mut spends_all_inputs = true;
+                                                       for request_input in request.outpoints() {
+                                                               if tx.input.iter().find(|input| input.previous_output == *request_input).is_none() {
+                                                                       spends_all_inputs = false;
+                                                                       break;
                                                                }
                                                        }
+                                                       set_equality = spends_all_inputs;
                                                }
 
                                                macro_rules! clean_claim_request_after_safety_delay {
@@ -999,6 +1063,37 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
                htlc_tx
        }
 
+       #[cfg(anchors)]
+       pub(crate) fn generate_external_htlc_claim(
+               &mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>
+       ) -> Option<ExternalHTLCClaim> {
+               let find_htlc = |holder_commitment: &HolderCommitmentTransaction| -> Option<ExternalHTLCClaim> {
+                       let trusted_tx = holder_commitment.trust();
+                       if outp.txid != trusted_tx.txid() {
+                               return None;
+                       }
+                       trusted_tx.htlcs().iter().enumerate()
+                               .find(|(_, htlc)| if let Some(output_index) = htlc.transaction_output_index {
+                                       output_index == outp.vout
+                               } else {
+                                       false
+                               })
+                               .map(|(htlc_idx, htlc)| {
+                                       let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
+                                       ExternalHTLCClaim {
+                                               commitment_txid: trusted_tx.txid(),
+                                               per_commitment_number: trusted_tx.commitment_number(),
+                                               htlc: htlc.clone(),
+                                               preimage: *preimage,
+                                               counterparty_sig: counterparty_htlc_sig,
+                                       }
+                               })
+               };
+               // Check if the HTLC spends from the current holder commitment or the previous one otherwise.
+               find_htlc(&self.holder_commitment)
+                       .or_else(|| self.prev_holder_commitment.as_ref().map(|c| find_htlc(c)).flatten())
+       }
+
        pub(crate) fn opt_anchors(&self) -> bool {
                self.channel_transaction_parameters.opt_anchors.is_some()
        }
index b135db5c07ff86cb6e9c4cdedcf111f4e1df8d11..dbf46298888ce20a8225c6a11bd88e1394c2552e 100644 (file)
@@ -26,6 +26,8 @@ use crate::ln::chan_utils;
 use crate::ln::msgs::DecodeError;
 use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
 use crate::chain::keysinterface::Sign;
+#[cfg(anchors)]
+use crate::chain::onchaintx::ExternalHTLCClaim;
 use crate::chain::onchaintx::OnchainTxHandler;
 use crate::util::logger::Logger;
 use crate::util::ser::{Readable, Writer, Writeable};
@@ -448,8 +450,13 @@ impl PackageSolvingData {
        }
        fn get_finalized_tx<Signer: Sign>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<Transaction> {
                match self {
-                       PackageSolvingData::HolderHTLCOutput(ref outp) => { return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage); }
-                       PackageSolvingData::HolderFundingOutput(ref outp) => { return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript)); }
+                       PackageSolvingData::HolderHTLCOutput(ref outp) => {
+                               debug_assert!(!outp.opt_anchors());
+                               return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage);
+                       }
+                       PackageSolvingData::HolderFundingOutput(ref outp) => {
+                               return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript));
+                       }
                        _ => { panic!("API Error!"); }
                }
        }
@@ -649,6 +656,25 @@ impl PackageTemplate {
                let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR;
                inputs_weight + witnesses_weight + transaction_weight + output_weight
        }
+       #[cfg(anchors)]
+       pub(crate) fn construct_malleable_package_with_external_funding<Signer: Sign>(
+               &self, onchain_handler: &mut OnchainTxHandler<Signer>,
+       ) -> Option<Vec<ExternalHTLCClaim>> {
+               debug_assert!(self.requires_external_funding());
+               let mut htlcs: Option<Vec<ExternalHTLCClaim>> = None;
+               for (previous_output, input) in &self.inputs {
+                       match input {
+                               PackageSolvingData::HolderHTLCOutput(ref outp) => {
+                                       debug_assert!(outp.opt_anchors());
+                                       onchain_handler.generate_external_htlc_claim(&previous_output, &outp.preimage).map(|htlc| {
+                                               htlcs.get_or_insert_with(|| Vec::with_capacity(self.inputs.len())).push(htlc);
+                                       });
+                               }
+                               _ => debug_assert!(false, "Expected HolderHTLCOutputs to not be aggregated with other input types"),
+                       }
+               }
+               htlcs
+       }
        pub(crate) fn finalize_malleable_package<L: Deref, Signer: Sign>(
                &self, onchain_handler: &mut OnchainTxHandler<Signer>, value: u64, destination_script: Script, logger: &L
        ) -> Option<Transaction> where L::Target: Logger {