From 5bb7895dad1800b85f6faf8f7b42762f8d76c2a5 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 | 534 ++++++++++++++++++++++------- lightning/src/ln/channelmanager.rs | 311 ++++++++++++++--- 2 files changed, 674 insertions(+), 171 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 479b04fb8e4..8a919b0d8be 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2224,6 +2224,140 @@ impl ChannelContext where SP::Target: SignerProvider { } } + fn do_accept_channel_checks(&mut self, default_limits: &ChannelHandshakeLimits, + their_features: &InitFeatures, common_fields: &msgs::CommonAcceptChannelFields, channel_reserve_satoshis: u64, + ) -> Result<(), ChannelError> { + let peer_limits = if let Some(ref limits) = self.inbound_handshake_limits_override { limits } else { default_limits }; + + // Check sanity of message fields: + if !self.is_outbound() { + return Err(ChannelError::Close("Got an accept_channel message from an inbound peer".to_owned())); + } + if !matches!(self.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT) { + return Err(ChannelError::Close("Got an accept_channel message at a strange time".to_owned())); + } + if common_fields.dust_limit_satoshis > 21000000 * 100000000 { + return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", common_fields.dust_limit_satoshis))); + } + if channel_reserve_satoshis > self.channel_value_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", channel_reserve_satoshis, self.channel_value_satoshis))); + } + if common_fields.dust_limit_satoshis > self.holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) is bigger than our channel reserve ({})", common_fields.dust_limit_satoshis, self.holder_selected_channel_reserve_satoshis))); + } + if channel_reserve_satoshis > self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", + channel_reserve_satoshis, self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); + } + let full_channel_value_msat = (self.channel_value_satoshis - channel_reserve_satoshis) * 1000; + if common_fields.htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) is full channel value ({})", common_fields.htlc_minimum_msat, full_channel_value_msat))); + } + let max_delay_acceptable = u16::min(peer_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); + if common_fields.to_self_delay > max_delay_acceptable { + return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_delay_acceptable, common_fields.to_self_delay))); + } + if common_fields.max_accepted_htlcs < 1 { + return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); + } + if common_fields.max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", common_fields.max_accepted_htlcs, MAX_HTLCS))); + } + + // Now check against optional parameters as set by config... + if common_fields.htlc_minimum_msat > peer_limits.max_htlc_minimum_msat { + return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", common_fields.htlc_minimum_msat, peer_limits.max_htlc_minimum_msat))); + } + if common_fields.max_htlc_value_in_flight_msat < peer_limits.min_max_htlc_value_in_flight_msat { + return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", common_fields.max_htlc_value_in_flight_msat, peer_limits.min_max_htlc_value_in_flight_msat))); + } + if channel_reserve_satoshis > peer_limits.max_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", channel_reserve_satoshis, peer_limits.max_channel_reserve_satoshis))); + } + if common_fields.max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { + return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", common_fields.max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); + } + if common_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", common_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if common_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", common_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); + } + if common_fields.minimum_depth > peer_limits.max_minimum_depth { + return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, common_fields.minimum_depth))); + } + + if let Some(ty) = &common_fields.channel_type { + if *ty != self.channel_type { + return Err(ChannelError::Close("Channel Type in accept_channel didn't match the one sent in open_channel.".to_owned())); + } + } else if their_features.supports_channel_type() { + // Assume they've accepted the channel type as they said they understand it. + } else { + let channel_type = ChannelTypeFeatures::from_init(&their_features); + if channel_type != ChannelTypeFeatures::only_static_remote_key() { + return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); + } + self.channel_type = channel_type.clone(); + self.channel_transaction_parameters.channel_type_features = channel_type; + } + + let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { + match &common_fields.shutdown_scriptpubkey { + &Some(ref script) => { + // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything + if script.len() == 0 { + None + } else { + if !script::is_bolt2_compliant(&script, their_features) { + return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: {}", script))); + } + Some(script.clone()) + } + }, + // Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel + &None => { + return Err(ChannelError::Close("Peer is signaling upfront_shutdown but we don't get any script. Use 0-length script to opt-out".to_owned())); + } + } + } else { None }; + + self.counterparty_dust_limit_satoshis = common_fields.dust_limit_satoshis; + self.counterparty_max_htlc_value_in_flight_msat = cmp::min(common_fields.max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000); + self.counterparty_selected_channel_reserve_satoshis = Some(channel_reserve_satoshis); + self.counterparty_htlc_minimum_msat = common_fields.htlc_minimum_msat; + self.counterparty_max_accepted_htlcs = common_fields.max_accepted_htlcs; + + if peer_limits.trust_own_funding_0conf { + self.minimum_depth = Some(common_fields.minimum_depth); + } else { + self.minimum_depth = Some(cmp::max(1, common_fields.minimum_depth)); + } + + let counterparty_pubkeys = ChannelPublicKeys { + funding_pubkey: common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint::from(common_fields.revocation_basepoint), + payment_point: common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint::from(common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint::from(common_fields.htlc_basepoint) + }; + + self.channel_transaction_parameters.counterparty_parameters = Some(CounterpartyChannelTransactionParameters { + selected_contest_delay: common_fields.to_self_delay, + pubkeys: counterparty_pubkeys, + }); + + self.counterparty_cur_commitment_point = Some(common_fields.first_per_commitment_point); + self.counterparty_shutdown_scriptpubkey = counterparty_shutdown_scriptpubkey; + + self.channel_state = ChannelState::NegotiatingFunding( + NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT + ); + self.inbound_handshake_limits_override = None; // We're done enforcing limits on our peer's handshake now. + + Ok(()) + } + /// Returns the block hash in which our funding transaction was confirmed. pub fn get_funding_tx_confirmed_in(&self) -> Option { self.funding_tx_confirmed_in @@ -3645,7 +3779,7 @@ pub(crate) fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_ (commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000 } -fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, +pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, our_funding_satoshis: u64, funding_inputs: &Vec<(TxIn, Transaction)>, funding_outputs: &mut Vec, funding_feerate_sat_per_1000_weight: u32, total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32], @@ -4541,6 +4675,92 @@ impl Channel where Ok(()) } + #[cfg(dual_funding)] + 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())); + } + if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + let dual_funding_channel_context = self.dual_funding_channel_context.as_mut().ok_or( + ChannelError::Close("Have no context for dual-funded channel".to_owned()) + )?; + + let funding_script = self.context.get_funding_redeemscript(); + + let counterparty_keys = self.context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); + let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + + log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", + &self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + + let holder_signer = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number); + let initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &holder_signer, true, false, logger).tx; + { + let trusted_tx = initial_commitment_tx.trust(); + let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); + let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); + // They sign our commitment transaction, allowing us to broadcast the tx if we wish. + if let Err(_) = self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.get_counterparty_pubkeys().funding_pubkey) { + return Err(ChannelError::Close("Invalid funding_signed signature from peer".to_owned())); + } + } + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx, + msg.signature, + Vec::new(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()) + .map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?; + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo = self.context.get_funding_txo().unwrap(); + let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); + let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); + let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (funding_txo, funding_txo_script), + &self.context.channel_transaction_parameters, + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); + + channel_monitor.provide_initial_counterparty_commitment_tx( + counterparty_initial_bitcoin_tx.txid, Vec::new(), + self.context.cur_counterparty_commitment_transaction_number, + self.context.counterparty_cur_commitment_point.unwrap(), + counterparty_initial_commitment_tx.feerate_per_kw(), + counterparty_initial_commitment_tx.to_broadcaster_value_sat(), + counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); + + assert!(!self.context.channel_state.is_monitor_update_in_progress()); // We have no had any monitor(s) yet to fail update! + self.context.cur_holder_commitment_transaction_number -= 1; + self.context.cur_counterparty_commitment_transaction_number -= 1; + + 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).is_some(); + 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 { @@ -7622,135 +7842,8 @@ impl OutboundV1Channel where SP::Target: SignerProvider { // Message handlers pub fn accept_channel(&mut self, msg: &msgs::AcceptChannel, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures) -> Result<(), ChannelError> { - let peer_limits = if let Some(ref limits) = self.context.inbound_handshake_limits_override { limits } else { default_limits }; - - // Check sanity of message fields: - if !self.context.is_outbound() { - return Err(ChannelError::Close("Got an accept_channel message from an inbound peer".to_owned())); - } - if !matches!(self.context.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT) { - return Err(ChannelError::Close("Got an accept_channel message at a strange time".to_owned())); - } - if msg.common_fields.dust_limit_satoshis > 21000000 * 100000000 { - return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", msg.common_fields.dust_limit_satoshis))); - } - if msg.channel_reserve_satoshis > self.context.channel_value_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", msg.channel_reserve_satoshis, self.context.channel_value_satoshis))); - } - if msg.common_fields.dust_limit_satoshis > self.context.holder_selected_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("Dust limit ({}) is bigger than our channel reserve ({})", msg.common_fields.dust_limit_satoshis, self.context.holder_selected_channel_reserve_satoshis))); - } - if msg.channel_reserve_satoshis > self.context.channel_value_satoshis - self.context.holder_selected_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", - msg.channel_reserve_satoshis, self.context.channel_value_satoshis - self.context.holder_selected_channel_reserve_satoshis))); - } - let full_channel_value_msat = (self.context.channel_value_satoshis - msg.channel_reserve_satoshis) * 1000; - if msg.common_fields.htlc_minimum_msat >= full_channel_value_msat { - return Err(ChannelError::Close(format!("Minimum htlc value ({}) is full channel value ({})", msg.common_fields.htlc_minimum_msat, full_channel_value_msat))); - } - let max_delay_acceptable = u16::min(peer_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); - if msg.common_fields.to_self_delay > max_delay_acceptable { - return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_delay_acceptable, msg.common_fields.to_self_delay))); - } - if msg.common_fields.max_accepted_htlcs < 1 { - return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); - } - if msg.common_fields.max_accepted_htlcs > MAX_HTLCS { - return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg.common_fields.max_accepted_htlcs, MAX_HTLCS))); - } - - // Now check against optional parameters as set by config... - if msg.common_fields.htlc_minimum_msat > peer_limits.max_htlc_minimum_msat { - return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", msg.common_fields.htlc_minimum_msat, peer_limits.max_htlc_minimum_msat))); - } - if msg.common_fields.max_htlc_value_in_flight_msat < peer_limits.min_max_htlc_value_in_flight_msat { - return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", msg.common_fields.max_htlc_value_in_flight_msat, peer_limits.min_max_htlc_value_in_flight_msat))); - } - if msg.channel_reserve_satoshis > peer_limits.max_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg.channel_reserve_satoshis, peer_limits.max_channel_reserve_satoshis))); - } - if msg.common_fields.max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { - return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg.common_fields.max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); - } - if msg.common_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg.common_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); - } - if msg.common_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg.common_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); - } - if msg.common_fields.minimum_depth > peer_limits.max_minimum_depth { - return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.common_fields.minimum_depth))); - } - - if let Some(ty) = &msg.common_fields.channel_type { - if *ty != self.context.channel_type { - return Err(ChannelError::Close("Channel Type in accept_channel didn't match the one sent in open_channel.".to_owned())); - } - } else if their_features.supports_channel_type() { - // Assume they've accepted the channel type as they said they understand it. - } else { - let channel_type = ChannelTypeFeatures::from_init(&their_features); - if channel_type != ChannelTypeFeatures::only_static_remote_key() { - return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); - } - self.context.channel_type = channel_type.clone(); - self.context.channel_transaction_parameters.channel_type_features = channel_type; - } - - let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { - match &msg.common_fields.shutdown_scriptpubkey { - &Some(ref script) => { - // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything - if script.len() == 0 { - None - } else { - if !script::is_bolt2_compliant(&script, their_features) { - return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: {}", script))); - } - Some(script.clone()) - } - }, - // Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel - &None => { - return Err(ChannelError::Close("Peer is signaling upfront_shutdown but we don't get any script. Use 0-length script to opt-out".to_owned())); - } - } - } else { None }; - - self.context.counterparty_dust_limit_satoshis = msg.common_fields.dust_limit_satoshis; - self.context.counterparty_max_htlc_value_in_flight_msat = cmp::min(msg.common_fields.max_htlc_value_in_flight_msat, self.context.channel_value_satoshis * 1000); - self.context.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis); - self.context.counterparty_htlc_minimum_msat = msg.common_fields.htlc_minimum_msat; - self.context.counterparty_max_accepted_htlcs = msg.common_fields.max_accepted_htlcs; - - if peer_limits.trust_own_funding_0conf { - self.context.minimum_depth = Some(msg.common_fields.minimum_depth); - } else { - self.context.minimum_depth = Some(cmp::max(1, msg.common_fields.minimum_depth)); - } - - let counterparty_pubkeys = ChannelPublicKeys { - funding_pubkey: msg.common_fields.funding_pubkey, - revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint), - payment_point: msg.common_fields.payment_basepoint, - delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg.common_fields.delayed_payment_basepoint), - htlc_basepoint: HtlcBasepoint::from(msg.common_fields.htlc_basepoint) - }; - - self.context.channel_transaction_parameters.counterparty_parameters = Some(CounterpartyChannelTransactionParameters { - selected_contest_delay: msg.common_fields.to_self_delay, - pubkeys: counterparty_pubkeys, - }); - - self.context.counterparty_cur_commitment_point = Some(msg.common_fields.first_per_commitment_point); - self.context.counterparty_shutdown_scriptpubkey = counterparty_shutdown_scriptpubkey; - - self.context.channel_state = ChannelState::NegotiatingFunding( - NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT - ); - self.context.inbound_handshake_limits_override = None; // We're done enforcing limits on our peer's handshake now. - - Ok(()) + 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. @@ -8279,6 +8372,43 @@ impl OutboundV2Channel where SP::Target: SignerProvider { self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, signer_provider, entropy_source, true /* is_initiator */, funding_inputs) } + + pub fn funding_tx_constructed( + mut self, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result<(Channel, msgs::CommitmentSigned), (Self, ChannelError)> + where + L::Target: Logger + { + let res = get_initial_commitment_signed(&mut self.context, transaction, est_block, + signer_provider, logger); + let commitment_signed = match res { + Ok(commitment_signed) => commitment_signed, + Err(err) => return Err((self, err)), + }; + + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + }; + + Ok((channel, commitment_signed)) + } + + pub fn accept_channel_v2(&mut self, msg: &msgs::AcceptChannelV2, default_limits: &ChannelHandshakeLimits, + their_features: &InitFeatures) -> Result<(), ChannelError> { + // According to the spec we MUST fail the negotiation if `require_confirmed_inputs` is set in + // `accept_channel2` but we cannot provide confirmed inputs. We're not going to check if the user + // upheld this requirement, so we just defer the failure to the counterparty's checks during + // interactive transaction construction and remain blissfully unaware here. + + // Now we can generate the `channel_id` since we have our counterparty's `revocation_basepoint`. + self.context.channel_id = ChannelId::v2_from_revocation_basepoints( + &self.context.get_holder_pubkeys().revocation_basepoint, &RevocationBasepoint::from(msg.common_fields.revocation_basepoint)); + self.dual_funding_context.their_funding_satoshis = msg.funding_satoshis; + self.context.do_accept_channel_checks( + default_limits, their_features, &msg.common_fields, get_v2_channel_reserve_satoshis( + msg.common_fields.dust_limit_satoshis, self.context.channel_value_satoshis)) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -8442,6 +8572,27 @@ impl InboundV2Channel where SP::Target: SignerProvider { self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, signer_provider, entropy_source, false /* is_initiator */, funding_inputs) } + + pub fn funding_tx_constructed( + mut self, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result<(Channel, msgs::CommitmentSigned), (Self, ChannelError)> + where + L::Target: Logger + { + let res = get_initial_commitment_signed(&mut self.context, transaction, est_block, + signer_provider, logger); + let commitment_signed = match res { + Ok(commitment_signed) => commitment_signed, + Err(err) => return Err((self, err)), + }; + + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + }; + + Ok((channel, commitment_signed)) + } } // Unfunded channel utilities @@ -8469,6 +8620,129 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } +/// If an Err is returned, it is a ChannelError::Close +fn get_initial_remote_commitment_tx_signature(context: &mut ChannelContext, logger: &L) +-> Result<(CommitmentTransaction, Signature), ChannelError> +where + SP::Target: SignerProvider, + L::Target: Logger +{ + let counterparty_keys = context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = context.build_commitment_transaction( + context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + + let holder_keys = context.build_holder_transaction_keys(context.cur_holder_commitment_transaction_number); + let initial_commitment_tx = context.build_commitment_transaction( + context.cur_holder_commitment_transaction_number, &holder_keys, true, true, logger).tx; + + match &context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + let signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; + Ok((counterparty_initial_commitment_tx, signature)) + } + } +} + +fn get_initial_counterparty_commitment_signature( + context: &mut ChannelContext, logger: &L +) -> Result +where + SP::Target: SignerProvider, + L::Target: Logger +{ + let counterparty_keys = context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = context.build_commitment_transaction( + context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + match context.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ref ecdsa) => { + Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!(), + } +} + +fn get_initial_commitment_signed( + context: &mut ChannelContext, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L +) -> Result +where + SP::Target: SignerProvider, + L::Target: Logger +{ + if !matches!( + context.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)) { + 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 context.commitment_secrets.get_min_seen_secret() != (1 << 48) || + context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to initial commitment_signed"); + } + + let funding_redeemscript = context.get_funding_redeemscript().to_v0_p2wsh(); + let funding_outpoint_index = transaction.output.iter().enumerate().find_map( + |(idx, output)| { + if output.script_pubkey == funding_redeemscript { Some(idx as u16) } else { None } + }).expect("funding transaction contains funding output"); + let funding_txo = OutPoint { txid: transaction.txid(), index: funding_outpoint_index }; + context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let signature = match get_initial_counterparty_commitment_signature(context, logger) { + Ok(res) => res, + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + context.channel_transaction_parameters.funding_outpoint = None; + return Err(e); + } + }; + + // This is an externally observable change before we finish all our checks. In particular + // funding_created_signature may fail. + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let (counterparty_initial_commitment_tx, signature) = + match get_initial_remote_commitment_tx_signature(context, logger) { + Ok(res) => res, + Err(ChannelError::Close(e)) => { + context.channel_transaction_parameters.funding_outpoint = None; + return Err(ChannelError::Close(e)); + }, + Err(e) => { + // The only error we know how to handle is ChannelError::Close, so we fall over here + // to make sure we don't continue with an inconsistent state. + panic!("unexpected error type from funding_created_signature {:?}", e); + } + }; + + // Now that we're past error-generating stuff, update our local state: + + let funding_txo_script = context.get_funding_redeemscript().to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound()); + let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); + monitor_signer.provide_channel_parameters(&context.channel_transaction_parameters); + + context.channel_state = ChannelState::FundingNegotiated; + context.cur_counterparty_commitment_transaction_number -= 1; + context.cur_holder_commitment_transaction_number -= 1; + + log_info!(logger, "Generated commitment_signed for peer for channel {}", &context.channel_id()); + + Ok(msgs::CommitmentSigned { + channel_id: context.channel_id.clone(), + htlc_signatures: vec![], + signature, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) +} + const SERIALIZATION_VERSION: u8 = 3; const MIN_SERIALIZATION_VERSION: u8 = 3; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 48f3a5ac19b..d19f49cb0c4 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -46,7 +46,7 @@ use crate::ln::{inbound_payment, ChannelId, PaymentHash, PaymentPreimage, Paymen use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; pub use crate::ln::channel::{InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails}; #[cfg(dual_funding)] -use crate::ln::channel::InboundV2Channel; +use crate::ln::channel::{InboundV2Channel, OutboundV2Channel}; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; @@ -58,6 +58,8 @@ 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}; +#[cfg(dual_funding)] +use crate::ln::msgs::CommitmentUpdate; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; @@ -2571,8 +2573,70 @@ where /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option) -> Result { - if channel_value_satoshis < 1000 { - return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", channel_value_satoshis) }); + self.create_channel_internal(false, their_network_key, channel_value_satoshis, None, push_msat, + user_channel_id, temporary_channel_id, override_config) + } + + /// Creates a new outbound dual-funded channel to the given remote node and with the given value + /// contributed by us. + /// + /// `user_channel_id` will be provided back as in + /// [`Event::FundingGenerationReady::user_channel_id`] to allow tracking of which events + /// correspond with which `create_channel` call. Note that the `user_channel_id` defaults to a + /// randomized value for inbound channels. `user_channel_id` has no meaning inside of LDK, it + /// is simply copied to events and otherwise ignored. + /// + /// `funding_satoshis` is the amount we are contributing to the channel. + /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24. + /// + /// The `funding_inputs` parameter accepts ([`TxIn`], [`Transaction`]) pairs which will + /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due). + /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is + /// less than `funding_satoshis`. + // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us. + /// + /// LDK will create a change output for any non-dust amounts that remain after subtracting contribution + /// to channel capacity and fees from the provided input amounts. + // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may + /// be able to do some more interesting things at the same time as funding a channel, like making + /// some low priority on-chain payment. + /// + /// The `funding_conf_target` parameter sets the priority of the funding transaction for appropriate + /// fee estimation. If `None`, then [`ConfirmationTarget::Normal`] is used. + /// + /// Raises [`APIError::ChannelUnavailable`] if the channel cannot be opened due to failing to + /// generate a shutdown scriptpubkey or destination script set by + /// [`SignerProvider::get_shutdown_scriptpubkey`] or [`SignerProvider::get_destination_script`]. + /// + /// Note that we do not check if you are currently connected to the given peer. If no + /// connection is available, the outbound `open_channel` message may fail to send, resulting in + /// the channel eventually being silently forgotten (dropped on reload). + /// + /// Returns the new Channel's temporary `channel_id`. This ID will appear as + /// [`Event::FundingGenerationReady::temporary_channel_id`] and in + /// [`ChannelDetails::channel_id`] until after + /// [`ChannelManager::funding_transaction_generated`] is called, swapping the Channel's ID for + /// one derived from the funding transaction's TXID. If the counterparty rejects the channel + /// immediately, this temporary ID will appear in [`Event::ChannelClosed::channel_id`]. + /// + /// [`Event::FundingGenerationReady::user_channel_id`]: events::Event::FundingGenerationReady::user_channel_id + /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id + /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id + /// [`ConfirmationTarget::Normal`]: chain::chaininterface::ConfirmationTarget + pub fn create_dual_funded_channel(&self, their_network_key: PublicKey, funding_satoshis: u64, + funding_conf_target: Option, user_channel_id: u128, + override_config: Option) -> Result + { + self.create_channel_internal(true, their_network_key, funding_satoshis, funding_conf_target, 0, + user_channel_id, None, override_config) + } + + fn create_channel_internal(&self, is_v2: bool, their_network_key: PublicKey, funding_satoshis: u64, + funding_conf_target: Option, push_msat: u64, user_channel_id: u128, + temporary_channel_id: Option, override_config: Option, + ) -> Result { + if funding_satoshis < 1000 { + return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", funding_satoshis) }); } let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -2592,24 +2656,76 @@ where } } - let channel = { - let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - let their_features = &peer_state.latest_features; - let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration }; - match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, - their_features, channel_value_satoshis, push_msat, user_channel_id, config, - self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) - { - Ok(res) => res, - Err(e) => { - self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); - return Err(e); - }, - } + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + let their_features = &peer_state.latest_features; + let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration }; + + // TODO(dual_funding): Merge this with below when cfg is removed. + #[cfg(not(dual_funding))] + let (channel_phase, msg_send_event) = { + let channel = { + match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, + their_features, funding_satoshis, push_msat, user_channel_id, config, + self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) + { + Ok(res) => res, + Err(e) => { + self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); + return Err(e); + }, + } + }; + let res = channel.get_open_channel(self.chain_hash); + let event = events::MessageSendEvent::SendOpenChannel { + node_id: their_network_key, + msg: res, + }; + (ChannelPhase::UnfundedOutboundV1(channel), event) + }; + + #[cfg(dual_funding)] + let (channel_phase, msg_send_event) = if is_v2 { + let channel = { + match OutboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, + their_features, funding_satoshis, user_channel_id, config, + self.best_block.read().unwrap().height(), outbound_scid_alias, + funding_conf_target.unwrap_or(ConfirmationTarget::NonAnchorChannelFee)) + { + Ok(res) => res, + Err(e) => { + self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); + return Err(e); + }, + } + }; + let res = channel.get_open_channel_v2(self.chain_hash); + let event = events::MessageSendEvent::SendOpenChannelV2 { + node_id: their_network_key, + msg: res, + }; + (ChannelPhase::UnfundedOutboundV2(channel), event) + } else { + let channel = { + match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, + their_features, funding_satoshis, push_msat, user_channel_id, config, + self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) + { + Ok(res) => res, + Err(e) => { + self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); + return Err(e); + }, + } + }; + let res = channel.get_open_channel(self.chain_hash); + let event = events::MessageSendEvent::SendOpenChannel { + node_id: their_network_key, + msg: res, + }; + (ChannelPhase::UnfundedOutboundV1(channel), event) }; - let res = channel.get_open_channel(self.chain_hash); - let temporary_channel_id = channel.context.channel_id(); + let temporary_channel_id = channel_phase.context().channel_id(); match peer_state.channel_by_id.entry(temporary_channel_id) { hash_map::Entry::Occupied(_) => { if cfg!(fuzzing) { @@ -2618,13 +2734,10 @@ where panic!("RNG is bad???"); } }, - hash_map::Entry::Vacant(entry) => { entry.insert(ChannelPhase::UnfundedOutboundV1(channel)); } + hash_map::Entry::Vacant(entry) => { entry.insert(channel_phase); } } - peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { - node_id: their_network_key, - msg: res, - }); + peer_state.pending_msg_events.push(msg_send_event); Ok(temporary_channel_id) } @@ -6632,6 +6745,55 @@ where Ok(()) } + #[cfg(dual_funding)] + fn internal_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) -> Result<(), MsgHandleErrInternal> { + // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are + // likely to be lost on restart! + 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.common_fields.temporary_channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + let (chan, channel_id, holder_funding_satoshis, counterparty_funding_satoshis, user_channel_id) = { + match peer_state.channel_by_id.remove(&msg.common_fields.temporary_channel_id) { + Some(phase) => { + match phase { + ChannelPhase::UnfundedOutboundV2(mut chan) => { + if let Err(err) = chan.accept_channel_v2(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features) { + let (_, res) = convert_chan_phase_err!(self, err, chan, &msg.common_fields.temporary_channel_id, UNFUNDED_CHANNEL); + let _: Result<(), _> = handle_error!(self, Err(res), *counterparty_node_id); + } + let channel_id = chan.context.channel_id(); + let holder_funding_satoshis = chan.dual_funding_context.our_funding_satoshis; + let user_channel_id = chan.context.get_user_id(); + (chan, channel_id, holder_funding_satoshis, msg.funding_satoshis, user_channel_id) + }, + _ => { + peer_state.channel_by_id.insert(msg.common_fields.temporary_channel_id, phase); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected accept_channel2 message from peer with counterparty_node_id {}", counterparty_node_id), msg.common_fields.temporary_channel_id)); + } + } + }, + None => 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.common_fields.temporary_channel_id)) + } + }; + + peer_state.channel_by_id.insert(chan.context.channel_id(), ChannelPhase::UnfundedOutboundV2(chan)); + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((events::Event::FundingInputsContributionReady { + channel_id, + counterparty_node_id: *counterparty_node_id, + holder_funding_satoshis, + counterparty_funding_satoshis, + user_channel_id, + } , None)); + Ok(()) + } + fn internal_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) -> Result<(), MsgHandleErrInternal> { let best_block = *self.best_block.read().unwrap(); @@ -6959,6 +7121,7 @@ where #[cfg(dual_funding)] fn internal_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) -> 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(|| { @@ -6988,8 +7151,47 @@ where }; peer_state.pending_msg_events.push(msg_send_event); } - if let Some(_tx) = tx_opt { - // TODO(dual_funding): Handle this unsigned transaction. + if let Some(tx) = tx_opt { + let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); + let res = match channel_phase { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.funding_tx_constructed(tx, best_block, &self.signer_provider, &self.logger).map_err( + |(chan, err)| { + (ChannelPhase::UnfundedOutboundV2(chan), err) + } + ) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.funding_tx_constructed(tx, best_block, &self.signer_provider, &self.logger).map_err( + |(chan, err)| { + (ChannelPhase::UnfundedInboundV2(chan), err) + } + ) + }, + _ => { + todo!(); + } + }; + match res { + Ok((channel, commitment_signed)) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { + node_id: counterparty_node_id.clone(), + updates: CommitmentUpdate { + commitment_signed, + update_add_htlcs: vec![], + update_fulfill_htlcs: vec![], + update_fail_htlcs: vec![], + update_fail_malformed_htlcs: vec![], + update_fee: None, + }, + }); + peer_state.channel_by_id.insert(channel_id.clone(), ChannelPhase::Funded(channel)); + }, + Err((channel_phase, _channel_error)) => { + peer_state.channel_by_id.insert(channel_id, channel_phase); + // TODO(dual_funding): Handle the channel error. + }, + } } }, Err(tx_abort_msg) => peer_state.pending_msg_events.push( @@ -7408,6 +7610,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(|| { @@ -7418,18 +7621,39 @@ where 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) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { - let logger = WithChannelContext::from(&self.logger, &chan.context); - let funding_txo = chan.context.get_funding_txo(); - let monitor_update_opt = try_chan_phase_entry!(self, 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 { - return try_chan_phase_entry!(self, Err(ChannelError::Close( - "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry); + match chan_phase_entry.get_mut() { + ChannelPhase::Funded(chan) => { + let logger = WithChannelContext::from(&self.logger, &chan.context); + let funding_txo = chan.context.get_funding_txo(); + + let mut has_dual_funding_channel_context = false; + #[cfg(dual_funding)] + { + has_dual_funding_channel_context = chan.dual_funding_channel_context.is_some(); + } + + if has_dual_funding_channel_context { + #[cfg(dual_funding)] + { + let monitor = try_chan_phase_entry!(self, + chan.commitment_signed_initial_v2(&msg, best_block, &self.signer_provider, &&logger), chan_phase_entry); + if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { + handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR); + } else { + try_chan_phase_entry!(self, Err(ChannelError::Close("Channel funding outpoint was a duplicate".to_owned())), chan_phase_entry) + } + } + } else { + let monitor_update_opt = try_chan_phase_entry!(self, 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(()) + }, + _ => return try_chan_phase_entry!(self, Err(ChannelError::Close( + "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)) @@ -9296,9 +9520,11 @@ where #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.common_fields.temporary_channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // accept_channel2 message - pre-funded channels are never written so there should be no + // change to the contents. + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_accept_channel_v2(counterparty_node_id, msg), *counterparty_node_id); } fn handle_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) { @@ -13211,6 +13437,9 @@ mod tests { expect_pending_htlcs_forwardable!(nodes[0]); } + + // Dual-funding: V2 Channel Establishment Tests + // TODO(dual_funding): Complete these. } #[cfg(ldk_bench)]