X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonchaintx.rs;h=2d7d417d255ac77dfecb1791dd24bca42d4166ca;hb=151d4ac0a3a6aa94d3f93995b71c874b7ea95967;hp=fce935549024ae697e5e611bb730382ce8f5f83f;hpb=6b8a5166476228f93a06fe55db40fd9715b118ad;p=rust-lightning diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index fce93554..2d7d417d 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -1,33 +1,41 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! The logic to build claims and bump in-flight transactions until confirmations. //! -//! OnchainTxHandler objetcs are fully-part of ChannelMonitor and encapsulates all +//! OnchainTxHandler objects are fully-part of ChannelMonitor and encapsulates all //! building, tracking, bumping and notifications functions. use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut, SigHashType}; use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint; use bitcoin::blockdata::script::Script; -use bitcoin::util::bip143; -use bitcoin_hashes::sha256d::Hash as Sha256dHash; +use bitcoin::hash_types::Txid; -use secp256k1::{Secp256k1, Signature}; -use secp256k1; +use bitcoin::secp256k1::{Secp256k1, Signature}; +use bitcoin::secp256k1; use ln::msgs::DecodeError; -use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest}; -use ln::channelmanager::HTLCSource; +use ln::channelmanager::PaymentPreimage; use ln::chan_utils; -use ln::chan_utils::{HTLCType, LocalCommitmentTransaction, TxCreationKeys, HTLCOutputInCommitment}; +use ln::chan_utils::{TxCreationKeys, ChannelTransactionParameters, HolderCommitmentTransaction}; use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT}; -use chain::keysinterface::ChannelKeys; +use chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest}; +use chain::keysinterface::{ChannelKeys, KeysInterface}; use util::logger::Logger; -use util::ser::{ReadableArgs, Readable, Writer, Writeable}; +use util::ser::{Readable, ReadableArgs, Writer, Writeable, VecWriter}; use util::byte_utils; -use std::collections::{HashMap, hash_map, HashSet}; -use std::sync::Arc; +use std::collections::{HashMap, hash_map}; use std::cmp; use std::ops::Deref; +use std::mem::replace; const MAX_ALLOC_SIZE: usize = 64*1024; @@ -38,9 +46,9 @@ enum OnchainEvent { /// Outpoint under claim process by our own tx, once this one get enough confirmations, we remove it from /// bump-txn candidate buffer. Claim { - claim_request: Sha256dHash, + claim_request: Txid, }, - /// Claim tx aggregate multiple claimable outpoints. One of the outpoint may be claimed by a remote party tx. + /// Claim tx aggregate multiple claimable outpoints. One of the outpoint may be claimed by a counterparty party tx. /// In this case, we need to drop the outpoint and regenerate a new claim tx. By safety, we keep tracking /// the outpoint to be sure to resurect it back to the claim tx if reorgs happen. ContentiousOutpoint { @@ -49,15 +57,6 @@ enum OnchainEvent { } } -/// Cache public keys and feerate used to compute any HTLC transaction. -/// We only keep state for latest 2 commitment transactions as we should -/// never have to generate HTLC txn for revoked local commitment -struct HTLCTxCache { - local_keys: TxCreationKeys, - feerate_per_kw: u64, - per_htlc: HashMap)> -} - /// Higher-level cache structure needed to re-generate bumped claim txn if needed #[derive(Clone, PartialEq)] pub struct ClaimTxBumpMaterial { @@ -65,7 +64,7 @@ pub struct ClaimTxBumpMaterial { // much time for confirmation and we need to bump it. height_timer: Option, // Tracked in case of reorg to wipe out now-superflous bump material - feerate_previous: u64, + feerate_previous: u32, // Soonest timelocks among set of outpoints claimed, used to compute // a priority of not feerate soonest_timelock: u32, @@ -76,7 +75,7 @@ pub struct ClaimTxBumpMaterial { impl Writeable for ClaimTxBumpMaterial { fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { self.height_timer.write(writer)?; - writer.write_all(&byte_utils::be64_to_array(self.feerate_previous))?; + writer.write_all(&byte_utils::be32_to_array(self.feerate_previous))?; writer.write_all(&byte_utils::be32_to_array(self.soonest_timelock))?; writer.write_all(&byte_utils::be64_to_array(self.per_input_material.len() as u64))?; for (outp, tx_material) in self.per_input_material.iter() { @@ -103,38 +102,85 @@ impl Readable for ClaimTxBumpMaterial { } } -#[derive(PartialEq)] -pub(super) enum InputDescriptors { +#[derive(PartialEq, Clone, Copy)] +pub(crate) enum InputDescriptors { RevokedOfferedHTLC, RevokedReceivedHTLC, OfferedHTLC, ReceivedHTLC, - RevokedOutput, // either a revoked to_local output on commitment tx, a revoked HTLC-Timeout output or a revoked HTLC-Success output + RevokedOutput, // either a revoked to_holder output on commitment tx, a revoked HTLC-Timeout output or a revoked HTLC-Success output +} + +impl Writeable for InputDescriptors { + fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { + match self { + &InputDescriptors::RevokedOfferedHTLC => { + writer.write_all(&[0; 1])?; + }, + &InputDescriptors::RevokedReceivedHTLC => { + writer.write_all(&[1; 1])?; + }, + &InputDescriptors::OfferedHTLC => { + writer.write_all(&[2; 1])?; + }, + &InputDescriptors::ReceivedHTLC => { + writer.write_all(&[3; 1])?; + } + &InputDescriptors::RevokedOutput => { + writer.write_all(&[4; 1])?; + } + } + Ok(()) + } +} + +impl Readable for InputDescriptors { + fn read(reader: &mut R) -> Result { + let input_descriptor = match ::read(reader)? { + 0 => { + InputDescriptors::RevokedOfferedHTLC + }, + 1 => { + InputDescriptors::RevokedReceivedHTLC + }, + 2 => { + InputDescriptors::OfferedHTLC + }, + 3 => { + InputDescriptors::ReceivedHTLC + }, + 4 => { + InputDescriptors::RevokedOutput + } + _ => return Err(DecodeError::InvalidValue), + }; + Ok(input_descriptor) + } } macro_rules! subtract_high_prio_fee { - ($self: ident, $fee_estimator: expr, $value: expr, $predicted_weight: expr, $used_feerate: expr) => { + ($logger: ident, $fee_estimator: expr, $value: expr, $predicted_weight: expr, $used_feerate: expr) => { { - $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority); - let mut fee = $used_feerate * ($predicted_weight as u64) / 1000; + $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority).into(); + let mut fee = $used_feerate as u64 * $predicted_weight / 1000; if $value <= fee { - $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); - fee = $used_feerate * ($predicted_weight as u64) / 1000; - if $value <= fee { - $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background); - fee = $used_feerate * ($predicted_weight as u64) / 1000; + $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal).into(); + fee = $used_feerate as u64 * $predicted_weight / 1000; + if $value <= fee.into() { + $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background).into(); + fee = $used_feerate as u64 * $predicted_weight / 1000; if $value <= fee { - log_error!($self, "Failed to generate an on-chain punishment tx as even low priority fee ({} sat) was more than the entire claim balance ({} sat)", + log_error!($logger, "Failed to generate an on-chain punishment tx as even low priority fee ({} sat) was more than the entire claim balance ({} sat)", fee, $value); false } else { - log_warn!($self, "Used low priority fee for on-chain punishment tx as high priority fee was more than the entire claim balance ({} sat)", + log_warn!($logger, "Used low priority fee for on-chain punishment tx as high priority fee was more than the entire claim balance ({} sat)", $value); $value -= fee; true } } else { - log_warn!($self, "Used medium priority fee for on-chain punishment tx as high priority fee was more than the entire claim balance ({} sat)", + log_warn!($logger, "Used medium priority fee for on-chain punishment tx as high priority fee was more than the entire claim balance ({} sat)", $value); $value -= fee; true @@ -147,19 +193,65 @@ macro_rules! subtract_high_prio_fee { } } +impl Readable for Option>> { + fn read(reader: &mut R) -> Result { + match Readable::read(reader)? { + 0u8 => Ok(None), + 1u8 => { + let vlen: u64 = Readable::read(reader)?; + let mut ret = Vec::with_capacity(cmp::min(vlen as usize, MAX_ALLOC_SIZE / ::std::mem::size_of::>())); + for _ in 0..vlen { + ret.push(match Readable::read(reader)? { + 0u8 => None, + 1u8 => Some((::read(reader)? as usize, Readable::read(reader)?)), + _ => return Err(DecodeError::InvalidValue) + }); + } + Ok(Some(ret)) + }, + _ => Err(DecodeError::InvalidValue), + } + } +} + +impl Writeable for Option>> { + fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { + match self { + &Some(ref vec) => { + 1u8.write(writer)?; + (vec.len() as u64).write(writer)?; + for opt in vec.iter() { + match opt { + &Some((ref idx, ref sig)) => { + 1u8.write(writer)?; + (*idx as u64).write(writer)?; + sig.write(writer)?; + }, + &None => 0u8.write(writer)?, + } + } + }, + &None => 0u8.write(writer)?, + } + Ok(()) + } +} + /// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and /// do RBF bumping if possible. pub struct OnchainTxHandler { destination_script: Script, - funding_redeemscript: Script, - local_commitment: Option, - prev_local_commitment: Option, - current_htlc_cache: Option, - prev_htlc_cache: Option, - local_csv: u16, + holder_commitment: HolderCommitmentTransaction, + // holder_htlc_sigs and prev_holder_htlc_sigs are in the order as they appear in the commitment + // transaction outputs (hence the Option<>s inside the Vec). The first usize is the index in + // the set of HTLCs in the HolderCommitmentTransaction. + holder_htlc_sigs: Option>>, + prev_holder_commitment: Option, + prev_holder_htlc_sigs: Option>>, key_storage: ChanSigner, + pub(crate) channel_transaction_parameters: ChannelTransactionParameters, // Used to track claiming requests. If claim tx doesn't confirm before height timer expiration we need to bump // it (RBF or CPFP). If an input has been part of an aggregate tx at first claim try, we need to keep it within @@ -173,9 +265,9 @@ pub struct OnchainTxHandler { // us and is immutable until all outpoint of the claimable set are post-anti-reorg-delay solved. // Entry is cache of elements need to generate a bumped claiming transaction (see ClaimTxBumpMaterial) #[cfg(test)] // Used in functional_test to verify sanitization - pub pending_claim_requests: HashMap, + pub pending_claim_requests: HashMap, #[cfg(not(test))] - pending_claim_requests: HashMap, + pending_claim_requests: HashMap, // Used to link outpoints claimed in a connected block to a pending claim request. // Key is outpoint than monitor parsing has detected we have keys/scripts to claim @@ -184,56 +276,33 @@ pub struct OnchainTxHandler { // post-anti-reorg-delay solved, confirmaiton_block is used to erase entry if // block with output gets disconnected. #[cfg(test)] // Used in functional_test to verify sanitization - pub claimable_outpoints: HashMap, + pub claimable_outpoints: HashMap, #[cfg(not(test))] - claimable_outpoints: HashMap, + claimable_outpoints: HashMap, onchain_events_waiting_threshold_conf: HashMap>, + latest_height: u32, + secp_ctx: Secp256k1, - logger: Arc } -impl OnchainTxHandler { +impl OnchainTxHandler { pub(crate) fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { self.destination_script.write(writer)?; - self.funding_redeemscript.write(writer)?; - self.local_commitment.write(writer)?; - self.prev_local_commitment.write(writer)?; - - macro_rules! serialize_htlc_cache { - ($cache: expr) => { - $cache.local_keys.write(writer)?; - $cache.feerate_per_kw.write(writer)?; - writer.write_all(&byte_utils::be64_to_array($cache.per_htlc.len() as u64))?; - for (_, &(ref htlc, ref sig)) in $cache.per_htlc.iter() { - htlc.write(writer)?; - if let &Some(ref their_sig) = sig { - 1u8.write(writer)?; - writer.write_all(&their_sig.serialize_compact())?; - } else { - 0u8.write(writer)?; - } - } - } - } - - if let Some(ref current) = self.current_htlc_cache { - writer.write_all(&[1; 1])?; - serialize_htlc_cache!(current); - } else { - writer.write_all(&[0; 1])?; - } + self.holder_commitment.write(writer)?; + self.holder_htlc_sigs.write(writer)?; + self.prev_holder_commitment.write(writer)?; + self.prev_holder_htlc_sigs.write(writer)?; - if let Some(ref prev) = self.prev_htlc_cache { - writer.write_all(&[1; 1])?; - serialize_htlc_cache!(prev); - } else { - writer.write_all(&[0; 1])?; - } - self.local_csv.write(writer)?; + self.channel_transaction_parameters.write(writer)?; - self.key_storage.write(writer)?; + let mut key_data = VecWriter(Vec::new()); + self.key_storage.write(&mut key_data)?; + assert!(key_data.0.len() < std::usize::MAX); + assert!(key_data.0.len() < std::u32::MAX as usize); + (key_data.0.len() as u32).write(writer)?; + writer.write_all(&key_data.0[..])?; writer.write_all(&byte_utils::be64_to_array(self.pending_claim_requests.len() as u64))?; for (ref ancestor_claim_txid, claim_tx_data) in self.pending_claim_requests.iter() { @@ -266,60 +335,32 @@ impl OnchainTxHandler { } } } + self.latest_height.write(writer)?; Ok(()) } } -impl ReadableArgs> for OnchainTxHandler { - fn read(reader: &mut R, logger: Arc) -> Result { +impl<'a, K: KeysInterface> ReadableArgs<&'a K> for OnchainTxHandler { + fn read(reader: &mut R, keys_manager: &'a K) -> Result { let destination_script = Readable::read(reader)?; - let funding_redeemscript = Readable::read(reader)?; - let local_commitment = Readable::read(reader)?; - let prev_local_commitment = Readable::read(reader)?; - macro_rules! read_htlc_cache { - () => { - { - let local_keys = Readable::read(reader)?; - let feerate_per_kw = Readable::read(reader)?; - let htlcs_count: u64 = Readable::read(reader)?; - let mut per_htlc = HashMap::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / 32)); - for _ in 0..htlcs_count { - let htlc: HTLCOutputInCommitment = Readable::read(reader)?; - let sigs = match ::read(reader)? { - 0 => None, - 1 => Some(Readable::read(reader)?), - _ => return Err(DecodeError::InvalidValue), - }; - per_htlc.insert(htlc.transaction_output_index.unwrap(), (htlc, sigs)); - } - HTLCTxCache { - local_keys, - feerate_per_kw, - per_htlc - } - } - } - } + let holder_commitment = Readable::read(reader)?; + let holder_htlc_sigs = Readable::read(reader)?; + let prev_holder_commitment = Readable::read(reader)?; + let prev_holder_htlc_sigs = Readable::read(reader)?; - let current_htlc_cache = match ::read(reader)? { - 0 => None, - 1 => { - Some(read_htlc_cache!()) - } - _ => return Err(DecodeError::InvalidValue), - }; + let channel_parameters = Readable::read(reader)?; - let prev_htlc_cache = match ::read(reader)? { - 0 => None, - 1 => { - Some(read_htlc_cache!()) - } - _ => return Err(DecodeError::InvalidValue), - }; - let local_csv = Readable::read(reader)?; - - let key_storage = Readable::read(reader)?; + let keys_len: u32 = Readable::read(reader)?; + let mut keys_data = Vec::with_capacity(cmp::min(keys_len as usize, MAX_ALLOC_SIZE)); + while keys_data.len() != keys_len as usize { + // Read 1KB at a time to avoid accidentally allocating 4GB on corrupted channel keys + let mut data = [0; 1024]; + let read_slice = &mut data[0..cmp::min(1024, keys_len as usize - keys_data.len())]; + reader.read_exact(read_slice)?; + keys_data.extend_from_slice(read_slice); + } + let key_storage = keys_manager.read_chan_signer(&keys_data)?; let pending_claim_requests_len: u64 = Readable::read(reader)?; let mut pending_claim_requests = HashMap::with_capacity(cmp::min(pending_claim_requests_len as usize, MAX_ALLOC_SIZE / 128)); @@ -363,49 +404,48 @@ impl ReadableArgs> for OnchainTx } onchain_events_waiting_threshold_conf.insert(height_target, events); } + let latest_height = Readable::read(reader)?; Ok(OnchainTxHandler { destination_script, - funding_redeemscript, - local_commitment, - prev_local_commitment, - current_htlc_cache, - prev_htlc_cache, - local_csv, + holder_commitment, + holder_htlc_sigs, + prev_holder_commitment, + prev_holder_htlc_sigs, key_storage, + channel_transaction_parameters: channel_parameters, claimable_outpoints, pending_claim_requests, onchain_events_waiting_threshold_conf, + latest_height, secp_ctx: Secp256k1::new(), - logger, }) } } impl OnchainTxHandler { - pub(super) fn new(destination_script: Script, keys: ChanSigner, funding_redeemscript: Script, local_csv: u16, logger: Arc) -> Self { + pub(crate) fn new(destination_script: Script, keys: ChanSigner, channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction) -> Self { let key_storage = keys; OnchainTxHandler { destination_script, - funding_redeemscript, - local_commitment: None, - prev_local_commitment: None, - current_htlc_cache: None, - prev_htlc_cache: None, - local_csv, + holder_commitment, + holder_htlc_sigs: None, + prev_holder_commitment: None, + prev_holder_htlc_sigs: None, key_storage, + channel_transaction_parameters: channel_parameters, pending_claim_requests: HashMap::new(), claimable_outpoints: HashMap::new(), onchain_events_waiting_threshold_conf: HashMap::new(), + latest_height: 0, secp_ctx: Secp256k1::new(), - logger, } } - pub(super) fn get_witnesses_weight(inputs: &[InputDescriptors]) -> usize { + pub(crate) fn get_witnesses_weight(inputs: &[InputDescriptors]) -> usize { let mut tx_weight = 2; // count segwit flags for inp in inputs { // We use expected weight (and not actual) as signatures and time lock delays may vary @@ -418,7 +458,7 @@ impl OnchainTxHandler { &InputDescriptors::RevokedReceivedHTLC => { 1 + 1 + 73 + 1 + 33 + 1 + 139 }, - // number_of_witness_elements + sig_length + remotehtlc_sig + preimage_length + preimage + witness_script_length + witness_script + // number_of_witness_elements + sig_length + counterpartyhtlc_sig + preimage_length + preimage + witness_script_length + witness_script &InputDescriptors::OfferedHTLC => { 1 + 1 + 73 + 1 + 32 + 1 + 133 }, @@ -451,13 +491,16 @@ impl OnchainTxHandler { /// Lightning security model (i.e being able to redeem/timeout HTLC or penalize coutnerparty onchain) lays on the assumption of claim transactions getting confirmed before timelock expiration /// (CSV or CLTV following cases). In case of high-fee spikes, claim tx may stuck in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or Child-Pay-For-Parent. - fn generate_claim_tx(&self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: F) -> Option<(Option, u64, Transaction)> - where F::Target: FeeEstimator + /// Panics if there are signing errors, because signing operations in reaction to on-chain events + /// are not expected to fail, and if they do, we may lose funds. + fn generate_claim_tx(&mut self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: &F, logger: &L) -> Option<(Option, u32, Transaction)> + where F::Target: FeeEstimator, + L::Target: Logger, { if cached_claim_datas.per_input_material.len() == 0 { return None } // But don't prune pending claiming request yet, we may have to resurrect HTLCs let mut inputs = Vec::new(); for outp in cached_claim_datas.per_input_material.keys() { - log_trace!(self, "Outpoint {}:{}", outp.txid, outp.vout); + log_trace!(logger, "Outpoint {}:{}", outp.txid, outp.vout); inputs.push(TxIn { previous_output: *outp, script_sig: Script::new(), @@ -478,29 +521,29 @@ impl OnchainTxHandler { macro_rules! RBF_bump { ($amount: expr, $old_feerate: expr, $fee_estimator: expr, $predicted_weight: expr) => { { - let mut used_feerate; + let mut used_feerate: u32; // If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee... let new_fee = if $old_feerate < $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) { let mut value = $amount; - if subtract_high_prio_fee!(self, $fee_estimator, value, $predicted_weight, used_feerate) { + if subtract_high_prio_fee!(logger, $fee_estimator, value, $predicted_weight, used_feerate) { // Overflow check is done in subtract_high_prio_fee - $amount - value + ($amount - value) } else { - log_trace!(self, "Can't new-estimation bump new claiming tx, amount {} is too small", $amount); + log_trace!(logger, "Can't new-estimation bump new claiming tx, amount {} is too small", $amount); return None; } // ...else just increase the previous feerate by 25% (because that's a nice number) } else { - let fee = $old_feerate * $predicted_weight / 750; + let fee = $old_feerate as u64 * ($predicted_weight as u64) / 750; if $amount <= fee { - log_trace!(self, "Can't 25% bump new claiming tx, amount {} is too small", $amount); + log_trace!(logger, "Can't 25% bump new claiming tx, amount {} is too small", $amount); return None; } fee }; - let previous_fee = $old_feerate * $predicted_weight / 1000; - let min_relay_fee = MIN_RELAY_FEE_SAT_PER_1000_WEIGHT * $predicted_weight / 1000; + let previous_fee = $old_feerate as u64 * ($predicted_weight as u64) / 1000; + let min_relay_fee = MIN_RELAY_FEE_SAT_PER_1000_WEIGHT * ($predicted_weight as u64) / 1000; // BIP 125 Opt-in Full Replace-by-Fee Signaling // * 3. The replacement transaction pays an absolute fee of at least the sum paid by the original transactions. // * 4. The replacement transaction must also pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting. @@ -509,7 +552,7 @@ impl OnchainTxHandler { } else { new_fee }; - Some((new_fee, new_fee * 1000 / $predicted_weight)) + Some((new_fee, new_fee * 1000 / ($predicted_weight as u64))) } } } @@ -522,37 +565,38 @@ impl OnchainTxHandler { let mut dynamic_fee = true; for per_outp_material in cached_claim_datas.per_input_material.values() { match per_outp_material { - &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!() }); + &InputMaterial::Revoked { ref input_descriptor, ref amount, .. } => { + inputs_witnesses_weight += Self::get_witnesses_weight(&[*input_descriptor]); amt += *amount; }, - &InputMaterial::RemoteHTLC { ref preimage, ref amount, .. } => { + &InputMaterial::CounterpartyHTLC { ref preimage, ref htlc, .. } => { inputs_witnesses_weight += Self::get_witnesses_weight(if preimage.is_some() { &[InputDescriptors::OfferedHTLC] } else { &[InputDescriptors::ReceivedHTLC] }); - amt += *amount; + amt += htlc.amount_msat / 1000; + }, + &InputMaterial::HolderHTLC { .. } => { + dynamic_fee = false; }, - &InputMaterial::LocalHTLC { .. } => { return None; } &InputMaterial::Funding { .. } => { dynamic_fee = false; } } } - if dynamic_fee { - let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight; + let predicted_weight = (bumped_tx.get_weight() + inputs_witnesses_weight) as u64; let mut new_feerate; // If old feerate is 0, first iteration of this claim, use normal fee calculation if cached_claim_datas.feerate_previous != 0 { - if let Some((new_fee, feerate)) = RBF_bump!(amt, cached_claim_datas.feerate_previous, fee_estimator, predicted_weight as u64) { + if let Some((new_fee, feerate)) = RBF_bump!(amt, cached_claim_datas.feerate_previous, fee_estimator, predicted_weight) { // If new computed fee is superior at the whole claimable amount burn all in fees - if new_fee > amt { + if new_fee as u64 > amt { bumped_tx.output[0].value = 0; } else { - bumped_tx.output[0].value = amt - new_fee; + bumped_tx.output[0].value = amt - new_fee as u64; } new_feerate = feerate; } else { return None; } } else { - if subtract_high_prio_fee!(self, fee_estimator, amt, predicted_weight, new_feerate) { + if subtract_high_prio_fee!(logger, fee_estimator, amt, predicted_weight, new_feerate) { bumped_tx.output[0].value = amt; } else { return None; } } @@ -560,64 +604,70 @@ impl OnchainTxHandler { for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() { match per_outp_material { - &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], &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); - if *is_htlc { - bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec()); - } else { - bumped_tx.input[i].witness.push(vec!(1)); + &InputMaterial::Revoked { ref per_commitment_point, ref counterparty_delayed_payment_base_key, ref counterparty_htlc_base_key, ref per_commitment_key, ref input_descriptor, ref amount, ref htlc, ref on_counterparty_tx_csv } => { + if let Ok(chan_keys) = TxCreationKeys::derive_new(&self.secp_ctx, &per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { + + let witness_script = if let Some(ref htlc) = *htlc { + chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key) + } else { + chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, *on_counterparty_tx_csv, &chan_keys.broadcaster_delayed_payment_key) + }; + + let sig = self.key_storage.sign_justice_transaction(&bumped_tx, i, *amount, &per_commitment_key, htlc, &self.secp_ctx).expect("sign justice tx"); + bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[i].witness[0].push(SigHashType::All as u8); + if htlc.is_some() { + bumped_tx.input[i].witness.push(chan_keys.revocation_key.clone().serialize().to_vec()); + } else { + bumped_tx.input[i].witness.push(vec!(1)); + } + bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + + log_trace!(logger, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if *input_descriptor == InputDescriptors::RevokedOutput { "to_holder" } else if *input_descriptor == InputDescriptors::RevokedOfferedHTLC { "offered" } else if *input_descriptor == InputDescriptors::RevokedReceivedHTLC { "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 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], &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); - if let &Some(preimage) = preimage { - bumped_tx.input[i].witness.push(preimage.clone().0.to_vec()); - } else { - bumped_tx.input[i].witness.push(vec![]); + &InputMaterial::CounterpartyHTLC { ref per_commitment_point, ref counterparty_delayed_payment_base_key, ref counterparty_htlc_base_key, ref preimage, ref htlc } => { + if let Ok(chan_keys) = TxCreationKeys::derive_new(&self.secp_ctx, &per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { + let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); + + if !preimage.is_some() { bumped_tx.lock_time = htlc.cltv_expiry }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation + let sig = self.key_storage.sign_counterparty_htlc_transaction(&bumped_tx, i, &htlc.amount_msat / 1000, &per_commitment_point, htlc, &self.secp_ctx).expect("sign counterparty HTLC tx"); + bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[i].witness[0].push(SigHashType::All as u8); + if let &Some(preimage) = preimage { + bumped_tx.input[i].witness.push(preimage.0.to_vec()); + } else { + // Due to BIP146 (MINIMALIF) this must be a zero-length element to relay. + bumped_tx.input[i].witness.push(vec![]); + } + bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + log_trace!(logger, "Going to broadcast Claim Transaction {} claiming counterparty {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate); } - 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); }, _ => unreachable!() } } - log_trace!(self, "...with timer {}", new_timer.unwrap()); - assert!(predicted_weight >= bumped_tx.get_weight()); - return Some((new_timer, new_feerate, bumped_tx)) + log_trace!(logger, "...with timer {}", new_timer.unwrap()); + assert!(predicted_weight >= bumped_tx.get_weight() as u64); + return Some((new_timer, new_feerate as u32, bumped_tx)) } else { for (_, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() { match per_outp_material { - &InputMaterial::LocalHTLC { .. } => { - //TODO : Given that Local Commitment Transaction and HTLC-Timeout/HTLC-Success are counter-signed by peer, we can't - // RBF them. Need a Lightning specs change and package relay modification : - // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html - return None; - }, - &InputMaterial::Funding { ref channel_value } => { - if self.local_commitment.is_some() { - let mut local_commitment = self.local_commitment.clone().unwrap(); - self.key_storage.sign_local_commitment(&mut local_commitment, &self.funding_redeemscript, *channel_value, &self.secp_ctx); - let signed_tx = local_commitment.with_valid_witness().clone(); - let mut amt_outputs = 0; - for outp in signed_tx.output.iter() { - amt_outputs += outp.value; - } - let feerate = (channel_value - amt_outputs) * 1000 / signed_tx.get_weight() as u64; + &InputMaterial::HolderHTLC { ref preimage, ref amount } => { + let htlc_tx = self.get_fully_signed_htlc_tx(outp, preimage); + if let Some(htlc_tx) = htlc_tx { + let feerate = (amount - htlc_tx.output[0].value) * 1000 / htlc_tx.get_weight() as u64; // Timer set to $NEVER given we can't bump tx without anchor outputs - log_trace!(self, "Going to broadcast Local Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid); - return Some((None, feerate, signed_tx)); + log_trace!(logger, "Going to broadcast Holder HTLC-{} claiming HTLC output {} from {}...", if preimage.is_some() { "Success" } else { "Timeout" }, outp.vout, outp.txid); + return Some((None, feerate as u32, htlc_tx)); } + return None; + }, + &InputMaterial::Funding { ref funding_redeemscript } => { + let signed_tx = self.get_fully_signed_holder_tx(funding_redeemscript); + // Timer set to $NEVER given we can't bump tx without anchor outputs + log_trace!(logger, "Going to broadcast Holder Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid); + return Some((None, self.holder_commitment.feerate_per_kw(), signed_tx)); } _ => unreachable!() } @@ -626,11 +676,20 @@ impl OnchainTxHandler { None } - pub(super) fn block_connected(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec, height: u32, broadcaster: B, fee_estimator: F) + /// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link + /// for this channel, provide new relevant on-chain transactions and/or new claim requests. + /// Formerly this was named `block_connected`, but it is now also used for claiming an HTLC output + /// if we receive a preimage after force-close. + pub(crate) fn update_claims_view(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec, latest_height: Option, broadcaster: &B, fee_estimator: &F, logger: &L) where B::Target: BroadcasterInterface, - F::Target: FeeEstimator + F::Target: FeeEstimator, + L::Target: Logger, { - log_trace!(self, "Block at height {} connected with {} claim requests", height, claimable_outpoints.len()); + let height = match latest_height { + Some(h) => h, + None => self.latest_height, + }; + log_trace!(logger, "Updating claims view at height {} with {} matched transactions and {} claim requests", height, txn_matched.len(), claimable_outpoints.len()); let mut new_claims = Vec::new(); let mut aggregated_claim = HashMap::new(); let mut aggregated_soonest = ::std::u32::MAX; @@ -639,8 +698,8 @@ impl OnchainTxHandler { // <= 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 let Some(_) = self.claimable_outpoints.get(&req.outpoint) { log_trace!(logger, "Bouncing off outpoint {}:{}, already registered its claiming request", req.outpoint.txid, req.outpoint.vout); } else { + log_trace!(logger, "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); @@ -657,23 +716,23 @@ impl OnchainTxHandler { // Generate claim transactions and track them to bump if necessary at // height timer expiration (i.e in how many blocks we're going to take action). - for claim in new_claims { - let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock: claim.0, per_input_material: claim.1.clone() }; - if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) { + for (soonest_timelock, claim) in new_claims.drain(..) { + let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock, per_input_material: claim }; + if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator, &*logger) { claim_material.height_timer = new_timer; claim_material.feerate_previous = new_feerate; let txid = tx.txid(); - self.pending_claim_requests.insert(txid, claim_material); - for k in claim.1.keys() { - log_trace!(self, "Registering claiming request for {}:{}", k.txid, k.vout); + for k in claim_material.per_input_material.keys() { + log_trace!(logger, "Registering claiming request for {}:{}", k.txid, k.vout); self.claimable_outpoints.insert(k.clone(), (txid, height)); } - log_trace!(self, "Broadcast onchain {}", log_tx!(tx)); + self.pending_claim_requests.insert(txid, claim_material); + log_trace!(logger, "Broadcast onchain {}", log_tx!(tx)); broadcaster.broadcast_transaction(&tx); } } - let mut bump_candidates = HashSet::new(); + let mut bump_candidates = HashMap::new(); for tx in txn_matched { // Scan all input to verify is one of the outpoint spent is of interest for us let mut claimed_outputs_material = Vec::new(); @@ -730,7 +789,7 @@ impl OnchainTxHandler { } //TODO: recompute soonest_timelock to avoid wasting a bit on fees if at_least_one_drop { - bump_candidates.insert(first_claim_txid_height.0.clone()); + bump_candidates.insert(first_claim_txid_height.0.clone(), claim_material.clone()); } } break; //No need to iterate further, either tx is our or their @@ -778,34 +837,29 @@ impl OnchainTxHandler { for (first_claim_txid, ref claim_data) in self.pending_claim_requests.iter() { if let Some(h) = claim_data.height_timer { if h == height { - bump_candidates.insert(*first_claim_txid); + bump_candidates.insert(*first_claim_txid, (*claim_data).clone()); } } } // 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) { - if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) { - log_trace!(self, "Broadcast onchain {}", log_tx!(bump_tx)); - broadcaster.broadcast_transaction(&bump_tx); - Some((new_timer, new_feerate)) - } else { None } - } else { unreachable!(); } - } { + log_trace!(logger, "Bumping {} candidates", bump_candidates.len()); + for (first_claim_txid, claim_material) in bump_candidates.iter() { + if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator, &*logger) { + log_trace!(logger, "Broadcast onchain {}", log_tx!(bump_tx)); + broadcaster.broadcast_transaction(&bump_tx); if let Some(claim_material) = self.pending_claim_requests.get_mut(first_claim_txid) { claim_material.height_timer = new_timer; claim_material.feerate_previous = new_feerate; - } else { unreachable!(); } + } } } } - pub(super) fn block_disconnected(&mut self, height: u32, broadcaster: B, fee_estimator: F) + pub(crate) fn block_disconnected(&mut self, height: u32, broadcaster: B, fee_estimator: F, logger: L) where B::Target: BroadcasterInterface, - F::Target: FeeEstimator + F::Target: FeeEstimator, + L::Target: Logger, { let mut bump_candidates = HashMap::new(); if let Some(events) = self.onchain_events_waiting_threshold_conf.remove(&(height + ANTI_REORG_DELAY - 1)) { @@ -828,7 +882,7 @@ impl OnchainTxHandler { } } for (_, claim_material) in bump_candidates.iter_mut() { - if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) { + if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &&*fee_estimator, &&*logger) { claim_material.height_timer = new_timer; claim_material.feerate_previous = new_feerate; broadcaster.broadcast_transaction(&bump_tx); @@ -850,47 +904,103 @@ impl OnchainTxHandler { } } - pub(super) fn provide_latest_local_tx(&mut self, tx: LocalCommitmentTransaction, local_keys: chan_utils::TxCreationKeys, feerate_per_kw: u64, htlc_outputs: Vec<(HTLCOutputInCommitment, Option, Option)>) -> Result<(), ()> { - // To prevent any unsafe state discrepancy between offchain and onchain, once local - // commitment transaction has been signed due to an event (either block height for - // HTLC-timeout or channel force-closure), don't allow any further update of local - // commitment transaction view to avoid delivery of revocation secret to counterparty - // for the aformentionned signed transaction. - if let Some(ref local_commitment) = self.local_commitment { - if local_commitment.has_local_sig() { return Err(()) } - } - self.prev_local_commitment = self.local_commitment.take(); - self.local_commitment = Some(tx); - self.prev_htlc_cache = self.current_htlc_cache.take(); - let mut per_htlc = HashMap::with_capacity(htlc_outputs.len()); - for htlc in htlc_outputs { - if htlc.0.transaction_output_index.is_some() { // Discard dust HTLC as we will never have to generate onchain tx for them - per_htlc.insert(htlc.0.transaction_output_index.unwrap(), (htlc.0, htlc.1)); + pub(crate) fn provide_latest_holder_tx(&mut self, tx: HolderCommitmentTransaction) { + self.prev_holder_commitment = Some(replace(&mut self.holder_commitment, tx)); + self.holder_htlc_sigs = None; + } + + // Normally holder HTLCs are signed at the same time as the holder commitment tx. However, + // in some configurations, the holder commitment tx has been signed and broadcast by a + // ChannelMonitor replica, so we handle that case here. + fn sign_latest_holder_htlcs(&mut self) { + if self.holder_htlc_sigs.is_none() { + let (_sig, sigs) = self.key_storage.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment"); + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, sigs)); + } + } + + // Normally only the latest commitment tx and HTLCs need to be signed. However, in some + // configurations we may have updated our holder commitment but a replica of the ChannelMonitor + // broadcast the previous one before we sync with it. We handle that case here. + fn sign_prev_holder_htlcs(&mut self) { + if self.prev_holder_htlc_sigs.is_none() { + if let Some(ref holder_commitment) = self.prev_holder_commitment { + let (_sig, sigs) = self.key_storage.sign_holder_commitment_and_htlcs(holder_commitment, &self.secp_ctx).expect("sign previous holder commitment"); + self.prev_holder_htlc_sigs = Some(Self::extract_holder_sigs(holder_commitment, sigs)); } } - self.current_htlc_cache = Some(HTLCTxCache { - local_keys, - feerate_per_kw, - per_htlc - }); - Ok(()) } - pub(super) fn get_fully_signed_local_tx(&mut self, channel_value_satoshis: u64) -> Option { - if let Some(ref mut local_commitment) = self.local_commitment { - self.key_storage.sign_local_commitment(local_commitment, &self.funding_redeemscript, channel_value_satoshis, &self.secp_ctx); - return Some(local_commitment.with_valid_witness().clone()); + fn extract_holder_sigs(holder_commitment: &HolderCommitmentTransaction, sigs: Vec) -> Vec> { + let mut ret = Vec::new(); + for (htlc_idx, (holder_sig, htlc)) in sigs.iter().zip(holder_commitment.htlcs().iter()).enumerate() { + let tx_idx = htlc.transaction_output_index.unwrap(); + if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); } + ret[tx_idx as usize] = Some((htlc_idx, holder_sig.clone())); } - None + ret } - #[cfg(test)] - pub(super) fn get_fully_signed_copy_local_tx(&mut self, channel_value_satoshis: u64) -> Option { - if let Some(ref mut local_commitment) = self.local_commitment { - let mut local_commitment = local_commitment.clone(); - self.key_storage.unsafe_sign_local_commitment(&mut local_commitment, &self.funding_redeemscript, channel_value_satoshis, &self.secp_ctx); - return Some(local_commitment.with_valid_witness().clone()); + //TODO: getting lastest holder transactions should be infallible and result in us "force-closing the channel", but we may + // have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after Channel::get_outbound_funding_created, + // before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing + // to monitor before. + pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction { + let (sig, htlc_sigs) = self.key_storage.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("signing holder commitment"); + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, htlc_sigs)); + self.holder_commitment.add_holder_sig(funding_redeemscript, sig) + } + + #[cfg(any(test, feature="unsafe_revoked_tx_signing"))] + pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction { + let (sig, htlc_sigs) = self.key_storage.unsafe_sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment"); + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, htlc_sigs)); + self.holder_commitment.add_holder_sig(funding_redeemscript, sig) + } + + pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { + let mut htlc_tx = None; + let commitment_txid = self.holder_commitment.trust().txid(); + // Check if the HTLC spends from the current holder commitment + if commitment_txid == outp.txid { + self.sign_latest_holder_htlcs(); + if let &Some(ref htlc_sigs) = &self.holder_htlc_sigs { + let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap(); + let trusted_tx = self.holder_commitment.trust(); + let counterparty_htlc_sig = self.holder_commitment.counterparty_htlc_sigs[*htlc_idx]; + htlc_tx = Some(trusted_tx + .get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage)); + } } - None + // If the HTLC doesn't spend the current holder commitment, check if it spends the previous one + if htlc_tx.is_none() && self.prev_holder_commitment.is_some() { + let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().trust().txid(); + if commitment_txid == outp.txid { + self.sign_prev_holder_htlcs(); + if let &Some(ref htlc_sigs) = &self.prev_holder_htlc_sigs { + let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap(); + let holder_commitment = self.prev_holder_commitment.as_ref().unwrap(); + let trusted_tx = holder_commitment.trust(); + let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[*htlc_idx]; + htlc_tx = Some(trusted_tx + .get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage)); + } + } + } + htlc_tx + } + + #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] + pub(crate) fn unsafe_get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { + let latest_had_sigs = self.holder_htlc_sigs.is_some(); + let prev_had_sigs = self.prev_holder_htlc_sigs.is_some(); + let ret = self.get_fully_signed_htlc_tx(outp, preimage); + if !latest_had_sigs { + self.holder_htlc_sigs = None; + } + if !prev_had_sigs { + self.prev_holder_htlc_sigs = None; + } + ret } }