diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 09db795e13a..d18122d85de 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -1180,6 +1180,26 @@ 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 (funding_outpoint, funding_script) in + update.internal_renegotiated_funding_data() + { + 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..bfa5acc3bd5 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -113,6 +113,37 @@ pub struct ChannelMonitorUpdate { pub channel_id: Option, } +impl ChannelMonitorUpdate { + pub(crate) fn internal_renegotiated_funding_data( + &self, + ) -> impl Iterator + '_ { + self.updates.iter().filter_map(|update| 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(); + Some((funding_outpoint, funding_script)) + }, + _ => None, + }) + } + + /// Returns a `Vec` of new (funding outpoint, funding script) to monitor the chain for as a + /// result of a renegotiated funding transaction. + #[cfg(c_bindings)] + pub fn renegotiated_funding_data(&self) -> Vec<(OutPoint, ScriptBuf)> { + self.internal_renegotiated_funding_data().collect() + } + + /// Returns an iterator of new (funding outpoint, funding script) to monitor the chain for as a + /// result of a renegotiated funding transaction. + #[cfg(not(c_bindings))] + pub fn renegotiated_funding_data(&self) -> impl Iterator + '_ { + self.internal_renegotiated_funding_data() + } +} + /// LDK prior to 0.1 used this constant as the [`ChannelMonitorUpdate::update_id`] for any /// [`ChannelMonitorUpdate`]s which were generated after the channel was closed. const LEGACY_CLOSED_CHANNEL_UPDATE_ID: u64 = u64::MAX; @@ -640,6 +671,11 @@ pub(crate) enum ChannelMonitorUpdateStep { ShutdownScript { scriptpubkey: ScriptBuf, }, + RenegotiatedFunding { + channel_parameters: ChannelTransactionParameters, + holder_commitment_tx: HolderCommitmentTransaction, + counterparty_commitment_tx: CommitmentTransaction, + }, } impl ChannelMonitorUpdateStep { @@ -653,6 +689,7 @@ impl ChannelMonitorUpdateStep { ChannelMonitorUpdateStep::CommitmentSecret { .. } => "CommitmentSecret", ChannelMonitorUpdateStep::ChannelForceClosed { .. } => "ChannelForceClosed", ChannelMonitorUpdateStep::ShutdownScript { .. } => "ShutdownScript", + ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => "RenegotiatedFunding", } } } @@ -691,6 +728,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 +1066,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 +1569,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 +1739,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 +1966,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 +3559,128 @@ 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(); + + let expected_non_dust_htlc_count = htlcs_with_sources + .iter() + // Non-dust HTLCs always come first, so the position of the first dust HTLC is equal to + // our non-dust HTLC count. + .position(|(htlc, _)| htlc.transaction_output_index.is_none()) + .unwrap_or(htlcs_with_sources.len()); + if alternative_htlcs.len() != expected_non_dust_htlc_count { + log_error!( + logger, + "Received alternative counterparty commitment with HTLC count mismatch" + ); + return Err(()); + } + + 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()); + if !alternative_htlc.is_data_equal(htlc) { + log_error!( + logger, + "Received alternative counterparty commitment with non-dust HTLC mismatch" + ); + return Err(()); + } + 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 +3760,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 +3822,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 +4005,9 @@ impl ChannelMonitorImpl { } => { Some(commitment_tx.clone()) }, + &ChannelMonitorUpdateStep::RenegotiatedFunding { ref counterparty_commitment_tx, .. } => { + Some(counterparty_commitment_tx.clone()) + }, _ => None, } }).collect() @@ -5438,6 +5681,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 +5699,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 +5815,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 fb58b51d4dc..3385bac45ad 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1821,7 +1821,32 @@ 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()) + // Not having a signing session implies they've already sent `splice_locked`, + // which must always come after the initial commitment signed is sent. + .unwrap_or(true); + #[cfg(splicing)] + let res = if has_negotiated_pending_splice && !session_received_commitment_signed { + 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 }, @@ -2148,6 +2173,7 @@ impl FundingScope { #[cfg(splicing)] struct PendingSplice { pub our_funding_contribution: i64, + funding: Option, /// The funding txid used in the `splice_locked` sent to the counterparty. sent_funding_txid: Option, @@ -6603,6 +6629,95 @@ where Ok(channel_monitor) } + /// Handles an incoming `commitment_signed` message for the first commitment transaction of the + /// channel's new funding transaction. This assumes our `commitment_signed` was already sent + /// when the [`InteractiveTxSigningSession`] was initialized, so we do not need to send one in + /// response. As a result, a single [`ChannelMonitorUpdate`] will get queued that tracks the new + /// set of channel parameters, as well as the initial holder and counterparty commitment + /// transactions. We hold back sending our `tx_signatures` until the monitor update is + /// persisted, such that we're able to enforce the holder commitment transaction onchain once + /// the new funding transaction is signed and broadcast. + /// + /// Note that our `commitment_signed` send did not include a monitor update. This is due to: + /// 1. Updates cannot be made since the state machine is paused until `tx_signatures`. + /// 2. We're still able to abort negotiation until `tx_signatures`. + #[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, + )?; + // This corresponds to the same `commitment_signed` we sent earlier, which we know to be the + // same since the state machine is paused until `tx_signatures` are exchanged. + 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.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); + self.context.monitor_pending_tx_signatures = tx_signatures; + + Ok(self.push_ret_blockable_mon_update(monitor_update)) + } + pub fn commitment_signed( &mut self, msg: &msgs::CommitmentSigned, logger: &L, ) -> Result, ChannelError> @@ -9830,6 +9945,7 @@ where self.pending_splice = Some(PendingSplice { our_funding_contribution: our_funding_contribution_satoshis, + funding: None, sent_funding_txid: None, received_funding_txid: None, });