From 851472502ed84ae17f2ec1f460ecdce2d30bd4ec Mon Sep 17 00:00:00 2001 From: jbesraa Date: Thu, 9 May 2024 16:37:33 +0300 Subject: [PATCH] Add `FundingTxBroadcastSafe` event The `FundingTxBroadcastSafe` event indicates that we have received `funding_signed` message from our counterparty and that you should broadcast the funding transaction. This event is only emitted if upon generating the funding transaction you call `ChannelManager::unsafe_manual_funding_transaction_generated` that will emit this event instead of `ChannelPending` event. `ChannelManager::unsafe_manual_funding_transaction_generated` wont check if the funding transaction is signed, those its unsafe. It is manual because you are responsibile on broadcasting the transaction once the event is received. --- lightning/src/events/mod.rs | 64 ++++++- lightning/src/ln/channel.rs | 48 +++++ lightning/src/ln/channelmanager.rs | 209 ++++++++++++++++++---- lightning/src/ln/functional_test_utils.rs | 33 ++++ lightning/src/ln/functional_tests.rs | 85 +++++++++ 5 files changed, 399 insertions(+), 40 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 887851864c..a81394a6bf 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -579,6 +579,37 @@ pub enum Event { /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels user_channel_id: u128, }, + /// Used to indicate that the counterparty node has provided the signature(s) required to + /// recover our funds in case they go offline. + /// + /// It is safe (and your responsibility) to broadcast the funding transaction upon receiving this + /// event. + /// + /// This event is only emitted if you called + /// [`ChannelManager::unsafe_manual_funding_transaction_generated`] instead of + /// [`ChannelManager::funding_transaction_generated`]. + /// + /// [`ChannelManager::unsafe_manual_funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::unsafe_manual_funding_transaction_generated + /// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated + FundingTxBroadcastSafe { + /// The `channel_id` indicating which channel has reached this stage. + channel_id: ChannelId, + /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound + /// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if + /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to `true`. Otherwise + /// `user_channel_id` will be randomized for an inbound channel. + /// + /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + user_channel_id: u128, + /// The outpoint of the channel's funding transaction. + funding_txo: OutPoint, + /// The `node_id` of the channel counterparty. + counterparty_node_id: PublicKey, + /// The `temporary_channel_id` this channel used to be known by during channel establishment. + former_temporary_channel_id: ChannelId, + }, /// Indicates that we've been offered a payment and it needs to be claimed via calling /// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`]. /// @@ -1528,7 +1559,17 @@ impl Writeable for Event { (0, payment_id, required), (2, invoice, required), (4, responder, option), - }) + }); + }, + &Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_txo, ref counterparty_node_id, ref former_temporary_channel_id} => { + 43u8.write(writer)?; + write_tlv_fields!(writer, { + (0, channel_id, required), + (2, user_channel_id, required), + (4, funding_txo, required), + (6, counterparty_node_id, required), + (8, former_temporary_channel_id, required), + }); }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write @@ -1981,6 +2022,27 @@ impl MaybeReadable for Event { }; f() }, + 43u8 => { + let mut channel_id = RequiredWrapper(None); + let mut user_channel_id = RequiredWrapper(None); + let mut funding_txo = RequiredWrapper(None); + let mut counterparty_node_id = RequiredWrapper(None); + let mut former_temporary_channel_id = RequiredWrapper(None); + read_tlv_fields!(reader, { + (0, channel_id, required), + (2, user_channel_id, required), + (4, funding_txo, required), + (6, counterparty_node_id, required), + (8, former_temporary_channel_id, required) + }); + Ok(Some(Event::FundingTxBroadcastSafe { + channel_id: channel_id.0.unwrap(), + user_channel_id: user_channel_id.0.unwrap(), + funding_txo: funding_txo.0.unwrap(), + counterparty_node_id: counterparty_node_id.0.unwrap(), + former_temporary_channel_id: former_temporary_channel_id.0.unwrap(), + })) + }, // Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. // Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt // reads. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f5c47260f6..2cb36deedd 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1331,7 +1331,12 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { counterparty_forwarding_info: Option, pub(crate) channel_transaction_parameters: ChannelTransactionParameters, + /// The transaction which funds this channel. Note that for manually-funded channels (i.e., + /// is_manual_broadcast is true) this will be a dummy empty transaction. funding_transaction: Option, + /// This flag indicates that it is the user's responsibility to validated and broadcast the + /// funding transaction. + is_manual_broadcast: bool, is_batch_funding: Option<()>, counterparty_cur_commitment_point: Option, @@ -1419,6 +1424,9 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { // We track whether we already emitted a `ChannelPending` event. channel_pending_event_emitted: bool, + // We track whether we already emitted a `FundingTxBroadcastSafe` event. + funding_tx_broadcast_safe_event_emitted: bool, + // We track whether we already emitted a `ChannelReady` event. channel_ready_event_emitted: bool, @@ -1758,6 +1766,7 @@ impl ChannelContext where SP::Target: SignerProvider { outbound_scid_alias: 0, channel_pending_event_emitted: false, + funding_tx_broadcast_safe_event_emitted: false, channel_ready_event_emitted: false, #[cfg(any(test, fuzzing))] @@ -1769,6 +1778,8 @@ impl ChannelContext where SP::Target: SignerProvider { local_initiated_shutdown: None, blocked_monitor_updates: Vec::new(), + + is_manual_broadcast: false, }; Ok(channel_context) @@ -1982,6 +1993,7 @@ impl ChannelContext where SP::Target: SignerProvider { outbound_scid_alias, channel_pending_event_emitted: false, + funding_tx_broadcast_safe_event_emitted: false, channel_ready_event_emitted: false, #[cfg(any(test, fuzzing))] @@ -1992,6 +2004,7 @@ impl ChannelContext where SP::Target: SignerProvider { blocked_monitor_updates: Vec::new(), local_initiated_shutdown: None, + is_manual_broadcast: false, }) } @@ -2370,6 +2383,10 @@ impl ChannelContext where SP::Target: SignerProvider { self.config.options.forwarding_fee_proportional_millionths } + pub fn is_manual_broadcast(&self) -> bool { + self.is_manual_broadcast + } + pub fn get_cltv_expiry_delta(&self) -> u16 { cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA) } @@ -2404,6 +2421,11 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_pending_event_emitted } + // Returns whether we already emitted a `FundingTxBroadcastSafe` event. + pub(crate) fn funding_tx_broadcast_safe_event_emitted(&self) -> bool { + self.funding_tx_broadcast_safe_event_emitted + } + // Remembers that we already emitted a `ChannelPending` event. pub(crate) fn set_channel_pending_event_emitted(&mut self) { self.channel_pending_event_emitted = true; @@ -2419,6 +2441,11 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_ready_event_emitted = true; } + // Remembers that we already emitted a `FundingTxBroadcastSafe` event. + pub(crate) fn set_funding_tx_broadcast_safe_event_emitted(&mut self) { + self.funding_tx_broadcast_safe_event_emitted = true; + } + /// Tracks the number of ticks elapsed since the previous [`ChannelConfig`] was updated. Once /// [`EXPIRE_PREV_CONFIG_TICKS`] is reached, the previous config is considered expired and will /// no longer be considered when forwarding HTLCs. @@ -2455,6 +2482,17 @@ impl ChannelContext where SP::Target: SignerProvider { did_channel_update } + /// Marking the channel as manual broadcast is used in order to prevent LDK from automatically + /// broadcasting the funding transaction. + /// + /// This is useful if you wish to get hold of the funding transaction before it is broadcasted + /// via [`Event::FundingTxBroadcastSafe`] event. + /// + /// [`Event::FundingTxBroadcastSafe`]: crate::events::Event::FundingTxBroadcastSafe + pub fn set_manual_broadcast(&mut self) { + self.is_manual_broadcast = true; + } + /// Returns true if funding_signed was sent/received and the /// funding transaction has been broadcast if necessary. pub fn is_funding_broadcast(&self) -> bool { @@ -8705,6 +8743,7 @@ impl Writeable for Channel where SP::Target: SignerProvider { let channel_pending_event_emitted = Some(self.context.channel_pending_event_emitted); let channel_ready_event_emitted = Some(self.context.channel_ready_event_emitted); + let funding_tx_broadcast_safe_event_emitted = Some(self.context.funding_tx_broadcast_safe_event_emitted); // `user_id` used to be a single u64 value. In order to remain backwards compatible with // versions prior to 0.0.113, the u128 is serialized as two separate u64 values. Therefore, @@ -8717,6 +8756,7 @@ impl Writeable for Channel where SP::Target: SignerProvider { if !self.context.monitor_pending_update_adds.is_empty() { monitor_pending_update_adds = Some(&self.context.monitor_pending_update_adds); } + let is_manual_broadcast = Some(self.context.is_manual_broadcast); // `current_point` will become optional when async signing is implemented. let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point()); @@ -8761,6 +8801,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { (45, cur_holder_commitment_point, option), (47, next_holder_commitment_point, option), (49, self.context.local_initiated_shutdown, option), // Added in 0.0.122 + (51, is_manual_broadcast, option), // Added in 0.0.124 + (53, funding_tx_broadcast_safe_event_emitted, option) // Added in 0.0.124 }); Ok(()) @@ -9049,6 +9091,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch let mut outbound_scid_alias = None; let mut channel_pending_event_emitted = None; let mut channel_ready_event_emitted = None; + let mut funding_tx_broadcast_safe_event_emitted = None; let mut user_id_high_opt: Option = None; let mut channel_keys_id: Option<[u8; 32]> = None; @@ -9072,6 +9115,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch let mut cur_holder_commitment_point_opt: Option = None; let mut next_holder_commitment_point_opt: Option = None; + let mut is_manual_broadcast = None; read_tlv_fields!(reader, { (0, announcement_sigs, option), @@ -9106,6 +9150,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch (45, cur_holder_commitment_point_opt, option), (47, next_holder_commitment_point_opt, option), (49, local_initiated_shutdown, option), + (51, is_manual_broadcast, option), + (53, funding_tx_broadcast_safe_event_emitted, option), }); let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id { @@ -9346,6 +9392,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch // Later in the ChannelManager deserialization phase we scan for channels and assign scid aliases if its missing outbound_scid_alias: outbound_scid_alias.unwrap_or(0), + funding_tx_broadcast_safe_event_emitted: funding_tx_broadcast_safe_event_emitted.unwrap_or(false), channel_pending_event_emitted: channel_pending_event_emitted.unwrap_or(true), channel_ready_event_emitted: channel_ready_event_emitted.unwrap_or(true), @@ -9358,6 +9405,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch local_initiated_shutdown, blocked_monitor_updates: blocked_monitor_updates.unwrap(), + is_manual_broadcast: is_manual_broadcast.unwrap_or(false), }, #[cfg(any(dual_funding, splicing))] dual_funding_channel_context: None, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9e573a705f..76f006a009 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -698,6 +698,51 @@ struct ClaimablePayment { htlcs: Vec, } +/// Represent the channel funding transaction type. +enum FundingType { + /// This variant is useful when we want LDK to validate the funding transaction and + /// broadcast it automatically. + /// + /// This is the normal flow. + Checked(Transaction), + /// This variant is useful when we want to loosen the validation checks and allow to + /// manually broadcast the funding transaction, leaving the responsibility to the caller. + /// + /// This is useful in cases of constructing the funding transaction as part of another + /// flow and the caller wants to perform the validation and broadcasting. An example of such + /// scenario could be when constructing the funding transaction as part of a Payjoin + /// transaction. + Unchecked(OutPoint), +} + +impl FundingType { + fn txid(&self) -> Txid { + match self { + FundingType::Checked(tx) => tx.txid(), + FundingType::Unchecked(outp) => outp.txid, + } + } + + fn transaction_or_dummy(&self) -> Transaction { + match self { + FundingType::Checked(tx) => tx.clone(), + FundingType::Unchecked(_) => Transaction { + version: bitcoin::transaction::Version::TWO, + lock_time: bitcoin::absolute::LockTime::ZERO, + input: Vec::new(), + output: Vec::new(), + }, + } + } + + fn is_manual_broadcast(&self) -> bool { + match self { + FundingType::Checked(_) => false, + FundingType::Unchecked(_) => true, + } + } +} + /// Information about claimable or being-claimed payments struct ClaimablePayments { /// Map from payment hash to the payment data and any HTLCs which are to us and can be @@ -2567,6 +2612,21 @@ macro_rules! send_channel_ready { } }} } +macro_rules! emit_funding_tx_broadcast_safe_event { + ($locked_events: expr, $channel: expr, $funding_txo: expr) => { + if !$channel.context.funding_tx_broadcast_safe_event_emitted() { + $locked_events.push_back((events::Event::FundingTxBroadcastSafe { + channel_id: $channel.context.channel_id(), + user_channel_id: $channel.context.get_user_id(), + funding_txo: $funding_txo, + counterparty_node_id: $channel.context.get_counterparty_node_id(), + former_temporary_channel_id: $channel.context.temporary_channel_id() + .expect("Unreachable: FundingTxBroadcastSafe event feature added to channel establishment process in LDK v0.0.124 where this should never be None."), + }, None)); + $channel.context.set_funding_tx_broadcast_safe_event_emitted(); + } + } +} macro_rules! emit_channel_pending_event { ($locked_events: expr, $channel: expr) => { @@ -4236,9 +4296,9 @@ where /// Handles the generation of a funding transaction, optionally (for tests) with a function /// which checks the correctness of the funding transaction given the associated channel. - fn funding_transaction_generated_intern, &Transaction) -> Result>( + fn funding_transaction_generated_intern) -> Result>( &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, is_batch_funding: bool, - mut find_funding_output: FundingOutput, + mut find_funding_output: FundingOutput, is_manual_broadcast: bool, ) -> Result<(), APIError> { let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) @@ -4263,7 +4323,7 @@ where let _: Result<(), _> = handle_error!(self, Err(err), counterparty); Err($api_err) } } } - match find_funding_output(&chan, &funding_transaction) { + match find_funding_output(&chan) { Ok(found_funding_txo) => funding_txo = found_funding_txo, Err(err) => { let chan_err = ChannelError::close(err.to_owned()); @@ -4302,6 +4362,9 @@ where msg, }); } + if is_manual_broadcast { + chan.context.set_manual_broadcast(); + } match peer_state.channel_by_id.entry(chan.context.channel_id()) { hash_map::Entry::Occupied(_) => { panic!("Generated duplicate funding txid?"); @@ -4331,9 +4394,10 @@ where #[cfg(test)] pub(crate) fn funding_transaction_generated_unchecked(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, output_index: u16) -> Result<(), APIError> { - self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_, tx| { - Ok(OutPoint { txid: tx.txid(), index: output_index }) - }) + let txid = funding_transaction.txid(); + self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_| { + Ok(OutPoint { txid, index: output_index }) + }, false) } /// Call this upon creation of a funding transaction for the given channel. @@ -4370,6 +4434,45 @@ where self.batch_funding_transaction_generated(&[(temporary_channel_id, counterparty_node_id)], funding_transaction) } + + /// **Unsafe**: This method does not check the validity of the provided output. It is the + /// caller's responsibility to ensure that. + /// + /// For a safer method, please refer to [`ChannelManager::funding_transaction_generated`]. + /// + /// Call this in response to a [`Event::FundingGenerationReady`] event. + /// + /// Note that if this method is called successfully, the funding transaction won't be + /// broadcasted and you are expected to broadcast it manually when receiving the + /// [`Event::FundingTxBroadcastSafe`] event. + /// + /// Returns an [`APIError::APIMisuseError`] if no output was found which matches the parameters + /// in [`Event::FundingGenerationReady`]. + /// + /// Returns [`APIError::ChannelUnavailable`] if a funding transaction has already been provided + /// for the channel or if the channel has been closed as indicated by [`Event::ChannelClosed`]. + /// + /// May panic if the output found in the funding transaction is duplicative with some other + /// channel (note that this should be trivially prevented by using unique funding transaction + /// keys per-channel). + /// + /// Note to keep the miner incentives aligned in moving the blockchain forward, we recommend + /// the wallet software generating the funding transaction to apply anti-fee sniping as + /// implemented by Bitcoin Core wallet. See for + /// more details. + /// + /// [`Event::FundingGenerationReady`]: crate::events::Event::FundingGenerationReady + /// [`Event::FundingTxBroadcastSafe`]: crate::events::Event::FundingTxBroadcastSafe + /// [`Event::ChannelClosed`]: crate::events::Event::ChannelClosed + /// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated + pub fn unsafe_manual_funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding: OutPoint) -> Result<(), APIError> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + + let temporary_channels = &[(temporary_channel_id, counterparty_node_id)]; + return self.batch_funding_transaction_generated_intern(temporary_channels, FundingType::Unchecked(funding)); + + } + /// Call this upon creation of a batch funding transaction for the given channels. /// /// Return values are identical to [`Self::funding_transaction_generated`], respective to @@ -4393,28 +4496,35 @@ where } } } - if funding_transaction.output.len() > u16::max_value() as usize { - result = result.and(Err(APIError::APIMisuseError { - err: "Transaction had more than 2^16 outputs, which is not supported".to_owned() - })); - } - { - let height = self.best_block.read().unwrap().height; - // Transactions are evaluated as final by network mempools if their locktime is strictly - // lower than the next block height. However, the modules constituting our Lightning - // node might not have perfect sync about their blockchain views. Thus, if the wallet - // module is ahead of LDK, only allow one more block of headroom. - if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) && - funding_transaction.lock_time.is_block_height() && - funding_transaction.lock_time.to_consensus_u32() > height + 1 - { + result.and(self.batch_funding_transaction_generated_intern(temporary_channels, FundingType::Checked(funding_transaction))) + } + + fn batch_funding_transaction_generated_intern(&self, temporary_channels: &[(&ChannelId, &PublicKey)], funding: FundingType) -> Result<(), APIError> { + let mut result = Ok(()); + if let FundingType::Checked(funding_transaction) = &funding { + if funding_transaction.output.len() > u16::max_value() as usize { result = result.and(Err(APIError::APIMisuseError { - err: "Funding transaction absolute timelock is non-final".to_owned() + err: "Transaction had more than 2^16 outputs, which is not supported".to_owned() })); } + { + let height = self.best_block.read().unwrap().height; + // Transactions are evaluated as final by network mempools if their locktime is strictly + // lower than the next block height. However, the modules constituting our Lightning + // node might not have perfect sync about their blockchain views. Thus, if the wallet + // module is ahead of LDK, only allow one more block of headroom. + if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) && + funding_transaction.lock_time.is_block_height() && + funding_transaction.lock_time.to_consensus_u32() > height + 1 + { + result = result.and(Err(APIError::APIMisuseError { + err: "Funding transaction absolute timelock is non-final".to_owned() + })); + } + } } - let txid = funding_transaction.txid(); + let txid = funding.txid(); let is_batch_funding = temporary_channels.len() > 1; let mut funding_batch_states = if is_batch_funding { Some(self.funding_batch_states.lock().unwrap()) @@ -4432,27 +4542,33 @@ where btree_map::Entry::Vacant(vacant) => Some(vacant.insert(Vec::new())), } }); + let is_manual_broadcast = funding.is_manual_broadcast(); for &(temporary_channel_id, counterparty_node_id) in temporary_channels { result = result.and_then(|_| self.funding_transaction_generated_intern( temporary_channel_id, counterparty_node_id, - funding_transaction.clone(), + funding.transaction_or_dummy(), is_batch_funding, - |chan, tx| { + |chan| { let mut output_index = None; let expected_spk = chan.context.get_funding_redeemscript().to_p2wsh(); - for (idx, outp) in tx.output.iter().enumerate() { - if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() { - if output_index.is_some() { - return Err("Multiple outputs matched the expected script and value"); + let outpoint = match &funding { + FundingType::Checked(tx) => { + for (idx, outp) in tx.output.iter().enumerate() { + if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() { + if output_index.is_some() { + return Err("Multiple outputs matched the expected script and value"); + } + output_index = Some(idx as u16); + } } - output_index = Some(idx as u16); - } - } - if output_index.is_none() { - return Err("No output matched the script_pubkey and value in the FundingGenerationReady event"); - } - let outpoint = OutPoint { txid: tx.txid(), index: output_index.unwrap() }; + if output_index.is_none() { + return Err("No output matched the script_pubkey and value in the FundingGenerationReady event"); + } + OutPoint { txid, index: output_index.unwrap() } + }, + FundingType::Unchecked(outpoint) => outpoint.clone(), + }; if let Some(funding_batch_state) = funding_batch_state.as_mut() { // TODO(dual_funding): We only do batch funding for V1 channels at the moment, but we'll probably // need to fix this somehow to not rely on using the outpoint for the channel ID if we @@ -4460,7 +4576,8 @@ where funding_batch_state.push((ChannelId::v1_from_funding_outpoint(outpoint), *counterparty_node_id, false)); } Ok(outpoint) - }) + }, + is_manual_broadcast) ); } if let Err(ref e) = result { @@ -6596,8 +6713,22 @@ where } if let Some(tx) = funding_broadcastable { - log_info!(logger, "Broadcasting funding transaction with txid {}", tx.txid()); - self.tx_broadcaster.broadcast_transactions(&[&tx]); + if channel.context.is_manual_broadcast() { + log_info!(logger, "Not broadcasting funding transaction with txid {} as it is manually managed", tx.txid()); + let mut pending_events = self.pending_events.lock().unwrap(); + match channel.context.get_funding_txo() { + Some(funding_txo) => { + emit_funding_tx_broadcast_safe_event!(pending_events, channel, funding_txo.into_bitcoin_outpoint()) + }, + None => { + log_error!(logger, "Channel resumed without a funding txo, this should never happen!"); + return (htlc_forwards, decode_update_add_htlcs); + } + }; + } else { + log_info!(logger, "Broadcasting funding transaction with txid {}", tx.txid()); + self.tx_broadcaster.broadcast_transactions(&[&tx]); + } } { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 8c08d37f5f..955cbdee1b 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1165,6 +1165,39 @@ pub fn create_coinbase_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, true) } +pub fn create_funding_tx_without_witness_data<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, + expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128) -> (ChannelId, Transaction, OutPoint) { + let chan_id = *node.network_chan_count.borrow(); + + let events = node.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::FundingGenerationReady { ref temporary_channel_id, ref counterparty_node_id, ref channel_value_satoshis, ref output_script, user_channel_id } => { + assert_eq!(counterparty_node_id, expected_counterparty_node_id); + assert_eq!(*channel_value_satoshis, expected_chan_value); + assert_eq!(user_channel_id, expected_user_chan_id); + + let dummy_outpoint = bitcoin::OutPoint { txid: bitcoin::Txid::from_slice(&[1; 32]).unwrap(), vout: 0 }; + let dummy_script_sig = bitcoin::ScriptBuf::new(); + let dummy_sequence = bitcoin::Sequence::ZERO; + let dummy_witness = bitcoin::Witness::new(); + let input = vec![TxIn { + previous_output: dummy_outpoint, + script_sig: dummy_script_sig, + sequence: dummy_sequence, + witness: dummy_witness, + }]; + + let tx = Transaction { version: transaction::Version(chan_id as i32), lock_time: LockTime::ZERO, input, output: vec![TxOut { + value: Amount::from_sat(*channel_value_satoshis), script_pubkey: output_script.clone(), + }]}; + let funding_outpoint = OutPoint { txid: tx.txid(), index: 0 }; + (*temporary_channel_id, tx, funding_outpoint) + }, + _ => panic!("Unexpected event"), + } +} + fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128, coinbase: bool) -> (ChannelId, Transaction, OutPoint) { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 38be3d8b17..0d40bb4f6a 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -3783,6 +3783,32 @@ fn test_peer_disconnected_before_funding_broadcasted() { , [nodes[0].node.get_our_node_id()], 1000000); } +#[test] +fn test_unsafe_manual_funding_transaction_generated() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let expected_temporary_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, None).unwrap(); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel); + let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); + nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel); + + let (temporary_channel_id, _, funding_outpoint) = create_funding_tx_without_witness_data(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42); + assert_eq!(temporary_channel_id, expected_temporary_channel_id); + + assert!(nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), funding_outpoint).is_ok()); + let node_0_msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + match node_0_msg_events[0] { + MessageSendEvent::SendFundingCreated { ref node_id, .. } => { + assert_eq!(node_id, &nodes[1].node.get_our_node_id()); + }, + _ => panic!("Unexpected event"), + } +} + #[test] fn test_simple_peer_disconnect() { // Test that we can reconnect when there are no lost messages @@ -11205,3 +11231,62 @@ fn test_accept_inbound_channel_errors_queued() { assert_eq!(get_err_msg(&nodes[1], &nodes[0].node.get_our_node_id()).channel_id, open_channel_msg.common_fields.temporary_channel_id); } + +#[test] +fn test_funding_signed_event() { + let mut cfg = UserConfig::default(); + cfg.channel_handshake_config.minimum_depth = 1; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg), Some(cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + assert!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None, None).is_ok()); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + + nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel); + let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel); + let (temporary_channel_id, tx, funding_outpoint) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100_000, 42); + nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), funding_outpoint).unwrap(); + check_added_monitors!(nodes[0], 0); + + let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created); + check_added_monitors!(nodes[1], 1); + expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id()); + + let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id()); + nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed); + check_added_monitors!(nodes[0], 1); + let events = &nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match &events[0] { + crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => { + assert_eq!(funding_txo.txid, funding_outpoint.txid); + assert_eq!(funding_txo.vout, funding_outpoint.index.into()); + }, + _ => panic!("Unexpected event"), + }; + match &events[1] { + crate::events::Event::ChannelPending { counterparty_node_id, .. } => { + assert_eq!(*&nodes[1].node.get_our_node_id(), *counterparty_node_id); + }, + _ => panic!("Unexpected event"), + }; + + mine_transaction(&nodes[0], &tx); + mine_transaction(&nodes[1], &tx); + + let as_channel_ready = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReady, nodes[0].node.get_our_node_id()); + nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_channel_ready); + let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id()); + nodes[0].node.handle_channel_ready(&nodes[1].node.get_our_node_id(), &as_channel_ready); + + expect_channel_ready_event(&nodes[0], &nodes[1].node.get_our_node_id()); + expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id()); + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); +} +