X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonchaintx.rs;h=6b5da67c1a526de38c7905694f4dde9063278b34;hb=7159d1546ae92281a7e0533813a6e7558f16354a;hp=3b8238ddcae3d0b3f79d2cb63dbb4ea96eab10ad;hpb=5a2ed0324738e170e0b3b4dab3431242c7221a13;p=rust-lightning diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index 3b8238dd..6b5da67c 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -10,7 +10,7 @@ use bitcoin::util::bip143; use bitcoin_hashes::sha256d::Hash as Sha256dHash; -use secp256k1::Secp256k1; +use secp256k1::{Secp256k1, Signature}; use secp256k1; use ln::msgs::DecodeError; @@ -137,14 +137,63 @@ macro_rules! subtract_high_prio_fee { } } +impl Readable for Option>> { + fn read(reader: &mut R) -> Result { + match Readable::read(reader)? { + 0u8 => Ok(None), + 1u8 => { + let vlen: u64 = Readable::read(reader)?; + let mut ret = Vec::with_capacity(cmp::min(vlen as usize, MAX_ALLOC_SIZE / ::std::mem::size_of::>())); + for _ in 0..vlen { + ret.push(match Readable::read(reader)? { + 0u8 => None, + 1u8 => Some((::read(reader)? as usize, Readable::read(reader)?)), + _ => return Err(DecodeError::InvalidValue) + }); + } + Ok(Some(ret)) + }, + _ => Err(DecodeError::InvalidValue), + } + } +} + +impl Writeable for Option>> { + fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { + match self { + &Some(ref vec) => { + 1u8.write(writer)?; + (vec.len() as u64).write(writer)?; + for opt in vec.iter() { + match opt { + &Some((ref idx, ref sig)) => { + 1u8.write(writer)?; + (*idx as u64).write(writer)?; + sig.write(writer)?; + }, + &None => 0u8.write(writer)?, + } + } + }, + &None => 0u8.write(writer)?, + } + Ok(()) + } +} + /// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and /// do RBF bumping if possible. pub struct OnchainTxHandler { destination_script: Script, - funding_redeemscript: Script, local_commitment: Option, + // local_htlc_sigs and prev_local_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, key_storage: ChanSigner, @@ -185,9 +234,10 @@ pub struct OnchainTxHandler { impl OnchainTxHandler { pub(crate) fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { self.destination_script.write(writer)?; - self.funding_redeemscript.write(writer)?; self.local_commitment.write(writer)?; + self.local_htlc_sigs.write(writer)?; self.prev_local_commitment.write(writer)?; + self.prev_local_htlc_sigs.write(writer)?; self.local_csv.write(writer)?; @@ -231,10 +281,11 @@ impl OnchainTxHandler { impl ReadableArgs> for OnchainTxHandler { 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 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)?; @@ -285,9 +336,10 @@ impl ReadableArgs> for OnchainTx Ok(OnchainTxHandler { destination_script, - funding_redeemscript, local_commitment, + local_htlc_sigs, prev_local_commitment, + prev_local_htlc_sigs, local_csv, key_storage, claimable_outpoints, @@ -300,15 +352,16 @@ impl ReadableArgs> for OnchainTx } impl OnchainTxHandler { - pub(super) fn new(destination_script: Script, keys: ChanSigner, funding_redeemscript: Script, local_csv: u16, logger: Arc) -> Self { + pub(super) fn new(destination_script: Script, keys: ChanSigner, local_csv: u16, logger: Arc) -> Self { let key_storage = keys; OnchainTxHandler { destination_script, - funding_redeemscript, local_commitment: None, + local_htlc_sigs: None, prev_local_commitment: None, + prev_local_htlc_sigs: None, local_csv, key_storage, pending_claim_requests: HashMap::new(), @@ -515,19 +568,7 @@ impl OnchainTxHandler { for (_, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() { match per_outp_material { &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(); - } - } + 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 @@ -536,16 +577,11 @@ impl OnchainTxHandler { } return None; }, - &InputMaterial::Funding { ref channel_value } => { - 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; + &InputMaterial::Funding { ref funding_redeemscript } => { + let signed_tx = self.get_fully_signed_local_tx(funding_redeemscript).unwrap(); // 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)); + return Some((None, self.local_commitment.as_ref().unwrap().feerate_per_kw, signed_tx)); } _ => unreachable!() } @@ -585,17 +621,17 @@ impl OnchainTxHandler { // Generate claim transactions and track them to bump if necessary at // height timer expiration (i.e in how many blocks we're going to take action). - for claim in new_claims { - let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock: claim.0, per_input_material: claim.1.clone() }; + for (soonest_timelock, claim) in new_claims.drain(..) { + let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock, per_input_material: claim }; if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) { claim_material.height_timer = new_timer; claim_material.feerate_previous = new_feerate; let txid = tx.txid(); - self.pending_claim_requests.insert(txid, claim_material); - for k in claim.1.keys() { + for k in claim_material.per_input_material.keys() { log_trace!(self, "Registering claiming request for {}:{}", k.txid, k.vout); self.claimable_outpoints.insert(k.clone(), (txid, height)); } + self.pending_claim_requests.insert(txid, claim_material); log_trace!(self, "Broadcast onchain {}", log_tx!(tx)); broadcaster.broadcast_transaction(&tx); } @@ -781,41 +817,113 @@ impl OnchainTxHandler { if let Some(ref local_commitment) = self.local_commitment { if local_commitment.has_local_sig() { return Err(()) } } + 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(()) } + 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_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"); + } + } + } + } + } + //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 { + pub(super) fn get_fully_signed_local_tx(&mut self, funding_redeemscript: &Script) -> 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); + match self.key_storage.sign_local_commitment(local_commitment, &self.secp_ctx) { + Ok(sig) => local_commitment.add_local_sig(funding_redeemscript, sig), + Err(_) => return None, + } 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 { + 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 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); + match self.key_storage.sign_local_commitment(&local_commitment, &self.secp_ctx) { + Ok(sig) => local_commitment.add_local_sig(funding_redeemscript, sig), + Err(_) => return None, + } 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(); + pub(super) 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)); + } } } - None + if self.prev_local_commitment.is_some() { + let commitment_txid = self.prev_local_commitment.as_ref().unwrap().txid(); + if commitment_txid == outp.txid { + self.sign_prev_local_htlcs(); + if let &Some(ref htlc_sigs) = &self.prev_local_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)); + } + } + } + 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(); + let ret = self.get_fully_signed_htlc_tx(outp, preimage); + if !latest_had_sigs { + self.local_htlc_sigs = None; + } + if !prev_had_sigs { + self.prev_local_htlc_sigs = None; + } + ret } }