X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonchaintx.rs;h=053502495268bb7c184a01ccd309a98ee89ccc10;hb=d2955be5cf7fa96057c416a7899e7db6a0c5e622;hp=7698fa421a5eafff3910e62d1b1d592dc241c0a2;hpb=ea238a24b4c02d06c29cb17320fcb463c91c7cd4;p=rust-lightning diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index 7698fa42..05350249 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -1,6 +1,15 @@ +// 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}; @@ -11,35 +20,56 @@ use bitcoin::hash_types::Txid; use bitcoin::secp256k1::{Secp256k1, Signature}; use bitcoin::secp256k1; -use bitcoin::secp256k1::key::PublicKey; use ln::msgs::DecodeError; -use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest}; -use ln::channelmanager::PaymentPreimage; +use ln::PaymentPreimage; use ln::chan_utils; -use ln::chan_utils::{TxCreationKeys, LocalCommitmentTransaction, 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::{Sign, KeysInterface}; use util::logger::Logger; -use util::ser::{Readable, Writer, Writeable}; +use util::ser::{Readable, ReadableArgs, Writer, Writeable, VecWriter}; use util::byte_utils; -use std::collections::{HashMap, hash_map}; +use std::collections::HashMap; use std::cmp; use std::ops::Deref; +use std::mem::replace; const MAX_ALLOC_SIZE: usize = 64*1024; +/// An entry for an [`OnchainEvent`], stating the block height when the event was observed and the +/// transaction causing it. +/// +/// Used to determine when the on-chain event can be considered safe from a chain reorganization. +#[derive(PartialEq)] +struct OnchainEventEntry { + txid: Txid, + height: u32, + event: OnchainEvent, +} + +impl OnchainEventEntry { + fn confirmation_threshold(&self) -> u32 { + self.height + ANTI_REORG_DELAY - 1 + } + + fn has_reached_confirmation_threshold(&self, height: u32) -> bool { + height >= self.confirmation_threshold() + } +} + /// Upon discovering of some classes of onchain tx by ChannelMonitor, we may have to take actions on it /// once they mature to enough confirmations (ANTI_REORG_DELAY) -#[derive(Clone, PartialEq)] +#[derive(PartialEq)] 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: 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 { @@ -48,14 +78,6 @@ enum OnchainEvent { } } -/// Cache remote basepoint to compute any transaction on -/// remote outputs, either justice or preimage/timeout transactions. -struct RemoteTxCache { - remote_delayed_payment_base_key: PublicKey, - remote_htlc_base_key: PublicKey, - per_htlc: HashMap> -} - /// Higher-level cache structure needed to re-generate bumped claim txn if needed #[derive(Clone, PartialEq)] pub struct ClaimTxBumpMaterial { @@ -63,7 +85,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, @@ -74,7 +96,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() { @@ -107,7 +129,7 @@ pub(crate) enum InputDescriptors { 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 { @@ -160,14 +182,14 @@ impl Readable for InputDescriptors { macro_rules! subtract_high_prio_fee { ($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!($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); @@ -239,21 +261,18 @@ impl Writeable for Option>> { /// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and /// do RBF bumping if possible. -pub struct OnchainTxHandler { +pub struct OnchainTxHandler { destination_script: Script, - local_commitment: Option, - // local_htlc_sigs and prev_local_htlc_sigs are in the order as they appear in the commitment + 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 LocalCommitmentTransaction (including those which do not appear in - // the commitment transaction). - local_htlc_sigs: Option>>, - prev_local_commitment: Option, - prev_local_htlc_sigs: Option>>, - local_csv: u16, - remote_tx_cache: RemoteTxCache, - remote_csv: u16, + // the set of HTLCs in the HolderCommitmentTransaction. + holder_htlc_sigs: Option>>, + prev_holder_commitment: Option, + prev_holder_htlc_sigs: Option>>, - key_storage: ChanSigner, + signer: ChannelSigner, + 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 @@ -282,34 +301,29 @@ pub struct OnchainTxHandler { #[cfg(not(test))] claimable_outpoints: HashMap, - onchain_events_waiting_threshold_conf: HashMap>, + onchain_events_awaiting_threshold_conf: Vec, + + latest_height: u32, secp_ctx: Secp256k1, } -impl OnchainTxHandler { +impl OnchainTxHandler { pub(crate) fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { self.destination_script.write(writer)?; - self.local_commitment.write(writer)?; - self.local_htlc_sigs.write(writer)?; - self.prev_local_commitment.write(writer)?; - self.prev_local_htlc_sigs.write(writer)?; - - self.local_csv.write(writer)?; - - self.remote_tx_cache.remote_delayed_payment_base_key.write(writer)?; - self.remote_tx_cache.remote_htlc_base_key.write(writer)?; - writer.write_all(&byte_utils::be64_to_array(self.remote_tx_cache.per_htlc.len() as u64))?; - for (ref txid, ref htlcs) in self.remote_tx_cache.per_htlc.iter() { - writer.write_all(&txid[..])?; - writer.write_all(&byte_utils::be64_to_array(htlcs.len() as u64))?; - for &ref htlc in htlcs.iter() { - htlc.write(writer)?; - } - } - self.remote_csv.write(writer)?; + self.holder_commitment.write(writer)?; + self.holder_htlc_sigs.write(writer)?; + self.prev_holder_commitment.write(writer)?; + self.prev_holder_htlc_sigs.write(writer)?; + + self.channel_transaction_parameters.write(writer)?; - self.key_storage.write(writer)?; + let mut key_data = VecWriter(Vec::new()); + self.signer.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() { @@ -324,65 +338,48 @@ impl OnchainTxHandler { claim_and_height.1.write(writer)?; } - writer.write_all(&byte_utils::be64_to_array(self.onchain_events_waiting_threshold_conf.len() as u64))?; - for (ref target, ref events) in self.onchain_events_waiting_threshold_conf.iter() { - writer.write_all(&byte_utils::be32_to_array(**target))?; - writer.write_all(&byte_utils::be64_to_array(events.len() as u64))?; - for ev in events.iter() { - match *ev { - OnchainEvent::Claim { ref claim_request } => { - writer.write_all(&[0; 1])?; - claim_request.write(writer)?; - }, - OnchainEvent::ContentiousOutpoint { ref outpoint, ref input_material } => { - writer.write_all(&[1; 1])?; - outpoint.write(writer)?; - input_material.write(writer)?; - } + writer.write_all(&byte_utils::be64_to_array(self.onchain_events_awaiting_threshold_conf.len() as u64))?; + for ref entry in self.onchain_events_awaiting_threshold_conf.iter() { + entry.txid.write(writer)?; + writer.write_all(&byte_utils::be32_to_array(entry.height))?; + match entry.event { + OnchainEvent::Claim { ref claim_request } => { + writer.write_all(&[0; 1])?; + claim_request.write(writer)?; + }, + OnchainEvent::ContentiousOutpoint { ref outpoint, ref input_material } => { + writer.write_all(&[1; 1])?; + outpoint.write(writer)?; + input_material.write(writer)?; } } } + self.latest_height.write(writer)?; Ok(()) } } -impl Readable for OnchainTxHandler { - fn read(reader: &mut R) -> 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 local_commitment = Readable::read(reader)?; - let local_htlc_sigs = Readable::read(reader)?; - let prev_local_commitment = Readable::read(reader)?; - let prev_local_htlc_sigs = Readable::read(reader)?; - - let local_csv = Readable::read(reader)?; - - let remote_tx_cache = { - let remote_delayed_payment_base_key = Readable::read(reader)?; - let remote_htlc_base_key = Readable::read(reader)?; - let per_htlc_len: u64 = Readable::read(reader)?; - let mut per_htlc = HashMap::with_capacity(cmp::min(per_htlc_len as usize, MAX_ALLOC_SIZE / 64)); - for _ in 0..per_htlc_len { - let txid: Txid = Readable::read(reader)?; - let htlcs_count: u64 = Readable::read(reader)?; - let mut htlcs = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / 32)); - for _ in 0..htlcs_count { - let htlc = Readable::read(reader)?; - htlcs.push(htlc); - } - if let Some(_) = per_htlc.insert(txid, htlcs) { - return Err(DecodeError::InvalidValue); - } - } - RemoteTxCache { - remote_delayed_payment_base_key, - remote_htlc_base_key, - per_htlc, - } - }; - let remote_csv = Readable::read(reader)?; - - let key_storage = Readable::read(reader)?; + 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 channel_parameters = 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 signer = 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)); @@ -399,82 +396,71 @@ impl Readable for OnchainTxHandler::read(reader)? { - 0 => { - let claim_request = Readable::read(reader)?; - OnchainEvent::Claim { - claim_request - } - }, - 1 => { - let outpoint = Readable::read(reader)?; - let input_material = Readable::read(reader)?; - OnchainEvent::ContentiousOutpoint { - outpoint, - input_material - } + let txid = Readable::read(reader)?; + let height = Readable::read(reader)?; + let event = match ::read(reader)? { + 0 => { + let claim_request = Readable::read(reader)?; + OnchainEvent::Claim { + claim_request } - _ => return Err(DecodeError::InvalidValue), - }; - events.push(ev); - } - onchain_events_waiting_threshold_conf.insert(height_target, events); + }, + 1 => { + let outpoint = Readable::read(reader)?; + let input_material = Readable::read(reader)?; + OnchainEvent::ContentiousOutpoint { + outpoint, + input_material + } + } + _ => return Err(DecodeError::InvalidValue), + }; + onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { txid, height, event }); } + let latest_height = Readable::read(reader)?; + + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&keys_manager.get_secure_random_bytes()); Ok(OnchainTxHandler { destination_script, - local_commitment, - local_htlc_sigs, - prev_local_commitment, - prev_local_htlc_sigs, - local_csv, - remote_tx_cache, - remote_csv, - key_storage, + holder_commitment, + holder_htlc_sigs, + prev_holder_commitment, + prev_holder_htlc_sigs, + signer, + channel_transaction_parameters: channel_parameters, claimable_outpoints, pending_claim_requests, - onchain_events_waiting_threshold_conf, - secp_ctx: Secp256k1::new(), + onchain_events_awaiting_threshold_conf, + latest_height, + secp_ctx, }) } } -impl OnchainTxHandler { - pub(super) fn new(destination_script: Script, keys: ChanSigner, local_csv: u16, remote_delayed_payment_base_key: PublicKey, remote_htlc_base_key: PublicKey, remote_csv: u16) -> Self { - - let key_storage = keys; - - let remote_tx_cache = RemoteTxCache { - remote_delayed_payment_base_key, - remote_htlc_base_key, - per_htlc: HashMap::new(), - }; - +impl OnchainTxHandler { + pub(crate) fn new(destination_script: Script, signer: ChannelSigner, channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1) -> Self { OnchainTxHandler { destination_script, - local_commitment: None, - local_htlc_sigs: None, - prev_local_commitment: None, - prev_local_htlc_sigs: None, - local_csv, - remote_tx_cache, - remote_csv, - key_storage, + holder_commitment, + holder_htlc_sigs: None, + prev_holder_commitment: None, + prev_holder_htlc_sigs: None, + signer, + channel_transaction_parameters: channel_parameters, pending_claim_requests: HashMap::new(), claimable_outpoints: HashMap::new(), - onchain_events_waiting_threshold_conf: HashMap::new(), + onchain_events_awaiting_threshold_conf: Vec::new(), + latest_height: 0, - secp_ctx: Secp256k1::new(), + secp_ctx, } } - 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 @@ -487,7 +473,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 }, @@ -520,7 +506,9 @@ 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(&mut self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: F, logger: L) -> Option<(Option, u64, Transaction)> + /// 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, { @@ -548,20 +536,20 @@ 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!(logger, $fee_estimator, value, $predicted_weight, used_feerate) { // Overflow check is done in subtract_high_prio_fee - $amount - value + ($amount - value) } else { 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!(logger, "Can't 25% bump new claiming tx, amount {} is too small", $amount); return None; @@ -569,8 +557,8 @@ impl OnchainTxHandler { 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. @@ -579,7 +567,7 @@ impl OnchainTxHandler { } else { new_fee }; - Some((new_fee, new_fee * 1000 / $predicted_weight)) + Some((new_fee, new_fee * 1000 / ($predicted_weight as u64))) } } } @@ -596,11 +584,11 @@ impl OnchainTxHandler { 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::LocalHTLC { .. } => { + &InputMaterial::HolderHTLC { .. } => { dynamic_fee = false; }, &InputMaterial::Funding { .. } => { @@ -609,16 +597,16 @@ impl OnchainTxHandler { } } 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; } @@ -631,96 +619,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 per_commitment_point, ref per_commitment_key, ref input_descriptor, ref amount } => { - if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, &self.remote_tx_cache.remote_delayed_payment_base_key, &self.remote_tx_cache.remote_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { - - let mut this_htlc = None; - if *input_descriptor != InputDescriptors::RevokedOutput { - if let Some(htlcs) = self.remote_tx_cache.per_htlc.get(&outp.txid) { - for htlc in htlcs { - if htlc.transaction_output_index.unwrap() == outp.vout { - this_htlc = Some(htlc); - } - } - } - } + &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(tx_keys) = TxCreationKeys::derive_new(&self.secp_ctx, &per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, &self.signer.pubkeys().revocation_basepoint, &self.signer.pubkeys().htlc_basepoint) { - let witness_script = if *input_descriptor != InputDescriptors::RevokedOutput && this_htlc.is_some() { - chan_utils::get_htlc_redeemscript_with_explicit_keys(&this_htlc.unwrap(), &chan_keys.a_htlc_key, &chan_keys.b_htlc_key, &chan_keys.revocation_key) - } else if *input_descriptor != InputDescriptors::RevokedOutput { - return None; + let witness_script = if let Some(ref htlc) = *htlc { + chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &tx_keys.broadcaster_htlc_key, &tx_keys.countersignatory_htlc_key, &tx_keys.revocation_key) } else { - chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, self.remote_csv, &chan_keys.a_delayed_payment_key) + chan_utils::get_revokeable_redeemscript(&tx_keys.revocation_key, *on_counterparty_tx_csv, &tx_keys.broadcaster_delayed_payment_key) }; - let is_htlc = *input_descriptor != InputDescriptors::RevokedOutput; - if let Ok(sig) = self.key_storage.sign_justice_transaction(&bumped_tx, i, &witness_script, *amount, &per_commitment_key, &chan_keys.revocation_key, is_htlc, &self.secp_ctx) { - 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(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()); - } else { return None; } - //TODO: panic ? + let sig = self.signer.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(tx_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_local" } else if *input_descriptor == InputDescriptors::RevokedOfferedHTLC { "offered" } else if *input_descriptor == InputDescriptors::RevokedReceivedHTLC { "received" } else { "" }, outp.vout, outp.txid, new_feerate); + 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); } }, - &InputMaterial::RemoteHTLC { ref per_commitment_point, ref preimage, ref amount, ref locktime } => { - if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, &self.remote_tx_cache.remote_delayed_payment_base_key, &self.remote_tx_cache.remote_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { - let mut this_htlc = None; - if let Some(htlcs) = self.remote_tx_cache.per_htlc.get(&outp.txid) { - for htlc in htlcs { - if htlc.transaction_output_index.unwrap() == outp.vout { - this_htlc = Some(htlc); - } - } - } - if this_htlc.is_none() { return None; } - let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&this_htlc.unwrap(), &chan_keys.a_htlc_key, &chan_keys.b_htlc_key, &chan_keys.revocation_key); - - 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 - if let Ok(sig) = self.key_storage.sign_remote_htlc_transaction(&bumped_tx, i, &witness_script, *amount, &per_commitment_point, preimage, &self.secp_ctx) { - 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()); + &InputMaterial::CounterpartyHTLC { ref per_commitment_point, ref counterparty_delayed_payment_base_key, ref counterparty_htlc_base_key, ref preimage, ref htlc } => { + if let Ok(tx_keys) = TxCreationKeys::derive_new(&self.secp_ctx, &per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, &self.signer.pubkeys().revocation_basepoint, &self.signer.pubkeys().htlc_basepoint) { + let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &tx_keys.broadcaster_htlc_key, &tx_keys.countersignatory_htlc_key, &tx_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.signer.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![]); } - log_trace!(logger, "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); + 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); } }, _ => unreachable!() } } log_trace!(logger, "...with timer {}", new_timer.unwrap()); - assert!(predicted_weight >= bumped_tx.get_weight()); - return Some((new_timer, new_feerate, bumped_tx)) + 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 { ref preimage, ref amount } => { + &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!(logger, "Going to broadcast Local HTLC-{} claiming HTLC output {} from {}...", if preimage.is_some() { "Success" } else { "Timeout" }, outp.vout, outp.txid); - return Some((None, feerate, htlc_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_local_tx(funding_redeemscript).unwrap(); + 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 Local Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid); - return Some((None, self.local_commitment.as_ref().unwrap().feerate_per_kw, signed_tx)); + 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!() } @@ -729,12 +691,20 @@ impl OnchainTxHandler { None } - pub(super) fn block_connected(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec, height: u32, broadcaster: B, fee_estimator: F, logger: L) + /// 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, L::Target: Logger, { - log_trace!(logger, "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; @@ -772,7 +742,7 @@ impl OnchainTxHandler { self.claimable_outpoints.insert(k.clone(), (txid, height)); } self.pending_claim_requests.insert(txid, claim_material); - log_trace!(logger, "Broadcast onchain {}", log_tx!(tx)); + log_info!(logger, "Broadcasting onchain {}", log_tx!(tx)); broadcaster.broadcast_transaction(&tx); } } @@ -801,16 +771,13 @@ impl OnchainTxHandler { macro_rules! clean_claim_request_after_safety_delay { () => { - let new_event = OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() }; - match self.onchain_events_waiting_threshold_conf.entry(height + ANTI_REORG_DELAY - 1) { - hash_map::Entry::Occupied(mut entry) => { - if !entry.get().contains(&new_event) { - entry.get_mut().push(new_event); - } - }, - hash_map::Entry::Vacant(entry) => { - entry.insert(vec![new_event]); - } + let entry = OnchainEventEntry { + txid: tx.txid(), + height, + event: OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() } + }; + if !self.onchain_events_awaiting_threshold_conf.contains(&entry) { + self.onchain_events_awaiting_threshold_conf.push(entry); } } } @@ -844,24 +811,23 @@ impl OnchainTxHandler { } } for (outpoint, input_material) in claimed_outputs_material.drain(..) { - let new_event = OnchainEvent::ContentiousOutpoint { outpoint, input_material }; - match self.onchain_events_waiting_threshold_conf.entry(height + ANTI_REORG_DELAY - 1) { - hash_map::Entry::Occupied(mut entry) => { - if !entry.get().contains(&new_event) { - entry.get_mut().push(new_event); - } - }, - hash_map::Entry::Vacant(entry) => { - entry.insert(vec![new_event]); - } + let entry = OnchainEventEntry { + txid: tx.txid(), + height, + event: OnchainEvent::ContentiousOutpoint { outpoint, input_material }, + }; + if !self.onchain_events_awaiting_threshold_conf.contains(&entry) { + self.onchain_events_awaiting_threshold_conf.push(entry); } } } // After security delay, either our claim tx got enough confs or outpoint is definetely out of reach - if let Some(events) = self.onchain_events_waiting_threshold_conf.remove(&height) { - for ev in events { - match ev { + let onchain_events_awaiting_threshold_conf = + self.onchain_events_awaiting_threshold_conf.drain(..).collect::>(); + for entry in onchain_events_awaiting_threshold_conf { + if entry.has_reached_confirmation_threshold(height) { + match entry.event { OnchainEvent::Claim { claim_request } => { // We may remove a whole set of claim outpoints here, as these one may have // been aggregated in a single tx and claimed so atomically @@ -875,13 +841,15 @@ impl OnchainTxHandler { self.claimable_outpoints.remove(&outpoint); } } + } else { + self.onchain_events_awaiting_threshold_conf.push(entry); } } // Check if any pending claim request must be rescheduled for (first_claim_txid, ref claim_data) in self.pending_claim_requests.iter() { - if let Some(h) = claim_data.height_timer { - if h == height { + if let Some(height_timer) = claim_data.height_timer { + if height >= height_timer { bump_candidates.insert(*first_claim_txid, (*claim_data).clone()); } } @@ -891,7 +859,7 @@ impl OnchainTxHandler { 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)); + log_info!(logger, "Broadcasting 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; @@ -901,17 +869,43 @@ impl OnchainTxHandler { } } - pub(super) fn block_disconnected(&mut self, height: u32, broadcaster: B, fee_estimator: F, logger: L) + pub(crate) fn transaction_unconfirmed( + &mut self, + txid: &Txid, + broadcaster: B, + fee_estimator: F, + logger: L, + ) where + B::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, + { + let mut height = None; + for entry in self.onchain_events_awaiting_threshold_conf.iter() { + if entry.txid == *txid { + height = Some(entry.height); + break; + } + } + + if let Some(height) = height { + self.block_disconnected(height, broadcaster, fee_estimator, logger); + } + } + + pub(crate) fn block_disconnected(&mut self, height: u32, broadcaster: B, fee_estimator: F, logger: L) where B::Target: BroadcasterInterface, 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)) { - //- our claim tx on a commitment tx output - //- resurect outpoint back in its claimable set and regenerate tx - for ev in events { - match ev { + let onchain_events_awaiting_threshold_conf = + self.onchain_events_awaiting_threshold_conf.drain(..).collect::>(); + for entry in onchain_events_awaiting_threshold_conf { + if entry.height >= height { + //- our claim tx on a commitment tx output + //- resurect outpoint back in its claimable set and regenerate tx + match entry.event { OnchainEvent::ContentiousOutpoint { outpoint, input_material } => { if let Some(ancestor_claimable_txid) = self.claimable_outpoints.get(&outpoint) { if let Some(claim_material) = self.pending_claim_requests.get_mut(&ancestor_claimable_txid.0) { @@ -924,12 +918,15 @@ impl OnchainTxHandler { }, _ => {}, } + } else { + self.onchain_events_awaiting_threshold_conf.push(entry); } } 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, &*logger) { + 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; + log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx)); broadcaster.broadcast_transaction(&bump_tx); } } @@ -940,7 +937,7 @@ impl OnchainTxHandler { // right now if one of the outpoint get disconnected, just erase whole pending claim request. let mut remove_request = Vec::new(); self.claimable_outpoints.retain(|_, ref v| - if v.1 == height { + if v.1 >= height { remove_request.push(v.0.clone()); false } else { true }); @@ -949,122 +946,112 @@ impl OnchainTxHandler { } } - pub(super) fn provide_latest_local_tx(&mut self, tx: LocalCommitmentTransaction) -> 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 self.local_htlc_sigs.is_some() || self.prev_local_htlc_sigs.is_some() { - return Err(()); - } - self.prev_local_commitment = self.local_commitment.take(); - self.local_commitment = Some(tx); - Ok(()) + pub(crate) fn get_relevant_txids(&self) -> Vec { + let mut txids: Vec = self.onchain_events_awaiting_threshold_conf + .iter() + .map(|entry| entry.txid) + .collect(); + txids.sort_unstable(); + txids.dedup(); + txids } - fn sign_latest_local_htlcs(&mut self) { - if let Some(ref local_commitment) = self.local_commitment { - if let Ok(sigs) = self.key_storage.sign_local_commitment_htlc_transactions(local_commitment, self.local_csv, &self.secp_ctx) { - self.local_htlc_sigs = Some(Vec::new()); - let ret = self.local_htlc_sigs.as_mut().unwrap(); - for (htlc_idx, (local_sig, &(ref htlc, _))) in sigs.iter().zip(local_commitment.per_htlc.iter()).enumerate() { - if let Some(tx_idx) = htlc.transaction_output_index { - if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); } - ret[tx_idx as usize] = Some((htlc_idx, local_sig.expect("Did not receive a signature for a non-dust HTLC"))); - } else { - assert!(local_sig.is_none(), "Received a signature for a dust HTLC"); - } - } - } + 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.signer.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)); } } - fn sign_prev_local_htlcs(&mut self) { - if let Some(ref local_commitment) = self.prev_local_commitment { - if let Ok(sigs) = self.key_storage.sign_local_commitment_htlc_transactions(local_commitment, self.local_csv, &self.secp_ctx) { - self.prev_local_htlc_sigs = Some(Vec::new()); - let ret = self.prev_local_htlc_sigs.as_mut().unwrap(); - for (htlc_idx, (local_sig, &(ref htlc, _))) in sigs.iter().zip(local_commitment.per_htlc.iter()).enumerate() { - if let Some(tx_idx) = htlc.transaction_output_index { - if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); } - ret[tx_idx as usize] = Some((htlc_idx, local_sig.expect("Did not receive a signature for a non-dust HTLC"))); - } else { - assert!(local_sig.is_none(), "Received a signature for a dust HTLC"); - } - } + + // 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.signer.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)); } } } - //TODO: getting lastest local transactions should be infaillible and result in us "force-closing the channel", but we may - // have empty local 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(super) fn get_fully_signed_local_tx(&mut self, funding_redeemscript: &Script) -> Option { - if let Some(ref mut local_commitment) = self.local_commitment { - match self.key_storage.sign_local_commitment(local_commitment, &self.secp_ctx) { - Ok(sig) => Some(local_commitment.add_local_sig(funding_redeemscript, sig)), - Err(_) => return None, - } - } else { - None + 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())); } + ret } - pub(super) fn provide_latest_remote_tx(&mut self, commitment_txid: Txid, htlcs: Vec) { - self.remote_tx_cache.per_htlc.insert(commitment_txid, htlcs); + //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.signer.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(test)] - pub(super) fn get_fully_signed_copy_local_tx(&mut self, funding_redeemscript: &Script) -> Option { - if let Some(ref mut local_commitment) = self.local_commitment { - let local_commitment = local_commitment.clone(); - match self.key_storage.sign_local_commitment(&local_commitment, &self.secp_ctx) { - Ok(sig) => Some(local_commitment.add_local_sig(funding_redeemscript, sig)), - Err(_) => return None, - } - } else { - None - } + #[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.signer.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(super) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { + pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { let mut htlc_tx = None; - if self.local_commitment.is_some() { - let commitment_txid = self.local_commitment.as_ref().unwrap().txid(); - if commitment_txid == outp.txid { - self.sign_latest_local_htlcs(); - if let &Some(ref htlc_sigs) = &self.local_htlc_sigs { - let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap(); - htlc_tx = Some(self.local_commitment.as_ref().unwrap() - .get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.local_csv)); - } + 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)); } } - if self.prev_local_commitment.is_some() { - let commitment_txid = self.prev_local_commitment.as_ref().unwrap().txid(); + // 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_local_htlcs(); - if let &Some(ref htlc_sigs) = &self.prev_local_htlc_sigs { + 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(); - htlc_tx = Some(self.prev_local_commitment.as_ref().unwrap() - .get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.local_csv)); + 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(test)] - pub(super) fn unsafe_get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { - let latest_had_sigs = self.local_htlc_sigs.is_some(); - let prev_had_sigs = self.prev_local_htlc_sigs.is_some(); + #[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.local_htlc_sigs = None; + self.holder_htlc_sigs = None; } if !prev_had_sigs { - self.prev_local_htlc_sigs = None; + self.prev_holder_htlc_sigs = None; } ret }