From 24ba5848d5a0a9ef17ce743d7071a2002965d83e Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 15 Sep 2023 13:19:24 +0200 Subject: [PATCH] Implement support for accepting V2 channels --- lightning/src/events/mod.rs | 27 +- lightning/src/ln/channel.rs | 245 +++++++++++++-- lightning/src/ln/channelmanager.rs | 485 +++++++++++++++++++++-------- lightning/src/ln/interactivetxs.rs | 37 ++- 4 files changed, 638 insertions(+), 156 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 033086981..7f50f5a68 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -606,6 +606,20 @@ impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason, (10, UnexpectedError) => {}, ); +/// Used to indicate the kind of funding for this channel by the channel acceptor (us). +/// +/// Allows the differentiation between a request for a dual-funded and non-dual-funded channel. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum InboundChannelFunds { + /// For a non-dual-funded channel, the `push_msat` value from the channel initiator to us. + PushMsat(u64), + /// Indicates the open request is for a dual funded channel. + /// + /// Note that these channels do not support starting with initial funds pushed from the counterparty, + /// who is the channel opener in this case. + DualFunded, +} + /// An Event which you should probably take some action in response to. /// /// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use @@ -1337,9 +1351,10 @@ pub enum Event { }, /// Indicates a request to open a new channel by a peer. /// - /// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the request, - /// call [`ChannelManager::force_close_without_broadcasting_txn`]. Note that a ['ChannelClosed`] - /// event will _not_ be triggered if the channel is rejected. + /// To accept the request (and in the case of a dual-funded channel, not contribute funds), + /// call [`ChannelManager::accept_inbound_channel`]. + /// To reject the request, call [`ChannelManager::force_close_without_broadcasting_txn`]. + /// Note that a ['ChannelClosed`] event will _not_ be triggered if the channel is rejected. /// /// The event is only triggered when a new open channel request is received and the /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. @@ -1373,8 +1388,10 @@ pub enum Event { counterparty_node_id: PublicKey, /// The channel value of the requested channel. funding_satoshis: u64, - /// Our starting balance in the channel if the request is accepted, in milli-satoshi. - push_msat: u64, + /// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to + /// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`, + /// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel. + channel_negotiation_type: InboundChannelFunds, /// The features that this channel will operate with. If you reject the channel, a /// well-behaved counterparty may automatically re-attempt the channel with a new set of /// feature flags. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 636b0bd5d..6ab3c663d 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -9,11 +9,13 @@ use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; -use bitcoin::script::{Script, ScriptBuf, Builder}; -use bitcoin::transaction::Transaction; +use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash}; +use bitcoin::transaction::{Transaction, TxIn}; use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::consensus::encode; +use bitcoin::absolute::LockTime; +use bitcoin::Weight; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -28,7 +30,10 @@ use bitcoin::secp256k1; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; -use crate::ln::interactivetxs::InteractiveTxConstructor; +use crate::ln::interactivetxs::{ + get_output_weight, HandleTxCompleteResult, InteractiveTxConstructor, InteractiveTxConstructorArgs, + InteractiveTxMessageSend, InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT, +}; use crate::ln::msgs; use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError}; use crate::ln::script::{self, ShutdownScript}; @@ -45,14 +50,14 @@ use crate::ln::chan_utils::{ use crate::ln::chan_utils; use crate::ln::onion_utils::HTLCFailReason; use crate::chain::BestBlock; -use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator}; +use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS}; 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::routing::gossip::NodeId; -use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +use crate::util::ser::{Readable, ReadableArgs, TransactionU16LenLimited, Writeable, Writer}; use crate::util::logger::{Logger, Record, WithContext}; use crate::util::errors::APIError; use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, MaxDustHTLCExposure}; @@ -1637,6 +1642,101 @@ impl InitialRemoteCommitmentReceiver for InboundV1Channel whe } } +pub(super) trait InteractivelyFunded where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext; + + fn context_mut(&mut self) -> &mut ChannelContext; + + fn interactive_tx_constructor_mut(&mut self) -> &mut Option; + + fn dual_funding_context(&self) -> &DualFundingChannelContext; + + fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { + HandleTxCompleteResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_complete(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } +} + +impl InteractivelyFunded for OutboundV2Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + &self.context + } + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + fn dual_funding_context(&self) -> &DualFundingChannelContext { + &self.dual_funding_context + } + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + &mut self.interactive_tx_constructor + } +} + +impl InteractivelyFunded for InboundV2Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + &self.context + } + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + fn dual_funding_context(&self) -> &DualFundingChannelContext { + &self.dual_funding_context + } + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + &mut self.interactive_tx_constructor + } +} + impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator, @@ -3766,6 +3866,16 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_transaction_parameters.channel_type_features = self.channel_type.clone(); Ok(()) } + + // Interactive transaction construction + + pub fn tx_signatures(&self, msg: &msgs::TxSignatures) -> Result { + todo!(); + } + + pub fn tx_abort(&self, msg: &msgs::TxAbort) -> Result { + todo!(); + } } // Internal utility functions for channels @@ -3823,6 +3933,47 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis)) } +pub(super) fn calculate_our_funding_satoshis( + is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)], + total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32, + holder_dust_limit_satoshis: u64, +) -> Result { + let mut total_input_satoshis = 0u64; + let mut our_contributed_weight = 0u64; + + for (idx, input) in funding_inputs.iter().enumerate() { + if let Some(output) = input.1.as_transaction().output.get(input.0.previous_output.vout as usize) { + total_input_satoshis = total_input_satoshis.saturating_add(output.value.to_sat()); + } else { + return Err(APIError::APIMisuseError { + err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", + input.1.as_transaction().compute_txid(), input.0.previous_output.vout, idx) }); + } + } + our_contributed_weight = our_contributed_weight.saturating_add(total_witness_weight.to_wu()); + + // If we are the initiator, we must pay for weight of all common fields in the funding transaction. + if is_initiator { + our_contributed_weight = our_contributed_weight + .saturating_add(TX_COMMON_FIELDS_WEIGHT) + // The weight of a P2WSH output to be added later. + // + // NOTE: The witness script hash given here is irrelevant as it's a fixed size and we just want + // to calculate the contributed weight, so we use an all-zero hash. + .saturating_add(get_output_weight(&ScriptBuf::new_p2wsh( + &WScriptHash::from_raw_hash(Hash::all_zeros()) + )).to_wu()) + } + + let funding_satoshis = total_input_satoshis + .saturating_sub(fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight)); + if funding_satoshis < holder_dust_limit_satoshis { + Ok(0) + } else { + Ok(funding_satoshis) + } +} + /// Context for dual-funded channels. pub(super) struct DualFundingChannelContext { /// The amount in satoshis we will be contributing to the channel. @@ -3831,9 +3982,15 @@ pub(super) struct DualFundingChannelContext { 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: u32, + pub funding_tx_locktime: LockTime, /// The feerate set by the initiator to be used for the funding transaction. pub funding_feerate_sat_per_1000_weight: u32, + /// The funding inputs we will be contributing to the channel. + /// + /// Note that the `our_funding_satoshis` field is equal to the total value of `our_funding_inputs` + /// minus any fees paid for our contributed weight. This means that change will never be generated + /// and the maximum value possible will go towards funding the channel. + pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, } // Holder designates channel data owned for the benefit of the user client. @@ -8140,7 +8297,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { /// should be sent back to the counterparty node. /// /// [`msgs::AcceptChannel`]: crate::ln::msgs::AcceptChannel - pub fn accept_inbound_channel(&mut self) -> msgs::AcceptChannel { + pub fn accept_inbound_channel(&self) -> msgs::AcceptChannel { if self.context.is_outbound() { panic!("Tried to send accept_channel for an outbound channel?"); } @@ -8272,8 +8429,9 @@ impl OutboundV2Channel where SP::Target: SignerProvider { pub fn new( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64, - user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64, - funding_confirmation_target: ConfirmationTarget, logger: L, + funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig, + current_chain_height: u32, outbound_scid_alias: u64, funding_confirmation_target: ConfirmationTarget, + logger: L, ) -> Result, APIError> where ES::Target: EntropySource, F::Target: FeeEstimator, @@ -8289,7 +8447,11 @@ impl OutboundV2Channel where SP::Target: SignerProvider { funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); let funding_feerate_sat_per_1000_weight = fee_estimator.bounded_sat_per_1000_weight(funding_confirmation_target); - let funding_tx_locktime = current_chain_height; + let funding_tx_locktime = LockTime::from_height(current_chain_height) + .map_err(|_| APIError::APIMisuseError { + err: format!( + "Provided current chain height of {} doesn't make sense for a height-based timelock for the funding transaction", + current_chain_height) })?; let chan = Self { context: ChannelContext::new_for_outbound_channel( @@ -8317,6 +8479,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { their_funding_satoshis: 0, funding_tx_locktime, funding_feerate_sat_per_1000_weight, + our_funding_inputs: funding_inputs, }, interactive_tx_constructor: None, }; @@ -8381,7 +8544,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { }, funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw, second_per_commitment_point, - locktime: self.dual_funding_context.funding_tx_locktime, + locktime: self.dual_funding_context.funding_tx_locktime.to_consensus_u32(), require_confirmed_inputs: None, } } @@ -8402,13 +8565,22 @@ impl InboundV2Channel where SP::Target: SignerProvider { pub fn new( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, - their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_satoshis: u64, user_id: u128, - config: &UserConfig, current_chain_height: u32, logger: &L, + their_features: &InitFeatures, msg: &msgs::OpenChannelV2, + funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, total_witness_weight: Weight, + user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L, ) -> Result, ChannelError> where ES::Target: EntropySource, F::Target: FeeEstimator, L::Target: Logger, { + let funding_satoshis = calculate_our_funding_satoshis( + false, &funding_inputs, total_witness_weight, msg.funding_feerate_sat_per_1000_weight, + msg.common_fields.dust_limit_satoshis + ).map_err(|_| ChannelError::Close( + ( + "Failed to accept channel".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + )))?; let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis); let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( channel_value_satoshis, msg.common_fields.dust_limit_satoshis); @@ -8457,26 +8629,43 @@ impl InboundV2Channel where SP::Target: SignerProvider { &context.get_counterparty_pubkeys().revocation_basepoint); context.channel_id = channel_id; - let chan = Self { - context, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, - dual_funding_context: DualFundingChannelContext { - our_funding_satoshis: funding_satoshis, - their_funding_satoshis: msg.common_fields.funding_satoshis, - funding_tx_locktime: msg.locktime, - funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, - }, - interactive_tx_constructor: None, + 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(), }; - Ok(chan) + let interactive_tx_constructor = Some(InteractiveTxConstructor::new( + InteractiveTxConstructorArgs { + entropy_source, + channel_id: context.channel_id, + feerate_sat_per_kw: dual_funding_context.funding_feerate_sat_per_1000_weight, + funding_tx_locktime: dual_funding_context.funding_tx_locktime, + is_initiator: false, + inputs_to_contribute: funding_inputs, + outputs_to_contribute: Vec::new(), + expected_remote_shared_funding_output: Some((context.get_funding_redeemscript(), context.channel_value_satoshis)), + } + ).map_err(|_| ChannelError::Close(( + "V2 channel rejected due to sender error".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) } + )))?); + + Ok(Self { + context, + dual_funding_context, + interactive_tx_constructor, + unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + }) } /// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which /// should be sent back to the counterparty node. /// /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 - pub fn accept_inbound_dual_funded_channel(&mut self) -> msgs::AcceptChannelV2 { + pub fn accept_inbound_dual_funded_channel(&self) -> msgs::AcceptChannelV2 { if self.context.is_outbound() { debug_assert!(false, "Tried to send accept_channel for an outbound channel?"); } @@ -8493,9 +8682,9 @@ impl InboundV2Channel where SP::Target: SignerProvider { self.generate_accept_channel_v2_message() } - /// This function is used to explicitly generate a [`msgs::AcceptChannel`] message for an - /// inbound channel. If the intention is to accept an inbound channel, use - /// [`InboundV1Channel::accept_inbound_channel`] instead. + /// This function is used to explicitly generate a [`msgs::AcceptChannelV2`] message for an + /// inbound dual-funded channel. If the intention is to accept a V1 established inbound channel, + /// use [`InboundV1Channel::accept_inbound_channel`] instead. /// /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 fn generate_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c3a35b773..9a7ded1b5 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -18,7 +18,7 @@ //! imply it needs to fail HTLCs/payments/channels it manages). use bitcoin::block::Header; -use bitcoin::transaction::Transaction; +use bitcoin::transaction::{Transaction, TxIn}; use bitcoin::constants::ChainHash; use bitcoin::key::constants::SECRET_KEY_SIZE; use bitcoin::network::Network; @@ -30,7 +30,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; -use bitcoin::{secp256k1, Sequence}; +use bitcoin::{secp256k1, Sequence, Weight}; use crate::events::FundingInfo; use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; @@ -42,14 +42,13 @@ use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::channelmonitor::{Balance, ChannelMonitor, ChannelMonitorUpdate, WithChannelMonitor, ChannelMonitorUpdateStep, HTLC_FAIL_BACK_BUFFER, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, MonitorEvent}; use crate::chain::transaction::{OutPoint, TransactionData}; -use crate::events; -use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination, PaymentFailureReason, ReplayEvent}; +use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFunds, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination, PaymentFailureReason, ReplayEvent}; // Since this struct is returned in `list_channels` methods, expose it here in case users want to // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; +use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; use crate::ln::channel_state::ChannelDetails; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] @@ -85,6 +84,7 @@ use crate::util::wakers::{Future, Notifier}; use crate::util::scid_utils::fake_scid; use crate::util::string::UntrustedString; use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter}; +use crate::util::ser::TransactionU16LenLimited; use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; @@ -1357,11 +1357,22 @@ impl PeerState where SP::Target: SignerProvider { } } +#[derive(Clone)] +pub(super) enum OpenChannelMessage { + V1(msgs::OpenChannel), + V2(msgs::OpenChannelV2), +} + +pub(super) enum OpenChannelMessageRef<'a> { + V1(&'a msgs::OpenChannel), + V2(&'a msgs::OpenChannelV2), +} + /// A not-yet-accepted inbound (from counterparty) channel. Once /// accepted, the parameters will be used to construct a channel. pub(super) struct InboundChannelRequest { /// The original OpenChannel message. - pub open_channel_msg: msgs::OpenChannel, + pub open_channel_msg: OpenChannelMessage, /// The number of ticks remaining before the request expires. pub ticks_remaining: i32, } @@ -7591,7 +7602,7 @@ where /// [`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> { - self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id) + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id, vec![], Weight::from_wu(0)) } /// Accepts a request to open a channel after a [`events::Event::OpenChannelRequest`], treating @@ -7613,11 +7624,14 @@ where /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { - self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id) + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id, vec![], Weight::from_wu(0)) } - fn do_accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, user_channel_id: u128) -> Result<(), APIError> { - + fn do_accept_inbound_channel( + &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, + user_channel_id: u128, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, + total_witness_weight: Weight, + ) -> Result<(), APIError> { let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(*temporary_channel_id), None); let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -7642,12 +7656,44 @@ where let res = match peer_state.inbound_channel_request_by_id.remove(temporary_channel_id) { Some(unaccepted_channel) => { let best_block_height = self.best_block.read().unwrap().height; - InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, - &unaccepted_channel.open_channel_msg, user_channel_id, &self.default_configuration, best_block_height, - &self.logger, accept_0conf).map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id)) + match unaccepted_channel.open_channel_msg { + OpenChannelMessage::V1(open_channel_msg) => { + InboundV1Channel::new( + &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, + &self.channel_type_features(), &peer_state.latest_features, &open_channel_msg, + user_channel_id, &self.default_configuration, best_block_height, &self.logger, accept_0conf + ).map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id) + ).map(|channel| { + let message_send_event = events::MessageSendEvent::SendAcceptChannel { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_channel(), + }; + (*temporary_channel_id, ChannelPhase::UnfundedInboundV1(channel), message_send_event) + }) + }, + OpenChannelMessage::V2(open_channel_msg) => { + InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, + &open_channel_msg, funding_inputs, total_witness_weight, user_channel_id, + &self.default_configuration, best_block_height, &self.logger + ).map_err(|_| MsgHandleErrInternal::from_chan_no_close( + ChannelError::Close( + ( + "V2 channel rejected due to sender error".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ) + ), *temporary_channel_id) + ).map(|channel| { + let message_send_event = events::MessageSendEvent::SendAcceptChannelV2 { + node_id: channel.context.get_counterparty_node_id(), + msg: channel.accept_inbound_dual_funded_channel() + }; + (channel.context.channel_id(), ChannelPhase::UnfundedInboundV2(channel), message_send_event) + }) + }, + } }, - _ => { + None => { let err_str = "No such channel awaiting to be accepted.".to_owned(); log_error!(logger, "{}", err_str); @@ -7655,10 +7701,14 @@ where } }; - match res { + // We have to match below instead of map_err on the above as in the map_err closure the borrow checker + // would consider peer_state moved even though we would bail out with the `?` operator. + let (channel_id, mut channel_phase, message_send_event) = match res { + Ok(res) => res, Err(err) => { mem::drop(peer_state_lock); mem::drop(per_peer_state); + // TODO(dunxen): Find/make less icky way to do this. match handle_error!(self, Result::<(), MsgHandleErrInternal>::Err(err), *counterparty_node_id) { Ok(_) => unreachable!("`handle_error` only returns Err as we've passed in an Err"), Err(e) => { @@ -7666,55 +7716,50 @@ where }, } } - Ok(mut channel) => { - if accept_0conf { - // This should have been correctly configured by the call to InboundV1Channel::new. - debug_assert!(channel.context.minimum_depth().unwrap() == 0); - } else if channel.context.get_channel_type().requires_zero_conf() { - let send_msg_err_event = events::MessageSendEvent::HandleError { - node_id: channel.context.get_counterparty_node_id(), - action: msgs::ErrorAction::SendErrorMessage{ - msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "No zero confirmation channels accepted".to_owned(), } - } - }; - peer_state.pending_msg_events.push(send_msg_err_event); - let err_str = "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned(); - log_error!(logger, "{}", err_str); + }; - return Err(APIError::APIMisuseError { err: err_str }); - } else { - // If this peer already has some channels, a new channel won't increase our number of peers - // with unfunded channels, so as long as we aren't over the maximum number of unfunded - // channels per-peer we can accept channels from a peer with existing ones. - if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS { - let send_msg_err_event = events::MessageSendEvent::HandleError { - node_id: channel.context.get_counterparty_node_id(), - action: msgs::ErrorAction::SendErrorMessage{ - msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), } - } - }; - peer_state.pending_msg_events.push(send_msg_err_event); - let err_str = "Too many peers with unfunded channels, refusing to accept new ones".to_owned(); - log_error!(logger, "{}", err_str); + if accept_0conf { + // This should have been correctly configured by the call to Inbound(V1/V2)Channel::new. + debug_assert!(channel_phase.context().minimum_depth().unwrap() == 0); + } else if channel_phase.context().get_channel_type().requires_zero_conf() { + let send_msg_err_event = events::MessageSendEvent::HandleError { + node_id: channel_phase.context().get_counterparty_node_id(), + action: msgs::ErrorAction::SendErrorMessage{ + msg: msgs::ErrorMessage { channel_id: *temporary_channel_id, data: "No zero confirmation channels accepted".to_owned(), } + } + }; + peer_state.pending_msg_events.push(send_msg_err_event); + let err_str = "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned(); + log_error!(logger, "{}", err_str); - return Err(APIError::APIMisuseError { err: err_str }); + return Err(APIError::APIMisuseError { err: err_str }); + } else { + // If this peer already has some channels, a new channel won't increase our number of peers + // with unfunded channels, so as long as we aren't over the maximum number of unfunded + // channels per-peer we can accept channels from a peer with existing ones. + if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS { + let send_msg_err_event = events::MessageSendEvent::HandleError { + node_id: channel_phase.context().get_counterparty_node_id(), + action: msgs::ErrorAction::SendErrorMessage{ + msg: msgs::ErrorMessage { channel_id: *temporary_channel_id, data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), } } - } + }; + peer_state.pending_msg_events.push(send_msg_err_event); + let err_str = "Too many peers with unfunded channels, refusing to accept new ones".to_owned(); + log_error!(logger, "{}", err_str); - // Now that we know we have a channel, assign an outbound SCID alias. - let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + return Err(APIError::APIMisuseError { err: err_str }); + } + } - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: channel.context.get_counterparty_node_id(), - msg: channel.accept_inbound_channel(), - }); + // Now that we know we have a channel, assign an outbound SCID alias. + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + channel_phase.context_mut().set_outbound_scid_alias(outbound_scid_alias); - peer_state.channel_by_id.insert(temporary_channel_id.clone(), ChannelPhase::UnfundedInboundV1(channel)); + peer_state.pending_msg_events.push(message_send_event); + peer_state.channel_by_id.insert(channel_id, channel_phase); - Ok(()) - }, - } + Ok(()) } /// Gets the number of peers which match the given filter and do not have any funded, outbound, @@ -7777,17 +7822,24 @@ where num_unfunded_channels + peer.inbound_channel_request_by_id.len() } - fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannel) -> Result<(), MsgHandleErrInternal> { + fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: OpenChannelMessageRef<'_>) -> Result<(), MsgHandleErrInternal> { + let common_fields = match msg { + OpenChannelMessageRef::V1(msg) => &msg.common_fields, + OpenChannelMessageRef::V2(msg) => &msg.common_fields, + }; + + // Do common open_channel(2) checks + // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are // likely to be lost on restart! - if msg.common_fields.chain_hash != self.chain_hash { + if common_fields.chain_hash != self.chain_hash { return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } if !self.default_configuration.accept_inbound_channels { return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } // Get the number of peers with channels, but without funded ones. We don't care too much @@ -7802,7 +7854,7 @@ where 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.clone()) + common_fields.temporary_channel_id) })?; let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; @@ -7816,44 +7868,51 @@ where { return Err(MsgHandleErrInternal::send_err_msg_no_close( "Have too many peers with unfunded channels, not accepting new ones".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } let best_block_height = self.best_block.read().unwrap().height; if Self::unfunded_channel_count(peer_state, best_block_height) >= MAX_UNFUNDED_CHANS_PER_PEER { return Err(MsgHandleErrInternal::send_err_msg_no_close( format!("Refusing more than {} unfunded channels.", MAX_UNFUNDED_CHANS_PER_PEER), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } - let channel_id = msg.common_fields.temporary_channel_id; + let channel_id = common_fields.temporary_channel_id; let channel_exists = peer_state.has_channel(&channel_id); if channel_exists { return Err(MsgHandleErrInternal::send_err_msg_no_close( "temporary_channel_id collision for the same peer!".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } + // We can get the channel type at this point already as we'll need it immediately in both the + // manual and the automatic acceptance cases. + let channel_type = channel::channel_type_from_open_channel( + common_fields, &peer_state.latest_features, &self.channel_type_features() + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, common_fields.temporary_channel_id))?; + // If we're doing manual acceptance checks on the channel, then defer creation until we're sure we want to accept. if self.default_configuration.manually_accept_inbound_channels { - let channel_type = channel::channel_type_from_open_channel( - &msg.common_fields, &peer_state.latest_features, &self.channel_type_features() - ).map_err(|e| - MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id) - )?; let mut pending_events = self.pending_events.lock().unwrap(); - let is_announced = (msg.common_fields.channel_flags & 1) == 1; + let is_announced = (common_fields.channel_flags & 1) == 1; pending_events.push_back((events::Event::OpenChannelRequest { - temporary_channel_id: msg.common_fields.temporary_channel_id.clone(), - counterparty_node_id: counterparty_node_id.clone(), - funding_satoshis: msg.common_fields.funding_satoshis, - push_msat: msg.push_msat, + temporary_channel_id: common_fields.temporary_channel_id, + counterparty_node_id: *counterparty_node_id, + funding_satoshis: common_fields.funding_satoshis, + channel_negotiation_type: match msg { + OpenChannelMessageRef::V1(msg) => InboundChannelFunds::PushMsat(msg.push_msat), + OpenChannelMessageRef::V2(_) => InboundChannelFunds::DualFunded, + }, channel_type, is_announced, - params: msg.common_fields.channel_parameters(), + params: common_fields.channel_parameters(), }, None)); peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest { - open_channel_msg: msg.clone(), + open_channel_msg: match msg { + OpenChannelMessageRef::V1(msg) => OpenChannelMessage::V1(msg.clone()), + OpenChannelMessageRef::V2(msg) => OpenChannelMessage::V2(msg.clone()), + }, ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, }); return Ok(()); @@ -7863,36 +7922,47 @@ where let mut random_bytes = [0u8; 16]; random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]); let user_channel_id = u128::from_be_bytes(random_bytes); - let mut channel = match InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, - &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false) - { - Err(e) => { - return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id)); - }, - Ok(res) => res - }; - let channel_type = channel.context.get_channel_type(); if channel_type.requires_zero_conf() { - return Err(MsgHandleErrInternal::send_err_msg_no_close( - "No zero confirmation channels accepted".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), common_fields.temporary_channel_id)); } if channel_type.requires_anchors_zero_fee_htlc_tx() { - return Err(MsgHandleErrInternal::send_err_msg_no_close( - "No channels with anchor outputs accepted".to_owned(), - msg.common_fields.temporary_channel_id.clone())); - } + return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), common_fields.temporary_channel_id)); + } + + let (mut channel_phase, message_send_event) = match msg { + OpenChannelMessageRef::V1(msg) => { + let channel = InboundV1Channel::new( + &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, + &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, + &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; + let message_send_event = events::MessageSendEvent::SendAcceptChannel { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_channel(), + }; + (ChannelPhase::UnfundedInboundV1(channel), message_send_event) + }, + OpenChannelMessageRef::V2(msg) => { + let channel = InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, + &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), + &peer_state.latest_features, msg, vec![], Weight::from_wu(0), user_channel_id, + &self.default_configuration, best_block_height, &self.logger + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; + let message_send_event = events::MessageSendEvent::SendAcceptChannelV2 { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_dual_funded_channel(), + }; + (ChannelPhase::UnfundedInboundV2(channel), message_send_event) + }, + }; let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + channel_phase.context_mut().set_outbound_scid_alias(outbound_scid_alias); + + peer_state.pending_msg_events.push(message_send_event); + peer_state.channel_by_id.insert(channel_phase.context().channel_id(), channel_phase); - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: counterparty_node_id.clone(), - msg: channel.accept_inbound_channel(), - }); - peer_state.channel_by_id.insert(channel_id, ChannelPhase::UnfundedInboundV1(channel)); Ok(()) } @@ -7912,7 +7982,7 @@ where hash_map::Entry::Occupied(mut phase) => { match phase.get_mut() { ChannelPhase::UnfundedOutboundV1(chan) => { - try_chan_phase_entry!(self, peer_state, chan.accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), phase); + try_chan_phase_entry!(self, peer_state, chan.accept_channel(msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), phase); (chan.context.get_value_satoshis(), chan.context.get_funding_redeemscript().to_p2wsh(), chan.context.get_user_id()) }, _ => { @@ -8092,6 +8162,145 @@ where } } + fn internal_tx_msg) -> Result>( + &self, counterparty_node_id: &PublicKey, channel_id: ChannelId, tx_msg_handler: HandleTxMsgFn + ) -> 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), + 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(channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let msg_send_event = match tx_msg_handler(channel_phase) { + Ok(msg_send_event) => msg_send_event, + Err(tx_msg_str) => return Err(MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn( + format!("Got a {tx_msg_str} message with no interactive transaction construction expected or in-progress") + ), channel_id)), + }; + peer_state.pending_msg_events.push(msg_send_event); + 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), channel_id) + ) + } + } + } + + fn internal_tx_add_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddInput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_add_input"), + } + }) + } + + fn internal_tx_add_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddOutput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_add_output"), + } + }) + } + + fn internal_tx_remove_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveInput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_remove_input(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_remove_input(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_remove_input"), + } + }) + } + + fn internal_tx_remove_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveOutput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_remove_output(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_remove_output(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_remove_output"), + } + }) + } + + fn internal_tx_complete(&self, counterparty_node_id: PublicKey, msg: &msgs::TxComplete) -> 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 (msg_send_event_opt, tx_opt) = match channel_phase { + ChannelPhase::UnfundedInboundV2(channel) => channel.tx_complete(msg).into_msg_send_event_or_tx(counterparty_node_id), + ChannelPhase::UnfundedOutboundV2(channel) => channel.tx_complete(msg).into_msg_send_event_or_tx(counterparty_node_id), + _ => try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( + ( + "Got a tx_complete message with no interactive transaction construction expected or in-progress".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + }; + if let Some(msg_send_event) = msg_send_event_opt { + peer_state.pending_msg_events.push(msg_send_event); + } + if let Some(tx) = tx_opt { + // TODO(dual_funding): Handle this unsigned transaction. + } + 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_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_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_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> { // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error // closing a channel), so any changes are likely to be lost on restart! @@ -10838,7 +11047,7 @@ where // open_channel message - pre-funded channels are never written so there should be no // change to the contents. let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { - let res = self.internal_open_channel(&counterparty_node_id, msg); + let res = self.internal_open_channel(&counterparty_node_id, OpenChannelMessageRef::V1(msg)); let persist = match &res { Err(e) if e.closes_channel() => { debug_assert!(false, "We shouldn't close a new channel"); @@ -10852,9 +11061,21 @@ where } fn handle_open_channel_v2(&self, counterparty_node_id: PublicKey, msg: &msgs::OpenChannelV2) { - 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 + // open_channel message - pre-funded channels are never written so there should be no + // change to the contents. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let res = self.internal_open_channel(&counterparty_node_id, OpenChannelMessageRef::V2(msg)); + let persist = match &res { + Err(e) if e.closes_channel() => { + debug_assert!(false, "We shouldn't close a new channel"); + NotifyOption::DoPersist + }, + _ => NotifyOption::SkipPersistHandleEvents, + }; + let _ = handle_error!(self, res, counterparty_node_id); + persist + }); } fn handle_accept_channel(&self, counterparty_node_id: PublicKey, msg: &msgs::AcceptChannel) { @@ -11384,33 +11605,53 @@ where } fn handle_tx_add_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddInput) { - 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_add_input 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_add_input(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_add_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddOutput) { - 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_add_output 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_add_output(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_remove_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveInput) { - 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_remove_input 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_remove_input(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_remove_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveOutput) { - 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_remove_output 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_remove_output(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_complete(&self, counterparty_node_id: PublicKey, msg: &msgs::TxComplete) { - 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_complete 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_complete(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_signatures(&self, counterparty_node_id: PublicKey, msg: &msgs::TxSignatures) { diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index b54ed1ff8..870055b79 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -1166,14 +1166,49 @@ where serial_id } -pub(crate) enum HandleTxCompleteValue { +pub(super) enum HandleTxCompleteValue { SendTxMessage(InteractiveTxMessageSend), SendTxComplete(InteractiveTxMessageSend, ConstructedTransaction), NegotiationComplete(ConstructedTransaction), } +impl HandleTxCompleteValue { + pub fn into_msg_send_event_or_tx( + self, counterparty_node_id: PublicKey, + ) -> (Option, Option) { + match self { + HandleTxCompleteValue::SendTxMessage(msg) => { + (Some(msg.into_msg_send_event(counterparty_node_id)), None) + }, + HandleTxCompleteValue::SendTxComplete(msg, tx) => { + (Some(msg.into_msg_send_event(counterparty_node_id)), Some(tx)) + }, + HandleTxCompleteValue::NegotiationComplete(tx) => (None, Some(tx)), + } + } +} + pub(super) struct HandleTxCompleteResult(pub Result); +impl HandleTxCompleteResult { + pub fn into_msg_send_event_or_tx( + self, counterparty_node_id: PublicKey, + ) -> (Option, Option) { + match self.0 { + Ok(interactive_tx_msg_send) => { + interactive_tx_msg_send.into_msg_send_event_or_tx(counterparty_node_id) + }, + Err(tx_abort_msg) => ( + Some(MessageSendEvent::SendTxAbort { + node_id: counterparty_node_id, + msg: tx_abort_msg, + }), + None, + ), + } + } +} + pub(super) struct InteractiveTxConstructorArgs<'a, ES: Deref> where ES::Target: EntropySource, -- 2.39.5