diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index bfa5acc3bd5..f62f06222e5 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -676,6 +676,9 @@ pub(crate) enum ChannelMonitorUpdateStep { holder_commitment_tx: HolderCommitmentTransaction, counterparty_commitment_tx: CommitmentTransaction, }, + RenegotiatedFundingLocked { + funding_txid: Txid, + }, } impl ChannelMonitorUpdateStep { @@ -690,6 +693,7 @@ impl ChannelMonitorUpdateStep { ChannelMonitorUpdateStep::ChannelForceClosed { .. } => "ChannelForceClosed", ChannelMonitorUpdateStep::ShutdownScript { .. } => "ShutdownScript", ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => "RenegotiatedFunding", + ChannelMonitorUpdateStep::RenegotiatedFundingLocked { .. } => "RenegotiatedFundingLocked", } } } @@ -733,6 +737,9 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep, (3, holder_commitment_tx, required), (5, counterparty_commitment_tx, required), }, + (12, RenegotiatedFundingLocked) => { + (1, funding_txid, required), + }, ); /// Indicates whether the balance is derived from a cooperative close, a force-close @@ -1209,8 +1216,6 @@ pub(crate) struct ChannelMonitorImpl { // interface knows about the TXOs that we want to be notified of spends of. We could probably // be smart and derive them from the above storage fields, but its much simpler and more // Obviously Correct (tm) if we just keep track of them explicitly. - // - // TODO: Remove entries for stale funding transactions on `splice_locked`. outputs_to_watch: HashMap>, #[cfg(any(test, feature = "_test_utils"))] @@ -3681,6 +3686,43 @@ impl ChannelMonitorImpl { Ok(()) } + fn promote_funding(&mut self, new_funding_txid: Txid) -> Result<(), ()> { + let has_pending_splice = self + .pending_funding + .iter() + .any(|funding| funding.channel_parameters.splice_parent_funding_txid.is_some()); + + let new_funding = self + .pending_funding + .iter_mut() + .find(|funding| funding.funding_txid() == new_funding_txid); + if new_funding.is_none() { + return Err(()); + } + let mut new_funding = new_funding.unwrap(); + + // `first_confirmed_funding_txo` is set to the first outpoint for the channel upon init. + // If an RBF happens and it confirms, this will no longer be accurate, so update it now + // if we know the RBF doesn't belong to a splice. + if !has_pending_splice + && self.first_confirmed_funding_txo == self.funding.funding_outpoint() + { + self.first_confirmed_funding_txo = new_funding.funding_outpoint(); + } + + mem::swap(&mut self.funding, &mut new_funding); + self.onchain_tx_handler.update_after_renegotiated_funding_locked( + self.funding.current_holder_commitment_tx.clone(), + self.funding.prev_holder_commitment_tx.clone(), + ); + + for funding in self.pending_funding.drain(..) { + self.outputs_to_watch.remove(&funding.funding_txid()); + } + + Ok(()) + } + #[rustfmt::skip] fn update_monitor( &mut self, updates: &ChannelMonitorUpdate, broadcaster: &B, fee_estimator: &F, logger: &WithChannelMonitor @@ -3771,6 +3813,13 @@ impl ChannelMonitorImpl { ret = Err(()); } }, + ChannelMonitorUpdateStep::RenegotiatedFundingLocked { funding_txid } => { + log_trace!(logger, "Updating ChannelMonitor with locked renegotiated funding txid {}", funding_txid); + if let Err(_) = self.promote_funding(*funding_txid) { + log_error!(logger, "Unknown funding with txid {} became locked", funding_txid); + ret = Err(()); + } + }, ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => { log_trace!(logger, "Updating ChannelMonitor: channel force closed, should broadcast: {}", should_broadcast); self.lockdown_from_offchain = true; @@ -3823,7 +3872,8 @@ impl ChannelMonitorImpl { |ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTX { .. } |ChannelMonitorUpdateStep::ShutdownScript { .. } |ChannelMonitorUpdateStep::CommitmentSecret { .. } - |ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => + |ChannelMonitorUpdateStep::RenegotiatedFunding { .. } + |ChannelMonitorUpdateStep::RenegotiatedFundingLocked { .. } => 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) diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index fd0f0d9abf5..89952ebee6b 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -1216,6 +1216,15 @@ impl OnchainTxHandler { self.prev_holder_commitment = Some(replace(&mut self.holder_commitment, tx)); } + /// Replaces the current/prev holder commitment transactions spending the currently confirmed + /// funding outpoint with those spending the new funding outpoint. + pub(crate) fn update_after_renegotiated_funding_locked( + &mut self, current: HolderCommitmentTransaction, prev: Option, + ) { + self.holder_commitment = current; + self.prev_holder_commitment = prev; + } + // Deprecated as of 0.2, only use in cases where it was not previously available. pub(crate) fn channel_parameters(&self) -> &ChannelTransactionParameters { &self.channel_transaction_parameters diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 56273d126ce..6a5ba528e43 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -9673,11 +9673,25 @@ where .get_funding_txo() .expect("Splice FundingScope should always have a funding_txo") }); + + let monitor_update_opt = funding_promoted.then(|| { + self.context.latest_monitor_update_id += 1; + let monitor_update = ChannelMonitorUpdate { + update_id: self.context.latest_monitor_update_id, + updates: vec![ChannelMonitorUpdateStep::RenegotiatedFundingLocked { + funding_txid: funding_txo.unwrap().txid, + }], + channel_id: Some(self.context.channel_id()), + }; + self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); + self.push_ret_blockable_mon_update(monitor_update) + }).flatten(); + let announcement_sigs = funding_promoted .then(|| self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)) .flatten(); - return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo)), announcement_sigs)); + return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt)), announcement_sigs)); } } @@ -9842,6 +9856,20 @@ where .get_funding_txo() .expect("Splice FundingScope should always have a funding_txo") }); + + let monitor_update_opt = funding_promoted.then(|| { + self.context.latest_monitor_update_id += 1; + let monitor_update = ChannelMonitorUpdate { + update_id: self.context.latest_monitor_update_id, + updates: vec![ChannelMonitorUpdateStep::RenegotiatedFundingLocked { + funding_txid: funding_txo.unwrap().txid, + }], + channel_id: Some(self.context.channel_id()), + }; + self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); + self.push_ret_blockable_mon_update(monitor_update) + }).flatten(); + let announcement_sigs = funding_promoted .then(|| chain_node_signer .and_then(|(chain_hash, node_signer, user_config)| @@ -9850,7 +9878,7 @@ where ) .flatten(); - return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo)), timed_out_htlcs, announcement_sigs)); + return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt)), timed_out_htlcs, announcement_sigs)); } } @@ -10345,7 +10373,10 @@ where pub fn splice_locked( &mut self, msg: &msgs::SpliceLocked, node_signer: &NS, chain_hash: ChainHash, user_config: &UserConfig, best_block: &BestBlock, logger: &L, - ) -> Result<(Option, Option), ChannelError> + ) -> Result< + (Option, Option, Option), + ChannelError, + > where NS::Target: NodeSigner, L::Target: Logger, @@ -10371,6 +10402,7 @@ where .iter_mut() .find(|funding| funding.get_funding_txid() == Some(sent_funding_txid)) { + // TODO: Do we need to do any of this after the channel is resumed? log_info!( logger, "Promoting splice funding txid {} for channel {}", @@ -10378,10 +10410,30 @@ where &self.context.channel_id, ); promote_splice_funding!(self, funding); + let funding_txo = self .funding .get_funding_txo() .expect("Splice FundingScope should always have a funding_txo"); + + self.context.latest_monitor_update_id += 1; + let monitor_update = ChannelMonitorUpdate { + update_id: self.context.latest_monitor_update_id, + updates: vec![ChannelMonitorUpdateStep::RenegotiatedFundingLocked { + funding_txid: funding_txo.txid, + }], + channel_id: Some(self.context.channel_id()), + }; + self.monitor_updating_paused( + false, + false, + false, + Vec::new(), + Vec::new(), + Vec::new(), + ); + let monitor_update_opt = self.push_ret_blockable_mon_update(monitor_update); + let announcement_sigs = self.get_announcement_sigs( node_signer, chain_hash, @@ -10389,7 +10441,8 @@ where best_block.height, logger, ); - return Ok((Some(funding_txo), announcement_sigs)); + + return Ok((Some(funding_txo), monitor_update_opt, announcement_sigs)); } let err = "unknown splice funding txid"; @@ -10413,7 +10466,7 @@ where } pending_splice.received_funding_txid = Some(msg.splice_txid); - Ok((None, None)) + Ok((None, None, None)) } // Send stuff to our remote peers: diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9e7031bf8cb..d1e6cbd3b28 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10222,7 +10222,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &self.best_block.read().unwrap(), &&logger, ); - let (funding_txo, announcement_sigs_opt) = + let (funding_txo, monitor_update_opt, announcement_sigs_opt) = try_channel_entry!(self, peer_state, result, chan_entry); if funding_txo.is_some() { @@ -10256,6 +10256,21 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }, ); } + + if let Some(monitor_update) = monitor_update_opt { + let funding_txo = funding_txo.expect( + "Monitor update is only guaranteed if the splice funding was promoted", + ); + handle_new_monitor_update!( + self, + funding_txo, + monitor_update, + peer_state_lock, + peer_state, + per_peer_state, + chan + ); + } } else { return Err(MsgHandleErrInternal::send_err_msg_no_close( "Channel is not funded, cannot splice".to_owned(), @@ -12280,7 +12295,7 @@ where pub(super) enum FundingConfirmedMessage { Establishment(msgs::ChannelReady), #[cfg(splicing)] - Splice(msgs::SpliceLocked, Option), + Splice(msgs::SpliceLocked, Option, Option), } impl< @@ -12315,6 +12330,7 @@ where // during initialization prior to the chain_monitor being fully configured in some cases. // See the docs for `ChannelManagerReadArgs` for more. + let mut monitor_updates = Vec::new(); let mut failed_channels = Vec::new(); let mut timed_out_htlcs = Vec::new(); { @@ -12354,25 +12370,32 @@ where } }, #[cfg(splicing)] - Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo)) => { + Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt)) => { + let counterparty_node_id = funded_channel.context.get_counterparty_node_id(); + let channel_id = funded_channel.context.channel_id(); + if funding_txo.is_some() { let mut short_to_chan_info = self.short_to_chan_info.write().unwrap(); insert_short_channel_id!(short_to_chan_info, funded_channel); let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push_back((events::Event::ChannelReady { - channel_id: funded_channel.context.channel_id(), + channel_id, user_channel_id: funded_channel.context.get_user_id(), - counterparty_node_id: funded_channel.context.get_counterparty_node_id(), + counterparty_node_id, funding_txo: funding_txo.map(|outpoint| outpoint.into_bitcoin_outpoint()), channel_type: funded_channel.funding.get_channel_type().clone(), }, None)); } pending_msg_events.push(MessageSendEvent::SendSpliceLocked { - node_id: funded_channel.context.get_counterparty_node_id(), + node_id: counterparty_node_id, msg: splice_locked, }); + + if let Some(monitor_update) = monitor_update_opt { + monitor_updates.push((counterparty_node_id, channel_id, monitor_update)); + } }, None => {}, } @@ -12470,6 +12493,31 @@ where } } + #[cfg(splicing)] + for (counterparty_node_id, channel_id, monitor_update) in monitor_updates { + let per_peer_state = self.per_peer_state.read().unwrap(); + if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) { + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + if let Some(channel) = peer_state + .channel_by_id + .get_mut(&channel_id) + .and_then(Channel::as_funded_mut) + { + let funding_txo = channel.funding.get_funding_txo().unwrap(); + handle_new_monitor_update!( + self, + funding_txo, + monitor_update, + peer_state_lock, + peer_state, + per_peer_state, + channel + ); + } + } + } + if let Some(height) = height_opt { self.claimable_payments.lock().unwrap().claimable_payments.retain(|payment_hash, payment| { payment.htlcs.retain(|htlc| {