From: Matt Corallo <649246+TheBlueMatt@users.noreply.github.com> Date: Sat, 18 Apr 2020 00:05:11 +0000 (+0000) Subject: Merge pull request #559 from ariard/2020-03-move-local-commitment X-Git-Tag: v0.0.12~84 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=02c1925c16fca0a5c37582ec70b3fe48ba076f8f;hp=a44454e1daa0864b431b7ffad0692c00e16078cd;p=rust-lightning Merge pull request #559 from ariard/2020-03-move-local-commitment Split parsing and transaction management for local transactions between Chanmon/Onchain --- diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 2b901886..58bdd722 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -24,7 +24,8 @@ use util::logger::Logger; use util::ser::{Writeable, Writer, Readable}; use ln::chan_utils; -use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys}; +use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, LocalCommitmentTransaction}; +use ln::channelmanager::PaymentPreimage; use ln::msgs; use std::sync::Arc; @@ -215,6 +216,26 @@ pub trait ChannelKeys : Send+Clone { /// making the callee generate it via some util function we expose)! fn sign_remote_commitment(&self, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()>; + /// Create a signature for a local commitment transaction + /// + /// TODO: Document the things someone using this interface should enforce before signing. + /// TODO: Add more input vars to enable better checking (preferably removing commitment_tx and + /// TODO: Ensure test-only version doesn't enforce uniqueness of signature when it's enforced in this method + /// making the callee generate it via some util function we expose)! + fn sign_local_commitment(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1); + + /// Create a signature for a local commitment transaction without enforcing one-time signing. + /// + /// Testing revocation logic by our test framework needs to sign multiple local commitment + /// transactions. This unsafe test-only version doesn't enforce one-time signing security + /// requirement. + #[cfg(test)] + fn unsafe_sign_local_commitment(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1); + + /// Signs a transaction created by build_htlc_transaction. If the transaction is an + /// HTLC-Success transaction, preimage must be set! + /// TODO: should be merged with sign_local_commitment as a slice of HTLC transactions to sign + fn sign_htlc_transaction(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option, local_csv: u16, secp_ctx: &Secp256k1); /// Create a signature for a (proposed) closing transaction. /// /// Note that, due to rounding, there may be one "missing" satoshi, and either party may have @@ -342,6 +363,19 @@ impl ChannelKeys for InMemoryChannelKeys { Ok((commitment_sig, htlc_sigs)) } + fn sign_local_commitment(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1) { + local_commitment_tx.add_local_sig(&self.funding_key, funding_redeemscript, channel_value_satoshis, secp_ctx); + } + + #[cfg(test)] + fn unsafe_sign_local_commitment(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1) { + local_commitment_tx.add_local_sig(&self.funding_key, funding_redeemscript, channel_value_satoshis, secp_ctx); + } + + fn sign_htlc_transaction(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option, local_csv: u16, secp_ctx: &Secp256k1) { + local_commitment_tx.add_htlc_sig(&self.htlc_base_key, htlc_index, preimage, local_csv, secp_ctx); + } + fn sign_closing_transaction(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1) -> Result { if closing_tx.input.len() != 1 { return Err(()); } if closing_tx.input[0].witness.len() != 0 { return Err(()); } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 561448c8..566fd7b0 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -23,6 +23,10 @@ use secp256k1::key::{SecretKey, PublicKey}; use secp256k1::{Secp256k1, Signature}; use secp256k1; +use std::{cmp, mem}; + +const MAX_ALLOC_SIZE: usize = 64*1024; + pub(super) const HTLC_SUCCESS_TX_WEIGHT: u64 = 703; pub(super) const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663; @@ -355,7 +359,7 @@ impl_writeable!(HTLCOutputInCommitment, 1 + 8 + 4 + 32 + 5, { }); #[inline] -pub(super) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script { +pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script { let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner(); if htlc.offered { Builder::new().push_opcode(opcodes::all::OP_DUP) @@ -475,62 +479,44 @@ pub fn build_htlc_transaction(prev_hash: &Sha256dHash, feerate_per_kw: u64, to_s } } -/// Signs a transaction created by build_htlc_transaction. If the transaction is an -/// HTLC-Success transaction (ie htlc.offered is false), preimage must be set! -pub(crate) fn sign_htlc_transaction(tx: &mut Transaction, their_sig: &Signature, preimage: &Option, htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey, per_commitment_point: &PublicKey, htlc_base_key: &SecretKey, secp_ctx: &Secp256k1) -> Result<(Signature, Script), ()> { - if tx.input.len() != 1 { return Err(()); } - if tx.input[0].witness.len() != 0 { return Err(()); } - - let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&htlc, a_htlc_key, b_htlc_key, revocation_key); - - let our_htlc_key = derive_private_key(secp_ctx, per_commitment_point, htlc_base_key).map_err(|_| ())?; - let sighash = hash_to_message!(&bip143::SighashComponents::new(&tx).sighash_all(&tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]); - let local_tx = PublicKey::from_secret_key(&secp_ctx, &our_htlc_key) == *a_htlc_key; - let our_sig = secp_ctx.sign(&sighash, &our_htlc_key); - - tx.input[0].witness.push(Vec::new()); // First is the multisig dummy - - if local_tx { // b, then a - tx.input[0].witness.push(their_sig.serialize_der().to_vec()); - tx.input[0].witness.push(our_sig.serialize_der().to_vec()); - } else { - tx.input[0].witness.push(our_sig.serialize_der().to_vec()); - tx.input[0].witness.push(their_sig.serialize_der().to_vec()); - } - tx.input[0].witness[1].push(SigHashType::All as u8); - tx.input[0].witness[2].push(SigHashType::All as u8); - - if htlc.offered { - tx.input[0].witness.push(Vec::new()); - assert!(preimage.is_none()); - } else { - tx.input[0].witness.push(preimage.unwrap().0.to_vec()); - } - - tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec()); - - Ok((our_sig, htlc_redeemscript)) -} - #[derive(Clone)] /// We use this to track local commitment transactions and put off signing them until we are ready /// to broadcast. Eventually this will require a signer which is possibly external, but for now we /// just pass in the SecretKeys required. -pub(crate) struct LocalCommitmentTransaction { - tx: Transaction +pub struct LocalCommitmentTransaction { + tx: Transaction, + //TODO: modify Channel methods to integrate HTLC material at LocalCommitmentTransaction generation to drop Option here + local_keys: Option, + feerate_per_kw: Option, + per_htlc: Vec<(HTLCOutputInCommitment, Option, Option)> } impl LocalCommitmentTransaction { #[cfg(test)] pub fn dummy() -> Self { + let dummy_input = TxIn { + previous_output: OutPoint { + txid: Default::default(), + vout: 0, + }, + script_sig: Default::default(), + sequence: 0, + witness: vec![vec![], vec![], vec![]] + }; Self { tx: Transaction { version: 2, - input: Vec::new(), + input: vec![dummy_input], output: Vec::new(), lock_time: 0, - } } + }, + local_keys: None, + feerate_per_kw: None, + per_htlc: Vec::new() + } } - pub fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey) -> LocalCommitmentTransaction { + /// Generate a new LocalCommitmentTransaction based on a raw commitment transaction, + /// remote signature and both parties keys + pub(crate) fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey) -> LocalCommitmentTransaction { if tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); } if tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); } @@ -546,13 +532,20 @@ impl LocalCommitmentTransaction { tx.input[0].witness.push(Vec::new()); } - Self { tx } + Self { tx, + local_keys: None, + feerate_per_kw: None, + per_htlc: Vec::new() + } } + /// Get the txid of the local commitment transaction contained in this + /// LocalCommitmentTransaction pub fn txid(&self) -> Sha256dHash { self.tx.txid() } + /// Check if LocalCommitmentTransaction has already been signed by us pub fn has_local_sig(&self) -> bool { if self.tx.input.len() != 1 { panic!("Commitment transactions must have input count == 1!"); } if self.tx.input[0].witness.len() == 4 { @@ -567,6 +560,15 @@ impl LocalCommitmentTransaction { } } + /// Add local signature for LocalCommitmentTransaction, do nothing if signature is already + /// present + /// + /// Funding key is your key included in the 2-2 funding_outpoint lock. Should be provided + /// by your ChannelKeys. + /// Funding redeemscript is script locking funding_outpoint. This is the mutlsig script + /// between your own funding key and your counterparty's. Currently, this is provided in + /// ChannelKeys::sign_local_commitment() calls directly. + /// Channel value is amount locked in funding_outpoint. pub fn add_local_sig(&mut self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1) { if self.has_local_sig() { return; } let sighash = hash_to_message!(&bip143::SighashComponents::new(&self.tx) @@ -584,11 +586,74 @@ impl LocalCommitmentTransaction { self.tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec()); } - pub fn without_valid_witness(&self) -> &Transaction { &self.tx } + /// Get raw transaction without asserting if witness is complete + pub(crate) fn without_valid_witness(&self) -> &Transaction { &self.tx } + /// Get raw transaction with panics if witness is incomplete pub fn with_valid_witness(&self) -> &Transaction { assert!(self.has_local_sig()); &self.tx } + + /// Set HTLC cache to generate any local HTLC transaction spending one of htlc ouput + /// from this local commitment transaction + pub(crate) fn set_htlc_cache(&mut self, local_keys: TxCreationKeys, feerate_per_kw: u64, htlc_outputs: Vec<(HTLCOutputInCommitment, Option, Option)>) { + self.local_keys = Some(local_keys); + self.feerate_per_kw = Some(feerate_per_kw); + self.per_htlc = htlc_outputs; + } + + /// Add local signature for a htlc transaction, do nothing if a cached signed transaction is + /// already present + pub fn add_htlc_sig(&mut self, htlc_base_key: &SecretKey, htlc_index: u32, preimage: Option, local_csv: u16, secp_ctx: &Secp256k1) { + if self.local_keys.is_none() || self.feerate_per_kw.is_none() { return; } + let local_keys = self.local_keys.as_ref().unwrap(); + let txid = self.txid(); + for this_htlc in self.per_htlc.iter_mut() { + if this_htlc.0.transaction_output_index.unwrap() == htlc_index { + if this_htlc.2.is_some() { return; } // we already have a cached htlc transaction at provided index + let mut htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw.unwrap(), local_csv, &this_htlc.0, &local_keys.a_delayed_payment_key, &local_keys.revocation_key); + if !this_htlc.0.offered && preimage.is_none() { return; } // if we don't have preimage for HTLC-Success, don't try to generate + let htlc_secret = if !this_htlc.0.offered { preimage } else { None }; // if we have a preimage for HTLC-Timeout, don't use it that's likely a duplicate HTLC hash + if this_htlc.1.is_none() { return; } // we don't have any remote signature for this htlc + if htlc_tx.input.len() != 1 { return; } + if htlc_tx.input[0].witness.len() != 0 { return; } + + let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &local_keys.a_htlc_key, &local_keys.b_htlc_key, &local_keys.revocation_key); + + if let Ok(our_htlc_key) = derive_private_key(secp_ctx, &local_keys.per_commitment_point, htlc_base_key) { + let sighash = hash_to_message!(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, this_htlc.0.amount_msat / 1000)[..]); + let our_sig = secp_ctx.sign(&sighash, &our_htlc_key); + + htlc_tx.input[0].witness.push(Vec::new()); // First is the multisig dummy + + htlc_tx.input[0].witness.push(this_htlc.1.unwrap().serialize_der().to_vec()); + htlc_tx.input[0].witness.push(our_sig.serialize_der().to_vec()); + htlc_tx.input[0].witness[1].push(SigHashType::All as u8); + htlc_tx.input[0].witness[2].push(SigHashType::All as u8); + + if this_htlc.0.offered { + htlc_tx.input[0].witness.push(Vec::new()); + assert!(htlc_secret.is_none()); + } else { + htlc_tx.input[0].witness.push(htlc_secret.unwrap().0.to_vec()); + } + + htlc_tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec()); + + this_htlc.2 = Some(htlc_tx); + } else { return; } + } + } + } + /// Expose raw htlc transaction, guarante witness is complete if non-empty + pub fn htlc_with_valid_witness(&self, htlc_index: u32) -> &Option { + for this_htlc in self.per_htlc.iter() { + if this_htlc.0.transaction_output_index.unwrap() == htlc_index { + return &this_htlc.2; + } + } + &None + } } impl PartialEq for LocalCommitmentTransaction { // We dont care whether we are signed in equality comparison @@ -604,6 +669,14 @@ impl Writeable for LocalCommitmentTransaction { _ => panic!("local tx must have been well-formed!"), } } + self.local_keys.write(writer)?; + self.feerate_per_kw.write(writer)?; + writer.write_all(&byte_utils::be64_to_array(self.per_htlc.len() as u64))?; + for &(ref htlc, ref sig, ref htlc_tx) in self.per_htlc.iter() { + htlc.write(writer)?; + sig.write(writer)?; + htlc_tx.write(writer)?; + } Ok(()) } } @@ -616,12 +689,27 @@ impl Readable for LocalCommitmentTransaction { _ => return Err(DecodeError::InvalidValue), }, }; + let local_keys = Readable::read(reader)?; + let feerate_per_kw = Readable::read(reader)?; + let htlcs_count: u64 = Readable::read(reader)?; + let mut per_htlc = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / mem::size_of::<(HTLCOutputInCommitment, Option, Option)>())); + for _ in 0..htlcs_count { + let htlc: HTLCOutputInCommitment = Readable::read(reader)?; + let sigs = Readable::read(reader)?; + let htlc_tx = Readable::read(reader)?; + per_htlc.push((htlc, sigs, htlc_tx)); + } if tx.input.len() != 1 { // Ensure tx didn't hit the 0-input ambiguity case. return Err(DecodeError::InvalidValue); } - Ok(Self { tx }) + Ok(Self { + tx, + local_keys, + feerate_per_kw, + per_htlc, + }) } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 374d74f6..47936ea6 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -4433,7 +4433,7 @@ mod tests { assert_eq!(PublicKey::from_secret_key(&secp_ctx, chan_keys.funding_key()).serialize()[..], hex::decode("023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb").unwrap()[..]); - let keys_provider = Keys { chan_keys }; + let keys_provider = Keys { chan_keys: chan_keys.clone() }; let their_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let mut config = UserConfig::default(); @@ -4475,6 +4475,7 @@ mod tests { let mut unsigned_tx: (Transaction, Vec); + let mut localtx; macro_rules! test_commitment { ( $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr) => { unsigned_tx = { @@ -4489,8 +4490,8 @@ mod tests { let sighash = Message::from_slice(&bip143::SighashComponents::new(&unsigned_tx.0).sighash_all(&unsigned_tx.0.input[0], &redeemscript, chan.channel_value_satoshis)[..]).unwrap(); secp_ctx.verify(&sighash, &their_signature, chan.their_funding_pubkey()).unwrap(); - let mut localtx = LocalCommitmentTransaction::new_missing_local_sig(unsigned_tx.0.clone(), &their_signature, &PublicKey::from_secret_key(&secp_ctx, chan.local_keys.funding_key()), chan.their_funding_pubkey()); - localtx.add_local_sig(chan.local_keys.funding_key(), &redeemscript, chan.channel_value_satoshis, &chan.secp_ctx); + localtx = LocalCommitmentTransaction::new_missing_local_sig(unsigned_tx.0.clone(), &their_signature, &PublicKey::from_secret_key(&secp_ctx, chan.local_keys.funding_key()), chan.their_funding_pubkey()); + chan_keys.sign_local_commitment(&mut localtx, &redeemscript, chan.channel_value_satoshis, &chan.secp_ctx); assert_eq!(serialize(localtx.with_valid_witness())[..], hex::decode($tx_hex).unwrap()[..]); @@ -4498,11 +4499,11 @@ mod tests { } macro_rules! test_htlc_output { - ( $htlc_idx: expr, $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr ) => { + ( $htlc_idx: expr, $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr) => { let remote_signature = Signature::from_der(&hex::decode($their_sig_hex).unwrap()[..]).unwrap(); let ref htlc = unsigned_tx.1[$htlc_idx]; - let mut htlc_tx = chan.build_htlc_transaction(&unsigned_tx.0.txid(), &htlc, true, &keys, chan.feerate_per_kw); + let htlc_tx = chan.build_htlc_transaction(&unsigned_tx.0.txid(), &htlc, true, &keys, chan.feerate_per_kw); let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &keys); let htlc_sighash = Message::from_slice(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]).unwrap(); secp_ctx.verify(&htlc_sighash, &remote_signature, &keys.b_htlc_key).unwrap(); @@ -4519,8 +4520,12 @@ mod tests { assert!(preimage.is_some()); } - chan_utils::sign_htlc_transaction(&mut htlc_tx, &remote_signature, &preimage, &htlc, &keys.a_htlc_key, &keys.b_htlc_key, &keys.revocation_key, &keys.per_commitment_point, chan.local_keys.htlc_base_key(), &chan.secp_ctx).unwrap(); - assert_eq!(serialize(&htlc_tx)[..], + let mut per_htlc = Vec::new(); + per_htlc.push((htlc.clone(), Some(remote_signature), None)); + localtx.set_htlc_cache(keys.clone(), chan.feerate_per_kw, per_htlc); + chan_keys.sign_htlc_transaction(&mut localtx, $htlc_idx, preimage, chan.their_to_self_delay, &chan.secp_ctx); + + assert_eq!(serialize(localtx.htlc_with_valid_witness($htlc_idx).as_ref().unwrap())[..], hex::decode($tx_hex).unwrap()[..]); }; } diff --git a/lightning/src/ln/channelmonitor.rs b/lightning/src/ln/channelmonitor.rs index 2ac113b9..cba28982 100644 --- a/lightning/src/ln/channelmonitor.rs +++ b/lightning/src/ln/channelmonitor.rs @@ -409,7 +409,6 @@ impl PartialEq for OnchainDetection { struct LocalSignedTx { /// txid of the transaction in tx, just used to make comparison faster txid: Sha256dHash, - tx: LocalCommitmentTransaction, revocation_key: PublicKey, a_htlc_key: PublicKey, b_htlc_key: PublicKey, @@ -439,10 +438,11 @@ pub(crate) enum InputMaterial { locktime: u32, }, LocalHTLC { - witness_script: Script, - sigs: (Signature, Signature), preimage: Option, amount: u64, + }, + Funding { + channel_value: u64, } } @@ -465,13 +465,14 @@ impl Writeable for InputMaterial { writer.write_all(&byte_utils::be64_to_array(*amount))?; writer.write_all(&byte_utils::be32_to_array(*locktime))?; }, - &InputMaterial::LocalHTLC { ref witness_script, ref sigs, ref preimage, ref amount } => { + &InputMaterial::LocalHTLC { ref preimage, ref amount } => { writer.write_all(&[2; 1])?; - witness_script.write(writer)?; - sigs.0.write(writer)?; - sigs.1.write(writer)?; preimage.write(writer)?; writer.write_all(&byte_utils::be64_to_array(*amount))?; + }, + &InputMaterial::Funding { ref channel_value } => { + writer.write_all(&[3; 1])?; + channel_value.write(writer)?; } } Ok(()) @@ -510,16 +511,17 @@ impl Readable for InputMaterial { } }, 2 => { - let witness_script = Readable::read(reader)?; - let their_sig = Readable::read(reader)?; - let our_sig = Readable::read(reader)?; let preimage = Readable::read(reader)?; let amount = Readable::read(reader)?; InputMaterial::LocalHTLC { - witness_script, - sigs: (their_sig, our_sig), preimage, - amount + amount, + } + }, + 3 => { + let channel_value = Readable::read(reader)?; + InputMaterial::Funding { + channel_value } } _ => return Err(DecodeError::InvalidValue), @@ -767,6 +769,9 @@ pub struct ChannelMonitor { // Used just for ChannelManager to make sure it has the latest channel data during // deserialization current_remote_commitment_number: u64, + // Used just for ChannelManager to make sure it has the latest channel data during + // deserialization + current_local_commitment_number: u64, payment_preimages: HashMap, @@ -789,6 +794,9 @@ pub struct ChannelMonitor { #[cfg(not(test))] onchain_tx_handler: OnchainTxHandler, + // Used to detect programming bug due to unsafe monitor update sequence { ChannelForceClosed, LatestLocalCommitmentTXInfo } + lockdown_from_offchain: bool, + // We simply modify last_block_hash in Channel's block_connected so that serialization is // consistent but hopefully the users' copy handles block_connected in a consistent way. // (we do *not*, however, update them in update_monitor to ensure any local user copies keep @@ -823,6 +831,7 @@ impl PartialEq for ChannelMonitor { self.remote_hash_commitment_number != other.remote_hash_commitment_number || self.prev_local_signed_commitment_tx != other.prev_local_signed_commitment_tx || self.current_remote_commitment_number != other.current_remote_commitment_number || + self.current_local_commitment_number != other.current_local_commitment_number || self.current_local_signed_commitment_tx != other.current_local_signed_commitment_tx || self.payment_preimages != other.payment_preimages || self.pending_htlcs_updated != other.pending_htlcs_updated || @@ -953,7 +962,7 @@ impl ChannelMonitor { macro_rules! serialize_local_tx { ($local_tx: expr) => { - $local_tx.tx.write(writer)?; + $local_tx.txid.write(writer)?; writer.write_all(&$local_tx.revocation_key.serialize())?; writer.write_all(&$local_tx.a_htlc_key.serialize())?; writer.write_all(&$local_tx.b_htlc_key.serialize())?; @@ -995,6 +1004,12 @@ impl ChannelMonitor { writer.write_all(&byte_utils::be48_to_array(0))?; } + if for_local_storage { + writer.write_all(&byte_utils::be48_to_array(self.current_local_commitment_number))?; + } else { + writer.write_all(&byte_utils::be48_to_array(0))?; + } + writer.write_all(&byte_utils::be64_to_array(self.payment_preimages.len() as u64))?; for payment_preimage in self.payment_preimages.values() { writer.write_all(&payment_preimage.0[..])?; @@ -1041,6 +1056,8 @@ impl ChannelMonitor { } self.onchain_tx_handler.write(writer)?; + self.lockdown_from_offchain.write(writer)?; + Ok(()) } @@ -1098,7 +1115,7 @@ impl ChannelMonitor { onchain_detection: onchain_detection, their_htlc_base_key: Some(their_htlc_base_key.clone()), their_delayed_payment_base_key: Some(their_delayed_payment_base_key.clone()), - funding_redeemscript: Some(funding_redeemscript), + funding_redeemscript: Some(funding_redeemscript.clone()), channel_value_satoshis: Some(channel_value_satoshis), their_cur_revocation_points: None, @@ -1113,6 +1130,7 @@ impl ChannelMonitor { prev_local_signed_commitment_tx: None, current_local_signed_commitment_tx: None, current_remote_commitment_number: 1 << 48, + current_local_commitment_number: 0xffff_ffff_ffff, payment_preimages: HashMap::new(), pending_htlcs_updated: Vec::new(), @@ -1121,7 +1139,9 @@ impl ChannelMonitor { onchain_events_waiting_threshold_conf: HashMap::new(), outputs_to_watch: HashMap::new(), - onchain_tx_handler: OnchainTxHandler::new(destination_script.clone(), keys, logger.clone()), + onchain_tx_handler: OnchainTxHandler::new(destination_script.clone(), keys, funding_redeemscript, their_to_self_delay, logger.clone()), + + lockdown_from_offchain: false, last_block_hash: Default::default(), secp_ctx: Secp256k1::new(), @@ -1237,21 +1257,39 @@ impl ChannelMonitor { /// is important that any clones of this channel monitor (including remote clones) by kept /// up-to-date as our local commitment transaction is updated. /// Panics if set_their_to_self_delay has never been called. - pub(super) fn provide_latest_local_commitment_tx_info(&mut self, commitment_tx: LocalCommitmentTransaction, local_keys: chan_utils::TxCreationKeys, feerate_per_kw: u64, htlc_outputs: Vec<(HTLCOutputInCommitment, Option, Option)>) -> Result<(), MonitorUpdateError> { + pub(super) fn provide_latest_local_commitment_tx_info(&mut self, mut commitment_tx: LocalCommitmentTransaction, local_keys: chan_utils::TxCreationKeys, feerate_per_kw: u64, htlc_outputs: Vec<(HTLCOutputInCommitment, Option, Option)>) -> Result<(), MonitorUpdateError> { if self.their_to_self_delay.is_none() { return Err(MonitorUpdateError("Got a local commitment tx info update before we'd set basic information about the channel")); } + let txid = commitment_tx.txid(); + let sequence = commitment_tx.without_valid_witness().input[0].sequence as u64; + let locktime = commitment_tx.without_valid_witness().lock_time as u64; + let mut htlcs = Vec::with_capacity(htlc_outputs.len()); + for htlc in htlc_outputs.clone() { + if let Some(_) = htlc.0.transaction_output_index { + htlcs.push((htlc.0, htlc.1, None)); + } + } + commitment_tx.set_htlc_cache(local_keys.clone(), feerate_per_kw, htlcs); + // Returning a monitor error before updating tracking points means in case of using + // a concurrent watchtower implementation for same channel, if this one doesn't + // reject update as we do, you MAY have the latest local valid commitment tx onchain + // for which you want to spend outputs. We're NOT robust again this scenario right + // now but we should consider it later. + if let Err(_) = self.onchain_tx_handler.provide_latest_local_tx(commitment_tx) { + return Err(MonitorUpdateError("Local commitment signed has already been signed, no further update of LOCAL commitment transaction is allowed")); + } + self.current_local_commitment_number = 0xffff_ffff_ffff - ((((sequence & 0xffffff) << 3*8) | (locktime as u64 & 0xffffff)) ^ self.commitment_transaction_number_obscure_factor); self.prev_local_signed_commitment_tx = self.current_local_signed_commitment_tx.take(); self.current_local_signed_commitment_tx = Some(LocalSignedTx { - txid: commitment_tx.txid(), - tx: commitment_tx, + txid, revocation_key: local_keys.revocation_key, a_htlc_key: local_keys.a_htlc_key, b_htlc_key: local_keys.b_htlc_key, delayed_payment_key: local_keys.a_delayed_payment_key, per_commitment_point: local_keys.per_commitment_point, feerate_per_kw, - htlc_outputs, + htlc_outputs: htlc_outputs, }); Ok(()) } @@ -1274,8 +1312,10 @@ impl ChannelMonitor { pub(super) fn update_monitor_ooo(&mut self, mut updates: ChannelMonitorUpdate) -> Result<(), MonitorUpdateError> { for update in updates.updates.drain(..) { match update { - ChannelMonitorUpdateStep::LatestLocalCommitmentTXInfo { commitment_tx, local_keys, feerate_per_kw, htlc_outputs } => - self.provide_latest_local_commitment_tx_info(commitment_tx, local_keys, feerate_per_kw, htlc_outputs)?, + ChannelMonitorUpdateStep::LatestLocalCommitmentTXInfo { commitment_tx, local_keys, feerate_per_kw, htlc_outputs } => { + if self.lockdown_from_offchain { panic!(); } + self.provide_latest_local_commitment_tx_info(commitment_tx, local_keys, feerate_per_kw, htlc_outputs)? + }, ChannelMonitorUpdateStep::LatestRemoteCommitmentTXInfo { unsigned_commitment_tx, htlc_outputs, commitment_number, their_revocation_point } => self.provide_latest_remote_commitment_tx_info(&unsigned_commitment_tx, htlc_outputs, commitment_number, their_revocation_point), ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage } => @@ -1303,8 +1343,10 @@ impl ChannelMonitor { } for update in updates.updates.drain(..) { match update { - ChannelMonitorUpdateStep::LatestLocalCommitmentTXInfo { commitment_tx, local_keys, feerate_per_kw, htlc_outputs } => - self.provide_latest_local_commitment_tx_info(commitment_tx, local_keys, feerate_per_kw, htlc_outputs)?, + ChannelMonitorUpdateStep::LatestLocalCommitmentTXInfo { commitment_tx, local_keys, feerate_per_kw, htlc_outputs } => { + if self.lockdown_from_offchain { panic!(); } + self.provide_latest_local_commitment_tx_info(commitment_tx, local_keys, feerate_per_kw, htlc_outputs)? + }, ChannelMonitorUpdateStep::LatestRemoteCommitmentTXInfo { unsigned_commitment_tx, htlc_outputs, commitment_number, their_revocation_point } => self.provide_latest_remote_commitment_tx_info(&unsigned_commitment_tx, htlc_outputs, commitment_number, their_revocation_point), ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage } => @@ -1314,6 +1356,7 @@ impl ChannelMonitor { ChannelMonitorUpdateStep::RescueRemoteCommitmentTXInfo { their_current_per_commitment_point } => self.provide_rescue_remote_commitment_tx_info(their_current_per_commitment_point), ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => { + self.lockdown_from_offchain = true; if should_broadcast { self.broadcast_latest_local_commitment_txn(broadcaster); } else { @@ -1394,9 +1437,7 @@ impl ChannelMonitor { } pub(super) fn get_cur_local_commitment_number(&self) -> u64 { - if let &Some(ref local_tx) = &self.current_local_signed_commitment_tx { - 0xffff_ffff_ffff - ((((local_tx.tx.without_valid_witness().input[0].sequence as u64 & 0xffffff) << 3*8) | (local_tx.tx.without_valid_witness().lock_time as u64 & 0xffffff)) ^ self.commitment_transaction_number_obscure_factor) - } else { 0xffff_ffff_ffff } + self.current_local_commitment_number } /// Attempts to claim a remote commitment transaction's outputs using the revocation key and @@ -1656,8 +1697,8 @@ impl ChannelMonitor { (claimable_outpoints, Some((htlc_txid, tx.output.clone()))) } - fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx) -> (Vec, Vec, Option<(Script, SecretKey, Script)>) { - let mut res = Vec::with_capacity(local_tx.htlc_outputs.len()); + fn broadcast_by_local_state(&self, commitment_tx: &Transaction, local_tx: &LocalSignedTx) -> (Vec, Vec, Option<(Script, SecretKey, Script)>) { + let mut claim_requests = Vec::with_capacity(local_tx.htlc_outputs.len()); let mut watch_outputs = Vec::with_capacity(local_tx.htlc_outputs.len()); let redeemscript = chan_utils::get_revokeable_redeemscript(&local_tx.revocation_key, self.their_to_self_delay.unwrap(), &local_tx.delayed_payment_key); @@ -1665,54 +1706,23 @@ impl ChannelMonitor { Some((redeemscript.to_v0_p2wsh(), local_delayedkey, redeemscript)) } else { None }; - for &(ref htlc, ref sigs, _) in local_tx.htlc_outputs.iter() { + for &(ref htlc, _, _) in local_tx.htlc_outputs.iter() { if let Some(transaction_output_index) = htlc.transaction_output_index { - if let &Some(ref their_sig) = sigs { - if htlc.offered { - log_trace!(self, "Broadcasting HTLC-Timeout transaction against local commitment transactions"); - let mut htlc_timeout_tx = chan_utils::build_htlc_transaction(&local_tx.txid, local_tx.feerate_per_kw, self.their_to_self_delay.unwrap(), htlc, &local_tx.delayed_payment_key, &local_tx.revocation_key); - let (our_sig, htlc_script) = match - chan_utils::sign_htlc_transaction(&mut htlc_timeout_tx, their_sig, &None, htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key, &local_tx.per_commitment_point, &self.onchain_detection.keys.htlc_base_key(), &self.secp_ctx) { - Ok(res) => res, - Err(_) => continue, - }; - - let mut per_input_material = HashMap::with_capacity(1); - per_input_material.insert(htlc_timeout_tx.input[0].previous_output, InputMaterial::LocalHTLC { witness_script: htlc_script, sigs: (*their_sig, our_sig), preimage: None, amount: htlc.amount_msat / 1000}); - //TODO: with option_simplified_commitment track outpoint too - log_trace!(self, "Outpoint {}:{} is being being claimed", htlc_timeout_tx.input[0].previous_output.vout, htlc_timeout_tx.input[0].previous_output.txid); - res.push(htlc_timeout_tx); - } else { - if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) { - log_trace!(self, "Broadcasting HTLC-Success transaction against local commitment transactions"); - let mut htlc_success_tx = chan_utils::build_htlc_transaction(&local_tx.txid, local_tx.feerate_per_kw, self.their_to_self_delay.unwrap(), htlc, &local_tx.delayed_payment_key, &local_tx.revocation_key); - let (our_sig, htlc_script) = match - chan_utils::sign_htlc_transaction(&mut htlc_success_tx, their_sig, &Some(*payment_preimage), htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key, &local_tx.per_commitment_point, &self.onchain_detection.keys.htlc_base_key(), &self.secp_ctx) { - Ok(res) => res, - Err(_) => continue, - }; - - let mut per_input_material = HashMap::with_capacity(1); - per_input_material.insert(htlc_success_tx.input[0].previous_output, InputMaterial::LocalHTLC { witness_script: htlc_script, sigs: (*their_sig, our_sig), preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000}); - //TODO: with option_simplified_commitment track outpoint too - log_trace!(self, "Outpoint {}:{} is being being claimed", htlc_success_tx.input[0].previous_output.vout, htlc_success_tx.input[0].previous_output.txid); - res.push(htlc_success_tx); - } - } - watch_outputs.push(local_tx.tx.without_valid_witness().output[transaction_output_index as usize].clone()); - } else { panic!("Should have sigs for non-dust local tx outputs!") } + let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.payment_hash) { Some(*preimage) } else { None }; + claim_requests.push(ClaimRequest { absolute_timelock: ::std::u32::MAX, aggregable: false, outpoint: BitcoinOutPoint { txid: local_tx.txid, vout: transaction_output_index as u32 }, witness_data: InputMaterial::LocalHTLC { preimage, amount: htlc.amount_msat / 1000 }}); + watch_outputs.push(commitment_tx.output[transaction_output_index as usize].clone()); } } - (res, watch_outputs, broadcasted_local_revokable_script) + (claim_requests, watch_outputs, broadcasted_local_revokable_script) } /// Attempts to claim any claimable HTLCs in a commitment transaction which was not (yet) /// revoked using data in local_claimable_outpoints. /// Should not be used if check_spend_revoked_transaction succeeds. - fn check_spend_local_transaction(&mut self, tx: &Transaction, height: u32) -> (Vec, (Sha256dHash, Vec)) { + fn check_spend_local_transaction(&mut self, tx: &Transaction, height: u32) -> (Vec, (Sha256dHash, Vec)) { let commitment_txid = tx.txid(); - let mut local_txn = Vec::new(); + let mut claim_requests = Vec::new(); let mut watch_outputs = Vec::new(); macro_rules! wait_threshold_conf { @@ -1740,7 +1750,7 @@ impl ChannelMonitor { macro_rules! append_onchain_update { ($updates: expr) => { - local_txn.append(&mut $updates.0); + claim_requests = $updates.0; watch_outputs.append(&mut $updates.1); self.broadcasted_local_revokable_script = $updates.2; } @@ -1753,7 +1763,7 @@ impl ChannelMonitor { if local_tx.txid == commitment_txid { is_local_tx = true; log_trace!(self, "Got latest local commitment tx broadcast, searching for available HTLCs to claim"); - let mut res = self.broadcast_by_local_state(local_tx); + let mut res = self.broadcast_by_local_state(tx, local_tx); append_onchain_update!(res); } } @@ -1761,8 +1771,7 @@ impl ChannelMonitor { if local_tx.txid == commitment_txid { is_local_tx = true; log_trace!(self, "Got previous local commitment tx broadcast, searching for available HTLCs to claim"); - assert!(local_tx.tx.has_local_sig()); - let mut res = self.broadcast_by_local_state(local_tx); + let mut res = self.broadcast_by_local_state(tx, local_tx); append_onchain_update!(res); } } @@ -1788,7 +1797,7 @@ impl ChannelMonitor { } } - (local_txn, (commitment_txid, watch_outputs)) + (claim_requests, (commitment_txid, watch_outputs)) } /// Used by ChannelManager deserialization to broadcast the latest local state if its copy of @@ -1801,22 +1810,49 @@ impl ChannelMonitor { /// out-of-band the other node operator to coordinate with him if option is available to you. /// In any-case, choice is up to the user. pub fn get_latest_local_commitment_txn(&mut self) -> Vec { - // TODO: We should likely move all of the logic in here into OnChainTxHandler and unify it - // to ensure add_local_sig is only ever called once no matter what. This likely includes - // tracking state and panic!()ing if we get an update after force-closure/local-tx signing. log_trace!(self, "Getting signed latest local commitment transaction!"); - if let &mut Some(ref mut local_tx) = &mut self.current_local_signed_commitment_tx { - local_tx.tx.add_local_sig(&self.onchain_detection.keys.funding_key(), self.funding_redeemscript.as_ref().unwrap(), self.channel_value_satoshis.unwrap(), &self.secp_ctx); + if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_local_tx(self.channel_value_satoshis.unwrap()) { + let txid = commitment_tx.txid(); + let mut res = vec![commitment_tx]; + if let &Some(ref local_tx) = &self.current_local_signed_commitment_tx { + for htlc in local_tx.htlc_outputs.iter() { + if let Some(htlc_index) = htlc.0.transaction_output_index { + let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(*preimage) } else { None }; + if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(txid, htlc_index, preimage) { + res.push(htlc_tx); + } + } + } + // We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do. + // The data will be re-generated and tracked in check_spend_local_transaction if we get a confirmation. + } + return res } - if let &Some(ref local_tx) = &self.current_local_signed_commitment_tx { - let mut res = vec![local_tx.tx.with_valid_witness().clone()]; - res.append(&mut self.broadcast_by_local_state(local_tx).0); - // We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do. - // The data will be re-generated and tracked in check_spend_local_transaction if we get a confirmation. - res - } else { - Vec::new() + Vec::new() + } + + /// Unsafe test-only version of get_latest_local_commitment_txn used by our test framework + /// to bypass LocalCommitmentTransaction state update lockdown after signature and generate + /// revoked commitment transaction. + #[cfg(test)] + pub fn unsafe_get_latest_local_commitment_txn(&mut self) -> Vec { + log_trace!(self, "Getting signed copy of latest local commitment transaction!"); + if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_copy_local_tx(self.channel_value_satoshis.unwrap()) { + let txid = commitment_tx.txid(); + let mut res = vec![commitment_tx]; + if let &Some(ref local_tx) = &self.current_local_signed_commitment_tx { + for htlc in local_tx.htlc_outputs.iter() { + if let Some(htlc_index) = htlc.0.transaction_output_index { + let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(*preimage) } else { None }; + if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(txid, htlc_index, preimage) { + res.push(htlc_tx); + } + } + } + } + return res } + Vec::new() } /// Called by SimpleManyChannelMonitor::block_connected, which implements @@ -1855,14 +1891,11 @@ impl ChannelMonitor { watch_outputs.push(new_outputs); } if new_outpoints.is_empty() { - let (local_txn, new_outputs) = self.check_spend_local_transaction(&tx, height); - for tx in local_txn.iter() { - log_trace!(self, "Broadcast onchain {}", log_tx!(tx)); - broadcaster.broadcast_transaction(tx); - } + let (mut new_outpoints, new_outputs) = self.check_spend_local_transaction(&tx, height); if !new_outputs.1.is_empty() { watch_outputs.push(new_outputs); } + claimable_outpoints.append(&mut new_outpoints); } claimable_outpoints.append(&mut new_outpoints); } @@ -1886,22 +1919,17 @@ impl ChannelMonitor { let should_broadcast = if let Some(_) = self.current_local_signed_commitment_tx { self.would_broadcast_at_height(height) } else { false }; - if let Some(ref mut cur_local_tx) = self.current_local_signed_commitment_tx { - if should_broadcast { - cur_local_tx.tx.add_local_sig(&self.onchain_detection.keys.funding_key(), self.funding_redeemscript.as_ref().unwrap(), self.channel_value_satoshis.unwrap(), &self.secp_ctx); - } + if should_broadcast { + claimable_outpoints.push(ClaimRequest { absolute_timelock: height, aggregable: false, outpoint: BitcoinOutPoint { txid: self.onchain_detection.funding_info.as_ref().unwrap().0.txid.clone(), vout: self.onchain_detection.funding_info.as_ref().unwrap().0.index as u32 }, witness_data: InputMaterial::Funding { channel_value: self.channel_value_satoshis.unwrap() }}); } if let Some(ref cur_local_tx) = self.current_local_signed_commitment_tx { if should_broadcast { - log_trace!(self, "Broadcast onchain {}", log_tx!(cur_local_tx.tx.with_valid_witness())); - broadcaster.broadcast_transaction(&cur_local_tx.tx.with_valid_witness()); - let (txs, new_outputs, _) = self.broadcast_by_local_state(&cur_local_tx); - if !new_outputs.is_empty() { - watch_outputs.push((cur_local_tx.txid.clone(), new_outputs)); - } - for tx in txs { - log_trace!(self, "Broadcast onchain {}", log_tx!(tx)); - broadcaster.broadcast_transaction(&tx); + if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_local_tx(self.channel_value_satoshis.unwrap()) { + let (mut new_outpoints, new_outputs, _) = self.broadcast_by_local_state(&commitment_tx, cur_local_tx); + if !new_outputs.is_empty() { + watch_outputs.push((cur_local_tx.txid.clone(), new_outputs)); + } + claimable_outpoints.append(&mut new_outpoints); } } } @@ -2352,7 +2380,7 @@ impl ReadableArgs> for (Sha256dH macro_rules! read_local_tx { () => { { - let tx = ::read(reader)?; + let txid = Readable::read(reader)?; let revocation_key = Readable::read(reader)?; let a_htlc_key = Readable::read(reader)?; let b_htlc_key = Readable::read(reader)?; @@ -2373,8 +2401,8 @@ impl ReadableArgs> for (Sha256dH } LocalSignedTx { - txid: tx.txid(), - tx, revocation_key, a_htlc_key, b_htlc_key, delayed_payment_key, per_commitment_point, feerate_per_kw, + txid, + revocation_key, a_htlc_key, b_htlc_key, delayed_payment_key, per_commitment_point, feerate_per_kw, htlc_outputs: htlcs } } @@ -2398,6 +2426,7 @@ impl ReadableArgs> for (Sha256dH }; let current_remote_commitment_number = ::read(reader)?.0; + let current_local_commitment_number = ::read(reader)?.0; let payment_preimages_len: u64 = Readable::read(reader)?; let mut payment_preimages = HashMap::with_capacity(cmp::min(payment_preimages_len as usize, MAX_ALLOC_SIZE / 32)); @@ -2468,6 +2497,8 @@ impl ReadableArgs> for (Sha256dH } let onchain_tx_handler = ReadableArgs::read(reader, logger.clone())?; + let lockdown_from_offchain = Readable::read(reader)?; + Ok((last_block_hash.clone(), ChannelMonitor { latest_update_id, commitment_transaction_number_obscure_factor, @@ -2495,6 +2526,7 @@ impl ReadableArgs> for (Sha256dH prev_local_signed_commitment_tx, current_local_signed_commitment_tx, current_remote_commitment_number, + current_local_commitment_number, payment_preimages, pending_htlcs_updated, @@ -2505,6 +2537,8 @@ impl ReadableArgs> for (Sha256dH onchain_tx_handler, + lockdown_from_offchain, + last_block_hash, secp_ctx: Secp256k1::new(), logger, diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 95358208..cf0dc183 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -264,7 +264,7 @@ macro_rules! get_local_commitment_txn { let mut commitment_txn = None; for (funding_txo, monitor) in monitors.iter_mut() { if funding_txo.to_channel_id() == $channel_id { - commitment_txn = Some(monitor.get_latest_local_commitment_txn()); + commitment_txn = Some(monitor.unsafe_get_latest_local_commitment_txn()); break; } } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 8c0f0572..1be967d9 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -4,10 +4,12 @@ use chain::transaction::OutPoint; use chain::keysinterface::{ChannelKeys, KeysInterface, SpendableOutputDescriptor}; +use chain::chaininterface; use chain::chaininterface::{ChainListener, ChainWatchInterfaceUtil, BlockNotifier}; use ln::channel::{COMMITMENT_TX_BASE_WEIGHT, COMMITMENT_TX_WEIGHT_PER_HTLC}; use ln::channelmanager::{ChannelManager,ChannelManagerReadArgs,HTLCForwardInfo,RAACommitmentOrder, PaymentPreimage, PaymentHash, PaymentSecret, PaymentSendFailure, BREAKDOWN_TIMEOUT}; use ln::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ManyChannelMonitor, ANTI_REORG_DELAY}; +use ln::channelmonitor; use ln::channel::{Channel, ChannelError}; use ln::{chan_utils, onion_utils}; use ln::router::{Route, RouteHop}; @@ -2335,48 +2337,32 @@ fn claim_htlc_outputs_single_tx() { } let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(node_txn.len(), 21); + assert_eq!(node_txn.len(), 9); // ChannelMonitor: justice tx revoked offered htlc, justice tx revoked received htlc, justice tx revoked to_local (3) // ChannelManager: local commmitment + local HTLC-timeout (2) - // ChannelMonitor: bumped justice tx (4), after one increase, bumps on HTLC aren't generated not being substantial anymore - // ChannelMonito r: local commitment + local HTLC-timeout (14) - - assert_eq!(node_txn[0], node_txn[5]); - assert_eq!(node_txn[0], node_txn[7]); - assert_eq!(node_txn[0], node_txn[9]); - assert_eq!(node_txn[0], node_txn[13]); - assert_eq!(node_txn[0], node_txn[15]); - assert_eq!(node_txn[0], node_txn[17]); - assert_eq!(node_txn[0], node_txn[19]); - - assert_eq!(node_txn[1], node_txn[6]); - assert_eq!(node_txn[1], node_txn[8]); - assert_eq!(node_txn[1], node_txn[10]); - assert_eq!(node_txn[1], node_txn[14]); - assert_eq!(node_txn[1], node_txn[16]); - assert_eq!(node_txn[1], node_txn[18]); - assert_eq!(node_txn[1], node_txn[20]); - - - // Check the pair local commitment and HTLC-timeout broadcast due to HTLC expiration and present 8 times (rebroadcast at every block from 200 to 206) - assert_eq!(node_txn[0].input.len(), 1); - check_spends!(node_txn[0], chan_1.3); - assert_eq!(node_txn[1].input.len(), 1); - let witness_script = node_txn[1].input[0].witness.last().unwrap(); - assert_eq!(witness_script.len(), OFFERED_HTLC_SCRIPT_WEIGHT); //Spending an offered htlc output - check_spends!(node_txn[1], node_txn[0]); + // ChannelMonitor: bumped justice tx, after one increase, bumps on HTLC aren't generated not being substantial anymore, bump on revoked to_local isn't generated due to more room for expiration (2) + // ChannelMonitor: local commitment + local HTLC-timeout (2) - // Justice transactions are indices 2-3-4 + // Check the pair local commitment and HTLC-timeout broadcast due to HTLC expiration assert_eq!(node_txn[2].input.len(), 1); + check_spends!(node_txn[2], chan_1.3); assert_eq!(node_txn[3].input.len(), 1); + let witness_script = node_txn[3].input[0].witness.last().unwrap(); + assert_eq!(witness_script.len(), OFFERED_HTLC_SCRIPT_WEIGHT); //Spending an offered htlc output + check_spends!(node_txn[3], node_txn[2]); + + // Justice transactions are indices 1-2-4 + assert_eq!(node_txn[0].input.len(), 1); + assert_eq!(node_txn[1].input.len(), 1); assert_eq!(node_txn[4].input.len(), 1); - check_spends!(node_txn[2], revoked_local_txn[0]); - check_spends!(node_txn[3], revoked_local_txn[0]); + + check_spends!(node_txn[0], revoked_local_txn[0]); + check_spends!(node_txn[1], revoked_local_txn[0]); check_spends!(node_txn[4], revoked_local_txn[0]); let mut witness_lens = BTreeSet::new(); - witness_lens.insert(node_txn[2].input[0].witness.last().unwrap().len()); - witness_lens.insert(node_txn[3].input[0].witness.last().unwrap().len()); + witness_lens.insert(node_txn[0].input[0].witness.last().unwrap().len()); + witness_lens.insert(node_txn[1].input[0].witness.last().unwrap().len()); witness_lens.insert(node_txn[4].input[0].witness.last().unwrap().len()); assert_eq!(witness_lens.len(), 3); assert_eq!(*witness_lens.iter().skip(0).next().unwrap(), 77); // revoked to_local @@ -2438,12 +2424,10 @@ fn test_htlc_on_chain_success() { nodes[2].block_notifier.block_connected(&Block { header, txdata: vec![commitment_tx[0].clone()]}, 1); check_closed_broadcast!(nodes[2], false); check_added_monitors!(nodes[2], 1); - let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 3 (commitment tx, 2*htlc-success tx), ChannelMonitor : 4 (2*2 * HTLC-Success tx) - assert_eq!(node_txn.len(), 7); + let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 3 (commitment tx, 2*htlc-success tx), ChannelMonitor : 2 (2 * HTLC-Success tx) + assert_eq!(node_txn.len(), 5); assert_eq!(node_txn[0], node_txn[3]); assert_eq!(node_txn[1], node_txn[4]); - assert_eq!(node_txn[0], node_txn[5]); - assert_eq!(node_txn[1], node_txn[6]); assert_eq!(node_txn[2], commitment_tx[0]); check_spends!(node_txn[0], commitment_tx[0]); check_spends!(node_txn[1], commitment_tx[0]); @@ -2488,15 +2472,11 @@ fn test_htlc_on_chain_success() { macro_rules! check_tx_local_broadcast { ($node: expr, $htlc_offered: expr, $commitment_tx: expr, $chan_tx: expr) => { { let mut node_txn = $node.tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(node_txn.len(), if $htlc_offered { 7 } else { 5 }); + assert_eq!(node_txn.len(), 5); // Node[1]: ChannelManager: 3 (commitment tx, 2*HTLC-Timeout tx), ChannelMonitor: 2 (timeout tx) - // Node[0]: ChannelManager: 3 (commtiemtn tx, 2*HTLC-Timeout tx), ChannelMonitor: 2 HTLC-timeout * 2 (block-rescan) + // Node[0]: ChannelManager: 3 (commtiemtn tx, 2*HTLC-Timeout tx), ChannelMonitor: 2 HTLC-timeout check_spends!(node_txn[0], $commitment_tx); check_spends!(node_txn[1], $commitment_tx); - if $htlc_offered { - assert_eq!(node_txn[0], node_txn[5]); - assert_eq!(node_txn[1], node_txn[6]); - } assert_ne!(node_txn[0].lock_time, 0); assert_ne!(node_txn[1].lock_time, 0); if $htlc_offered { @@ -2633,21 +2613,19 @@ fn test_htlc_on_chain_timeout() { let timeout_tx; { let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(node_txn.len(), 7); // ChannelManager : 2 (commitment tx, HTLC-Timeout tx), ChannelMonitor : (local commitment tx + HTLC-timeout) * 2 (block-rescan), timeout tx - assert_eq!(node_txn[0], node_txn[3]); - assert_eq!(node_txn[0], node_txn[5]); - assert_eq!(node_txn[1], node_txn[4]); - assert_eq!(node_txn[1], node_txn[6]); + assert_eq!(node_txn.len(), 5); // ChannelManager : 2 (commitment tx, HTLC-Timeout tx), ChannelMonitor : 2 (local commitment tx + HTLC-timeout), 1 timeout tx + assert_eq!(node_txn[1], node_txn[3]); + assert_eq!(node_txn[2], node_txn[4]); - check_spends!(node_txn[2], commitment_tx[0]); - assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT); + check_spends!(node_txn[0], commitment_tx[0]); + assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT); - check_spends!(node_txn[0], chan_2.3); - check_spends!(node_txn[1], node_txn[0]); - assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), 71); - assert_eq!(node_txn[1].clone().input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT); + check_spends!(node_txn[1], chan_2.3); + check_spends!(node_txn[2], node_txn[1]); + assert_eq!(node_txn[1].clone().input[0].witness.last().unwrap().len(), 71); + assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT); - timeout_tx = node_txn[2].clone(); + timeout_tx = node_txn[0].clone(); node_txn.clear(); } @@ -4377,8 +4355,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() { check_added_monitors!(nodes[0], 1); let revoked_htlc_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(revoked_htlc_txn.len(), 3); - assert_eq!(revoked_htlc_txn[0], revoked_htlc_txn[2]); + assert_eq!(revoked_htlc_txn.len(), 2); assert_eq!(revoked_htlc_txn[0].input.len(), 1); assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT); check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]); @@ -4434,8 +4411,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() { check_added_monitors!(nodes[1], 1); let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(revoked_htlc_txn.len(), 3); - assert_eq!(revoked_htlc_txn[0], revoked_htlc_txn[2]); + assert_eq!(revoked_htlc_txn.len(), 2); assert_eq!(revoked_htlc_txn[0].input.len(), 1); assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT); check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]); @@ -4504,9 +4480,8 @@ fn test_onchain_to_onchain_claim() { check_added_monitors!(nodes[2], 1); let c_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 2 (commitment tx, HTLC-Success tx), ChannelMonitor : 1 (HTLC-Success tx) - assert_eq!(c_txn.len(), 4); + assert_eq!(c_txn.len(), 3); assert_eq!(c_txn[0], c_txn[2]); - assert_eq!(c_txn[0], c_txn[3]); assert_eq!(commitment_tx[0], c_txn[1]); check_spends!(c_txn[1], chan_2.3); check_spends!(c_txn[2], c_txn[1]); @@ -4622,11 +4597,11 @@ fn test_duplicate_payment_hash_one_failure_one_success() { _ => panic!("Unexepected event"), } let htlc_success_txn: Vec<_> = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); - assert_eq!(htlc_success_txn.len(), 7); + assert_eq!(htlc_success_txn.len(), 5); // ChannelMonitor: HTLC-Success txn (*2 due to 2-HTLC outputs), ChannelManager: local commitment tx + HTLC-Success txn (*2 due to 2-HTLC outputs) check_spends!(htlc_success_txn[2], chan_2.3); check_spends!(htlc_success_txn[3], htlc_success_txn[2]); check_spends!(htlc_success_txn[4], htlc_success_txn[2]); - assert_eq!(htlc_success_txn[0], htlc_success_txn[5]); + assert_eq!(htlc_success_txn[0], htlc_success_txn[3]); assert_eq!(htlc_success_txn[0].input.len(), 1); assert_eq!(htlc_success_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT); assert_eq!(htlc_success_txn[1], htlc_success_txn[4]); @@ -7142,7 +7117,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() { check_added_monitors!(nodes[1], 1); let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(revoked_htlc_txn.len(), 6); + assert_eq!(revoked_htlc_txn.len(), 4); if revoked_htlc_txn[0].input[0].witness.last().unwrap().len() == ACCEPTED_HTLC_SCRIPT_WEIGHT { assert_eq!(revoked_htlc_txn[0].input.len(), 1); check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]); @@ -7608,3 +7583,64 @@ fn test_simple_mpp() { // ...but with the right secret we should be able to claim all the way back claim_payment_along_route_with_secret(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_preimage, Some(payment_secret), 200_000); } + +#[test] +fn test_update_err_monitor_lockdown() { + // Our monitor will lock update of local commitment transaction if a broadcastion condition + // has been fulfilled (either force-close from Channel or block height requiring a HTLC- + // timeout). Trying to update monitor after lockdown should return a ChannelMonitorUpdateErr. + // + // This scenario may happen in a watchtower setup, where watchtower process a block height + // triggering a timeout while a slow-block-processing ChannelManager receives a local signed + // commitment at same time. + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Create some initial channel + let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::supported(), InitFeatures::supported()); + let outpoint = OutPoint { txid: chan_1.3.txid(), index: 0 }; + + // Rebalance the network to generate htlc in the two directions + send_payment(&nodes[0], &vec!(&nodes[1])[..], 10_000_000, 10_000_000); + + // Route a HTLC from node 0 to node 1 (but don't settle) + let preimage = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9_000_000).0; + + // Copy SimpleManyChannelMonitor to simulate a watchtower and update block height of node 0 until its ChannelMonitor timeout HTLC onchain + let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", 0))); + let watchtower = { + let monitors = nodes[0].chan_monitor.simple_monitor.monitors.lock().unwrap(); + let monitor = monitors.get(&outpoint).unwrap(); + let mut w = test_utils::TestVecWriter(Vec::new()); + monitor.write_for_disk(&mut w).unwrap(); + let new_monitor = <(Sha256dHash, channelmonitor::ChannelMonitor)>::read( + &mut ::std::io::Cursor::new(&w.0), Arc::new(test_utils::TestLogger::new())).unwrap().1; + assert!(new_monitor == *monitor); + let chain_monitor = Arc::new(chaininterface::ChainWatchInterfaceUtil::new(Network::Testnet, logger.clone() as Arc)); + let watchtower = test_utils::TestChannelMonitor::new(chain_monitor, &chanmon_cfgs[0].tx_broadcaster, logger.clone(), &chanmon_cfgs[0].fee_estimator); + assert!(watchtower.add_monitor(outpoint, new_monitor).is_ok()); + watchtower + }; + let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }; + watchtower.simple_monitor.block_connected(&header, 200, &vec![], &vec![]); + + // Try to update ChannelMonitor + assert!(nodes[1].node.claim_funds(preimage, &None, 9_000_000)); + check_added_monitors!(nodes[1], 1); + let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + assert_eq!(updates.update_fulfill_htlcs.len(), 1); + nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]); + if let Some(ref mut channel) = nodes[0].node.channel_state.lock().unwrap().by_id.get_mut(&chan_1.2) { + if let Ok((_, _, _, update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].fee_estimator) { + if let Err(_) = watchtower.simple_monitor.update_monitor(outpoint, update.clone()) {} else { assert!(false); } + if let Ok(_) = nodes[0].chan_monitor.update_monitor(outpoint, update) {} else { assert!(false); } + } else { assert!(false); } + } else { assert!(false); }; + // Our local monitor is in-sync and hasn't processed yet timeout + check_added_monitors!(nodes[0], 1); + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); +} diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index 40d9f18a..2f08fe29 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -15,14 +15,15 @@ use secp256k1; use ln::msgs::DecodeError; use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest}; -use ln::chan_utils::HTLCType; +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; 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; @@ -52,7 +53,7 @@ enum OnchainEvent { pub struct ClaimTxBumpMaterial { // At every block tick, used to check if pending claiming tx is taking too // much time for confirmation and we need to bump it. - height_timer: u32, + height_timer: Option, // Tracked in case of reorg to wipe out now-superflous bump material feerate_previous: u64, // Soonest timelocks among set of outpoints claimed, used to compute @@ -64,7 +65,7 @@ pub struct ClaimTxBumpMaterial { impl Writeable for ClaimTxBumpMaterial { fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { - writer.write_all(&byte_utils::be32_to_array(self.height_timer))?; + 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.soonest_timelock))?; writer.write_all(&byte_utils::be64_to_array(self.per_input_material.len() as u64))?; @@ -141,6 +142,10 @@ macro_rules! subtract_high_prio_fee { /// do RBF bumping if possible. pub struct OnchainTxHandler { destination_script: Script, + funding_redeemscript: Script, + local_commitment: Option, + prev_local_commitment: Option, + local_csv: u16, key_storage: ChanSigner, @@ -180,6 +185,11 @@ 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.prev_local_commitment.write(writer)?; + + self.local_csv.write(writer)?; self.key_storage.write(writer)?; @@ -221,6 +231,12 @@ 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 prev_local_commitment = Readable::read(reader)?; + + let local_csv = Readable::read(reader)?; let key_storage = Readable::read(reader)?; @@ -269,6 +285,10 @@ impl ReadableArgs> for OnchainTx Ok(OnchainTxHandler { destination_script, + funding_redeemscript, + local_commitment, + prev_local_commitment, + local_csv, key_storage, claimable_outpoints, pending_claim_requests, @@ -280,12 +300,16 @@ impl ReadableArgs> for OnchainTx } impl OnchainTxHandler { - pub(super) fn new(destination_script: Script, keys: ChanSigner, 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; OnchainTxHandler { destination_script, + funding_redeemscript, + local_commitment: None, + prev_local_commitment: None, + local_csv, key_storage, pending_claim_requests: HashMap::new(), claimable_outpoints: HashMap::new(), @@ -342,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<(u32, 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 @@ -407,9 +431,10 @@ impl OnchainTxHandler { // Compute new height timer to decide when we need to regenerate a new bumped version of the claim tx (if we // didn't receive confirmation of it before, or not enough reorg-safe depth on top of it). - let new_timer = Self::get_height_timer(height, cached_claim_datas.soonest_timelock); + let new_timer = Some(Self::get_height_timer(height, cached_claim_datas.soonest_timelock)); let mut inputs_witnesses_weight = 0; let mut amt = 0; + let mut dynamic_fee = true; for per_outp_material in cached_claim_datas.per_input_material.values() { match per_outp_material { &InputMaterial::Revoked { ref witness_script, ref is_htlc, ref amount, .. } => { @@ -420,72 +445,113 @@ impl OnchainTxHandler { inputs_witnesses_weight += Self::get_witnesses_weight(if preimage.is_some() { &[InputDescriptors::OfferedHTLC] } else { &[InputDescriptors::ReceivedHTLC] }); amt += *amount; }, - &InputMaterial::LocalHTLC { .. } => { return None; } - } - } - - let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight; - 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 new computed fee is superior at the whole claimable amount burn all in fees - if new_fee > amt { - bumped_tx.output[0].value = 0; - } else { - bumped_tx.output[0].value = amt - new_fee; + &InputMaterial::LocalHTLC { .. } => { + dynamic_fee = false; + }, + &InputMaterial::Funding { .. } => { + dynamic_fee = false; } - new_feerate = feerate; - } else { return None; } - } else { - if subtract_high_prio_fee!(self, fee_estimator, amt, predicted_weight, new_feerate) { - bumped_tx.output[0].value = amt; - } else { return None; } + } } - assert!(new_feerate != 0); - - for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() { - match per_outp_material { - &InputMaterial::Revoked { ref witness_script, ref pubkey, ref key, ref is_htlc, ref amount } => { - let sighash_parts = bip143::SighashComponents::new(&bumped_tx); - let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]); - let sig = self.secp_ctx.sign(&sighash, &key); - bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); - bumped_tx.input[i].witness[0].push(SigHashType::All as u8); - if *is_htlc { - bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec()); + if dynamic_fee { + let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight; + 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 new computed fee is superior at the whole claimable amount burn all in fees + if new_fee > amt { + bumped_tx.output[0].value = 0; } else { - bumped_tx.input[i].witness.push(vec!(1)); + bumped_tx.output[0].value = amt - new_fee; } - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); - log_trace!(self, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if !is_htlc { "to_local" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::OfferedHTLC) { "offered" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::AcceptedHTLC) { "received" } else { "" }, outp.vout, outp.txid, new_feerate); - }, - &InputMaterial::RemoteHTLC { ref witness_script, ref key, ref preimage, ref amount, ref locktime } => { - if !preimage.is_some() { bumped_tx.lock_time = *locktime }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation - let sighash_parts = bip143::SighashComponents::new(&bumped_tx); - let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]); - let sig = self.secp_ctx.sign(&sighash, &key); - bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); - bumped_tx.input[i].witness[0].push(SigHashType::All as u8); - if let &Some(preimage) = preimage { - bumped_tx.input[i].witness.push(preimage.clone().0.to_vec()); - } else { - bumped_tx.input[i].witness.push(vec![]); + new_feerate = feerate; + } else { return None; } + } else { + if subtract_high_prio_fee!(self, fee_estimator, amt, predicted_weight, new_feerate) { + bumped_tx.output[0].value = amt; + } else { return None; } + } + assert!(new_feerate != 0); + + for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() { + match per_outp_material { + &InputMaterial::Revoked { ref witness_script, ref pubkey, ref key, ref is_htlc, ref amount } => { + let sighash_parts = bip143::SighashComponents::new(&bumped_tx); + let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]); + let sig = self.secp_ctx.sign(&sighash, &key); + bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[i].witness[0].push(SigHashType::All as u8); + if *is_htlc { + bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec()); + } else { + bumped_tx.input[i].witness.push(vec!(1)); + } + bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + log_trace!(self, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if !is_htlc { "to_local" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::OfferedHTLC) { "offered" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::AcceptedHTLC) { "received" } else { "" }, outp.vout, outp.txid, new_feerate); + }, + &InputMaterial::RemoteHTLC { ref witness_script, ref key, ref preimage, ref amount, ref locktime } => { + if !preimage.is_some() { bumped_tx.lock_time = *locktime }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation + let sighash_parts = bip143::SighashComponents::new(&bumped_tx); + let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]); + let sig = self.secp_ctx.sign(&sighash, &key); + bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[i].witness[0].push(SigHashType::All as u8); + if let &Some(preimage) = preimage { + bumped_tx.input[i].witness.push(preimage.clone().0.to_vec()); + } else { + bumped_tx.input[i].witness.push(vec![]); + } + bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + log_trace!(self, "Going to broadcast Claim Transaction {} claiming remote {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate); + }, + _ => unreachable!() + } + } + log_trace!(self, "...with timer {}", new_timer.unwrap()); + assert!(predicted_weight >= bumped_tx.get_weight()); + return Some((new_timer, new_feerate, bumped_tx)) + } 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 } => { + 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 } => { + 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)); } - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); - log_trace!(self, "Going to broadcast Claim Transaction {} claiming remote {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate); - }, - &InputMaterial::LocalHTLC { .. } => { - //TODO : Given that Local Commitment Transaction and HTLC-Timeout/HTLC-Success are counter-signed by peer, we can't - // RBF them. Need a Lightning specs change and package relay modification : - // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html - return None; + _ => unreachable!() } } } - log_trace!(self, "...with timer {}", new_timer); - assert!(predicted_weight >= bumped_tx.get_weight()); - Some((new_timer, new_feerate, bumped_tx)) + None } pub(super) fn block_connected(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec, height: u32, broadcaster: B, fee_estimator: F) @@ -520,7 +586,7 @@ 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: 0, feerate_previous: 0, soonest_timelock: claim.0, per_input_material: claim.1.clone() }; + let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock: claim.0, per_input_material: claim.1.clone() }; if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) { claim_material.height_timer = new_timer; claim_material.feerate_previous = new_feerate; @@ -535,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(); @@ -592,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 @@ -638,27 +704,27 @@ impl OnchainTxHandler { // Check if any pending claim request must be rescheduled for (first_claim_txid, ref claim_data) in self.pending_claim_requests.iter() { - if claim_data.height_timer == height { - bump_candidates.insert(*first_claim_txid); + if let Some(h) = claim_data.height_timer { + if h == height { + 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!(); } - } { - 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!(); } + let mut pending_claim_updates = Vec::with_capacity(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) { + log_trace!(self, "Broadcast onchain {}", log_tx!(bump_tx)); + broadcaster.broadcast_transaction(&bump_tx); + pending_claim_updates.push((*first_claim_txid, new_timer, new_feerate)); + } + } + for updates in pending_claim_updates { + if let Some(claim_material) = self.pending_claim_requests.get_mut(&updates.0) { + claim_material.height_timer = updates.1; + claim_material.feerate_previous = updates.2; } } } @@ -709,4 +775,51 @@ impl OnchainTxHandler { self.pending_claim_requests.remove(&req); } } + + 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 let Some(ref local_commitment) = self.local_commitment { + if local_commitment.has_local_sig() { return Err(()) } + } + self.prev_local_commitment = self.local_commitment.take(); + self.local_commitment = Some(tx); + 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 + } } diff --git a/lightning/src/util/enforcing_trait_impls.rs b/lightning/src/util/enforcing_trait_impls.rs index 26c3a077..c48424f9 100644 --- a/lightning/src/util/enforcing_trait_impls.rs +++ b/lightning/src/util/enforcing_trait_impls.rs @@ -1,4 +1,5 @@ -use ln::chan_utils::{HTLCOutputInCommitment, TxCreationKeys, ChannelPublicKeys}; +use ln::chan_utils::{HTLCOutputInCommitment, TxCreationKeys, ChannelPublicKeys, LocalCommitmentTransaction}; +use ln::channelmanager::PaymentPreimage; use ln::msgs; use chain::keysinterface::{ChannelKeys, InMemoryChannelKeys}; @@ -6,6 +7,7 @@ use std::cmp; use std::sync::{Mutex, Arc}; use bitcoin::blockdata::transaction::Transaction; +use bitcoin::blockdata::script::Script; use secp256k1; use secp256k1::key::{SecretKey, PublicKey}; @@ -78,6 +80,19 @@ impl ChannelKeys for EnforcingChannelKeys { Ok(self.inner.sign_remote_commitment(feerate_per_kw, commitment_tx, keys, htlcs, to_self_delay, secp_ctx).unwrap()) } + fn sign_local_commitment(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1) { + self.inner.sign_local_commitment(local_commitment_tx, funding_redeemscript, channel_value_satoshis, secp_ctx) + } + + #[cfg(test)] + fn unsafe_sign_local_commitment(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1) { + self.inner.unsafe_sign_local_commitment(local_commitment_tx, funding_redeemscript, channel_value_satoshis, secp_ctx); + } + + fn sign_htlc_transaction(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option, local_csv: u16, secp_ctx: &Secp256k1) { + self.inner.sign_htlc_transaction(local_commitment_tx, htlc_index, preimage, local_csv, secp_ctx); + } + fn sign_closing_transaction(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1) -> Result { Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap()) }