X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;ds=sidebyside;f=lightning%2Fsrc%2Fln%2Fonchaintx.rs;h=3b8238ddcae3d0b3f79d2cb63dbb4ea96eab10ad;hb=09a39725f8d0016112d10605230f35e1d14a2ae2;hp=2270cdb7c6683e8f82ba71f9bbdb2ed235355a3d;hpb=2be1f72005d407ed0183b383deeaca6b88becc31;p=rust-lightning diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index 2270cdb7..3b8238dd 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -15,6 +15,7 @@ use secp256k1; use ln::msgs::DecodeError; use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest}; +use ln::channelmanager::PaymentPreimage; use ln::chan_utils::{HTLCType, LocalCommitmentTransaction}; use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT}; use chain::keysinterface::ChannelKeys; @@ -22,7 +23,7 @@ use util::logger::Logger; use util::ser::{ReadableArgs, Readable, Writer, Writeable}; use util::byte_utils; -use std::collections::{HashMap, hash_map, HashSet}; +use std::collections::{HashMap, hash_map}; use std::sync::Arc; use std::cmp; use std::ops::Deref; @@ -144,6 +145,7 @@ pub struct OnchainTxHandler { funding_redeemscript: Script, local_commitment: Option, prev_local_commitment: Option, + local_csv: u16, key_storage: ChanSigner, @@ -187,6 +189,8 @@ impl OnchainTxHandler { self.local_commitment.write(writer)?; self.prev_local_commitment.write(writer)?; + self.local_csv.write(writer)?; + self.key_storage.write(writer)?; writer.write_all(&byte_utils::be64_to_array(self.pending_claim_requests.len() as u64))?; @@ -228,9 +232,12 @@ impl ReadableArgs> for OnchainTx fn read(reader: &mut R, logger: Arc) -> 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)?; + let local_csv = Readable::read(reader)?; + let key_storage = Readable::read(reader)?; let pending_claim_requests_len: u64 = Readable::read(reader)?; @@ -281,6 +288,7 @@ impl ReadableArgs> for OnchainTx funding_redeemscript, local_commitment, prev_local_commitment, + local_csv, key_storage, claimable_outpoints, pending_claim_requests, @@ -292,7 +300,7 @@ impl ReadableArgs> for OnchainTx } impl OnchainTxHandler { - pub(super) fn new(destination_script: Script, keys: ChanSigner, funding_redeemscript: Script, logger: Arc) -> Self { + pub(super) fn new(destination_script: Script, keys: ChanSigner, funding_redeemscript: Script, local_csv: u16, logger: Arc) -> Self { let key_storage = keys; @@ -301,6 +309,7 @@ impl OnchainTxHandler { funding_redeemscript, local_commitment: None, prev_local_commitment: None, + local_csv, key_storage, pending_claim_requests: HashMap::new(), claimable_outpoints: HashMap::new(), @@ -357,7 +366,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(&self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: F) -> Option<(Option, u64, Transaction)> + fn generate_claim_tx(&mut self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: F) -> Option<(Option, u64, Transaction)> where F::Target: FeeEstimator { 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 @@ -436,13 +445,14 @@ impl OnchainTxHandler { inputs_witnesses_weight += Self::get_witnesses_weight(if preimage.is_some() { &[InputDescriptors::OfferedHTLC] } else { &[InputDescriptors::ReceivedHTLC] }); amt += *amount; }, - &InputMaterial::LocalHTLC { .. } => { return None; } + &InputMaterial::LocalHTLC { .. } => { + dynamic_fee = false; + }, &InputMaterial::Funding { .. } => { dynamic_fee = false; } } } - if dynamic_fee { let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight; let mut new_feerate; @@ -504,26 +514,38 @@ impl OnchainTxHandler { } 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 + &InputMaterial::LocalHTLC { ref preimage, ref amount } => { + let mut htlc_tx = None; + if let Some(ref mut local_commitment) = self.local_commitment { + if local_commitment.txid() == outp.txid { + self.key_storage.sign_htlc_transaction(local_commitment, outp.vout, *preimage, self.local_csv, &self.secp_ctx); + htlc_tx = local_commitment.htlc_with_valid_witness(outp.vout).clone(); + } + } + if let Some(ref mut prev_local_commitment) = self.prev_local_commitment { + if prev_local_commitment.txid() == outp.txid { + self.key_storage.sign_htlc_transaction(prev_local_commitment, outp.vout, *preimage, self.local_csv, &self.secp_ctx); + htlc_tx = prev_local_commitment.htlc_with_valid_witness(outp.vout).clone(); + } + } + 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 HTLC-{} claiming HTLC output {} from {}...", if preimage.is_some() { "Success" } else { "Timeout" }, outp.vout, outp.txid); + return Some((None, feerate, htlc_tx)); + } 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; - // 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)); + let signed_tx = self.get_fully_signed_local_tx(*channel_value).unwrap(); + 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; + // 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)); } _ => unreachable!() } @@ -579,7 +601,7 @@ impl OnchainTxHandler { } } - 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(); @@ -636,7 +658,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 @@ -684,27 +706,21 @@ 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!(); } - } { + 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) { + log_trace!(self, "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!(); } + } } } } @@ -769,4 +785,37 @@ impl OnchainTxHandler { self.local_commitment = Some(tx); Ok(()) } + + //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, 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()); + } + None + } + + #[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()); + } + None + } + + pub(super) fn get_fully_signed_htlc_tx(&mut self, txid: Sha256dHash, htlc_index: u32, preimage: Option) -> Option { + //TODO: store preimage in OnchainTxHandler + if let Some(ref mut local_commitment) = self.local_commitment { + if local_commitment.txid() == txid { + self.key_storage.sign_htlc_transaction(local_commitment, htlc_index, preimage, self.local_csv, &self.secp_ctx); + return local_commitment.htlc_with_valid_witness(htlc_index).clone(); + } + } + None + } }