From 75c058670cdfd2db97102d70d869757a77f1ac04 Mon Sep 17 00:00:00 2001 From: Alec Chen Date: Wed, 12 Jul 2023 13:14:10 -0500 Subject: [PATCH] Enable monitor to rebuild initial counterparty commitment tx Upon creating a channel monitor, it is provided with the initial counterparty commitment transaction info directly before the very first time it is persisted. Because of this, the very first counterparty commitment is not seen as an update in the persistence pipeline, and so our previous changes to the monitor and updates cannot be used to reconstruct this commitment. To be able to expose the counterparty's transaction for the very first commitment, we add a thin wrapper around `provide_latest_counterparty_commitment_tx`, that stores the necessary data needed to reconstruct the initial commitment transaction in the monitor. --- lightning/src/chain/channelmonitor.rs | 82 ++++++++++++++++++++++++++- lightning/src/ln/channel.rs | 21 +++++-- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index cb267524f..0c207fa8b 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -33,6 +33,7 @@ use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::{SecretKey, PublicKey}; use bitcoin::secp256k1; +use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::{PaymentHash, PaymentPreimage}; use crate::ln::msgs::DecodeError; use crate::ln::chan_utils; @@ -888,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. @@ -1078,6 +1087,7 @@ impl Writeable for ChannelMonitorImpl ChannelMonitor { best_block, counterparty_node_id: Some(counterparty_node_id), + initial_counterparty_commitment_info: None, }) } @@ -1236,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>)>, @@ -1376,6 +1407,22 @@ 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. @@ -2255,6 +2302,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. @@ -2684,6 +2750,17 @@ 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, @@ -4195,6 +4272,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), @@ -4204,6 +4282,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 { @@ -4259,6 +4338,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/channel.rs b/lightning/src/ln/channel.rs index 0dcb56ff6..a8e8c8053 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; @@ -6464,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); @@ -6496,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)) } } } @@ -6528,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; @@ -6569,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(); -- 2.39.5