X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonchaintx.rs;h=1b0060dabc7c59af2eab7e6d01cfbf42085dd102;hb=990d1de99af0777b66dc543a83548df51c762917;hp=ce1a882948f27da253ad84b985be14f652359f62;hpb=1d7ed17a647d5d0e66ab397fa89cc28cc5bc68c7;p=rust-lightning diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index ce1a8829..1b0060da 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,17 +20,16 @@ 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::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::{ChannelKeys, 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}; @@ -39,7 +47,7 @@ enum OnchainEvent { 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,15 +56,6 @@ enum OnchainEvent { } } -/// Cache remote basepoint to compute any transaction on -/// remote outputs, either justice or preimage/timeout transactions. -#[derive(PartialEq)] -pub(super) struct RemoteTxCache { - pub(super) remote_delayed_payment_base_key: PublicKey, - pub(super) remote_htlc_base_key: PublicKey, - pub(super) per_htlc: HashMap> -} - /// Higher-level cache structure needed to re-generate bumped claim txn if needed #[derive(Clone, PartialEq)] pub struct ClaimTxBumpMaterial { @@ -64,7 +63,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, @@ -75,7 +74,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() { @@ -108,7 +107,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 { @@ -161,14 +160,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); @@ -242,19 +241,16 @@ impl Writeable for Option>> { /// do RBF bumping if possible. 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: Option, + // 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, + 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 @@ -285,32 +281,27 @@ pub struct OnchainTxHandler { onchain_events_waiting_threshold_conf: HashMap>, + 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.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() { @@ -343,47 +334,32 @@ impl OnchainTxHandler { } } } + 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 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 key_storage = 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 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)); @@ -427,55 +403,48 @@ impl Readable for OnchainTxHandler 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 { + pub(crate) fn new(destination_script: Script, keys: ChanSigner, channel_parameters: ChannelTransactionParameters) -> Self { let key_storage = keys; - let remote_tx_cache = RemoteTxCache { - remote_delayed_payment_base_key, - remote_htlc_base_key, - per_htlc: HashMap::new(), - }; - 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, + holder_commitment: None, + 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(), } } - 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 @@ -488,7 +457,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 }, @@ -521,7 +490,7 @@ 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)> + 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, { @@ -549,20 +518,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; @@ -570,8 +539,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. @@ -580,7 +549,7 @@ impl OnchainTxHandler { } else { new_fee }; - Some((new_fee, new_fee * 1000 / $predicted_weight)) + Some((new_fee, new_fee * 1000 / ($predicted_weight as u64))) } } } @@ -597,11 +566,11 @@ impl OnchainTxHandler { inputs_witnesses_weight += Self::get_witnesses_weight(&[*input_descriptor]); amt += *amount; }, - &InputMaterial::RemoteHTLC { ref preimage, ref htlc, .. } => { + &InputMaterial::CounterpartyHTLC { ref preimage, ref htlc, .. } => { inputs_witnesses_weight += Self::get_witnesses_weight(if preimage.is_some() { &[InputDescriptors::OfferedHTLC] } else { &[InputDescriptors::ReceivedHTLC] }); amt += htlc.amount_msat / 1000; }, - &InputMaterial::LocalHTLC { .. } => { + &InputMaterial::HolderHTLC { .. } => { dynamic_fee = false; }, &InputMaterial::Funding { .. } => { @@ -610,16 +579,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; } @@ -632,16 +601,16 @@ 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 remote_delayed_payment_base_key, ref remote_htlc_base_key, ref per_commitment_key, ref input_descriptor, ref amount, ref htlc } => { - if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, remote_delayed_payment_base_key, remote_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { + &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.a_htlc_key, &chan_keys.b_htlc_key, &chan_keys.revocation_key) + 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, self.remote_csv, &chan_keys.a_delayed_payment_key) + chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, *on_counterparty_tx_csv, &chan_keys.broadcaster_delayed_payment_key) }; - if let Ok(sig) = self.key_storage.sign_justice_transaction(&bumped_tx, i, &witness_script, *amount, &per_commitment_key, &chan_keys.revocation_key, htlc.is_some(), &self.secp_ctx) { + if let Ok(sig) = self.key_storage.sign_justice_transaction(&bumped_tx, i, *amount, &per_commitment_key, 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 htlc.is_some() { @@ -653,15 +622,15 @@ impl OnchainTxHandler { } else { return None; } //TODO: panic ? - 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 remote_delayed_payment_base_key, ref remote_htlc_base_key, ref preimage, ref htlc } => { - if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, remote_delayed_payment_base_key, remote_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.a_htlc_key, &chan_keys.b_htlc_key, &chan_keys.revocation_key); + &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 - if let Ok(sig) = self.key_storage.sign_remote_htlc_transaction(&bumped_tx, i, &witness_script, htlc.amount_msat / 1000, &per_commitment_point, preimage, &self.secp_ctx) { + if let Ok(sig) = self.key_storage.sign_counterparty_htlc_transaction(&bumped_tx, i, &htlc.amount_msat / 1000, &per_commitment_point, 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 let &Some(preimage) = preimage { @@ -672,33 +641,33 @@ impl OnchainTxHandler { } bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); } - 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); + 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).unwrap(); // 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.as_ref().unwrap().feerate_per_kw(), signed_tx)); } _ => unreachable!() } @@ -707,12 +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, 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; @@ -879,7 +856,7 @@ impl OnchainTxHandler { } } - pub(super) fn block_disconnected(&mut self, height: u32, broadcaster: B, fee_estimator: F, logger: L) + 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, @@ -905,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, &*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; broadcaster.broadcast_transaction(&bump_tx); @@ -927,61 +904,47 @@ 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 provide_latest_holder_tx(&mut self, tx: HolderCommitmentTransaction) { + self.prev_holder_commitment = self.holder_commitment.take(); + self.holder_commitment = Some(tx); } - 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"); - } - } + fn sign_latest_holder_htlcs(&mut self) { + if let Some(ref holder_commitment) = self.holder_commitment { + if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) { + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(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"); - } - } + + fn sign_prev_holder_htlcs(&mut self) { + if let Some(ref holder_commitment) = self.prev_holder_commitment { + if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) { + 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, + 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 + } + + //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(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)), + pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Option { + if let Some(ref mut holder_commitment) = self.holder_commitment { + match self.key_storage.sign_holder_commitment(&holder_commitment, &self.secp_ctx) { + Ok(sig) => { + Some(holder_commitment.add_holder_sig(funding_redeemscript, sig)) + }, Err(_) => return None, } } else { @@ -989,16 +952,13 @@ impl OnchainTxHandler { } } - pub(super) fn provide_latest_remote_tx(&mut self, commitment_txid: Txid, htlcs: Vec) { - self.remote_tx_cache.per_htlc.insert(commitment_txid, htlcs); - } - - #[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)), + #[cfg(any(test, feature="unsafe_revoked_tx_signing"))] + pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Option { + if let Some(ref mut holder_commitment) = self.holder_commitment { + match self.key_storage.sign_holder_commitment(holder_commitment, &self.secp_ctx) { + Ok(sig) => { + Some(holder_commitment.add_holder_sig(funding_redeemscript, sig)) + }, Err(_) => return None, } } else { @@ -1006,43 +966,49 @@ impl OnchainTxHandler { } } - 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 self.holder_commitment.is_some() { + let commitment_txid = self.holder_commitment.as_ref().unwrap().trust().txid(); if commitment_txid == outp.txid { - self.sign_latest_local_htlcs(); - if let &Some(ref htlc_sigs) = &self.local_htlc_sigs { + 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(); - htlc_tx = Some(self.local_commitment.as_ref().unwrap() - .get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.local_csv)); + let holder_commitment = self.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)); } } } - if self.prev_local_commitment.is_some() { - let commitment_txid = self.prev_local_commitment.as_ref().unwrap().txid(); + if 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 }