diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index a740fa3dbcb..913bc78dad9 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -29,8 +29,8 @@ use bitcoin::hash_types::{BlockHash, Txid}; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::chain::channelmonitor::{ - Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, - WithChannelMonitor, + Balance, ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, MonitorEvent, + TransactionOutputs, WithChannelMonitor, }; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput}; @@ -1032,6 +1032,35 @@ where panic!("{}", err_str); }, } + + // We may need to start monitoring for any alternative funding transactions. + if let Some(ref chain_source) = self.chain_source { + for update in &update.updates { + match update { + ChannelMonitorUpdateStep::RenegotiatedFunding { + channel_parameters, .. + } => { + let funding_outpoint = channel_parameters.funding_outpoint + .expect("Renegotiated funding must always have known outpoint"); + let funding_script = + channel_parameters.make_funding_redeemscript().to_p2wsh(); + log_trace!( + &logger, + "Registering renegotiated funding outpoint {} with the filter to monitor confirmations and spends", + &funding_outpoint + ); + chain_source.register_tx(&funding_outpoint.txid, &funding_script); + chain_source.register_output(WatchedOutput { + block_hash: None, + outpoint: funding_outpoint, + script_pubkey: funding_script, + }); + }, + _ => {}, + } + } + } + if update_res.is_err() { ChannelMonitorUpdateStatus::InProgress } else { diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 54f170bcbe3..3a71dcb2233 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -640,6 +640,11 @@ pub(crate) enum ChannelMonitorUpdateStep { ShutdownScript { scriptpubkey: ScriptBuf, }, + RenegotiatedFunding { + channel_parameters: ChannelTransactionParameters, + holder_commitment_tx: HolderCommitmentTransaction, + counterparty_commitment_tx: CommitmentTransaction, + }, } impl ChannelMonitorUpdateStep { @@ -653,6 +658,7 @@ impl ChannelMonitorUpdateStep { ChannelMonitorUpdateStep::CommitmentSecret { .. } => "CommitmentSecret", ChannelMonitorUpdateStep::ChannelForceClosed { .. } => "ChannelForceClosed", ChannelMonitorUpdateStep::ShutdownScript { .. } => "ShutdownScript", + ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => "RenegotiatedFunding", } } } @@ -691,6 +697,11 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep, (0, htlc_outputs, required_vec), (2, commitment_tx, required), }, + (10, RenegotiatedFunding) => { + (1, channel_parameters, (required: ReadableArgs, None)), + (3, holder_commitment_tx, required), + (5, counterparty_commitment_tx, required), + }, ); /// Indicates whether the balance is derived from a cooperative close, a force-close @@ -1024,9 +1035,69 @@ struct FundingScope { prev_holder_commitment_tx: Option, } +impl FundingScope { + fn funding_outpoint(&self) -> OutPoint { + let funding_outpoint = self.channel_parameters.funding_outpoint.as_ref(); + *funding_outpoint.expect("Funding outpoint must be set for active monitor") + } + + fn funding_txid(&self) -> Txid { + self.funding_outpoint().txid + } +} + +impl Writeable for FundingScope { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + write_tlv_fields!(w, { + (1, self.channel_parameters, (required: ReadableArgs, None)), + (3, self.current_counterparty_commitment_txid, required), + (5, self.prev_counterparty_commitment_txid, option), + (7, self.current_holder_commitment_tx, required), + (9, self.prev_holder_commitment_tx, option), + (11, self.counterparty_claimable_outpoints, required), + }); + Ok(()) + } +} + +impl Readable for FundingScope { + fn read(r: &mut R) -> Result { + let mut channel_parameters = RequiredWrapper(None); + let mut current_counterparty_commitment_txid = RequiredWrapper(None); + let mut prev_counterparty_commitment_txid = None; + let mut current_holder_commitment_tx = RequiredWrapper(None); + let mut prev_holder_commitment_tx = None; + let mut counterparty_claimable_outpoints = RequiredWrapper(None); + + read_tlv_fields!(r, { + (1, channel_parameters, (required: ReadableArgs, None)), + (3, current_counterparty_commitment_txid, required), + (5, prev_counterparty_commitment_txid, option), + (7, current_holder_commitment_tx, required), + (9, prev_holder_commitment_tx, option), + (11, counterparty_claimable_outpoints, required), + }); + + let channel_parameters: ChannelTransactionParameters = channel_parameters.0.unwrap(); + let redeem_script = channel_parameters.make_funding_redeemscript(); + + Ok(Self { + script_pubkey: redeem_script.to_p2wsh(), + redeem_script, + channel_parameters, + current_counterparty_commitment_txid: current_counterparty_commitment_txid.0.unwrap(), + prev_counterparty_commitment_txid, + current_holder_commitment_tx: current_holder_commitment_tx.0.unwrap(), + prev_holder_commitment_tx, + counterparty_claimable_outpoints: counterparty_claimable_outpoints.0.unwrap(), + }) + } +} + #[derive(Clone, PartialEq)] pub(crate) struct ChannelMonitorImpl { funding: FundingScope, + pending_funding: Vec, latest_update_id: u64, commitment_transaction_number_obscure_factor: u64, @@ -1467,6 +1538,7 @@ impl Writeable for ChannelMonitorImpl { (27, self.first_confirmed_funding_txo, required), (29, self.initial_counterparty_commitment_tx, option), (31, self.funding.channel_parameters, required), + (32, self.pending_funding, optional_vec), }); Ok(()) @@ -1636,6 +1708,7 @@ impl ChannelMonitor { current_holder_commitment_tx: initial_holder_commitment_tx, prev_holder_commitment_tx: None, }, + pending_funding: vec![], latest_update_id: 0, commitment_transaction_number_obscure_factor, @@ -1862,14 +1935,16 @@ impl ChannelMonitor { { let lock = self.inner.lock().unwrap(); let logger = WithChannelMonitor::from_impl(logger, &*lock, None); - log_trace!(&logger, "Registering funding outpoint {}", &lock.get_funding_txo()); - let funding_outpoint = lock.get_funding_txo(); - filter.register_tx(&funding_outpoint.txid, &lock.funding.script_pubkey); + for funding in core::iter::once(&lock.funding).chain(&lock.pending_funding) { + let funding_outpoint = funding.funding_outpoint(); + log_trace!(&logger, "Registering funding outpoint {} with the filter to monitor confirmations", &funding_outpoint); + filter.register_tx(&funding_outpoint.txid, &funding.script_pubkey); + } for (txid, outputs) in lock.get_outputs_to_watch().iter() { for (index, script_pubkey) in outputs.iter() { assert!(*index <= u16::MAX as u32); let outpoint = OutPoint { txid: *txid, index: *index as u16 }; - log_trace!(logger, "Registering outpoint {} with the filter for monitoring spends", outpoint); + log_trace!(logger, "Registering outpoint {} with the filter to monitor spend", outpoint); filter.register_output(WatchedOutput { block_hash: None, outpoint, @@ -3453,6 +3528,109 @@ impl ChannelMonitorImpl { ); } + fn renegotiated_funding( + &mut self, logger: &WithChannelMonitor, + channel_parameters: &ChannelTransactionParameters, + alternative_holder_commitment_tx: &HolderCommitmentTransaction, + alternative_counterparty_commitment_tx: &CommitmentTransaction, + ) -> Result<(), ()> + where + L::Target: Logger, + { + let redeem_script = channel_parameters.make_funding_redeemscript(); + let script_pubkey = redeem_script.to_p2wsh(); + let alternative_counterparty_commitment_txid = + alternative_counterparty_commitment_tx.trust().txid(); + + // Both the current counterparty commitment and the alternative one share the same set of + // non-dust and dust HTLCs in the same order, though the index of each non-dust HTLC may be + // different. + // + // We clone all HTLCs and their sources to use in the alternative funding scope, and update + // each non-dust HTLC with their corresponding index in the alternative counterparty + // commitment. + let current_counterparty_commitment_htlcs = + if let Some(txid) = &self.funding.current_counterparty_commitment_txid { + self.funding.counterparty_claimable_outpoints.get(txid).unwrap() + } else { + debug_assert!(false); + log_error!( + logger, + "Received funding renegotiation while initial funding negotiation is still pending" + ); + return Err(()); + }; + let mut htlcs_with_sources = current_counterparty_commitment_htlcs.clone(); + let alternative_htlcs = alternative_counterparty_commitment_tx.nondust_htlcs(); + // The left side only tracks non-dust, while the right side tracks dust as well. + debug_assert!(alternative_htlcs.len() <= current_counterparty_commitment_htlcs.len()); + for (alternative_htlc, (htlc, _)) in + alternative_htlcs.iter().zip(htlcs_with_sources.iter_mut()) + { + debug_assert!(htlc.transaction_output_index.is_some()); + debug_assert!(alternative_htlc.transaction_output_index.is_some()); + debug_assert!(alternative_htlc.is_data_equal(htlc)); + htlc.transaction_output_index = alternative_htlc.transaction_output_index; + } + + let mut counterparty_claimable_outpoints = new_hash_map(); + counterparty_claimable_outpoints + .insert(alternative_counterparty_commitment_txid, htlcs_with_sources); + + // TODO(splicing): Enforce any necessary RBF validity checks. + let alternative_funding = FundingScope { + script_pubkey: script_pubkey.clone(), + redeem_script, + channel_parameters: channel_parameters.clone(), + current_counterparty_commitment_txid: Some(alternative_counterparty_commitment_txid), + prev_counterparty_commitment_txid: None, + counterparty_claimable_outpoints, + current_holder_commitment_tx: alternative_holder_commitment_tx.clone(), + prev_holder_commitment_tx: None, + }; + let alternative_funding_outpoint = alternative_funding.funding_outpoint(); + + if self + .pending_funding + .iter() + .any(|funding| funding.funding_txid() == alternative_funding_outpoint.txid) + { + log_error!( + logger, + "Renegotiated funding transaction with a duplicate funding txid {}", + alternative_funding_outpoint.txid + ); + return Err(()); + } + + if let Some(parent_funding_txid) = channel_parameters.splice_parent_funding_txid.as_ref() { + // Only one splice can be negotiated at a time after we've exchanged `channel_ready` + // (implying our funding is confirmed) that spends our currently locked funding. + if !self.pending_funding.is_empty() { + log_error!( + logger, + "Negotiated splice while channel is pending channel_ready/splice_locked" + ); + return Err(()); + } + if *parent_funding_txid != self.funding.funding_txid() { + log_error!( + logger, + "Negotiated splice that does not spend currently locked funding transaction" + ); + return Err(()); + } + } + + self.outputs_to_watch.insert( + alternative_funding_outpoint.txid, + vec![(alternative_funding_outpoint.index as u32, script_pubkey)], + ); + self.pending_funding.push(alternative_funding); + + Ok(()) + } + #[rustfmt::skip] fn update_monitor( &mut self, updates: &ChannelMonitorUpdate, broadcaster: &B, fee_estimator: &F, logger: &WithChannelMonitor @@ -3532,6 +3710,17 @@ impl ChannelMonitorImpl { ret = Err(()); } }, + ChannelMonitorUpdateStep::RenegotiatedFunding { + channel_parameters, holder_commitment_tx, counterparty_commitment_tx, + } => { + log_trace!(logger, "Updating ChannelMonitor with alternative holder and counterparty commitment transactions for funding txid {}", + channel_parameters.funding_outpoint.unwrap().txid); + if let Err(_) = self.renegotiated_funding( + logger, channel_parameters, holder_commitment_tx, counterparty_commitment_tx, + ) { + ret = Err(()); + } + }, ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => { log_trace!(logger, "Updating ChannelMonitor: channel force closed, should broadcast: {}", should_broadcast); self.lockdown_from_offchain = true; @@ -3583,7 +3772,8 @@ impl ChannelMonitorImpl { |ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. } |ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTX { .. } |ChannelMonitorUpdateStep::ShutdownScript { .. } - |ChannelMonitorUpdateStep::CommitmentSecret { .. } => + |ChannelMonitorUpdateStep::CommitmentSecret { .. } + |ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => is_pre_close_update = true, // After a channel is closed, we don't communicate with our peer about it, so the // only things we will update is getting a new preimage (from a different channel) @@ -3765,6 +3955,9 @@ impl ChannelMonitorImpl { } => { Some(commitment_tx.clone()) }, + &ChannelMonitorUpdateStep::RenegotiatedFunding { ref counterparty_commitment_tx, .. } => { + Some(counterparty_commitment_tx.clone()) + }, _ => None, } }).collect() @@ -5438,6 +5631,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut payment_preimages_with_info: Option> = None; let mut first_confirmed_funding_txo = RequiredWrapper(None); let mut channel_parameters = None; + let mut pending_funding = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -5455,6 +5649,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (27, first_confirmed_funding_txo, (default_value, outpoint)), (29, initial_counterparty_commitment_tx, option), (31, channel_parameters, (option: ReadableArgs, None)), + (32, pending_funding, optional_vec), }); if let Some(payment_preimages_with_info) = payment_preimages_with_info { if payment_preimages_with_info.len() != payment_preimages.len() { @@ -5570,6 +5765,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP current_holder_commitment_tx, prev_holder_commitment_tx, }, + pending_funding: pending_funding.unwrap_or(vec![]), latest_update_id, commitment_transaction_number_obscure_factor, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f396d8dab98..2dd607a969d 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1798,7 +1798,33 @@ where res }, ChannelPhase::Funded(mut funded_channel) => { - let res = funded_channel.commitment_signed(msg, logger).map(|monitor_update_opt| (None, monitor_update_opt)); + #[cfg(splicing)] + let has_negotiated_pending_splice = funded_channel.pending_splice.as_ref() + .map(|pending_splice| pending_splice.funding.is_some()) + .unwrap_or(false); + #[cfg(splicing)] + let session_received_commitment_signed = funded_channel + .interactive_tx_signing_session + .as_ref() + .map(|session| session.has_received_commitment_signed()); + #[cfg(splicing)] + let res = if has_negotiated_pending_splice + // Not having a signing session implies they've already sent `splice_locked`, + // which must always come after the initial commitment signed is sent. + && !session_received_commitment_signed.unwrap_or(true) + { + funded_channel + .splice_initial_commitment_signed(msg, logger) + .map(|monitor_update_opt| (None, monitor_update_opt)) + } else { + funded_channel.commitment_signed(msg, logger) + .map(|monitor_update_opt| (None, monitor_update_opt)) + }; + + #[cfg(not(splicing))] + let res = funded_channel.commitment_signed(msg, logger) + .map(|monitor_update_opt| (None, monitor_update_opt)); + self.phase = ChannelPhase::Funded(funded_channel); res }, @@ -2072,6 +2098,7 @@ impl FundingScope { #[cfg(splicing)] struct PendingSplice { pub our_funding_contribution: i64, + funding: Option, } /// Contains everything about the channel including state, and various flags. @@ -6393,6 +6420,87 @@ where Ok(channel_monitor) } + #[cfg(splicing)] + pub fn splice_initial_commitment_signed( + &mut self, msg: &msgs::CommitmentSigned, logger: &L, + ) -> Result, ChannelError> + where + L::Target: Logger, + { + if !self.context.channel_state.is_interactive_signing() + || self.context.channel_state.is_their_tx_signatures_sent() + { + return Err(ChannelError::close( + "Received splice initial commitment_signed during invalid state".to_owned(), + )); + } + + let pending_splice_funding = self + .pending_splice + .as_ref() + .and_then(|pending_splice| pending_splice.funding.as_ref()) + .expect("Funding must exist for negotiated pending splice"); + let holder_commitment_data = self.context.validate_commitment_signed( + pending_splice_funding, + &self.holder_commitment_point, + msg, + logger, + )?; + let counterparty_commitment_tx = self + .context + .build_commitment_transaction( + pending_splice_funding, + self.context.cur_counterparty_commitment_transaction_number, + &self.context.counterparty_cur_commitment_point.unwrap(), + false, + false, + logger, + ) + .tx; + + { + let counterparty_trusted_tx = counterparty_commitment_tx.trust(); + let counterparty_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + log_trace!( + logger, + "Splice initial counterparty tx for channel {} is: txid {} tx {}", + &self.context.channel_id(), + counterparty_bitcoin_tx.txid, + encode::serialize_hex(&counterparty_bitcoin_tx.transaction) + ); + } + + log_info!(logger, "Received splice initial commitment_signed from peer for channel {} with funding txid {}", + &self.context.channel_id(), pending_splice_funding.get_funding_txo().unwrap().txid); + + self.context.latest_monitor_update_id += 1; + let monitor_update = ChannelMonitorUpdate { + update_id: self.context.latest_monitor_update_id, + updates: vec![ChannelMonitorUpdateStep::RenegotiatedFunding { + channel_parameters: pending_splice_funding.channel_transaction_parameters.clone(), + holder_commitment_tx: holder_commitment_data.commitment_tx, + counterparty_commitment_tx, + }], + channel_id: Some(self.context.channel_id()), + }; + + let tx_signatures = self + .interactive_tx_signing_session + .as_mut() + .expect("Signing session must exist for negotiated pending splice") + .received_commitment_signed(); + self.context.monitor_pending_tx_signatures = tx_signatures; + + if self.context.channel_state.is_monitor_update_in_progress() { + // There may be a pending monitor update for a `revoke_and_ack` or preimage we have to + // handle first. + } else { + self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); + } + + Ok(self.push_ret_blockable_mon_update(monitor_update)) + } + #[rustfmt::skip] pub fn commitment_signed(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result, ChannelError> where L::Target: Logger @@ -9340,6 +9448,7 @@ where self.pending_splice = Some(PendingSplice { our_funding_contribution: our_funding_contribution_satoshis, + funding: None, }); let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);