From 982e25de0e5731cb5c7a6d8d5736acf2ee3184cc Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Thu, 19 Oct 2023 15:47:33 +0200 Subject: [PATCH] Handle initial commitment_signed for V2 channels --- lightning/src/ln/channel.rs | 296 ++++++++++++++++++++++++++--- lightning/src/ln/channelmanager.rs | 217 ++++++++++++++++++--- 2 files changed, 461 insertions(+), 52 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 0d8e53515..1904a2a38 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -32,7 +32,7 @@ use crate::types::payment::{PaymentPreimage, PaymentHash}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; use crate::ln::interactivetxs::{ get_output_weight, HandleTxCompleteResult, InteractiveTxConstructor, InteractiveTxConstructorArgs, - InteractiveTxMessageSend, InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT, + InteractiveTxSigningSession, InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT, }; use crate::ln::msgs; use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError}; @@ -55,7 +55,7 @@ use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Channel use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient}; -use crate::events::ClosureReason; +use crate::events::{ClosureReason, Event}; use crate::routing::gossip::NodeId; use crate::util::ser::{Readable, ReadableArgs, TransactionU16LenLimited, Writeable, Writer}; use crate::util::logger::{Logger, Record, WithContext}; @@ -1642,6 +1642,20 @@ impl InitialRemoteCommitmentReceiver for InboundV1Channel whe } } +impl InitialRemoteCommitmentReceiver for Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + &self.context + } + + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + + fn received_msg(&self) -> &'static str { + "commitment_signed" + } +} + pub(super) trait InteractivelyFunded where SP::Target: SignerProvider { fn context(&self) -> &ChannelContext; @@ -1705,6 +1719,87 @@ pub(super) trait InteractivelyFunded where SP::Target: SignerProvider }), }) } + + fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let our_funding_satoshis = self.dual_funding_context().our_funding_satoshis; + let context = self.context_mut(); + + let mut output_index = None; + let expected_spk = context.get_funding_redeemscript().to_p2wsh(); + for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { + if outp.script_pubkey() == &expected_spk && outp.value() == context.get_value_satoshis() { + if output_index.is_some() { + return Err(ChannelError::Close( + ( + "Multiple outputs matched the expected script and value".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + output_index = Some(idx as u16); + } + } + let outpoint = if let Some(output_index) = output_index { + OutPoint { txid: signing_session.unsigned_tx.compute_txid(), index: output_index } + } else { + return Err(ChannelError::Close( + ( + "No output matched the funding script_pubkey".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + }; + context.channel_transaction_parameters.funding_outpoint = Some(outpoint); + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let commitment_signed = context.get_initial_commitment_signed(logger); + let commitment_signed = match commitment_signed { + Ok(commitment_signed) => { + context.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); + commitment_signed + }, + Err(err) => { + context.channel_transaction_parameters.funding_outpoint = None; + return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))) + }, + }; + + let funding_ready_for_sig_event = None; + if signing_session.local_inputs_count() == 0 { + debug_assert_eq!(our_funding_satoshis, 0); + if signing_session.provide_holder_witnesses(context.channel_id, Vec::new()).is_err() { + debug_assert!( + false, + "Zero inputs were provided & zero witnesses were provided, but a count mismatch was somehow found", + ); + } + } else { + // TODO(dual_funding): Send event for signing if we've contributed funds. + // Inform the user that SIGHASH_ALL must be used for all signatures when contributing + // inputs/signatures. + // Also warn the user that we don't do anything to prevent the counterparty from + // providing non-standard witnesses which will prevent the funding transaction from + // confirming. This warning must appear in doc comments wherever the user is contributing + // funds, whether they are initiator or acceptor. + // + // The following warning can be used when the APIs allowing contributing inputs become available: + //
+ // WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which + // will prevent the funding transaction from being relayed on the bitcoin network and hence being + // confirmed. + //
+ } + + context.channel_state = ChannelState::FundingNegotiated; + + // Clear the interactive transaction constructor + self.interactive_tx_constructor_mut().take(); + + Ok((commitment_signed, funding_ready_for_sig_event)) + } } impl InteractivelyFunded for OutboundV2Channel where SP::Target: SignerProvider { @@ -3079,7 +3174,7 @@ impl ChannelContext where SP::Target: SignerProvider { } /// Gets the redeemscript for the funding transaction output (ie the funding transaction output - /// pays to get_funding_redeemscript().to_v0_p2wsh()). + /// pays to get_funding_redeemscript().to_p2wsh()). /// Panics if called before accept_channel/InboundV1Channel::new pub fn get_funding_redeemscript(&self) -> ScriptBuf { make_funding_redeemscript(&self.get_holder_pubkeys().funding_pubkey, self.counterparty_funding_pubkey()) @@ -3867,14 +3962,75 @@ impl ChannelContext where SP::Target: SignerProvider { Ok(()) } - // Interactive transaction construction + /// Asserts that the commitment tx numbers have not advanced from their initial number. + fn assert_no_commitment_advancement(&self, msg_name: &str) { + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { + debug_assert!(false, "Should not have advanced channel commitment tx numbers prior to {}", + msg_name); + } + } - pub fn tx_signatures(&self, msg: &msgs::TxSignatures) -> Result { - todo!(); + fn get_initial_counterparty_commitment_signature( + &self, logger: &L + ) -> Result + where + SP::Target: SignerProvider, + L::Target: Logger + { + let counterparty_keys = self.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.build_commitment_transaction( + self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + match self.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ref ecdsa) => { + ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &self.secp_ctx) + .map(|(signature, _)| signature) + .map_err(|_| ChannelError::Close( + ( + "Failed to get signatures for new commitment_signed".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!(), + } } - pub fn tx_abort(&self, msg: &msgs::TxAbort) -> Result { - todo!(); + fn get_initial_commitment_signed( + &mut self, logger: &L + ) -> Result + where + SP::Target: SignerProvider, + L::Target: Logger + { + if !matches!( + self.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)) { + panic!("Tried to get an initial commitment_signed messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); + } + self.assert_no_commitment_advancement("initial commitment_signed"); + + let signature = match self.get_initial_counterparty_commitment_signature(logger) { + Ok(res) => res, + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + return Err(e); + } + }; + + log_info!(logger, "Generated commitment_signed for peer for channel {}", &self.channel_id()); + + Ok(msgs::CommitmentSigned { + channel_id: self.channel_id, + htlc_signatures: vec![], + signature, + batch: None, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) } } @@ -3978,8 +4134,6 @@ pub(super) fn calculate_our_funding_satoshis( pub(super) struct DualFundingChannelContext { /// The amount in satoshis we will be contributing to the channel. pub our_funding_satoshis: u64, - /// The amount in satoshis our counterparty will be contributing to the channel. - pub their_funding_satoshis: u64, /// The funding transaction locktime suggested by the initiator. If set by us, it is always set /// to the current block height to align incentives against fee-sniping. pub funding_tx_locktime: LockTime, @@ -3997,6 +4151,7 @@ pub(super) struct DualFundingChannelContext { // Counterparty designates channel data owned by the another channel participant entity. pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, + pub interactive_tx_signing_session: Option, } #[cfg(any(test, fuzzing))] @@ -4776,6 +4931,33 @@ impl Channel where Ok(()) } + pub fn commitment_signed_initial_v2( + &mut self, msg: &msgs::CommitmentSigned, best_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result::EcdsaSigner>, ChannelError> + where L::Target: Logger + { + if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { + return Err(ChannelError::Close( + ( + "Received initial commitment_signed before funding transaction constructed!".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + self.context.assert_no_commitment_advancement("initial commitment_signed"); + + let (channel_monitor, _) = self.initial_commitment_signed( + self.context.channel_id(), msg.signature, + self.context.cur_counterparty_commitment_transaction_number, best_block, signer_provider, logger)?; + + log_info!(logger, "Received initial commitment_signed from peer for channel {}", &self.context.channel_id()); + + let need_channel_ready = self.check_get_channel_ready(0, logger).is_some(); + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + Ok(channel_monitor) + } + pub fn commitment_signed(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result, ChannelError> where L::Target: Logger { @@ -5446,6 +5628,56 @@ impl Channel where } } + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures) -> Result<(Option, Option), ChannelError> { + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + if msg.witnesses.len() != signing_session.remote_inputs_count() { + return Err(ChannelError::Warn( + "Witness count did not match contributed input count".to_string() + )); + } + + for witness in &msg.witnesses { + if witness.is_empty() { + return Err(ChannelError::Close( + ( + "Unexpected empty witness in tx_signatures received".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + // TODO(dual_funding): Check all sigs are SIGHASH_ALL. + + // TODO(dual_funding): I don't see how we're going to be able to ensure witness-standardness + // for spending. Doesn't seem to be anything in rust-bitcoin. + } + + if msg.tx_hash != signing_session.unsigned_tx.compute_txid() { + return Err(ChannelError::Close( + ( + "The txid for the transaction does not match".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + let (tx_signatures_opt, funding_tx_opt) = signing_session.received_tx_signatures(msg.clone()) + .map_err(|_| ChannelError::Warn("Witness count did not match contributed input count".to_string()))?; + if funding_tx_opt.is_some() { + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + } + self.context.funding_transaction = funding_tx_opt.clone(); + + // Clear out the signing session + self.interactive_tx_signing_session = None; + + Ok((tx_signatures_opt, funding_tx_opt)) + } else { + Err(ChannelError::Close(( + "Unexpected tx_signatures. No funding transaction awaiting signatures".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))) + } + } + /// Queues up an outbound update fee by placing it in the holding cell. You should call /// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the /// commitment update. @@ -8033,11 +8265,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { ) { panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement("funding_created"); self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); @@ -8144,7 +8372,8 @@ impl OutboundV1Channel where SP::Target: SignerProvider { &mut self, msg: &msgs::AcceptChannel, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures ) -> Result<(), ChannelError> { - self.context.do_accept_channel_checks(default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) + self.context.do_accept_channel_checks( + default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) } /// Handles a funding_signed message from the remote end. @@ -8161,11 +8390,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { return Err((self, ChannelError::close("Received funding_signed in strange state!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement("funding_created"); let (channel_monitor, _) = match self.initial_commitment_signed( self.context.channel_id(), @@ -8180,6 +8405,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let mut channel = Channel { context: self.context, + interactive_tx_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); @@ -8378,11 +8604,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // channel. return Err((self, ChannelError::close("Received funding_created after we got the channel!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement("funding_created"); let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); @@ -8408,6 +8630,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // `ChannelMonitor`. let mut channel = Channel { context: self.context, + interactive_tx_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); channel.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); @@ -8476,7 +8699,6 @@ impl OutboundV2Channel where SP::Target: SignerProvider { unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: 0, funding_tx_locktime, funding_feerate_sat_per_1000_weight, our_funding_inputs: funding_inputs, @@ -8548,6 +8770,15 @@ impl OutboundV2Channel where SP::Target: SignerProvider { require_confirmed_inputs: None, } } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -8631,7 +8862,6 @@ impl InboundV2Channel where SP::Target: SignerProvider { let dual_funding_context = DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: msg.common_fields.funding_satoshis, funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, our_funding_inputs: funding_inputs.clone(), @@ -8733,6 +8963,15 @@ impl InboundV2Channel where SP::Target: SignerProvider { pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // Unfunded channel utilities @@ -9798,6 +10037,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch blocked_monitor_updates: blocked_monitor_updates.unwrap(), is_manual_broadcast: is_manual_broadcast.unwrap_or(false), }, + interactive_tx_signing_session: None, }) } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8ba6f95f1..197214dee 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -58,7 +58,7 @@ use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htl use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; -use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; +use crate::ln::msgs::{ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; @@ -7322,7 +7322,7 @@ where /// Gets the node_id held by this ChannelManager pub fn get_our_node_id(&self) -> PublicKey { - self.our_network_pubkey.clone() + self.our_network_pubkey } fn handle_monitor_update_completion_actions>(&self, actions: I) { @@ -7599,6 +7599,10 @@ where /// for zero confirmations. Instead, `accept_inbound_channel_from_trusted_peer_0conf` must be /// used to accept such channels. /// + /// NOTE: LDK makes no attempt to prevent the counterparty from using non-standard inputs which + /// will prevent the funding transaction from being relayed on the bitcoin network and hence being + /// confirmed. + /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { @@ -8279,9 +8283,46 @@ where }; if let Some(msg_send_event) = msg_send_event_opt { peer_state.pending_msg_events.push(msg_send_event); - } - if let Some(signing_session) = signing_session_opt { - // TODO(dual_funding): Handle this unsigned transaction. + }; + if let Some(mut signing_session) = signing_session_opt { + let (commitment_signed, funding_ready_for_sig_event_opt) = match chan_phase_entry.get_mut() { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + _ => Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())), + }.map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?; + let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); + let channel = match channel_phase { + ChannelPhase::UnfundedOutboundV2(chan) => chan.into_channel(signing_session), + ChannelPhase::UnfundedInboundV2(chan) => chan.into_channel(signing_session), + _ => { + debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. + Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())) + }, + }.map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?; + peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel)); + if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((funding_ready_for_sig_event, None)); + } + peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { + node_id: counterparty_node_id, + updates: CommitmentUpdate { + commitment_signed, + update_add_htlcs: vec![], + update_fulfill_htlcs: vec![], + update_fail_htlcs: vec![], + update_fail_malformed_htlcs: vec![], + update_fee: None, + }, + }); } Ok(()) }, @@ -8291,16 +8332,115 @@ where } } - fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id)), *counterparty_node_id); + fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::Funded(chan) => { + let (tx_signatures_opt, funding_tx_opt) = try_chan_phase_entry!(self, peer_state, chan.tx_signatures(msg), chan_phase_entry); + if let Some(tx_signatures) = tx_signatures_opt { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + if let Some(ref funding_tx) = funding_tx_opt { + self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + { + let mut pending_events = self.pending_events.lock().unwrap(); + emit_channel_pending_event!(pending_events, chan); + } + } + }, + _ => try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( + ( + "Got an unexpected tx_signatures message".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } } - fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id)), *counterparty_node_id); + fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let tx_constructor = match channel_phase { + ChannelPhase::UnfundedInboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::UnfundedOutboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::Funded(_) => { + // TODO(splicing)/TODO(RBF): We'll also be doing interactive tx construction + // for a "ChannelPhase::Funded" when we want to bump the fee on an interactively + // constructed funding tx or during splicing. For now we send an error as we would + // never ack an RBF attempt or a splice for now: + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Warn( + "Got an unexpected tx_abort message: After initial funding transaction is signed, \ + splicing and RBF attempts of interactive funding transactions are not supported yet so \ + we don't have any negotiation in progress".into(), + )), chan_phase_entry) + } + ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) => { + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Warn( + "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel \ + establishment".into(), + )), chan_phase_entry) + }, + }; + // This checks for and resets the interactive negotiation state by `take()`ing it from the channel. + // The existence of the `tx_constructor` indicates that we have not moved into the signing + // phase for this interactively constructed transaction and hence we have not exchanged + // `tx_signatures`. Either way, we never close the channel upon receiving a `tx_abort`: + // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L574-L576 + if tx_constructor.take().is_some() { + let msg = msgs::TxAbort { + channel_id: msg.channel_id, + data: "Acknowledged tx_abort".to_string().into_bytes(), + }; + // NOTE: Since at this point we have not sent a `tx_abort` message for this negotiation + // previously (tx_constructor was `Some`), we need to echo back a tx_abort message according + // to the spec: + // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561 + // For rationale why we echo back `tx_abort`: + // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580 + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, + msg, + }); + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } } fn internal_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> { @@ -8669,6 +8809,7 @@ where } fn internal_commitment_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::CommitmentSigned) -> Result<(), MsgHandleErrInternal> { + let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -8682,10 +8823,32 @@ where if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_txo = chan.context.get_funding_txo(); - let monitor_update_opt = try_chan_phase_entry!(self, peer_state, chan.commitment_signed(&msg, &&logger), chan_phase_entry); - if let Some(monitor_update) = monitor_update_opt { - handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, - peer_state, per_peer_state, chan); + + if chan.interactive_tx_signing_session.is_some() { + let monitor = try_chan_phase_entry!( + self, peer_state, chan.commitment_signed_initial_v2(msg, best_block, &self.signer_provider, &&logger), + chan_phase_entry); + let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor); + if let Ok(persist_state) = monitor_res { + handle_new_monitor_update!(self, persist_state, peer_state_lock, peer_state, + per_peer_state, chan, INITIAL_MONITOR); + } else { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + log_error!(logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated"); + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( + ( + "Channel funding outpoint was a duplicate".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ) + )), chan_phase_entry) + } + } else { + let monitor_update_opt = try_chan_phase_entry!( + self, peer_state, chan.commitment_signed(msg, &&logger), chan_phase_entry); + if let Some(monitor_update) = monitor_update_opt { + handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, + peer_state, per_peer_state, chan); + } } Ok(()) } else { @@ -8693,7 +8856,7 @@ where "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry); } }, - hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + hash_map::Entry::Vacant(_) => Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) } } @@ -11657,9 +11820,8 @@ where } fn handle_tx_signatures(&self, counterparty_node_id: PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_tx_signatures(&counterparty_node_id, msg), counterparty_node_id); } fn handle_tx_init_rbf(&self, counterparty_node_id: PublicKey, msg: &msgs::TxInitRbf) { @@ -11675,9 +11837,13 @@ where } fn handle_tx_abort(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAbort) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_abort message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_abort(&counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn message_received(&self) { @@ -15497,6 +15663,9 @@ mod tests { expect_pending_htlcs_forwardable!(nodes[0]); } + + // Dual-funding: V2 Channel Establishment Tests + // TODO(dual_funding): Complete these. } #[cfg(ldk_bench)] -- 2.39.5