diff --git a/Cargo.toml b/Cargo.toml index ae5d1bc8f0..c90c3c3eed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,6 @@ check-cfg = [ "cfg(taproot)", "cfg(async_signing)", "cfg(require_route_graph_test)", - "cfg(dual_funding)", "cfg(splicing)", "cfg(async_payments)", ] diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index 0177112201..b5d3ee229f 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -137,8 +137,6 @@ RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=async_signing" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean -RUSTFLAGS="--cfg=dual_funding" cargo test --verbose --color always -p lightning -[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=splicing" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=async_payments" cargo test --verbose --color always -p lightning diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index 7ce87e0263..65b829deac 100644 --- a/lightning-types/src/features.rs +++ b/lightning-types/src/features.rs @@ -49,6 +49,8 @@ //! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#route-blinding) for more information). //! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown` //! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). +//! - `DualFund` - requires/supports V2 channel establishment +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#channel-establishment-v2) for more information). //! - `OnionMessages` - requires/supports forwarding onion messages //! (see [BOLT-7](https://github.com/lightning/bolts/pull/759/files) for more information). // TODO: update link @@ -147,7 +149,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, + RouteBlinding | ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -168,7 +170,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, + RouteBlinding | ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -499,6 +501,16 @@ mod sealed { supports_shutdown_anysegwit, requires_shutdown_anysegwit ); + define_feature!( + 29, + DualFund, + [InitContext, NodeContext], + "Feature flags for `option_dual_fund`.", + set_dual_fund_optional, + set_dual_fund_required, + supports_dual_fund, + requires_dual_fund + ); define_feature!( 31, Taproot, diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index a7cf68dcc1..a5cbaabe6e 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -589,6 +589,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 @@ -1293,9 +1307,14 @@ 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. + /// 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. + /// + /// 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. @@ -1329,8 +1348,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 46b35a9403..5696b781f2 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::witness::Witness; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -27,6 +29,11 @@ use bitcoin::secp256k1; use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; +use crate::ln::interactivetxs::{ + ConstructedTransaction, estimate_input_weight, get_output_weight, + HandleTxCompleteResult, InteractiveTxConstructor, InteractiveTxConstructorArgs, + InteractiveTxSigningSession, InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT, +}; use crate::ln::msgs; use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError}; use crate::ln::script::{self, ShutdownScript}; @@ -43,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, CLOSED_CHANNEL_UPDATE_ID}; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient}; -use crate::events::ClosureReason; +use crate::events::{ClosureReason, Event}; use crate::routing::gossip::NodeId; -use crate::util::ser::{Readable, ReadableArgs, 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}; @@ -1119,9 +1126,8 @@ impl_writeable_tlv_based!(PendingChannelMonitorUpdate, { pub(super) enum ChannelPhase where SP::Target: SignerProvider { UnfundedOutboundV1(OutboundV1Channel), UnfundedInboundV1(InboundV1Channel), - #[cfg(any(dual_funding, splicing))] + #[allow(dead_code)] // TODO(dual_funding): Remove once creating V2 channels is enabled. UnfundedOutboundV2(OutboundV2Channel), - #[cfg(any(dual_funding, splicing))] UnfundedInboundV2(InboundV2Channel), Funded(Channel), } @@ -1135,9 +1141,7 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::Funded(chan) => &chan.context, ChannelPhase::UnfundedOutboundV1(chan) => &chan.context, ChannelPhase::UnfundedInboundV1(chan) => &chan.context, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(chan) => &chan.context, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(chan) => &chan.context, } } @@ -1147,9 +1151,7 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::Funded(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedOutboundV1(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedInboundV1(ref mut chan) => &mut chan.context, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(ref mut chan) => &mut chan.context, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(ref mut chan) => &mut chan.context, } } @@ -1492,9 +1494,173 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { /// If we can't release a [`ChannelMonitorUpdate`] until some external action completes, we /// store it here and only release it to the `ChannelManager` once it asks for it. blocked_monitor_updates: Vec, + // If we've sent `commtiment_signed` for an interactive transaction construction, + // but have not received `tx_signatures` we MUST set `next_funding_txid` to the + // txid of that interactive transaction, else we MUST NOT set it. + next_funding_txid: Option, +} + +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 set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor); + + 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() + }), + }) + } + + fn internal_funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let our_funding_satoshis = self.dual_funding_context().our_funding_satoshis; + + let mut output_index = None; + let expected_spk = self.context().get_funding_redeemscript().to_p2wsh(); + for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { + if outp.script_pubkey() == &expected_spk && outp.value() == self.context().get_value_satoshis() { + if output_index.is_some() { + return Err(ChannelError::Close( + ( + "Multiple outputs matched the expected script and value".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + output_index = Some(idx as u16); + } + } + let outpoint = if let Some(output_index) = output_index { + OutPoint { txid: signing_session.unsigned_tx.txid(), index: output_index } + } else { + return Err(ChannelError::Close( + ( + "No output matched the funding script_pubkey".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + }; + self.context_mut().channel_transaction_parameters.funding_outpoint = Some(outpoint); + { + let channel_context = self.context_mut(); + channel_context.holder_signer.as_mut().provide_channel_parameters(&channel_context.channel_transaction_parameters); + } + + let commitment_signed = get_initial_commitment_signed(self.context_mut(), &signing_session.unsigned_tx, logger); + let commitment_signed = match commitment_signed { + Ok(commitment_signed) => commitment_signed, + Err(err) => return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))), + }; + + let funding_ready_for_sig_event = None; + if our_funding_satoshis == 0 { + signing_session.provide_holder_witnesses(self.context().channel_id, Vec::new()); + } else { + // TODO(dual_funding): Send event for signing if we've contributed funds. + } + + // Clear the interactive transaction constructor + self.interactive_tx_constructor_mut().take(); + self.context_mut().channel_state = ChannelState::FundingNegotiated; + + Ok((commitment_signed, funding_ready_for_sig_event)) + } +} + +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 + } + fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor) { + self.interactive_tx_constructor = Some(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 + } + fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor) { + self.interactive_tx_constructor = Some(interactive_tx_constructor); + } } -impl ChannelContext where SP::Target: SignerProvider { +impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator, entropy_source: &'a ES, @@ -1832,6 +1998,8 @@ impl ChannelContext where SP::Target: SignerProvider { blocked_monitor_updates: Vec::new(), is_manual_broadcast: false, + + next_funding_txid: None, }; Ok(channel_context) @@ -2063,6 +2231,7 @@ impl ChannelContext where SP::Target: SignerProvider { blocked_monitor_updates: Vec::new(), local_initiated_shutdown: None, is_manual_broadcast: false, + next_funding_txid: None, }) } @@ -2834,7 +3003,7 @@ impl ChannelContext where SP::Target: SignerProvider { } /// Gets the redeemscript for the funding transaction output (ie the funding transaction output - /// pays to get_funding_redeemscript().to_v0_p2wsh()). + /// pays to get_funding_redeemscript().to_p2wsh()). /// Panics if called before accept_channel/InboundV1Channel::new pub fn get_funding_redeemscript(&self) -> ScriptBuf { make_funding_redeemscript(&self.get_holder_pubkeys().funding_pubkey, self.counterparty_funding_pubkey()) @@ -3621,6 +3790,15 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_transaction_parameters.channel_type_features = self.channel_type.clone(); Ok(()) } + + /// Panics if the commitment tx numbers have advanced from their initial number. + fn assert_no_commitment_advancement(&self) { + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + } } // Internal utility functions for channels @@ -3672,33 +3850,81 @@ pub(crate) fn get_legacy_default_holder_selected_channel_reserve_satoshis(channe /// /// This is used both for outbound and inbound channels and has lower bound /// of `dust_limit_satoshis`. -#[cfg(any(dual_funding, splicing))] fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satoshis: u64) -> u64 { // Fixed at 1% of channel value by spec. let (q, _) = channel_value_satoshis.overflowing_div(100); 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)], + 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()); + our_contributed_weight = our_contributed_weight.saturating_add(estimate_input_weight(output).to_wu()); + } 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) }); + } + } + + // 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. -#[cfg(any(dual_funding, splicing))] pub(super) struct DualFundingChannelContext { /// The amount in satoshis we will be contributing to the channel. pub our_funding_satoshis: u64, + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. /// The amount in satoshis our counterparty will be contributing to the channel. - pub their_funding_satoshis: u64, + pub their_funding_satoshis: Option, /// 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. // Counterparty designates channel data owned by the another channel participant entity. pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, - #[cfg(any(dual_funding, splicing))] + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. pub dual_funding_channel_context: Option, + /// The current interactive transaction construction session under negotiation. + pub interactive_tx_constructor: Option, + pub interactive_tx_signing_session: Option, } #[cfg(any(test, fuzzing))] @@ -4184,6 +4410,14 @@ impl Channel where self.context.channel_state.clear_waiting_for_batch(); } + pub fn set_next_funding_txid(&mut self, txid: &Txid) { + self.context.next_funding_txid = Some(*txid); + } + + pub fn clear_next_funding_txid(&mut self) { + self.context.next_funding_txid = None; + } + /// Unsets the existing funding information. /// /// This must only be used if the channel has not yet completed funding and has not been used. @@ -4469,6 +4703,105 @@ impl Channel where Ok(()) } + pub fn commitment_signed_initial_v2( + &mut self, msg: &msgs::CommitmentSigned, best_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result::EcdsaSigner>, ChannelError> + where L::Target: Logger + { + if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { + return Err(ChannelError::Close( + ( + "Received initial commitment_signed before funding transaction constructed!".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + self.context.assert_no_commitment_advancement(); + + 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(); + let initial_commitment_tx = self.context.build_commitment_transaction( + self.context.holder_commitment_point.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 self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.get_counterparty_pubkeys().funding_pubkey).is_err() { + return Err(ChannelError::Close( + ( + "Invalid funding_signed signature from peer".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + } + + 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(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + )))?; + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo = self.context.get_funding_txo().unwrap(); + let funding_txo_script = funding_redeemscript.to_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, self.context.is_outbound(), + 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! + if self.context.holder_commitment_point.advance(&self.context.holder_signer, &self.context.secp_ctx, logger).is_err() { + // We only fail to advance our commitment point/number if we're currently + // waiting for our signer to unblock and provide a commitment point. + // If we err here by the time we receive the initial commitment_signed, something has gone wrong. + debug_assert!(false, "We should be ready to advance our commitment point by the time we receive initial commitment_signed"); + return Err(ChannelError::close("Failed to advance holder commitment point".to_owned())); + } + 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, logger).is_some(); + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + Ok(channel_monitor) + } + pub fn commitment_signed(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result, ChannelError> where L::Target: Logger { @@ -5134,6 +5467,65 @@ impl Channel where } } + pub fn verify_interactive_tx_signatures(&mut self, _witnesses: &Vec) { + if let Some(ref mut _signing_session) = self.interactive_tx_signing_session { + // Check that sighash_all was used: + // TODO(dual_funding): Check sig for sighash + } + } + + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures) -> Result<(Option, Option), ChannelError> { + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + if msg.witnesses.len() != signing_session.remote_inputs_count() { + return Err(ChannelError::Close( + ( + "Witness count did not match contributed input count".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + for witness in &msg.witnesses { + if witness.is_empty() { + return Err(ChannelError::Close( + ( + "Unexpected empty witness in tx_signatures received".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + // TODO(dual_funding): Check all sigs are SIGHASH_ALL. + + // TODO(dual_funding): I don't see how we're going to be able to ensure witness-standardness + // for spending. Doesn't seem to be anything in rust-bitcoin. + } + + if msg.tx_hash != signing_session.unsigned_tx.txid() { + return Err(ChannelError::Close( + ( + "The txid for the transaction does not match".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + let (tx_signatures_opt, funding_tx_opt) = signing_session.received_tx_signatures(msg.clone()); + if funding_tx_opt.is_some() { + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + } + self.context.funding_transaction = funding_tx_opt.clone(); + + // Clear out the signing session + self.interactive_tx_signing_session = None; + + Ok((tx_signatures_opt, funding_tx_opt)) + } else { + return Err(ChannelError::Close( + ( + "Unexpected tx_signatures. No funding transaction awaiting signatures".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + } + /// Queues up an outbound update fee by placing it in the holding cell. You should call /// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the /// commitment update. @@ -7176,10 +7568,7 @@ impl Channel where next_remote_commitment_number: INITIAL_COMMITMENT_NUMBER - self.context.cur_counterparty_commitment_transaction_number - 1, your_last_per_commitment_secret: remote_last_secret, my_current_per_commitment_point: dummy_pubkey, - // TODO(dual_funding): If we've sent `commtiment_signed` for an interactive transaction - // construction but have not received `tx_signatures` we MUST set `next_funding_txid` to the - // txid of that interactive transaction, else we MUST NOT set it. - next_funding_txid: None, + next_funding_txid: self.context.next_funding_txid, } } @@ -7718,11 +8107,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { ) { panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement(); self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); @@ -7828,7 +8213,8 @@ impl OutboundV1Channel where SP::Target: SignerProvider { &mut self, msg: &msgs::AcceptChannel, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures ) -> Result<(), ChannelError> { - self.context.do_accept_channel_checks(default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) + self.context.do_accept_channel_checks( + default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) } /// Handles a funding_signed message from the remote end. @@ -7845,11 +8231,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { return Err((self, ChannelError::close("Received funding_signed in strange state!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement(); let funding_script = self.context.get_funding_redeemscript(); @@ -7929,8 +8311,9 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let mut channel = Channel { context: self.context, - #[cfg(any(dual_funding, splicing))] dual_funding_channel_context: None, + interactive_tx_constructor: None, + interactive_tx_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); @@ -8039,7 +8422,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { msg.push_msat, msg.common_fields.clone(), )?, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } + unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, }; Ok(chan) } @@ -8147,11 +8530,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // channel. return Err((self, ChannelError::close("Received funding_created after we got the channel!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement(); let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); @@ -8227,8 +8606,9 @@ impl InboundV1Channel where SP::Target: SignerProvider { // `ChannelMonitor`. let mut channel = Channel { context: self.context, - #[cfg(any(dual_funding, splicing))] dual_funding_channel_context: None, + interactive_tx_constructor: None, + interactive_tx_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); channel.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); @@ -8238,21 +8618,22 @@ impl InboundV1Channel where SP::Target: SignerProvider { } // A not-yet-funded outbound (from holder) channel using V2 channel establishment. -#[cfg(any(dual_funding, splicing))] pub(super) struct OutboundV2Channel where SP::Target: SignerProvider { pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, - #[cfg(any(dual_funding, splicing))] pub dual_funding_context: DualFundingChannelContext, + /// The current interactive transaction construction session under negotiation. + interactive_tx_constructor: Option, } -#[cfg(any(dual_funding, splicing))] impl OutboundV2Channel where SP::Target: SignerProvider { + #[allow(dead_code)] // TODO(dual_funding): Remove once creating V2 channels is enabled. 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, @@ -8268,7 +8649,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( @@ -8293,10 +8678,12 @@ impl OutboundV2Channel where SP::Target: SignerProvider { unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: 0, + their_funding_satoshis: None, funding_tx_locktime, funding_feerate_sat_per_1000_weight, - } + our_funding_inputs: funding_inputs, + }, + interactive_tx_constructor: None, }; Ok(chan) } @@ -8359,34 +8746,70 @@ 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, } } + + pub fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let (commitment_signed, funding_ready_for_sig_event) = match self.internal_funding_tx_constructed( + signing_session, logger, + ) { + Ok(res) => res, + Err(err) => return Err(err), + }; + + Ok((commitment_signed, funding_ready_for_sig_event)) + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + interactive_tx_constructor: None, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. -#[cfg(any(dual_funding, splicing))] pub(super) struct InboundV2Channel where SP::Target: SignerProvider { pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, pub dual_funding_context: DualFundingChannelContext, + /// The current interactive transaction construction session under negotiation. + interactive_tx_constructor: Option, } -#[cfg(any(dual_funding, splicing))] impl InboundV2Channel where SP::Target: SignerProvider { /// Creates a new dual-funded channel from a remote side's request for one. /// Assumes chain_hash has already been checked and corresponds with what we expect! 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, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, + their_features: &InitFeatures, msg: &msgs::OpenChannelV2, + funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, 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, 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); @@ -8435,18 +8858,45 @@ impl InboundV2Channel where SP::Target: SignerProvider { &context.get_counterparty_pubkeys().revocation_basepoint); context.channel_id = channel_id; - let chan = Self { + let mut channel = 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, + their_funding_satoshis: Some(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, + }, + interactive_tx_constructor: None, }; - Ok(chan) + match InteractiveTxConstructor::new( + InteractiveTxConstructorArgs { + entropy_source, + holder_node_id, + counterparty_node_id, + channel_id: channel.context.channel_id, + feerate_sat_per_kw: channel.dual_funding_context.funding_feerate_sat_per_1000_weight, + funding_tx_locktime: channel.dual_funding_context.funding_tx_locktime, + is_initiator: false, + inputs_to_contribute: channel.dual_funding_context.our_funding_inputs.clone(), + outputs_to_contribute: Vec::new(), + expected_remote_shared_funding_output: Some((channel.context().get_funding_redeemscript(), channel.context().channel_value_satoshis)), + } + ) { + Ok(tx_constructor) => { + channel.set_interactive_tx_constructor(tx_constructor); + }, + Err(_) => { + return Err(ChannelError::Close(( + "V2 channel rejected due to sender error".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))) + } + } + + Ok(channel) } /// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which @@ -8516,9 +8966,37 @@ impl InboundV2Channel where SP::Target: SignerProvider { /// /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 #[cfg(test)] + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + pub fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let (commitment_signed, funding_ready_for_sig_event) = match self.internal_funding_tx_constructed( + signing_session, logger, + ) { + Ok(res) => res, + Err(err) => return Err(err), + }; + + Ok((commitment_signed, funding_ready_for_sig_event)) + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + interactive_tx_constructor: None, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // Unfunded channel utilities @@ -8546,6 +9024,85 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } +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(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + )))?.0) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!(), + } +} + +fn get_initial_commitment_signed( + context: &mut ChannelContext, transaction: &ConstructedTransaction, 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.holder_commitment_point.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_p2wsh(); + let funding_outpoint_index = transaction.outputs().enumerate().find_map( + |(idx, output)| { + if output.tx_out().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); + } + }; + + if context.signer_pending_funding { + log_trace!(logger, "Counterparty commitment signature ready for funding_created message: clearing signer_pending_funding"); + context.signer_pending_funding = false; + } + + log_info!(logger, "Generated commitment_signed for peer for channel {}", &context.channel_id()); + + Ok(msgs::CommitmentSigned { + channel_id: context.channel_id, + htlc_signatures: vec![], + signature, + batch: None, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) +} + const SERIALIZATION_VERSION: u8 = 4; const MIN_SERIALIZATION_VERSION: u8 = 3; @@ -8974,7 +9531,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { (47, next_holder_commitment_point, option), (49, self.context.local_initiated_shutdown, option), // Added in 0.0.122 (51, is_manual_broadcast, option), // Added in 0.0.124 - (53, funding_tx_broadcast_safe_event_emitted, option) // Added in 0.0.124 + (53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124 + (55, self.context.next_funding_txid, option) // Added in 0.0.125 }); Ok(()) @@ -9583,9 +10141,14 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch blocked_monitor_updates: blocked_monitor_updates.unwrap(), is_manual_broadcast: is_manual_broadcast.unwrap_or(false), + // If we've sent `commtiment_signed` for an interactive transaction construction, + // but have not received `tx_signatures` we MUST set `next_funding_txid` to the + // txid of that interactive transaction, else we MUST NOT set it. + next_funding_txid: None, }, - #[cfg(any(dual_funding, splicing))] dual_funding_channel_context: None, + interactive_tx_constructor: None, + interactive_tx_signing_session: None, }) } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5c69a1a714..494e2094a1 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; @@ -42,13 +42,16 @@ use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, WithChannelMonitor, ChannelMonitorUpdateStep, HTLC_FAIL_BACK_BUFFER, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, MonitorEvent, CLOSED_CHANNEL_UPDATE_ID}; 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, PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channel::{self, Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; +use crate::ln::channel::{ + self, Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, + UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, + InboundV2Channel, WithChannelContext, InteractivelyFunded as _, +}; use crate::ln::channel_state::ChannelDetails; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] @@ -58,7 +61,7 @@ use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htl use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; -use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; +use crate::ln::msgs::{ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; @@ -83,6 +86,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; @@ -1135,9 +1139,7 @@ impl PeerState where SP::Target: SignerProvider { match phase { ChannelPhase::Funded(_) | ChannelPhase::UnfundedOutboundV1(_) => true, ChannelPhase::UnfundedInboundV1(_) => false, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(_) => true, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(_) => false, } ) @@ -1157,11 +1159,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, } @@ -2742,11 +2755,9 @@ macro_rules! convert_chan_phase_err { ChannelPhase::UnfundedInboundV1(channel) => { convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(channel) => { convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(channel) => { convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, @@ -3656,13 +3667,7 @@ where self.finish_close_channel(chan.context.force_shutdown(broadcast, closure_reason)); (self.get_channel_update_for_broadcast(&chan).ok(), chan.context.get_counterparty_node_id()) }, - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => { - self.finish_close_channel(chan_phase.context_mut().force_shutdown(false, closure_reason)); - // Unfunded channel has no update - (None, chan_phase.context().get_counterparty_node_id()) - }, - // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed. - #[cfg(any(dual_funding, splicing))] + ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => { self.finish_close_channel(chan_phase.context_mut().force_shutdown(false, closure_reason)); // Unfunded channel has no update @@ -4955,6 +4960,53 @@ where result } + /// Handles a signed funding transaction generated by interactive transaction construction and + /// provided by the client. + /// + /// Do NOT broadcast the funding transaction yourself. When we have safely received our + /// counterparty's signature(s) the funding transaction will automatically be broadcast via the + /// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed. + pub fn funding_transaction_signed( + &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, transaction: Transaction + ) -> Result<(), APIError> { + let witnesses: Vec<_> = transaction.input.into_iter().filter_map(|input| { + if input.witness.is_empty() { None } else { Some(input.witness) } + }).collect(); + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| APIError::ChannelUnavailable { + err: format!("Can't find a peer matching the passed counterparty node_id {}", + counterparty_node_id) })?; + + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + match peer_state.channel_by_id.get_mut(channel_id) { + Some(ChannelPhase::Funded(chan)) => { + chan.verify_interactive_tx_signatures(&witnesses); + if let Some(ref mut signing_session) = chan.interactive_tx_signing_session { + if let Some(tx_signatures) = signing_session.provide_holder_witnesses(*channel_id, witnesses) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + } else { + return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not expecting funding signatures", channel_id)}); + } + }, + Some(_) => return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not expecting funding signatures", channel_id)}), + None => return Err(APIError::ChannelUnavailable{ + err: format!("Channel with id {} not found for the passed counterparty node_id {}", channel_id, + counterparty_node_id) }), + } + + Ok(()) + } + /// Atomically applies partial updates to the [`ChannelConfig`] of the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel @@ -6181,12 +6233,10 @@ where process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context, pending_msg_events, counterparty_node_id) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(chan) => { process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context, pending_msg_events, counterparty_node_id) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(chan) => { process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context, pending_msg_events, counterparty_node_id) @@ -7070,7 +7120,7 @@ where /// Gets the node_id held by this ChannelManager pub fn get_our_node_id(&self) -> PublicKey { - self.our_network_pubkey.clone() + self.our_network_pubkey } fn handle_monitor_update_completion_actions>(&self, actions: I) { @@ -7329,7 +7379,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![]) } /// Accepts a request to open a channel after a [`events::Event::OpenChannelRequest`], treating @@ -7351,11 +7401,13 @@ 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![]) } - 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)>, + ) -> 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); @@ -7380,12 +7432,35 @@ 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(|channel| ChannelPhase::UnfundedInboundV1(channel)) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id)) + }, + OpenChannelMessage::V2(open_channel_msg) => { + InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, + &open_channel_msg, funding_inputs, user_channel_id, &self.default_configuration, best_block_height, + &self.logger + ) + .map(|channel| ChannelPhase::UnfundedInboundV2(channel)) + .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) + ) + }, + } }, - _ => { + None => { let err_str = "No such channel awaiting to be accepted.".to_owned(); log_error!(logger, "{}", err_str); @@ -7400,19 +7475,19 @@ where 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) => { - return Err(APIError::ChannelUnavailable { err: e.err }); + Err(APIError::ChannelUnavailable { err: e.err }) }, } } - Ok(mut channel) => { + Ok(mut channel_phase) => { 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() { + // 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.context.get_counterparty_node_id(), + node_id: channel_phase.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(), } + 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); @@ -7426,9 +7501,9 @@ where // 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(), + node_id: channel_phase.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(), } + 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); @@ -7441,14 +7516,31 @@ where // 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); - - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: channel.context.get_counterparty_node_id(), - msg: channel.accept_inbound_channel(), - }); + channel_phase.context_mut().set_outbound_scid_alias(outbound_scid_alias); - peer_state.channel_by_id.insert(temporary_channel_id.clone(), ChannelPhase::UnfundedInboundV1(channel)); + match channel_phase { + ChannelPhase::UnfundedInboundV1(mut channel) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { + node_id: channel.context.get_counterparty_node_id(), + msg: channel.accept_inbound_channel() }); + peer_state.channel_by_id.insert(*temporary_channel_id, + ChannelPhase::UnfundedInboundV1(channel)); + }, + ChannelPhase::UnfundedInboundV2(mut channel) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 { + node_id: channel.context.get_counterparty_node_id(), + msg: channel.accept_inbound_dual_funded_channel() }); + peer_state.channel_by_id.insert(channel.context.channel_id(), + ChannelPhase::UnfundedInboundV2(channel)); + }, + _ => { + debug_assert!(false); + // This should be unreachable, but if it is then we would have dropped the inbound channel request + // and there'd be nothing to clean up as we haven't added anything to the channel_by_id map yet. + return Err(APIError::APIMisuseError { + err: "Channel somehow changed to a non-inbound channel before accepting".to_owned() }) + } + } Ok(()) }, @@ -7498,8 +7590,6 @@ where num_unfunded_channels += 1; } }, - // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed. - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(chan) => { // Only inbound V2 channels that are not 0conf and that we do not contribute to will be // included in the unfunded count. @@ -7508,32 +7598,33 @@ where num_unfunded_channels += 1; } }, - ChannelPhase::UnfundedOutboundV1(_) => { + ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedOutboundV2(_) => { // Outbound channels don't contribute to the unfunded count in the DoS context. continue; }, - // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed. - #[cfg(any(dual_funding, splicing))] - ChannelPhase::UnfundedOutboundV2(_) => { - // Outbound channels don't contribute to the unfunded count in the DoS context. - continue; - } } } 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 (chain_hash, temporary_channel_id) = match msg { + OpenChannelMessageRef::V1(msg) => (msg.common_fields.chain_hash, msg.common_fields.temporary_channel_id), + OpenChannelMessageRef::V2(msg) => (msg.common_fields.chain_hash, msg.common_fields.temporary_channel_id), + }; + + // 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 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())); + 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())); + temporary_channel_id)); } // Get the number of peers with channels, but without funded ones. We don't care too much @@ -7548,7 +7639,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()) + temporary_channel_id.clone()) })?; let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; @@ -7562,83 +7653,144 @@ 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())); + temporary_channel_id.clone())); } 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())); + temporary_channel_id.clone())); } - let channel_id = msg.common_fields.temporary_channel_id; + let channel_id = 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())); - } + temporary_channel_id.clone())); + } + + // Version-specific checks and logic + match msg { + OpenChannelMessageRef::V1(msg) => { + // 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; + pending_events.push_back((events::Event::OpenChannelRequest { + temporary_channel_id: msg.common_fields.temporary_channel_id, + counterparty_node_id: *counterparty_node_id, + funding_satoshis: msg.common_fields.funding_satoshis, + channel_negotiation_type: InboundChannelFunds::PushMsat(msg.push_msat), + channel_type, + is_announced, + params: msg.common_fields.channel_parameters(), + }, None)); + peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest { + open_channel_msg: OpenChannelMessage::V1(msg.clone()), + ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, + }); + return Ok(()); + } - // 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; - 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, - channel_type, - is_announced, - params: msg.common_fields.channel_parameters(), - }, None)); - peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest { - open_channel_msg: msg.clone(), - ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, - }); - return Ok(()); - } + // Otherwise create the channel right now. + 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 + }; - // Otherwise create the channel right now. - 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)); + 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)); + } + 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)); + } + + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + channel.context.set_outbound_scid_alias(outbound_scid_alias); + + 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(temporary_channel_id, ChannelPhase::UnfundedInboundV1(channel)); }, - Ok(res) => res - }; + OpenChannelMessageRef::V2(msg) => { + // If we're doing manual acceptance checks on the channel, then defer creation until we're sure + // we want to accept and, optionally, contribute to the channel value. + 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; + pending_events.push_back((events::Event::OpenChannelRequest { + temporary_channel_id: msg.common_fields.temporary_channel_id, + counterparty_node_id: counterparty_node_id.clone(), + funding_satoshis: msg.common_fields.funding_satoshis, + channel_negotiation_type: InboundChannelFunds::DualFunded, + channel_type, + is_announced, + params: msg.common_fields.channel_parameters(), + }, None)); + peer_state.inbound_channel_request_by_id.insert(temporary_channel_id, InboundChannelRequest { + open_channel_msg: OpenChannelMessage::V2(msg.clone()), + ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, + }); + return Ok(()); + } - 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())); - } - 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())); - } + // Otherwise create the channel right now. + 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 InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, + &self.signer_provider, self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(), + &peer_state.latest_features, msg, vec![], user_channel_id, &self.default_configuration, + best_block_height, &self.logger) + { + Err(e) => { + return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id)); + }, + Ok(res) => res + }; - let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + 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)); + } + 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)); + } - 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)); + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + channel.context.set_outbound_scid_alias(outbound_scid_alias); + + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_dual_funded_channel(), + }); + peer_state.channel_by_id.insert(channel.context.channel_id(), ChannelPhase::UnfundedInboundV2(channel)); + }, + } Ok(()) } @@ -7658,7 +7810,7 @@ where hash_map::Entry::Occupied(mut phase) => { match phase.get_mut() { ChannelPhase::UnfundedOutboundV1(chan) => { - try_chan_phase_entry!(self, chan.accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), phase); + try_chan_phase_entry!(self, 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()) }, _ => { @@ -7838,6 +7990,273 @@ 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) => try_chan_phase_entry!(self, Err(ChannelError::Warn( + format!("Got a {tx_msg_str} message with no interactive transaction construction expected or in-progress") + )), chan_phase_entry) + }; + 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 (msg_send_event_opt, signing_session_opt) = match chan_phase_entry.get_mut() { + ChannelPhase::UnfundedInboundV2(channel) => { + channel.tx_complete(msg).into_msg_send_event(counterparty_node_id) + }, + ChannelPhase::UnfundedOutboundV2(channel) => { + channel.tx_complete(msg).into_msg_send_event(counterparty_node_id) + }, + _ => try_chan_phase_entry!(self, 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(mut signing_session) = signing_session_opt { + let funding_txid = signing_session.unsigned_tx.txid(); + match chan_phase_entry.get_mut() { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + _ => Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())), + }.and_then(|(commitment_signed, funding_ready_for_sig_event_opt)| { + let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); + match channel_phase { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.into_channel(signing_session) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.into_channel(signing_session) + }, + _ => { + debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. + Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())) + } + }.map(|channel| (channel_id, channel, funding_ready_for_sig_event_opt, commitment_signed)) + }).map(|(channel_id, mut channel, funding_ready_for_sig_event_opt, commitment_signed)| { + channel.set_next_funding_txid(&funding_txid); + peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel)); + if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((funding_ready_for_sig_event, None)); + } + peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { + node_id: counterparty_node_id, + updates: CommitmentUpdate { + commitment_signed, + update_add_htlcs: vec![], + update_fulfill_htlcs: vec![], + update_fail_htlcs: vec![], + update_fail_malformed_htlcs: vec![], + update_fee: None, + }, + }); + }).map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id)) + } else { + // We're not in a signing session yet so we don't need to do anything else. + 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) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::Funded(chan) => { + let (tx_signatures_opt, funding_tx_opt) = try_chan_phase_entry!(self, chan.tx_signatures(msg), chan_phase_entry); + chan.clear_next_funding_txid(); + if let Some(tx_signatures) = tx_signatures_opt { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + if let Some(ref funding_tx) = funding_tx_opt { + self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + { + let mut pending_events = self.pending_events.lock().unwrap(); + emit_channel_pending_event!(pending_events, chan); + } + } + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Got an unexpected tx_signatures message".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } + } + + fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let tx_constructor = match channel_phase { + ChannelPhase::UnfundedInboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::UnfundedOutboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::Funded(chan) => &mut chan.interactive_tx_constructor, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Got an unexpected tx_signatures message".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + }; + if tx_constructor.take().is_some() { + let msg = msgs::TxAbort { + channel_id: msg.channel_id, + data: "Acknowledged tx_abort".to_string().into_bytes(), + }; + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, + msg, + }); + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } + } + fn internal_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> { // 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! @@ -7936,21 +8355,14 @@ where peer_state_lock, peer_state, per_peer_state, chan); } }, - ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) => { + ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) | + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { let context = phase.context_mut(); let logger = WithChannelContext::from(&self.logger, context, None); log_error!(logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id); let mut chan = remove_channel_phase!(self, chan_phase_entry); finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); }, - // TODO(dual_funding): Combine this match arm with above. - #[cfg(any(dual_funding, splicing))] - ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { - let context = phase.context_mut(); - log_error!(self.logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id); - let mut chan = remove_channel_phase!(self, chan_phase_entry); - finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); - }, } } else { 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)) @@ -8213,6 +8625,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(|| { @@ -8223,21 +8636,46 @@ 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, None); - 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, None); + let funding_txo = chan.context.get_funding_txo(); + + if chan.interactive_tx_signing_session.is_some() { + let monitor = try_chan_phase_entry!( + self, chan.commitment_signed_initial_v2(msg, best_block, &self.signer_provider, &&logger), + chan_phase_entry); + let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor); + if let Ok(persist_state) = monitor_res { + handle_new_monitor_update!(self, persist_state, peer_state_lock, peer_state, + per_peer_state, chan, INITIAL_MONITOR); + } else { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + log_error!(logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated"); + try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Channel funding outpoint was a duplicate".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ) + )), chan_phase_entry) + } + } else { + let monitor_update_opt = try_chan_phase_entry!(self, 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(()) + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Got a commitment_signed message for an unfunded channel!".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry), } }, - hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + hash_map::Entry::Vacant(_) => Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) } } @@ -8871,7 +9309,7 @@ where } None } - ChannelPhase::UnfundedInboundV1(_) => None, + ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => None, } }; @@ -10093,9 +10531,7 @@ where peer_state.channel_by_id.retain(|_, phase| { match phase { // Retain unfunded channels. - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => true, - // TODO(dual_funding): Combine this match arm with above. - #[cfg(any(dual_funding, splicing))] + ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => true, ChannelPhase::Funded(channel) => { let res = f(channel); @@ -10335,7 +10771,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"); @@ -10349,9 +10785,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) { @@ -10577,11 +11025,9 @@ where ChannelPhase::UnfundedInboundV1(chan) => { &mut chan.context }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(chan) => { &mut chan.context }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(chan) => { &mut chan.context }, @@ -10742,8 +11188,6 @@ where }); } - // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed. - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(chan) => { pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 { node_id: chan.context.get_counterparty_node_id(), @@ -10751,21 +11195,12 @@ where }); }, - ChannelPhase::UnfundedInboundV1(_) => { + ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedInboundV2(_) => { // Since unfunded inbound channel maps are cleared upon disconnecting a peer, // they are not persisted and won't be recovered after a crash. // Therefore, they shouldn't exist at this point. debug_assert!(false); } - - // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed. - #[cfg(any(dual_funding, splicing))] - ChannelPhase::UnfundedInboundV2(channel) => { - // Since unfunded inbound channel maps are cleared upon disconnecting a peer, - // they are not persisted and won't be recovered after a crash. - // Therefore, they shouldn't exist at this point. - debug_assert!(false); - }, } } } @@ -10862,7 +11297,6 @@ where return; } }, - #[cfg(any(dual_funding, splicing))] Some(ChannelPhase::UnfundedOutboundV2(ref mut chan)) => { if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 { @@ -10872,9 +11306,7 @@ where return; } }, - None | Some(ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::Funded(_)) => (), - #[cfg(any(dual_funding, splicing))] - Some(ChannelPhase::UnfundedInboundV2(_)) => (), + None | Some(ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::Funded(_)) => (), } } @@ -10896,39 +11328,58 @@ 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_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_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_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_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_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_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_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_complete(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_signatures(&self, counterparty_node_id: PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_tx_signatures(&counterparty_node_id, msg), counterparty_node_id); } fn handle_tx_init_rbf(&self, counterparty_node_id: PublicKey, msg: &msgs::TxInitRbf) { @@ -10944,9 +11395,13 @@ where } fn handle_tx_abort(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAbort) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_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_abort(&counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn message_received(&self) { @@ -11324,6 +11779,7 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures { if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx { features.set_anchors_zero_fee_htlc_tx_optional(); } + features.set_dual_fund_optional(); features } @@ -14395,6 +14851,9 @@ mod tests { expect_pending_htlcs_forwardable!(nodes[0]); } + + // Dual-funding: V2 Channel Establishment Tests + // TODO(dual_funding): Complete these. } #[cfg(ldk_bench)] diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 15e911cbb4..554be591e0 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -15,18 +15,21 @@ use bitcoin::amount::Amount; use bitcoin::consensus::Encodable; use bitcoin::constants::WITNESS_SCALE_FACTOR; use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; +use bitcoin::secp256k1::PublicKey; use bitcoin::transaction::Version; -use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight}; +use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness}; use crate::chain::chaininterface::fee_for_weight; use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; +use crate::events::MessageSendEvent; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::msgs; -use crate::ln::msgs::SerialId; +use crate::ln::msgs::{CommitmentSigned, SerialId, TxSignatures}; use crate::ln::types::ChannelId; use crate::sign::{EntropySource, P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT}; use crate::util::ser::TransactionU16LenLimited; +use core::fmt::Display; use core::ops::Deref; /// The number of received `tx_add_input` messages during a negotiation at which point the @@ -43,7 +46,7 @@ const MAX_INPUTS_OUTPUTS_COUNT: usize = 252; /// The total weight of the common fields whose fee is paid by the initiator of the interactive /// transaction construction protocol. -const TX_COMMON_FIELDS_WEIGHT: u64 = (4 /* version */ + 4 /* locktime */ + 1 /* input count */ + +pub(crate) const TX_COMMON_FIELDS_WEIGHT: u64 = (4 /* version */ + 4 /* locktime */ + 1 /* input count */ + 1 /* output count */) * WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */; // BOLT 3 - Lower bounds for input weights @@ -108,6 +111,47 @@ pub(crate) enum AbortReason { InvalidLowFundingOutputValue, } +impl AbortReason { + pub fn into_tx_abort_msg(self, channel_id: ChannelId) -> msgs::TxAbort { + msgs::TxAbort { channel_id, data: self.to_string().into_bytes() } + } +} + +impl Display for AbortReason { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(match self { + AbortReason::InvalidStateTransition => "State transition was invalid", + AbortReason::UnexpectedCounterpartyMessage => "Unexpected message", + AbortReason::ReceivedTooManyTxAddInputs => "Too many `tx_add_input`s received", + AbortReason::ReceivedTooManyTxAddOutputs => "Too many `tx_add_output`s received", + AbortReason::IncorrectInputSequenceValue => { + "Input has a sequence value greater than 0xFFFFFFFD" + }, + AbortReason::IncorrectSerialIdParity => "Parity for `serial_id` was incorrect", + AbortReason::SerialIdUnknown => "The `serial_id` is unknown", + AbortReason::DuplicateSerialId => "The `serial_id` already exists", + AbortReason::PrevTxOutInvalid => "Invalid previous transaction output", + AbortReason::ExceededMaximumSatsAllowed => { + "Output amount exceeded total bitcoin supply" + }, + AbortReason::ExceededNumberOfInputsOrOutputs => "Too many inputs or outputs", + AbortReason::TransactionTooLarge => "Transaction weight is too large", + AbortReason::BelowDustLimit => "Output amount is below the dust limit", + AbortReason::InvalidOutputScript => "The output script is non-standard", + AbortReason::InsufficientFees => "Insufficient fees paid", + AbortReason::OutputsValueExceedsInputsValue => { + "Total value of outputs exceeds total value of inputs" + }, + AbortReason::InvalidTx => "The transaction is invalid", + AbortReason::MissingFundingOutput => "No shared funding output found", + AbortReason::DuplicateFundingOutput => "More than one funding output found", + AbortReason::InvalidLowFundingOutputValue => { + "Local part of funding output value is greater than the funding output value" + }, + }) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct ConstructedTransaction { holder_is_initiator: bool, @@ -122,6 +166,7 @@ pub(crate) struct ConstructedTransaction { remote_outputs_value_satoshis: u64, lock_time: AbsoluteLockTime, + holder_sends_tx_signatures_first: bool, } impl ConstructedTransaction { @@ -136,19 +181,39 @@ impl ConstructedTransaction { .iter() .fold(0u64, |value, (_, output)| value.saturating_add(output.local_value())); + let remote_inputs_value_satoshis = context.remote_inputs_value(); + let remote_outputs_value_satoshis = context.remote_outputs_value(); + let mut inputs: Vec = context.inputs.into_values().collect(); + let mut outputs: Vec = context.outputs.into_values().collect(); + // Inputs and outputs must be sorted by serial_id + inputs.sort_unstable_by_key(|input| input.serial_id()); + outputs.sort_unstable_by_key(|output| output.serial_id); + + // There is a strict ordering for `tx_signatures` exchange to prevent deadlocks. + let holder_sends_tx_signatures_first = + if local_inputs_value_satoshis == remote_inputs_value_satoshis { + // If the amounts are the same then the peer with the lowest pubkey lexicographically sends its + // tx_signatures first + context.holder_node_id.serialize() < context.counterparty_node_id.serialize() + } else { + // Otherwise the peer with the lowest contributed input value sends its tx_signatures first. + local_inputs_value_satoshis < remote_inputs_value_satoshis + }; + Self { holder_is_initiator: context.holder_is_initiator, local_inputs_value_satoshis, local_outputs_value_satoshis, - remote_inputs_value_satoshis: context.remote_inputs_value(), - remote_outputs_value_satoshis: context.remote_outputs_value(), + remote_inputs_value_satoshis, + remote_outputs_value_satoshis, - inputs: context.inputs.into_values().collect(), - outputs: context.outputs.into_values().collect(), + inputs, + outputs, lock_time: context.tx_locktime, + holder_sends_tx_signatures_first, } } @@ -166,11 +231,7 @@ impl ConstructedTransaction { } pub fn into_unsigned_tx(self) -> Transaction { - // Inputs and outputs must be sorted by serial_id - let ConstructedTransaction { mut inputs, mut outputs, .. } = self; - - inputs.sort_unstable_by_key(|input| input.serial_id()); - outputs.sort_unstable_by_key(|output| output.serial_id); + let ConstructedTransaction { inputs, outputs, .. } = self; let input: Vec = inputs.into_iter().map(|input| input.txin().clone()).collect(); let output: Vec = @@ -178,10 +239,154 @@ impl ConstructedTransaction { Transaction { version: Version::TWO, lock_time: self.lock_time, input, output } } + + pub fn outputs(&self) -> impl Iterator { + self.outputs.iter() + } + + pub fn inputs(&self) -> impl Iterator { + self.inputs.iter() + } + + pub fn txid(&self) -> Txid { + self.clone().into_unsigned_tx().compute_txid() + } + + pub fn add_local_witnesses(&mut self, witnesses: Vec) { + self.inputs + .iter_mut() + .filter(|input| { + !is_serial_id_valid_for_counterparty(self.holder_is_initiator, input.serial_id()) + }) + .map(|input| input.txin_mut()) + .zip(witnesses) + .for_each(|(input, witness)| input.witness = witness); + } + + pub fn add_remote_witnesses(&mut self, witnesses: Vec) { + self.inputs + .iter_mut() + .filter(|input| { + is_serial_id_valid_for_counterparty(self.holder_is_initiator, input.serial_id()) + }) + .map(|input| input.txin_mut()) + .zip(witnesses) + .for_each(|(input, witness)| input.witness = witness); + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct InteractiveTxSigningSession { + pub unsigned_tx: ConstructedTransaction, + holder_sends_tx_signatures_first: bool, + sent_commitment_signed: Option, + received_commitment_signed: Option, + holder_tx_signatures: Option, + counterparty_tx_signatures: Option, +} + +impl InteractiveTxSigningSession { + pub fn received_commitment_signed( + &mut self, commitment_signed: CommitmentSigned, + ) -> Option { + self.received_commitment_signed = Some(commitment_signed); + if self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn get_tx_signatures(&self) -> Option { + self.received_commitment_signed.as_ref().and_then(|_| self.holder_tx_signatures.clone()) + } + + pub fn received_tx_signatures( + &mut self, tx_signatures: TxSignatures, + ) -> (Option, Option) { + if self.counterparty_tx_signatures.is_some() { + return (None, None); + }; + self.unsigned_tx.add_remote_witnesses(tx_signatures.witnesses.clone()); + self.counterparty_tx_signatures = Some(tx_signatures); + + let holder_tx_signatures = if !self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + }; + + let funding_tx = if self.holder_tx_signatures.is_some() { + Some(self.finalize_funding_tx()) + } else { + None + }; + + (holder_tx_signatures, funding_tx) + } + + pub fn provide_holder_witnesses( + &mut self, channel_id: ChannelId, witnesses: Vec, + ) -> Option { + self.unsigned_tx.add_local_witnesses(witnesses.clone()); + self.holder_tx_signatures = Some(TxSignatures { + channel_id, + tx_hash: self.unsigned_tx.txid(), + witnesses: witnesses.into_iter().collect(), + shared_input_signature: None, + }); + if self.received_commitment_signed.is_some() + && (self.holder_sends_tx_signatures_first || self.counterparty_tx_signatures.is_some()) + { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn remote_inputs_count(&self) -> usize { + self.unsigned_tx + .inputs + .iter() + .filter(|input| { + is_serial_id_valid_for_counterparty( + self.unsigned_tx.holder_is_initiator, + input.serial_id(), + ) + }) + .count() + } + + pub fn local_inputs_count(&self) -> usize { + self.unsigned_tx + .inputs + .iter() + .filter(|input| { + !is_serial_id_valid_for_counterparty( + self.unsigned_tx.holder_is_initiator, + input.serial_id(), + ) + }) + .count() + } + + fn finalize_funding_tx(&mut self) -> Transaction { + let lock_time = self.unsigned_tx.lock_time; + let ConstructedTransaction { inputs, outputs, .. } = &mut self.unsigned_tx; + + Transaction { + version: Version::TWO, + lock_time, + input: inputs.iter().cloned().map(|input| input.into_txin()).collect(), + output: outputs.iter().cloned().map(|output| output.into_tx_out()).collect(), + } + } } #[derive(Debug)] struct NegotiationContext { + holder_node_id: PublicKey, + counterparty_node_id: PublicKey, holder_is_initiator: bool, received_tx_add_input_count: u16, received_tx_add_output_count: u16, @@ -227,17 +432,20 @@ pub(crate) fn get_output_weight(script_pubkey: &ScriptBuf) -> Weight { ) } -fn is_serial_id_valid_for_counterparty(holder_is_initiator: bool, serial_id: &SerialId) -> bool { +fn is_serial_id_valid_for_counterparty(holder_is_initiator: bool, serial_id: SerialId) -> bool { // A received `SerialId`'s parity must match the role of the counterparty. holder_is_initiator == serial_id.is_for_non_initiator() } impl NegotiationContext { fn new( - holder_is_initiator: bool, expected_shared_funding_output: (ScriptBuf, u64), - tx_locktime: AbsoluteLockTime, feerate_sat_per_kw: u32, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, holder_is_initiator: bool, + expected_shared_funding_output: (ScriptBuf, u64), tx_locktime: AbsoluteLockTime, + feerate_sat_per_kw: u32, ) -> Self { NegotiationContext { + holder_node_id, + counterparty_node_id, holder_is_initiator, received_tx_add_input_count: 0, received_tx_add_output_count: 0, @@ -269,7 +477,7 @@ impl NegotiationContext { } fn is_serial_id_valid_for_counterparty(&self, serial_id: &SerialId) -> bool { - is_serial_id_valid_for_counterparty(self.holder_is_initiator, serial_id) + is_serial_id_valid_for_counterparty(self.holder_is_initiator, *serial_id) } fn remote_inputs_value(&self) -> u64 { @@ -302,6 +510,12 @@ impl NegotiationContext { ) } + fn local_inputs_value(&self) -> u64 { + self.inputs + .iter() + .fold(0u64, |acc, (_, input)| acc.saturating_add(input.prev_output().value.to_sat())) + } + fn received_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> { // The interactive-txs spec calls for us to fail negotiation if the `prevtx` we receive is // invalid. However, we would not need to account for this explicit negotiation failure @@ -698,7 +912,7 @@ define_state!( ReceivedTxComplete, "We have received a `tx_complete` message and the counterparty is awaiting ours." ); -define_state!(NegotiationComplete, ConstructedTransaction, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); +define_state!(NegotiationComplete, InteractiveTxSigningSession, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); define_state!( NegotiationAborted, AbortReason, @@ -741,7 +955,15 @@ macro_rules! define_state_transitions { fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult { let context = self.into_negotiation_context(); let tx = context.validate_tx()?; - Ok(NegotiationComplete(tx)) + let signing_session = InteractiveTxSigningSession { + holder_sends_tx_signatures_first: tx.holder_sends_tx_signatures_first, + unsigned_tx: tx, + sent_commitment_signed: None, + received_commitment_signed: None, + holder_tx_signatures: None, + counterparty_tx_signatures: None, + }; + Ok(NegotiationComplete(signing_session)) } } @@ -810,10 +1032,13 @@ macro_rules! define_state_machine_transitions { impl StateMachine { fn new( - feerate_sat_per_kw: u32, is_initiator: bool, tx_locktime: AbsoluteLockTime, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, feerate_sat_per_kw: u32, + is_initiator: bool, tx_locktime: AbsoluteLockTime, expected_shared_funding_output: (ScriptBuf, u64), ) -> Self { let context = NegotiationContext::new( + holder_node_id, + counterparty_node_id, is_initiator, expected_shared_funding_output, tx_locktime, @@ -892,7 +1117,7 @@ pub struct LocalOrRemoteInput { } #[derive(Clone, Debug, Eq, PartialEq)] -enum InteractiveTxInput { +pub(crate) enum InteractiveTxInput { Local(LocalOrRemoteInput), Remote(LocalOrRemoteInput), // TODO(splicing) SharedInput should be added @@ -905,7 +1130,7 @@ pub struct SharedOwnedOutput { } impl SharedOwnedOutput { - fn new(tx_out: TxOut, local_owned: u64) -> SharedOwnedOutput { + pub fn new(tx_out: TxOut, local_owned: u64) -> SharedOwnedOutput { debug_assert!( local_owned <= tx_out.value.to_sat(), "SharedOwnedOutput: Inconsistent local_owned value {}, larger than output value {}", @@ -941,6 +1166,13 @@ impl OutputOwned { } } + fn into_tx_out(self) -> TxOut { + match self { + OutputOwned::Single(tx_out) | OutputOwned::SharedControlFullyOwned(tx_out) => tx_out, + OutputOwned::Shared(output) => output.tx_out, + } + } + fn value(&self) -> u64 { self.tx_out().value.to_sat() } @@ -979,30 +1211,34 @@ impl OutputOwned { } #[derive(Clone, Debug, Eq, PartialEq)] -struct InteractiveTxOutput { +pub(crate) struct InteractiveTxOutput { serial_id: SerialId, added_by: AddingRole, output: OutputOwned, } impl InteractiveTxOutput { - fn tx_out(&self) -> &TxOut { + pub fn tx_out(&self) -> &TxOut { self.output.tx_out() } - fn value(&self) -> u64 { + pub fn into_tx_out(self) -> TxOut { + self.output.into_tx_out() + } + + pub fn value(&self) -> u64 { self.tx_out().value.to_sat() } - fn local_value(&self) -> u64 { + pub fn local_value(&self) -> u64 { self.output.local_value(self.added_by) } - fn remote_value(&self) -> u64 { + pub fn remote_value(&self) -> u64 { self.output.remote_value(self.added_by) } - fn script_pubkey(&self) -> &ScriptBuf { + pub fn script_pubkey(&self) -> &ScriptBuf { &self.output.tx_out().script_pubkey } } @@ -1022,6 +1258,20 @@ impl InteractiveTxInput { } } + pub fn txin_mut(&mut self) -> &mut TxIn { + match self { + InteractiveTxInput::Local(input) => &mut input.input, + InteractiveTxInput::Remote(input) => &mut input.input, + } + } + + pub fn into_txin(self) -> TxIn { + match self { + InteractiveTxInput::Local(input) => input.input, + InteractiveTxInput::Remote(input) => input.input, + } + } + pub fn prev_output(&self) -> &TxOut { match self { InteractiveTxInput::Local(input) => &input.prev_output, @@ -1048,8 +1298,9 @@ impl InteractiveTxInput { } } -pub(crate) struct InteractiveTxConstructor { +pub(super) struct InteractiveTxConstructor { state_machine: StateMachine, + initiator_first_message: Option, channel_id: ChannelId, inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)>, outputs_to_contribute: Vec<(SerialId, OutputOwned)>, @@ -1062,6 +1313,39 @@ pub(crate) enum InteractiveTxMessageSend { TxComplete(msgs::TxComplete), } +impl InteractiveTxMessageSend { + pub fn into_msg_send_event(self, counterparty_node_id: PublicKey) -> MessageSendEvent { + match self { + InteractiveTxMessageSend::TxAddInput(msg) => { + MessageSendEvent::SendTxAddInput { node_id: counterparty_node_id, msg } + }, + InteractiveTxMessageSend::TxAddOutput(msg) => { + MessageSendEvent::SendTxAddOutput { node_id: counterparty_node_id, msg } + }, + InteractiveTxMessageSend::TxComplete(msg) => { + MessageSendEvent::SendTxComplete { node_id: counterparty_node_id, msg } + }, + } + } +} + +pub(super) struct InteractiveTxMessageSendResult( + pub Result, +); + +impl InteractiveTxMessageSendResult { + pub fn into_msg_send_event(self, counterparty_node_id: PublicKey) -> MessageSendEvent { + match self.0 { + Ok(interactive_tx_msg_send) => { + interactive_tx_msg_send.into_msg_send_event(counterparty_node_id) + }, + Err(tx_abort_msg) => { + MessageSendEvent::SendTxAbort { node_id: counterparty_node_id, msg: tx_abort_msg } + }, + } + } +} + // This macro executes a state machine transition based on a provided action. macro_rules! do_state_transition { ($self: ident, $transition: ident, $msg: expr) => {{ @@ -1090,8 +1374,57 @@ where pub(crate) enum HandleTxCompleteValue { SendTxMessage(InteractiveTxMessageSend), - SendTxComplete(InteractiveTxMessageSend, ConstructedTransaction), - NegotiationComplete(ConstructedTransaction), + SendTxComplete(InteractiveTxMessageSend, InteractiveTxSigningSession), + NegotiationComplete(InteractiveTxSigningSession), +} + +pub(super) struct HandleTxCompleteResult(pub Result); + +impl HandleTxCompleteResult { + pub fn into_msg_send_event( + self, counterparty_node_id: PublicKey, + ) -> (Option, Option) { + match self.0 { + Ok(tx_complete_res) => { + let (tx_msg_opt, signing_session_opt) = match tx_complete_res { + HandleTxCompleteValue::SendTxMessage(msg) => (Some(msg), None), + HandleTxCompleteValue::SendTxComplete(msg, signing_session) => { + (Some(msg), Some(signing_session)) + }, + HandleTxCompleteValue::NegotiationComplete(signing_session) => { + (None, Some(signing_session)) + }, + }; + ( + tx_msg_opt.map(|tx_msg| tx_msg.into_msg_send_event(counterparty_node_id)), + signing_session_opt, + ) + }, + 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, +{ + pub entropy_source: &'a ES, + pub holder_node_id: PublicKey, + pub counterparty_node_id: PublicKey, + pub channel_id: ChannelId, + pub feerate_sat_per_kw: u32, + pub is_initiator: bool, + pub funding_tx_locktime: AbsoluteLockTime, + pub inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>, + pub outputs_to_contribute: Vec, + pub expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>, } impl InteractiveTxConstructor { @@ -1103,20 +1436,26 @@ impl InteractiveTxConstructor { /// and its (local) contribution from the shared output: /// 0 when the whole value belongs to the remote node, or /// positive if owned also by local. - /// Note: The local value cannot be larger that the actual shared output. + /// Note: The local value cannot be larger than the actual shared output. /// - /// A tuple is returned containing the newly instantiate `InteractiveTxConstructor` and optionally - /// an initial wrapped `Tx_` message which the holder needs to send to the counterparty. - pub fn new( - entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, is_initiator: bool, - funding_tx_locktime: AbsoluteLockTime, - inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>, - outputs_to_contribute: Vec, - expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>, - ) -> Result<(Self, Option), AbortReason> + /// If the holder is the initiator, they need to send the first message which is a `TxAddInput` + /// message. + pub fn new(args: InteractiveTxConstructorArgs) -> Result where ES::Target: EntropySource, { + let InteractiveTxConstructorArgs { + entropy_source, + holder_node_id, + counterparty_node_id, + channel_id, + feerate_sat_per_kw, + is_initiator, + funding_tx_locktime, + inputs_to_contribute, + outputs_to_contribute, + expected_remote_shared_funding_output, + } = args; // Sanity check: There can be at most one shared output, local-added or remote-added let mut expected_shared_funding_output: Option<(ScriptBuf, u64)> = None; for output in &outputs_to_contribute { @@ -1149,6 +1488,8 @@ impl InteractiveTxConstructor { } if let Some(expected_shared_funding_output) = expected_shared_funding_output { let state_machine = StateMachine::new( + holder_node_id, + counterparty_node_id, feerate_sat_per_kw, is_initiator, funding_tx_locktime, @@ -1175,28 +1516,27 @@ impl InteractiveTxConstructor { .collect(); // In the same manner and for the same rationale as the inputs above, we'll shuffle the outputs. outputs_to_contribute.sort_unstable_by_key(|(serial_id, _)| *serial_id); - let mut constructor = - Self { state_machine, channel_id, inputs_to_contribute, outputs_to_contribute }; - let message_send = if is_initiator { - match constructor.maybe_send_message() { - Ok(msg_send) => Some(msg_send), - Err(_) => { - debug_assert!( - false, - "We should always be able to start our state machine successfully" - ); - None - }, - } - } else { - None + let mut constructor = Self { + state_machine, + initiator_first_message: None, + channel_id, + inputs_to_contribute, + outputs_to_contribute, }; - Ok((constructor, message_send)) + // We'll store the first message for the initiator. + if is_initiator { + constructor.initiator_first_message = Some(constructor.maybe_send_message()?); + } + Ok(constructor) } else { Err(AbortReason::MissingFundingOutput) } } + pub fn take_initiator_first_message(&mut self) -> Option { + self.initiator_first_message.take() + } + fn maybe_send_message(&mut self) -> Result { // We first attempt to send inputs we want to add, then outputs. Once we are done sending // them both, then we always send tx_complete. @@ -1295,8 +1635,8 @@ mod tests { use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::interactivetxs::{ generate_holder_serial_id, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor, - InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT, - MAX_RECEIVED_TX_ADD_OUTPUT_COUNT, + InteractiveTxConstructorArgs, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, + MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT, }; use crate::ln::types::ChannelId; use crate::sign::EntropySource; @@ -1308,7 +1648,7 @@ mod tests { use bitcoin::key::UntweakedPublicKey; use bitcoin::opcodes; use bitcoin::script::Builder; - use bitcoin::secp256k1::{Keypair, Secp256k1}; + use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; use bitcoin::transaction::Version; use bitcoin::{ OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash, @@ -1395,7 +1735,15 @@ mod tests { ES::Target: EntropySource, { let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); - let tx_locktime = AbsoluteLockTime::from_height(1337).unwrap(); + let funding_tx_locktime = AbsoluteLockTime::from_height(1337).unwrap(); + let holder_node_id = PublicKey::from_secret_key( + &Secp256k1::signing_only(), + &SecretKey::from_slice(&[42; 32]).unwrap(), + ); + let counterparty_node_id = PublicKey::from_secret_key( + &Secp256k1::signing_only(), + &SecretKey::from_slice(&[43; 32]).unwrap(), + ); // funding output sanity check let shared_outputs_by_a: Vec<_> = @@ -1448,16 +1796,18 @@ mod tests { } } - let (mut constructor_a, first_message_a) = match InteractiveTxConstructor::new( + let mut constructor_a = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs { entropy_source, channel_id, - TEST_FEERATE_SATS_PER_KW, - true, - tx_locktime, - session.inputs_a, - session.outputs_a.to_vec(), - session.a_expected_remote_shared_output, - ) { + feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW, + holder_node_id, + counterparty_node_id, + is_initiator: true, + funding_tx_locktime, + inputs_to_contribute: session.inputs_a, + outputs_to_contribute: session.outputs_a.to_vec(), + expected_remote_shared_funding_output: session.a_expected_remote_shared_output, + }) { Ok(r) => r, Err(abort_reason) => { assert_eq!( @@ -1469,16 +1819,18 @@ mod tests { return; }, }; - let (mut constructor_b, first_message_b) = match InteractiveTxConstructor::new( + let mut constructor_b = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs { entropy_source, + holder_node_id, + counterparty_node_id, channel_id, - TEST_FEERATE_SATS_PER_KW, - false, - tx_locktime, - session.inputs_b, - session.outputs_b.to_vec(), - session.b_expected_remote_shared_output, - ) { + feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW, + is_initiator: false, + funding_tx_locktime, + inputs_to_contribute: session.inputs_b, + outputs_to_contribute: session.outputs_b.to_vec(), + expected_remote_shared_funding_output: session.b_expected_remote_shared_output, + }) { Ok(r) => r, Err(abort_reason) => { assert_eq!( @@ -1514,17 +1866,17 @@ mod tests { } }; - assert!(first_message_b.is_none()); - let mut message_send_a = first_message_a; + let mut message_send_a = constructor_a.take_initiator_first_message(); let mut message_send_b = None; let mut final_tx_a = None; let mut final_tx_b = None; while final_tx_a.is_none() || final_tx_b.is_none() { if let Some(message_send_a) = message_send_a.take() { match handle_message_send(message_send_a, &mut constructor_b) { - Ok((msg_send, final_tx)) => { + Ok((msg_send, interactive_signing_session)) => { message_send_b = msg_send; - final_tx_b = final_tx; + final_tx_b = + interactive_signing_session.map(|session| session.unsigned_tx.txid()); }, Err(abort_reason) => { let error_culprit = match abort_reason { @@ -1546,9 +1898,10 @@ mod tests { } if let Some(message_send_b) = message_send_b.take() { match handle_message_send(message_send_b, &mut constructor_a) { - Ok((msg_send, final_tx)) => { + Ok((msg_send, interactive_signing_session)) => { message_send_a = msg_send; - final_tx_a = final_tx; + final_tx_a = + interactive_signing_session.map(|session| session.unsigned_tx.txid()); }, Err(abort_reason) => { let error_culprit = match abort_reason { @@ -1571,7 +1924,7 @@ mod tests { } assert!(message_send_a.is_none()); assert!(message_send_b.is_none()); - assert_eq!(final_tx_a.unwrap().into_unsigned_tx(), final_tx_b.unwrap().into_unsigned_tx()); + assert_eq!(final_tx_a.unwrap(), final_tx_b.unwrap()); assert!( session.expect_error.is_none(), "Missing expected error {:?}, Test: {}", diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index dceb52ab4a..ac834b5956 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -96,7 +96,6 @@ mod async_signer_tests; #[cfg(test)] #[allow(unused_mut)] mod offers_tests; -#[allow(dead_code)] // TODO(dual_funding): Exchange for dual_funding cfg pub(crate) mod interactivetxs; pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 5c536a09dc..1324a928d8 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -182,10 +182,10 @@ pub struct Pong { pub byteslen: u16, } -/// Contains fields that are both common to [`open_channel`] and `open_channel2` messages. +/// Contains fields that are both common to [`open_channel`] and [`open_channel2`] messages. /// /// [`open_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message -// TODO(dual_funding): Add spec link for `open_channel2`. +/// [`open_channel2`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel2-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CommonOpenChannelFields { /// The genesis hash of the blockchain where the channel is to be opened @@ -287,11 +287,11 @@ pub struct OpenChannel { pub channel_reserve_satoshis: u64, } -/// An open_channel2 message to be sent by or received from the channel initiator. +/// An [`open_channel2`] message to be sent by or received from the channel initiator. /// /// Used in V2 channel establishment /// -// TODO(dual_funding): Add spec link for `open_channel2`. +/// [`open_channel2`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel2-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct OpenChannelV2 { /// Common fields of `open_channel(2)`-like messages @@ -306,10 +306,10 @@ pub struct OpenChannelV2 { pub require_confirmed_inputs: Option<()>, } -/// Contains fields that are both common to [`accept_channel`] and `accept_channel2` messages. +/// Contains fields that are both common to [`accept_channel`] and [`accept_channel2`] messages. /// /// [`accept_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel-message -// TODO(dual_funding): Add spec link for `accept_channel2`. +/// [`accept_channel2`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel2-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CommonAcceptChannelFields { /// The same `temporary_channel_id` received from the initiator's `open_channel2` or `open_channel` message. @@ -369,11 +369,11 @@ pub struct AcceptChannel { pub next_local_nonce: Option, } -/// An accept_channel2 message to be sent by or received from the channel accepter. +/// An [`accept_channel2`] message to be sent by or received from the channel accepter. /// /// Used in V2 channel establishment /// -// TODO(dual_funding): Add spec link for `accept_channel2`. +/// [`accept_channel2`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel2-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct AcceptChannelV2 { /// Common fields of `accept_channel(2)`-like messages @@ -503,9 +503,9 @@ pub struct SpliceLocked { pub splice_txid: Txid, } -/// A tx_add_input message for adding an input during interactive transaction construction +/// A [`tx_add_input`] message for adding an input during interactive transaction construction /// -// TODO(dual_funding): Add spec link for `tx_add_input`. +/// [`tx_add_input`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_add_input-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxAddInput { /// The channel ID @@ -524,9 +524,9 @@ pub struct TxAddInput { pub shared_input_txid: Option, } -/// A tx_add_output message for adding an output during interactive transaction construction. +/// A [`tx_add_output`] message for adding an output during interactive transaction construction. /// -// TODO(dual_funding): Add spec link for `tx_add_output`. +/// [`tx_add_output`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_add_output-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxAddOutput { /// The channel ID @@ -540,9 +540,9 @@ pub struct TxAddOutput { pub script: ScriptBuf, } -/// A tx_remove_input message for removing an input during interactive transaction construction. +/// A [`tx_remove_input`] message for removing an input during interactive transaction construction. /// -// TODO(dual_funding): Add spec link for `tx_remove_input`. +/// [`tx_remove_input`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_remove_input-and-tx_remove_output-messages #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxRemoveInput { /// The channel ID @@ -551,9 +551,9 @@ pub struct TxRemoveInput { pub serial_id: SerialId, } -/// A tx_remove_output message for removing an output during interactive transaction construction. +/// A [`tx_remove_output`] message for removing an output during interactive transaction construction. /// -// TODO(dual_funding): Add spec link for `tx_remove_output`. +/// [`tx_remove_output`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_remove_input-and-tx_remove_output-messages #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxRemoveOutput { /// The channel ID @@ -562,20 +562,20 @@ pub struct TxRemoveOutput { pub serial_id: SerialId, } -/// A tx_complete message signalling the conclusion of a peer's transaction contributions during +/// [`A tx_complete`] message signalling the conclusion of a peer's transaction contributions during /// interactive transaction construction. /// -// TODO(dual_funding): Add spec link for `tx_complete`. +/// [`tx_complete`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_complete-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxComplete { /// The channel ID pub channel_id: ChannelId, } -/// A tx_signatures message containing the sender's signatures for a transaction constructed with +/// A [`tx_signatures`] message containing the sender's signatures for a transaction constructed with /// interactive transaction construction. /// -// TODO(dual_funding): Add spec link for `tx_signatures`. +/// [`tx_signatures`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_signatures-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxSignatures { /// The channel ID @@ -588,10 +588,10 @@ pub struct TxSignatures { pub shared_input_signature: Option, } -/// A tx_init_rbf message which initiates a replacement of the transaction after it's been +/// A [`tx_init_rbf`] message which initiates a replacement of the transaction after it's been /// completed. /// -// TODO(dual_funding): Add spec link for `tx_init_rbf`. +/// [`tx_init_rbf`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_init_rbf-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxInitRbf { /// The channel ID @@ -605,10 +605,10 @@ pub struct TxInitRbf { pub funding_output_contribution: Option, } -/// A tx_ack_rbf message which acknowledges replacement of the transaction after it's been +/// A [`tx_ack_rbf`] message which acknowledges replacement of the transaction after it's been /// completed. /// -// TODO(dual_funding): Add spec link for `tx_ack_rbf`. +/// [`tx_ack_rbf`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_ack_rbf-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxAckRbf { /// The channel ID @@ -618,9 +618,9 @@ pub struct TxAckRbf { pub funding_output_contribution: Option, } -/// A tx_abort message which signals the cancellation of an in-progress transaction negotiation. +/// A [`tx_abort`] message which signals the cancellation of an in-progress transaction negotiation. /// -// TODO(dual_funding): Add spec link for `tx_abort`. +/// [`tx_abort`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-tx_abort-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TxAbort { /// The channel ID diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 5293906f91..fe2f366863 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -331,6 +331,7 @@ impl ChannelMessageHandler for ErroringMessageHandler { features.set_basic_mpp_optional(); features.set_wumbo_optional(); features.set_shutdown_any_segwit_optional(); + features.set_dual_fund_optional(); features.set_channel_type_optional(); features.set_scid_privacy_optional(); features.set_zero_conf_optional();