diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 16a02b54a3b..7d698a22067 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -94,6 +94,17 @@ impl MonitorUpdateId { /// [`ChannelMonitorUpdateStatus::PermanentFailure`], in which case the channel will likely be /// closed without broadcasting the latest state. See /// [`ChannelMonitorUpdateStatus::PermanentFailure`] for more details. +/// +/// Third-party watchtowers may be built as a part of an implementation of this trait, with the +/// advantage that you can control whether to resume channel operation depending on if an update +/// has been persisted to a watchtower. For this, you may find the following methods useful: +/// [`ChannelMonitor::initial_counterparty_commitment_tx`], +/// [`ChannelMonitor::counterparty_commitment_txs_from_update`], +/// [`ChannelMonitor::sign_to_local_justice_tx`], [`TrustedCommitmentTransaction::revokeable_output_index`], +/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]. +/// +/// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index +/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]: crate::ln::chan_utils::TrustedCommitmentTransaction::build_to_local_justice_tx pub trait Persist { /// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is /// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup. diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 4d3b4d53ad7..da3970da8eb 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -31,12 +31,13 @@ use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use bitcoin::secp256k1; +use bitcoin::{secp256k1, EcdsaSighashType}; +use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::{PaymentHash, PaymentPreimage}; use crate::ln::msgs::DecodeError; use crate::ln::chan_utils; -use crate::ln::chan_utils::{CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCClaim, ChannelTransactionParameters, HolderCommitmentTransaction}; +use crate::ln::chan_utils::{CommitmentTransaction, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCClaim, ChannelTransactionParameters, HolderCommitmentTransaction, TxCreationKeys}; use crate::ln::channelmanager::{HTLCSource, SentHTLCId}; use crate::chain; use crate::chain::{BestBlock, WatchedOutput}; @@ -502,6 +503,9 @@ pub(crate) enum ChannelMonitorUpdateStep { htlc_outputs: Vec<(HTLCOutputInCommitment, Option>)>, commitment_number: u64, their_per_commitment_point: PublicKey, + feerate_per_kw: Option, + to_broadcaster_value_sat: Option, + to_countersignatory_value_sat: Option, }, PaymentPreimage { payment_preimage: PaymentPreimage, @@ -544,8 +548,11 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep, }, (1, LatestCounterpartyCommitmentTXInfo) => { (0, commitment_txid, required), + (1, feerate_per_kw, option), (2, commitment_number, required), + (3, to_broadcaster_value_sat, option), (4, their_per_commitment_point, required), + (5, to_countersignatory_value_sat, option), (6, htlc_outputs, required_vec), }, (2, PaymentPreimage) => { @@ -882,6 +889,14 @@ pub(crate) struct ChannelMonitorImpl { /// The node_id of our counterparty counterparty_node_id: Option, + + /// Initial counterparty commmitment data needed to recreate the commitment tx + /// in the persistence pipeline for third-party watchtowers. This will only be present on + /// monitors created after 0.0.117. + /// + /// Ordering of tuple data: (their_per_commitment_point, feerate_per_kw, to_broadcaster_sats, + /// to_countersignatory_sats) + initial_counterparty_commitment_info: Option<(PublicKey, u32, u64, u64)>, } /// Transaction outputs to watch for on-chain spends. @@ -1072,6 +1087,7 @@ impl Writeable for ChannelMonitorImpl ChannelMonitor { best_block, counterparty_node_id: Some(counterparty_node_id), + initial_counterparty_commitment_info: None, }) } @@ -1230,11 +1247,31 @@ impl ChannelMonitor { self.inner.lock().unwrap().provide_secret(idx, secret) } + /// A variant of `Self::provide_latest_counterparty_commitment_tx` used to provide + /// additional information to the monitor to store in order to recreate the initial + /// counterparty commitment transaction during persistence (mainly for use in third-party + /// watchtowers). + /// + /// This is used to provide the counterparty commitment information directly to the monitor + /// before the initial persistence of a new channel. + pub(crate) fn provide_initial_counterparty_commitment_tx( + &self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option>)>, + commitment_number: u64, their_cur_per_commitment_point: PublicKey, feerate_per_kw: u32, + to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, logger: &L, + ) + where L::Target: Logger + { + self.inner.lock().unwrap().provide_initial_counterparty_commitment_tx(txid, + htlc_outputs, commitment_number, their_cur_per_commitment_point, feerate_per_kw, + to_broadcaster_value_sat, to_countersignatory_value_sat, logger); + } + /// Informs this monitor of the latest counterparty (ie non-broadcastable) commitment transaction. /// The monitor watches for it to be broadcasted and then uses the HTLC information (and /// possibly future revocation/preimage information) to claim outputs where possible. /// We cache also the mapping hash:commitment number to lighten pruning of old preimages by watchtowers. - pub(crate) fn provide_latest_counterparty_commitment_tx( + #[cfg(test)] + fn provide_latest_counterparty_commitment_tx( &self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option>)>, @@ -1370,6 +1407,67 @@ impl ChannelMonitor { ret } + /// Gets the counterparty's initial commitment transaction. The returned commitment + /// transaction is unsigned. This is intended to be called during the initial persistence of + /// the monitor (inside an implementation of [`Persist::persist_new_channel`]), to allow for + /// watchtowers in the persistence pipeline to have enough data to form justice transactions. + /// + /// This is similar to [`Self::counterparty_commitment_txs_from_update`], except + /// that for the initial commitment transaction, we don't have a corresponding update. + /// + /// This will only return `Some` for channel monitors that have been created after upgrading + /// to LDK 0.0.117+. + /// + /// [`Persist::persist_new_channel`]: crate::chain::chainmonitor::Persist::persist_new_channel + pub fn initial_counterparty_commitment_tx(&self) -> Option { + self.inner.lock().unwrap().initial_counterparty_commitment_tx() + } + + /// Gets all of the counterparty commitment transactions provided by the given update. This + /// may be empty if the update doesn't include any new counterparty commitments. Returned + /// commitment transactions are unsigned. + /// + /// This is provided so that watchtower clients in the persistence pipeline are able to build + /// justice transactions for each counterparty commitment upon each update. It's intended to be + /// used within an implementation of [`Persist::update_persisted_channel`], which is provided + /// with a monitor and an update. Once revoked, signing a justice transaction can be done using + /// [`Self::sign_to_local_justice_tx`]. + /// + /// It is expected that a watchtower client may use this method to retrieve the latest counterparty + /// commitment transaction(s), and then hold the necessary data until a later update in which + /// the monitor has been updated with the corresponding revocation data, at which point the + /// monitor can sign the justice transaction. + /// + /// This will only return a non-empty list for monitor updates that have been created after + /// upgrading to LDK 0.0.117+. Note that no restriction lies on the monitors themselves, which + /// may have been created prior to upgrading. + /// + /// [`Persist::update_persisted_channel`]: crate::chain::chainmonitor::Persist::update_persisted_channel + pub fn counterparty_commitment_txs_from_update(&self, update: &ChannelMonitorUpdate) -> Vec { + self.inner.lock().unwrap().counterparty_commitment_txs_from_update(update) + } + + /// Wrapper around [`EcdsaChannelSigner::sign_justice_revoked_output`] to make + /// signing the justice transaction easier for implementors of + /// [`chain::chainmonitor::Persist`]. On success this method returns the provided transaction + /// signing the input at `input_idx`. This method will only produce a valid signature for + /// a transaction spending the `to_local` output of a commitment transaction, i.e. this cannot + /// be used for revoked HTLC outputs. + /// + /// `Value` is the value of the output being spent by the input at `input_idx`, committed + /// in the BIP 143 signature. + /// + /// This method will only succeed if this monitor has received the revocation secret for the + /// provided `commitment_number`. If a commitment number is provided that does not correspond + /// to the commitment transaction being revoked, this will return a signed transaction, but + /// the signature will not be valid. + /// + /// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::EcdsaChannelSigner::sign_justice_revoked_output + /// [`Persist`]: crate::chain::chainmonitor::Persist + pub fn sign_to_local_justice_tx(&self, justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64) -> Result { + self.inner.lock().unwrap().sign_to_local_justice_tx(justice_tx, input_idx, value, commitment_number) + } + pub(crate) fn get_min_seen_secret(&self) -> u64 { self.inner.lock().unwrap().get_min_seen_secret() } @@ -2226,6 +2324,25 @@ impl ChannelMonitorImpl { Ok(()) } + pub(crate) fn provide_initial_counterparty_commitment_tx( + &mut self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option>)>, + commitment_number: u64, their_per_commitment_point: PublicKey, feerate_per_kw: u32, + to_broadcaster_value: u64, to_countersignatory_value: u64, logger: &L + ) + where L::Target: Logger + { + self.initial_counterparty_commitment_info = Some((their_per_commitment_point.clone(), + feerate_per_kw, to_broadcaster_value, to_countersignatory_value)); + + #[cfg(debug_assertions)] { + let rebuilt_commitment_tx = self.initial_counterparty_commitment_tx().unwrap(); + debug_assert_eq!(rebuilt_commitment_tx.trust().txid(), txid); + } + + self.provide_latest_counterparty_commitment_tx(txid, htlc_outputs, commitment_number, + their_per_commitment_point, logger); + } + pub(crate) fn provide_latest_counterparty_commitment_tx(&mut self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option>)>, commitment_number: u64, their_per_commitment_point: PublicKey, logger: &L) where L::Target: Logger { // TODO: Encrypt the htlc_outputs data with the single-hash of the commitment transaction // so that a remote monitor doesn't learn anything unless there is a malicious close. @@ -2471,7 +2588,7 @@ impl ChannelMonitorImpl { ret = Err(()); } } - ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, htlc_outputs, commitment_number, their_per_commitment_point } => { + ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, htlc_outputs, commitment_number, their_per_commitment_point, .. } => { log_trace!(logger, "Updating ChannelMonitor with latest counterparty commitment transaction info"); self.provide_latest_counterparty_commitment_tx(*commitment_txid, htlc_outputs.clone(), *commitment_number, *their_per_commitment_point, logger) }, @@ -2543,6 +2660,10 @@ impl ChannelMonitorImpl { } } + #[cfg(debug_assertions)] { + self.counterparty_commitment_txs_from_update(updates); + } + // If the updates succeeded and we were in an already closed channel state, then there's no // need to refuse any updates we expect to receive afer seeing a confirmed commitment. if ret.is_ok() && updates.update_id == CLOSED_CHANNEL_UPDATE_ID && self.latest_update_id == updates.update_id { @@ -2651,6 +2772,91 @@ impl ChannelMonitorImpl { ret } + pub(crate) fn initial_counterparty_commitment_tx(&mut self) -> Option { + let (their_per_commitment_point, feerate_per_kw, to_broadcaster_value, + to_countersignatory_value) = self.initial_counterparty_commitment_info?; + let htlc_outputs = vec![]; + + let commitment_tx = self.build_counterparty_commitment_tx(INITIAL_COMMITMENT_NUMBER, + &their_per_commitment_point, to_broadcaster_value, to_countersignatory_value, + feerate_per_kw, htlc_outputs); + Some(commitment_tx) + } + + fn build_counterparty_commitment_tx( + &self, commitment_number: u64, their_per_commitment_point: &PublicKey, + to_broadcaster_value: u64, to_countersignatory_value: u64, feerate_per_kw: u32, + mut nondust_htlcs: Vec<(HTLCOutputInCommitment, Option>)> + ) -> CommitmentTransaction { + let broadcaster_keys = &self.onchain_tx_handler.channel_transaction_parameters + .counterparty_parameters.as_ref().unwrap().pubkeys; + let countersignatory_keys = + &self.onchain_tx_handler.channel_transaction_parameters.holder_pubkeys; + + let broadcaster_funding_key = broadcaster_keys.funding_pubkey; + let countersignatory_funding_key = countersignatory_keys.funding_pubkey; + let keys = TxCreationKeys::from_channel_static_keys(&their_per_commitment_point, + &broadcaster_keys, &countersignatory_keys, &self.onchain_tx_handler.secp_ctx); + let channel_parameters = + &self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable(); + + CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number, + to_broadcaster_value, to_countersignatory_value, broadcaster_funding_key, + countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs, + channel_parameters) + } + + pub(crate) fn counterparty_commitment_txs_from_update(&self, update: &ChannelMonitorUpdate) -> Vec { + update.updates.iter().filter_map(|update| { + match update { + &ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, + ref htlc_outputs, commitment_number, their_per_commitment_point, + feerate_per_kw: Some(feerate_per_kw), + to_broadcaster_value_sat: Some(to_broadcaster_value), + to_countersignatory_value_sat: Some(to_countersignatory_value) } => { + + let nondust_htlcs = htlc_outputs.iter().filter_map(|(htlc, _)| { + htlc.transaction_output_index.map(|_| (htlc.clone(), None)) + }).collect::>(); + + let commitment_tx = self.build_counterparty_commitment_tx(commitment_number, + &their_per_commitment_point, to_broadcaster_value, + to_countersignatory_value, feerate_per_kw, nondust_htlcs); + + debug_assert_eq!(commitment_tx.trust().txid(), commitment_txid); + + Some(commitment_tx) + }, + _ => None, + } + }).collect() + } + + pub(crate) fn sign_to_local_justice_tx( + &self, mut justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64 + ) -> Result { + let secret = self.get_secret(commitment_number).ok_or(())?; + let per_commitment_key = SecretKey::from_slice(&secret).map_err(|_| ())?; + let their_per_commitment_point = PublicKey::from_secret_key( + &self.onchain_tx_handler.secp_ctx, &per_commitment_key); + + let revocation_pubkey = chan_utils::derive_public_revocation_key( + &self.onchain_tx_handler.secp_ctx, &their_per_commitment_point, + &self.holder_revocation_basepoint); + let delayed_key = chan_utils::derive_public_key(&self.onchain_tx_handler.secp_ctx, + &their_per_commitment_point, + &self.counterparty_commitment_params.counterparty_delayed_payment_base_key); + let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, + self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key); + + let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output( + &justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx)?; + justice_tx.input[input_idx].witness.push_bitcoin_signature(&sig.serialize_der(), EcdsaSighashType::All); + justice_tx.input[input_idx].witness.push(&[1u8]); + justice_tx.input[input_idx].witness.push(revokeable_redeemscript.as_bytes()); + Ok(justice_tx) + } + /// Can only fail if idx is < get_min_seen_secret fn get_secret(&self, idx: u64) -> Option<[u8; 32]> { self.commitment_secrets.get_secret(idx) @@ -4113,6 +4319,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut confirmed_commitment_tx_counterparty_output = None; let mut spendable_txids_confirmed = Some(Vec::new()); let mut counterparty_fulfilled_htlcs = Some(HashMap::new()); + let mut initial_counterparty_commitment_info = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -4122,6 +4329,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (11, confirmed_commitment_tx_counterparty_output, option), (13, spendable_txids_confirmed, optional_vec), (15, counterparty_fulfilled_htlcs, option), + (17, initial_counterparty_commitment_info, option), }); Ok((best_block.block_hash(), ChannelMonitor::from_impl(ChannelMonitorImpl { @@ -4177,6 +4385,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP best_block, counterparty_node_id, + initial_counterparty_commitment_info, }))) } } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 85490afaec1..a12665dbaab 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -21,6 +21,8 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::ripemd160::Hash as Ripemd160; use bitcoin::hash_types::{Txid, PubkeyHash}; +use crate::chain::chaininterface::fee_for_weight; +use crate::chain::package::WEIGHT_REVOKED_OUTPUT; use crate::sign::EntropySource; use crate::ln::{PaymentHash, PaymentPreimage}; use crate::ln::msgs::DecodeError; @@ -1308,6 +1310,7 @@ pub struct CommitmentTransaction { commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, + to_broadcaster_delay: Option, // Added in 0.0.117 feerate_per_kw: u32, htlcs: Vec, // Note that on upgrades, some features of existing outputs may be missed. @@ -1341,6 +1344,7 @@ impl Writeable for CommitmentTransaction { let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features); write_tlv_fields!(writer, { (0, self.commitment_number, required), + (1, self.to_broadcaster_delay, option), (2, self.to_broadcaster_value_sat, required), (4, self.to_countersignatory_value_sat, required), (6, self.feerate_per_kw, required), @@ -1358,6 +1362,7 @@ impl Readable for CommitmentTransaction { fn read(reader: &mut R) -> Result { _init_and_read_tlv_fields!(reader, { (0, commitment_number, required), + (1, to_broadcaster_delay, option), (2, to_broadcaster_value_sat, required), (4, to_countersignatory_value_sat, required), (6, feerate_per_kw, required), @@ -1376,6 +1381,7 @@ impl Readable for CommitmentTransaction { commitment_number: commitment_number.0.unwrap(), to_broadcaster_value_sat: to_broadcaster_value_sat.0.unwrap(), to_countersignatory_value_sat: to_countersignatory_value_sat.0.unwrap(), + to_broadcaster_delay, feerate_per_kw: feerate_per_kw.0.unwrap(), keys: keys.0.unwrap(), built: built.0.unwrap(), @@ -1407,6 +1413,7 @@ impl CommitmentTransaction { commitment_number, to_broadcaster_value_sat, to_countersignatory_value_sat, + to_broadcaster_delay: Some(channel_parameters.contest_delay()), feerate_per_kw, htlcs, channel_type_features: channel_parameters.channel_type_features().clone(), @@ -1724,6 +1731,69 @@ impl<'a> TrustedCommitmentTransaction<'a> { ); htlc_tx } + + /// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to + /// the broadcaster, in the built transaction, if any exists. + /// + /// There are two cases where this may return `None`: + /// - The balance of the revokeable output is below the dust limit (only found on commitments + /// early in the channel's lifetime, i.e. before the channel reserve is met). + /// - This commitment was created before LDK 0.0.117. In this case, the + /// commitment transaction previously didn't contain enough information to locate the + /// revokeable output. + pub fn revokeable_output_index(&self) -> Option { + let revokeable_redeemscript = get_revokeable_redeemscript( + &self.keys.revocation_key, + self.to_broadcaster_delay?, + &self.keys.broadcaster_delayed_payment_key, + ); + let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh(); + let outputs = &self.inner.built.transaction.output; + outputs.iter().enumerate() + .find(|(_, out)| out.script_pubkey == revokeable_p2wsh) + .map(|(idx, _)| idx) + } + + /// Helper method to build an unsigned justice transaction spending the revokeable + /// `to_local` output to a destination script. Fee estimation accounts for the expected + /// revocation witness data that will be added when signed. + /// + /// This method will error if the given fee rate results in a fee greater than the value + /// of the output being spent, or if there exists no revokeable `to_local` output on this + /// commitment transaction. See [`Self::revokeable_output_index`] for more details. + /// + /// The built transaction will allow fee bumping with RBF, and this method takes + /// `feerate_per_kw` as an input such that multiple copies of a justice transaction at different + /// fee rates may be built. + pub fn build_to_local_justice_tx(&self, feerate_per_kw: u64, destination_script: Script) + -> Result { + let output_idx = self.revokeable_output_index().ok_or(())?; + let input = vec![TxIn { + previous_output: OutPoint { + txid: self.trust().txid(), + vout: output_idx as u32, + }, + script_sig: Script::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::new(), + }]; + let value = self.inner.built.transaction.output[output_idx].value; + let output = vec![TxOut { + script_pubkey: destination_script, + value, + }]; + let mut justice_tx = Transaction { + version: 2, + lock_time: PackedLockTime::ZERO, + input, + output, + }; + let weight = justice_tx.weight() as u64 + WEIGHT_REVOKED_OUTPUT; + let fee = fee_for_weight(feerate_per_kw as u32, weight); + justice_tx.output[0].value = value.checked_sub(fee).ok_or(())?; + Ok(justice_tx) + } + } /// Commitment transaction numbers which appear in the transactions themselves are XOR'd with a @@ -1758,14 +1828,14 @@ pub fn get_commitment_transaction_number_obscure_factor( #[cfg(test)] mod tests { - use super::CounterpartyCommitmentSecrets; + use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys}; use crate::{hex, chain}; use crate::prelude::*; use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment}; use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1}; use crate::util::test_utils; use crate::sign::{ChannelSigner, SignerProvider}; - use bitcoin::{Network, Txid}; + use bitcoin::{Network, Txid, Script}; use bitcoin::hashes::Hash; use crate::ln::PaymentHash; use bitcoin::hashes::hex::ToHex; @@ -1773,74 +1843,86 @@ mod tests { use bitcoin::PublicKey as BitcoinPublicKey; use crate::ln::features::ChannelTypeFeatures; - #[test] - fn test_anchors() { - let secp_ctx = Secp256k1::new(); + struct TestCommitmentTxBuilder { + commitment_number: u64, + holder_funding_pubkey: PublicKey, + counterparty_funding_pubkey: PublicKey, + keys: TxCreationKeys, + feerate_per_kw: u32, + htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>, + channel_parameters: ChannelTransactionParameters, + counterparty_pubkeys: ChannelPublicKeys, + } + + impl TestCommitmentTxBuilder { + fn new() -> Self { + let secp_ctx = Secp256k1::new(); + let seed = [42; 32]; + let network = Network::Testnet; + let keys_provider = test_utils::TestKeysInterface::new(&seed, network); + let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0)); + let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1)); + let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint; + let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap(); + let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); + let htlc_basepoint = &signer.pubkeys().htlc_basepoint; + let holder_pubkeys = signer.pubkeys(); + let counterparty_pubkeys = counterparty_signer.pubkeys().clone(); + let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint); + let channel_parameters = ChannelTransactionParameters { + holder_pubkeys: holder_pubkeys.clone(), + holder_selected_contest_delay: 0, + is_outbound_from_holder: false, + counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }), + funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), + channel_type_features: ChannelTypeFeatures::only_static_remote_key(), + }; + let htlcs_with_aux = Vec::new(); + + Self { + commitment_number: 0, + holder_funding_pubkey: holder_pubkeys.funding_pubkey, + counterparty_funding_pubkey: counterparty_pubkeys.funding_pubkey, + keys, + feerate_per_kw: 1, + htlcs_with_aux, + channel_parameters, + counterparty_pubkeys, + } + } - let seed = [42; 32]; - let network = Network::Testnet; - let keys_provider = test_utils::TestKeysInterface::new(&seed, network); - let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0)); - let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1)); - let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint; - let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap(); - let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); - let htlc_basepoint = &signer.pubkeys().htlc_basepoint; - let holder_pubkeys = signer.pubkeys(); - let counterparty_pubkeys = counterparty_signer.pubkeys(); - let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint); - let mut channel_parameters = ChannelTransactionParameters { - holder_pubkeys: holder_pubkeys.clone(), - holder_selected_contest_delay: 0, - is_outbound_from_holder: false, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }), - funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), - channel_type_features: ChannelTypeFeatures::only_static_remote_key(), - }; + fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction { + CommitmentTransaction::new_with_auxiliary_htlc_data( + self.commitment_number, to_broadcaster_sats, to_countersignatory_sats, + self.holder_funding_pubkey.clone(), + self.counterparty_funding_pubkey.clone(), + self.keys.clone(), self.feerate_per_kw, + &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable() + ) + } + } - let mut htlcs_with_aux: Vec<(_, ())> = Vec::new(); + #[test] + fn test_anchors() { + let mut builder = TestCommitmentTxBuilder::new(); // Generate broadcaster and counterparty outputs - let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( - 0, 1000, 2000, - holder_pubkeys.funding_pubkey, - counterparty_pubkeys.funding_pubkey, - keys.clone(), 1, - &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() - ); + let tx = builder.build(1000, 2000); assert_eq!(tx.built.transaction.output.len(), 2); - assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(counterparty_pubkeys.payment_point)).unwrap().script_pubkey()); + assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(builder.counterparty_pubkeys.payment_point)).unwrap().script_pubkey()); // Generate broadcaster and counterparty outputs as well as two anchors - channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); - let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( - 0, 1000, 2000, - holder_pubkeys.funding_pubkey, - counterparty_pubkeys.funding_pubkey, - keys.clone(), 1, - &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() - ); + builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + let tx = builder.build(1000, 2000); assert_eq!(tx.built.transaction.output.len(), 4); - assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&counterparty_pubkeys.payment_point).to_v0_p2wsh()); + assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&builder.counterparty_pubkeys.payment_point).to_v0_p2wsh()); // Generate broadcaster output and anchor - let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( - 0, 3000, 0, - holder_pubkeys.funding_pubkey, - counterparty_pubkeys.funding_pubkey, - keys.clone(), 1, - &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() - ); + let tx = builder.build(3000, 0); assert_eq!(tx.built.transaction.output.len(), 2); // Generate counterparty output and anchor - let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( - 0, 0, 3000, - holder_pubkeys.funding_pubkey, - counterparty_pubkeys.funding_pubkey, - keys.clone(), 1, - &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() - ); + let tx = builder.build(0, 3000); assert_eq!(tx.built.transaction.output.len(), 2); let received_htlc = HTLCOutputInCommitment { @@ -1860,15 +1942,10 @@ mod tests { }; // Generate broadcaster output and received and offered HTLC outputs, w/o anchors - channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key(); - let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( - 0, 3000, 0, - holder_pubkeys.funding_pubkey, - counterparty_pubkeys.funding_pubkey, - keys.clone(), 1, - &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())], - &channel_parameters.as_holder_broadcastable() - ); + builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key(); + builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())]; + let tx = builder.build(3000, 0); + let keys = &builder.keys.clone(); assert_eq!(tx.built.transaction.output.len(), 3); assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh()); assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh()); @@ -1878,15 +1955,9 @@ mod tests { "0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d"); // Generate broadcaster output and received and offered HTLC outputs, with anchors - channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); - let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( - 0, 3000, 0, - holder_pubkeys.funding_pubkey, - counterparty_pubkeys.funding_pubkey, - keys.clone(), 1, - &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())], - &channel_parameters.as_holder_broadcastable() - ); + builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())]; + let tx = builder.build(3000, 0); assert_eq!(tx.built.transaction.output.len(), 5); assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh()); assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh()); @@ -1896,6 +1967,61 @@ mod tests { "002087a3faeb1950a469c0e2db4a79b093a41b9526e5a6fc6ef5cb949bde3be379c7"); } + #[test] + fn test_finding_revokeable_output_index() { + let mut builder = TestCommitmentTxBuilder::new(); + + // Revokeable output present + let tx = builder.build(1000, 2000); + assert_eq!(tx.built.transaction.output.len(), 2); + assert_eq!(tx.trust().revokeable_output_index(), Some(0)); + + // Revokeable output present (but to_broadcaster_delay missing) + let tx = CommitmentTransaction { to_broadcaster_delay: None, ..tx }; + assert_eq!(tx.built.transaction.output.len(), 2); + assert_eq!(tx.trust().revokeable_output_index(), None); + + // Revokeable output not present (our balance is dust) + let tx = builder.build(0, 2000); + assert_eq!(tx.built.transaction.output.len(), 1); + assert_eq!(tx.trust().revokeable_output_index(), None); + } + + #[test] + fn test_building_to_local_justice_tx() { + let mut builder = TestCommitmentTxBuilder::new(); + + // Revokeable output not present (our balance is dust) + let tx = builder.build(0, 2000); + assert_eq!(tx.built.transaction.output.len(), 1); + assert!(tx.trust().build_to_local_justice_tx(253, Script::new()).is_err()); + + // Revokeable output present + let tx = builder.build(1000, 2000); + assert_eq!(tx.built.transaction.output.len(), 2); + + // Too high feerate + assert!(tx.trust().build_to_local_justice_tx(100_000, Script::new()).is_err()); + + // Generate a random public key for destination script + let secret_key = SecretKey::from_slice( + &hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100") + .unwrap()[..]).unwrap(); + let pubkey_hash = BitcoinPublicKey::new( + PublicKey::from_secret_key(&Secp256k1::new(), &secret_key)).wpubkey_hash().unwrap(); + let destination_script = Script::new_v0_p2wpkh(&pubkey_hash); + + let justice_tx = tx.trust().build_to_local_justice_tx(253, destination_script.clone()).unwrap(); + assert_eq!(justice_tx.input.len(), 1); + assert_eq!(justice_tx.input[0].previous_output.txid, tx.built.transaction.txid()); + assert_eq!(justice_tx.input[0].previous_output.vout, tx.trust().revokeable_output_index().unwrap() as u32); + assert!(justice_tx.input[0].sequence.is_rbf()); + + assert_eq!(justice_tx.output.len(), 1); + assert!(justice_tx.output[0].value < 1000); + assert_eq!(justice_tx.output[0].script_pubkey, destination_script); + } + #[test] fn test_per_commitment_storage() { // Test vectors from BOLT 3: diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f66f48c5049..a8e8c8053b6 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2534,7 +2534,13 @@ impl Channel where obscure_factor, holder_commitment_tx, best_block, self.context.counterparty_node_id); - channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_bitcoin_tx.txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger); + channel_monitor.provide_initial_counterparty_commitment_tx( + counterparty_initial_bitcoin_tx.txid, Vec::new(), + self.context.cur_counterparty_commitment_transaction_number, + self.context.counterparty_cur_commitment_point.unwrap(), + counterparty_initial_commitment_tx.feerate_per_kw(), + counterparty_initial_commitment_tx.to_broadcaster_value_sat(), + counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update! self.context.channel_state = ChannelState::FundingSent as u32; @@ -5289,7 +5295,9 @@ impl Channel where } self.context.resend_order = RAACommitmentOrder::RevokeAndACKFirst; - let (counterparty_commitment_txid, mut htlcs_ref) = self.build_commitment_no_state_update(logger); + let (mut htlcs_ref, counterparty_commitment_tx) = + self.build_commitment_no_state_update(logger); + let counterparty_commitment_txid = counterparty_commitment_tx.trust().txid(); let htlcs: Vec<(HTLCOutputInCommitment, Option>)> = htlcs_ref.drain(..).map(|(htlc, htlc_source)| (htlc, htlc_source.map(|source_ref| Box::new(source_ref.clone())))).collect(); @@ -5304,17 +5312,23 @@ impl Channel where commitment_txid: counterparty_commitment_txid, htlc_outputs: htlcs.clone(), commitment_number: self.context.cur_counterparty_commitment_transaction_number, - their_per_commitment_point: self.context.counterparty_cur_commitment_point.unwrap() + their_per_commitment_point: self.context.counterparty_cur_commitment_point.unwrap(), + feerate_per_kw: Some(counterparty_commitment_tx.feerate_per_kw()), + to_broadcaster_value_sat: Some(counterparty_commitment_tx.to_broadcaster_value_sat()), + to_countersignatory_value_sat: Some(counterparty_commitment_tx.to_countersignatory_value_sat()), }] }; self.context.channel_state |= ChannelState::AwaitingRemoteRevoke as u32; monitor_update } - fn build_commitment_no_state_update(&self, logger: &L) -> (Txid, Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>) where L::Target: Logger { + fn build_commitment_no_state_update(&self, logger: &L) + -> (Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>, CommitmentTransaction) + where L::Target: Logger + { let counterparty_keys = self.context.build_remote_transaction_keys(); let commitment_stats = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger); - let counterparty_commitment_txid = commitment_stats.tx.trust().txid(); + let counterparty_commitment_tx = commitment_stats.tx; #[cfg(any(test, fuzzing))] { @@ -5334,7 +5348,7 @@ impl Channel where } } - (counterparty_commitment_txid, commitment_stats.htlcs_included) + (commitment_stats.htlcs_included, counterparty_commitment_tx) } /// Only fails in case of signer rejection. Used for channel_reestablish commitment_signed @@ -6456,7 +6470,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { self.generate_accept_channel_message() } - fn funding_created_signature(&mut self, sig: &Signature, logger: &L) -> Result<(Txid, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { + fn funding_created_signature(&mut self, sig: &Signature, logger: &L) -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { let funding_script = self.context.get_funding_redeemscript(); let keys = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number); @@ -6488,7 +6502,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; // We sign "counterparty" commitment transaction, allowing them to broadcast the tx if they wish. - Ok((counterparty_initial_bitcoin_tx.txid, initial_commitment_tx, counterparty_signature)) + Ok((counterparty_initial_commitment_tx, initial_commitment_tx, counterparty_signature)) } } } @@ -6520,7 +6534,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // funding_created_signature may fail. self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); - let (counterparty_initial_commitment_txid, initial_commitment_tx, signature) = match self.funding_created_signature(&msg.signature, logger) { + let (counterparty_initial_commitment_tx, initial_commitment_tx, signature) = match self.funding_created_signature(&msg.signature, logger) { Ok(res) => res, Err(ChannelError::Close(e)) => { self.context.channel_transaction_parameters.funding_outpoint = None; @@ -6561,7 +6575,12 @@ impl InboundV1Channel where SP::Target: SignerProvider { obscure_factor, holder_commitment_tx, best_block, self.context.counterparty_node_id); - channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_commitment_txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger); + channel_monitor.provide_initial_counterparty_commitment_tx( + counterparty_initial_commitment_tx.trust().txid(), Vec::new(), + self.context.cur_counterparty_commitment_transaction_number, + self.context.counterparty_cur_commitment_point.unwrap(), self.context.feerate_per_kw, + counterparty_initial_commitment_tx.to_broadcaster_value_sat(), + counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); self.context.channel_state = ChannelState::FundingSent as u32; self.context.channel_id = funding_txo.to_channel_id(); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 1db4e873458..34568b07cad 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -10,7 +10,7 @@ //! A bunch of useful utilities for building networks of nodes and exchanging messages between //! nodes for functional tests. -use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch}; +use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist}; use crate::sign::EntropySource; use crate::chain::channelmonitor::ChannelMonitor; use crate::chain::transaction::OutPoint; @@ -2643,10 +2643,14 @@ pub fn create_chanmon_cfgs(node_count: usize) -> Vec { } pub fn create_node_cfgs<'a>(node_count: usize, chanmon_cfgs: &'a Vec) -> Vec> { + create_node_cfgs_with_persisters(node_count, chanmon_cfgs, chanmon_cfgs.iter().map(|c| &c.persister).collect()) +} + +pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a Vec, persisters: Vec<&'a impl Persist>) -> Vec> { let mut nodes = Vec::new(); for i in 0..node_count { - let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[i].chain_source), &chanmon_cfgs[i].tx_broadcaster, &chanmon_cfgs[i].logger, &chanmon_cfgs[i].fee_estimator, &chanmon_cfgs[i].persister, &chanmon_cfgs[i].keys_manager); + let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[i].chain_source), &chanmon_cfgs[i].tx_broadcaster, &chanmon_cfgs[i].logger, &chanmon_cfgs[i].fee_estimator, persisters[i], &chanmon_cfgs[i].keys_manager); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[i].logger)); let seed = [i as u8; 32]; nodes.push(NodeCfg { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index f5ba7166ca7..2fbc36ce9a5 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -17,7 +17,7 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::channelmonitor; use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY}; use crate::chain::transaction::OutPoint; -use crate::sign::{EcdsaChannelSigner, EntropySource}; +use crate::sign::{ChannelSigner, EcdsaChannelSigner, EntropySource, SignerProvider}; use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason}; use crate::ln::{PaymentPreimage, PaymentSecret, PaymentHash}; use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel}; @@ -31,7 +31,7 @@ use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures}; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction}; use crate::util::enforcing_trait_impls::EnforcingSigner; -use crate::util::test_utils; +use crate::util::test_utils::{self, WatchtowerPersister}; use crate::util::errors::APIError; use crate::util::ser::{Writeable, ReadableArgs}; use crate::util::string::UntrustedString; @@ -2554,6 +2554,72 @@ fn revoked_output_claim() { check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000); } +#[test] +fn test_forming_justice_tx_from_monitor_updates() { + do_test_forming_justice_tx_from_monitor_updates(true); + do_test_forming_justice_tx_from_monitor_updates(false); +} + +fn do_test_forming_justice_tx_from_monitor_updates(broadcast_initial_commitment: bool) { + // Simple test to make sure that the justice tx formed in WatchtowerPersister + // is properly formed and can be broadcasted/confirmed successfully in the event + // that a revoked commitment transaction is broadcasted + // (Similar to `revoked_output_claim` test but we get the justice tx + broadcast manually) + let chanmon_cfgs = create_chanmon_cfgs(2); + let destination_script0 = chanmon_cfgs[0].keys_manager.get_destination_script().unwrap(); + let destination_script1 = chanmon_cfgs[1].keys_manager.get_destination_script().unwrap(); + let persisters = vec![WatchtowerPersister::new(destination_script0), + WatchtowerPersister::new(destination_script1)]; + let node_cfgs = create_node_cfgs_with_persisters(2, &chanmon_cfgs, persisters.iter().collect()); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let (_, _, channel_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1); + let funding_txo = OutPoint { txid: funding_tx.txid(), index: 0 }; + + if !broadcast_initial_commitment { + // Send a payment to move the channel forward + send_payment(&nodes[0], &vec!(&nodes[1])[..], 5_000_000); + } + + // node[0] is gonna to revoke an old state thus node[1] should be able to claim the revoked output. + // We'll keep this commitment transaction to broadcast once it's revoked. + let revoked_local_txn = get_local_commitment_txn!(nodes[0], channel_id); + assert_eq!(revoked_local_txn.len(), 1); + let revoked_commitment_tx = &revoked_local_txn[0]; + + // Send another payment, now revoking the previous commitment tx + send_payment(&nodes[0], &vec!(&nodes[1])[..], 5_000_000); + + let justice_tx = persisters[1].justice_tx(funding_txo, &revoked_commitment_tx.txid()).unwrap(); + check_spends!(justice_tx, revoked_commitment_tx); + + mine_transactions(&nodes[1], &[revoked_commitment_tx, &justice_tx]); + mine_transactions(&nodes[0], &[revoked_commitment_tx, &justice_tx]); + + check_added_monitors!(nodes[1], 1); + check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false, + &[nodes[0].node.get_our_node_id()], 100_000); + get_announce_close_broadcast_events(&nodes, 1, 0); + + check_added_monitors!(nodes[0], 1); + check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false, + &[nodes[1].node.get_our_node_id()], 100_000); + + // Check that the justice tx has sent the revoked output value to nodes[1] + let monitor = get_monitor!(nodes[1], channel_id); + let total_claimable_balance = monitor.get_claimable_balances().iter().fold(0, |sum, balance| { + match balance { + channelmonitor::Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. } => sum + amount_satoshis, + _ => panic!("Unexpected balance type"), + } + }); + // On the first commitment, node[1]'s balance was below dust so it didn't have an output + let node1_channel_balance = if broadcast_initial_commitment { 0 } else { revoked_commitment_tx.output[0].value }; + let expected_claimable_balance = node1_channel_balance + justice_tx.output[0].value; + assert_eq!(total_claimable_balance, expected_claimable_balance); +} + + #[test] fn claim_htlc_outputs_shared_tx() { // Node revoked old state, htlcs haven't time out yet, claim them in shared justice tx diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index e7e29600dab..bcd460ee1b1 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -11,6 +11,7 @@ use crate::chain; use crate::chain::WatchedOutput; use crate::chain::chaininterface; use crate::chain::chaininterface::ConfirmationTarget; +use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; use crate::chain::chainmonitor; use crate::chain::chainmonitor::MonitorUpdateId; use crate::chain::channelmonitor; @@ -20,6 +21,7 @@ use crate::sign; use crate::events; use crate::events::bump_transaction::{WalletSource, Utxo}; use crate::ln::channelmanager; +use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::{msgs, wire}; use crate::ln::msgs::LightningError; @@ -272,6 +274,108 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { } } +struct JusticeTxData { + justice_tx: Transaction, + value: u64, + commitment_number: u64, +} + +pub(crate) struct WatchtowerPersister { + persister: TestPersister, + /// Upon a new commitment_signed, we'll get a + /// ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo. We'll store the justice tx + /// amount, and commitment number so we can build the justice tx after our counterparty + /// revokes it. + unsigned_justice_tx_data: Mutex>>, + /// After receiving a revoke_and_ack for a commitment number, we'll form and store the justice + /// tx which would be used to provide a watchtower with the data it needs. + watchtower_state: Mutex>>, + destination_script: Script, +} + +impl WatchtowerPersister { + pub(crate) fn new(destination_script: Script) -> Self { + WatchtowerPersister { + persister: TestPersister::new(), + unsigned_justice_tx_data: Mutex::new(HashMap::new()), + watchtower_state: Mutex::new(HashMap::new()), + destination_script, + } + } + + pub(crate) fn justice_tx(&self, funding_txo: OutPoint, commitment_txid: &Txid) + -> Option { + self.watchtower_state.lock().unwrap().get(&funding_txo).unwrap().get(commitment_txid).cloned() + } + + fn form_justice_data_from_commitment(&self, counterparty_commitment_tx: &CommitmentTransaction) + -> Option { + let trusted_tx = counterparty_commitment_tx.trust(); + let output_idx = trusted_tx.revokeable_output_index()?; + let built_tx = trusted_tx.built_transaction(); + let value = built_tx.transaction.output[output_idx as usize].value; + let justice_tx = trusted_tx.build_to_local_justice_tx( + FEERATE_FLOOR_SATS_PER_KW as u64, self.destination_script.clone()).ok()?; + let commitment_number = counterparty_commitment_tx.commitment_number(); + Some(JusticeTxData { justice_tx, value, commitment_number }) + } +} + +impl chainmonitor::Persist for WatchtowerPersister { + fn persist_new_channel(&self, funding_txo: OutPoint, + data: &channelmonitor::ChannelMonitor, id: MonitorUpdateId + ) -> chain::ChannelMonitorUpdateStatus { + let res = self.persister.persist_new_channel(funding_txo, data, id); + + assert!(self.unsigned_justice_tx_data.lock().unwrap() + .insert(funding_txo, VecDeque::new()).is_none()); + assert!(self.watchtower_state.lock().unwrap() + .insert(funding_txo, HashMap::new()).is_none()); + + let initial_counterparty_commitment_tx = data.initial_counterparty_commitment_tx() + .expect("First and only call expects Some"); + if let Some(justice_data) + = self.form_justice_data_from_commitment(&initial_counterparty_commitment_tx) { + self.unsigned_justice_tx_data.lock().unwrap() + .get_mut(&funding_txo).unwrap() + .push_back(justice_data); + } + res + } + + fn update_persisted_channel( + &self, funding_txo: OutPoint, update: Option<&channelmonitor::ChannelMonitorUpdate>, + data: &channelmonitor::ChannelMonitor, update_id: MonitorUpdateId + ) -> chain::ChannelMonitorUpdateStatus { + let res = self.persister.update_persisted_channel(funding_txo, update, data, update_id); + + if let Some(update) = update { + let commitment_txs = data.counterparty_commitment_txs_from_update(update); + let justice_datas = commitment_txs.into_iter() + .filter_map(|commitment_tx| self.form_justice_data_from_commitment(&commitment_tx)); + let mut channels_justice_txs = self.unsigned_justice_tx_data.lock().unwrap(); + let channel_state = channels_justice_txs.get_mut(&funding_txo).unwrap(); + channel_state.extend(justice_datas); + + while let Some(JusticeTxData { justice_tx, value, commitment_number }) = channel_state.front() { + let input_idx = 0; + let commitment_txid = justice_tx.input[input_idx].previous_output.txid; + match data.sign_to_local_justice_tx(justice_tx.clone(), input_idx, *value, *commitment_number) { + Ok(signed_justice_tx) => { + let dup = self.watchtower_state.lock().unwrap() + .get_mut(&funding_txo).unwrap() + .insert(commitment_txid, signed_justice_tx); + assert!(dup.is_none()); + channel_state.pop_front(); + }, + Err(_) => break, + } + } + } + res + } +} + pub struct TestPersister { /// The queue of update statuses we'll return. If none are queued, ::Completed will always be /// returned.